@@ -36,7 +36,8 @@ class SyncStreamQueueSourceTest {
3636 private ChannelConnector mockConnector ;
3737 private FlagSyncServiceBlockingStub blockingStub ;
3838 private FlagSyncServiceStub stub ;
39- private FlagSyncServiceStub errorStub ;
39+ private FlagSyncServiceStub syncErrorStub ;
40+ private FlagSyncServiceStub asyncErrorStub ;
4041 private StreamObserver <SyncFlagsResponse > observer ;
4142 private CountDownLatch latch ; // used to wait for observer to be initialized
4243
@@ -60,25 +61,76 @@ public void setup() throws Exception {
6061 .when (stub )
6162 .syncFlags (any (SyncFlagsRequest .class ), any (StreamObserver .class )); // Mock the initialize
6263
63- errorStub = mock (FlagSyncServiceStub .class );
64- when (errorStub .withDeadlineAfter (anyLong (), any ())).thenReturn (errorStub );
64+ syncErrorStub = mock (FlagSyncServiceStub .class );
65+ when (syncErrorStub .withDeadlineAfter (anyLong (), any ())).thenReturn (syncErrorStub );
6566 doAnswer ((Answer <Void >) invocation -> {
6667 Object [] args = invocation .getArguments ();
6768 observer = (StreamObserver <SyncFlagsResponse >) args [1 ];
6869 latch .countDown ();
6970 throw new StatusRuntimeException (io .grpc .Status .NOT_FOUND );
7071 })
71- .when (errorStub )
72+ .when (syncErrorStub )
7273 .syncFlags (any (SyncFlagsRequest .class ), any (StreamObserver .class )); // Mock the initialize
74+
75+ asyncErrorStub = mock (FlagSyncServiceStub .class );
76+ when (asyncErrorStub .withDeadlineAfter (anyLong (), any ())).thenReturn (asyncErrorStub );
77+ doAnswer ((Answer <Void >) invocation -> {
78+ Object [] args = invocation .getArguments ();
79+ observer = (StreamObserver <SyncFlagsResponse >) args [1 ];
80+ latch .countDown ();
81+
82+ // Start a thread to call onError after a short delay
83+ new Thread (() -> {
84+ try {
85+ Thread .sleep (10 ); // Wait 100ms before calling onError
86+ observer .onError (new StatusRuntimeException (io .grpc .Status .INTERNAL ));
87+ } catch (InterruptedException e ) {
88+ Thread .currentThread ().interrupt ();
89+ }
90+ })
91+ .start ();
92+
93+ return null ;
94+ })
95+ .when (asyncErrorStub )
96+ .syncFlags (any (SyncFlagsRequest .class ), any (StreamObserver .class )); // Mock the initialize
97+ }
98+
99+ @ Test
100+ void syncInitError_DoesNotBusyWait () throws Exception {
101+ // make sure we do not spin in a busy loop on immediately errors
102+
103+ int maxBackoffMs = 1000 ;
104+ SyncStreamQueueSource queueSource = new SyncStreamQueueSource (
105+ FlagdOptions .builder ().retryBackoffMaxMs (maxBackoffMs ).build (),
106+ mockConnector ,
107+ syncErrorStub ,
108+ blockingStub );
109+ latch = new CountDownLatch (1 );
110+ queueSource .init ();
111+ latch .await ();
112+
113+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
114+ QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
115+ assertNotNull (payload );
116+ assertEquals (QueuePayloadType .ERROR , payload .getType ());
117+ Thread .sleep (maxBackoffMs + (maxBackoffMs / 2 )); // wait 1.5x our delay for reties
118+
119+ // should have retried the stream (2 calls); initial + 1 retry
120+ // it's very important that the retry count is low, to confirm no busy-loop
121+ verify (syncErrorStub , times (2 )).syncFlags (any (), any ());
73122 }
74123
75124 @ Test
76- void initError_DoesNotBusyWait () throws Exception {
77- // make sure we do not spin in a busy loop on errors
125+ void asyncInitError_DoesNotBusyWait () throws Exception {
126+ // make sure we do not spin in a busy loop on async errors
78127
79128 int maxBackoffMs = 1000 ;
80129 SyncStreamQueueSource queueSource = new SyncStreamQueueSource (
81- FlagdOptions .builder ().retryBackoffMaxMs (maxBackoffMs ).build (), mockConnector , errorStub , blockingStub );
130+ FlagdOptions .builder ().retryBackoffMaxMs (maxBackoffMs ).build (),
131+ mockConnector ,
132+ asyncErrorStub ,
133+ blockingStub );
82134 latch = new CountDownLatch (1 );
83135 queueSource .init ();
84136 latch .await ();
@@ -91,7 +143,7 @@ void initError_DoesNotBusyWait() throws Exception {
91143
92144 // should have retried the stream (2 calls); initial + 1 retry
93145 // it's very important that the retry count is low, to confirm no busy-loop
94- verify (errorStub , times (2 )).syncFlags (any (), any ());
146+ verify (asyncErrorStub , times (2 )).syncFlags (any (), any ());
95147 }
96148
97149 @ Test
0 commit comments