3636import java .util .concurrent .Executors ;
3737import java .util .concurrent .Future ;
3838import java .util .concurrent .TimeUnit ;
39- import java .util .concurrent .atomic .AtomicInteger ;
4039import java .util .concurrent .atomic .AtomicReference ;
4140import org .apache .beam .runners .dataflow .worker .streaming .ExecutableWork ;
4241import org .apache .beam .runners .dataflow .worker .streaming .Watermarks ;
@@ -251,73 +250,103 @@ public void testPollWorkTargeted() {
251250
252251 @ Test
253252 public void testConcurrentStress () throws InterruptedException , ExecutionException {
254- final KeyGroupWorkQueue queue = new KeyGroupWorkQueue (fairQueue );
255- final int producerThreads = 4 ;
256- final int consumerThreads = 4 ;
257- final int tasksPerProducer = 1000 ;
258- final int totalTasks = producerThreads * tasksPerProducer ;
253+ KeyGroupWorkQueue queue = new KeyGroupWorkQueue (fairQueue );
254+ int producerThreads = 4 ;
255+ int consumerThreads = 4 ;
256+ int tasksPerProducer = 1000 ;
257+ int totalTasks = producerThreads * tasksPerProducer ;
258+ Runnable poisonPill =
259+ new Runnable () {
260+ @ Override
261+ public void run () {}
262+
263+ @ Override
264+ public String toString () {
265+ return "POISON_PILL" ;
266+ }
267+ };
259268
260269 ExecutorService executorService =
261270 Executors .newFixedThreadPool (producerThreads + consumerThreads );
262- final CountDownLatch startLatch = new CountDownLatch (1 );
263- final CountDownLatch doneLatch = new CountDownLatch (producerThreads + consumerThreads );
264- final AtomicInteger consumedCount = new AtomicInteger (0 );
271+ CountDownLatch startLatch = new CountDownLatch (1 );
272+ CountDownLatch producersDoneLatch = new CountDownLatch (producerThreads );
273+ CountDownLatch consumersDoneLatch = new CountDownLatch (consumerThreads );
274+ CountDownLatch consumedLatch = new CountDownLatch (totalTasks );
265275 List <Future <?>> futures = new ArrayList <>();
266276
267- // Start producers
268- for (int i = 0 ; i < producerThreads ; i ++) {
277+ // Start consumers
278+ for (int i = 0 ; i < consumerThreads ; i ++) {
279+ int consumerId = i ;
269280 futures .add (
270281 executorService .submit (
271282 () -> {
272283 try {
273284 startLatch .await ();
274- for (int j = 0 ; j < tasksPerProducer ; j ++) {
275- String compId = "comp-" + (j % 5 );
276- queue .offer (createQueuedWork (compId , 10 ));
285+ int iteration = consumerId % 4 ;
286+ while (true ) {
287+ int strategy = iteration ;
288+ iteration = (iteration + 1 ) % 4 ;
289+ Runnable task = null ;
290+ if (strategy == 0 ) {
291+ String compId = "comp-" + (consumedLatch .getCount () % 5 );
292+ task = queue .pollWork (compId , TEST_KEY_GROUP );
293+ } else if (strategy == 1 ) {
294+ task = queue .poll ();
295+ } else if (strategy == 2 ) {
296+ task = queue .poll (10 , TimeUnit .MICROSECONDS );
297+ } else if (strategy == 3 ) {
298+ task = queue .take ();
299+ }
300+
301+ if (task == poisonPill ) {
302+ break ;
303+ }
304+ if (task != null ) {
305+ consumedLatch .countDown ();
306+ }
277307 }
278308 } catch (Exception e ) {
279309 throw new RuntimeException (e );
280310 } finally {
281- doneLatch .countDown ();
311+ consumersDoneLatch .countDown ();
282312 }
283313 }));
284314 }
285315
286- // Start consumers (mix of poll and pollWork)
287- for (int i = 0 ; i < consumerThreads ; i ++) {
288- final int consumerId = i ;
316+ // Start producers
317+ for (int i = 0 ; i < producerThreads ; i ++) {
289318 futures .add (
290319 executorService .submit (
291320 () -> {
292321 try {
293322 startLatch .await ();
294- while (consumedCount .get () < totalTasks ) {
295- Runnable task ;
296- if (consumerId % 2 == 0 ) {
297- // Targeted poll
298- String compId = "comp-" + (consumedCount .get () % 5 );
299- task = queue .pollWork (compId , TEST_KEY_GROUP );
300- } else {
301- // Global poll
302- task = queue .poll ();
303- if (task == null ) {
304- task = queue .poll (10 , TimeUnit .MICROSECONDS );
305- }
306- }
307- if (task != null ) {
308- consumedCount .incrementAndGet ();
309- }
323+ for (int j = 0 ; j < tasksPerProducer ; j ++) {
324+ String compId = "comp-" + (j % 5 );
325+ queue .offer (createQueuedWork (compId , 10 ));
310326 }
311327 } catch (Exception e ) {
312328 throw new RuntimeException (e );
313329 } finally {
314- doneLatch .countDown ();
330+ producersDoneLatch .countDown ();
315331 }
316332 }));
317333 }
318334
335+ // Release the start latch to start the test
319336 startLatch .countDown ();
320- assertTrue (doneLatch .await (30 , TimeUnit .SECONDS ));
337+
338+ // Wait for all tasks to be consumed
339+ assertTrue (consumedLatch .await (30 , TimeUnit .SECONDS ));
340+
341+ // Send poison pills to stop all consumers
342+ for (int i = 0 ; i < consumerThreads ; i ++) {
343+ queue .offer (poisonPill );
344+ }
345+
346+ // Wait for consumers to finish
347+ assertTrue (consumersDoneLatch .await (30 , TimeUnit .SECONDS ));
348+ // Wait for producers to finish
349+ assertTrue (producersDoneLatch .await (30 , TimeUnit .SECONDS ));
321350
322351 // Check for exceptions in threads
323352 for (Future <?> future : futures ) {
@@ -355,10 +384,7 @@ public void testTakeBlocksAndWakesUp() throws InterruptedException {
355384 t .start ();
356385
357386 assertTrue (started .await (30 , TimeUnit .SECONDS ));
358- while (t .getState () != State .WAITING ) {
359- Thread .sleep (1 );
360- }
361- assertEquals (Thread .State .WAITING , t .getState ());
387+ waitForThreadState (t , State .WAITING );
362388
363389 queue .offer (task );
364390
@@ -391,10 +417,7 @@ public void testPollWithTimeout() throws InterruptedException {
391417 t1 .start ();
392418
393419 assertTrue (started .await (30 , TimeUnit .SECONDS ));
394- while (t1 .getState () != State .TIMED_WAITING ) {
395- Thread .sleep (1 );
396- }
397- assertEquals (Thread .State .TIMED_WAITING , t1 .getState ());
420+ waitForThreadState (t1 , State .TIMED_WAITING );
398421
399422 assertTrue (finished .await (30 , TimeUnit .SECONDS ));
400423 assertNull (result .get ());
@@ -420,10 +443,7 @@ public void testPollWithTimeout() throws InterruptedException {
420443 t2 .start ();
421444
422445 assertTrue (started2 .await (30 , TimeUnit .SECONDS ));
423- while (t2 .getState () != State .TIMED_WAITING ) {
424- Thread .sleep (1 );
425- }
426- assertEquals (Thread .State .TIMED_WAITING , t2 .getState ());
446+ waitForThreadState (t2 , State .TIMED_WAITING );
427447
428448 queue .offer (task );
429449
@@ -470,4 +490,15 @@ public void testPollWorkWithKeyGroup() {
470490 assertNull (polledNotExist );
471491 assertTrue (queue .isEmpty ());
472492 }
493+
494+ private void waitForThreadState (Thread t , State state ) throws InterruptedException {
495+ long timeoutMs = 30000 ;
496+ long start = System .currentTimeMillis ();
497+ while (t .getState () != state ) {
498+ if (System .currentTimeMillis () - start > timeoutMs ) {
499+ fail ("Thread did not reach " + state + " state within " + timeoutMs + "ms" );
500+ }
501+ Thread .sleep (1 );
502+ }
503+ }
473504}
0 commit comments