2929import io .aeron .test .driver .TestMediaDriver ;
3030import org .agrona .CloseHelper ;
3131import org .agrona .collections .MutableInteger ;
32+ import org .agrona .concurrent .NoOpIdleStrategy ;
3233import org .agrona .concurrent .UnsafeBuffer ;
3334import org .agrona .concurrent .status .CountersReader ;
3435import org .junit .jupiter .api .AfterEach ;
@@ -85,14 +86,17 @@ private static List<String> channels()
8586 @ RegisterExtension
8687 final SystemTestWatcher watcher = new SystemTestWatcher ();
8788
88- private void launch ()
89+ private void launch (final ThreadingMode threadingMode )
8990 {
9091 driverContext
9192 .aeronDirectoryName (CommonContext .generateRandomDirName ())
9293 .publicationTermBufferLength (TERM_BUFFER_LENGTH )
9394 .errorHandler (Tests ::onError )
9495 .dirDeleteOnStart (true )
95- .threadingMode (ThreadingMode .SHARED );
96+ .threadingMode (threadingMode )
97+ .senderIdleStrategy (NoOpIdleStrategy .INSTANCE )
98+ .receiverIdleStrategy (NoOpIdleStrategy .INSTANCE )
99+ .conductorIdleStrategy (NoOpIdleStrategy .INSTANCE );
96100
97101 driver = TestMediaDriver .launch (driverContext , watcher );
98102 watcher .dataCollector ().add (driver .context ().aeronDirectory ());
@@ -111,7 +115,7 @@ void after()
111115 @ InterruptAfter (10 )
112116 void shouldNotSimulateConnectionWhenNotConfigured (final String channel )
113117 {
114- launch ();
118+ launch (ThreadingMode . SHARED );
115119
116120 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
117121 publication = client .addPublication (channel , STREAM_ID );
@@ -126,7 +130,7 @@ void shouldNotSimulateConnectionWhenNotConfigured(final String channel)
126130 @ InterruptAfter (10 )
127131 void shouldSimulateConnectionWhenOnChannel (final String channel )
128132 {
129- launch ();
133+ launch (ThreadingMode . SHARED );
130134
131135 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
132136 publication = client .addPublication (channel + "|ssc=true" , STREAM_ID );
@@ -147,7 +151,7 @@ void shouldSimulateConnectionWithNoNetworkSubscriptions(final String channel)
147151 .timerIntervalNs (TimeUnit .MILLISECONDS .toNanos (100 ))
148152 .spiesSimulateConnection (true );
149153
150- launch ();
154+ launch (ThreadingMode . SHARED );
151155
152156 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
153157 publication = client .addPublication (channel , STREAM_ID );
@@ -198,7 +202,7 @@ void shouldSimulateConnectionWithSlowNetworkSubscription(final String channel)
198202 .timerIntervalNs (TimeUnit .MILLISECONDS .toNanos (100 ))
199203 .spiesSimulateConnection (true );
200204
201- launch ();
205+ launch (ThreadingMode . SHARED );
202206
203207 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
204208 subscription = client .addSubscription (channel , STREAM_ID );
@@ -247,7 +251,7 @@ void shouldSimulateConnectionWithLeavingNetworkSubscription(final String channel
247251 .timerIntervalNs (TimeUnit .MILLISECONDS .toNanos (100 ))
248252 .spiesSimulateConnection (true );
249253
250- launch ();
254+ launch (ThreadingMode . SHARED );
251255
252256 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
253257 subscription = client .addSubscription (channel , STREAM_ID );
@@ -290,7 +294,7 @@ else if (!isSubscriptionClosed)
290294 @ InterruptAfter (10 )
291295 void shouldNotHaveShortSendsWithMDCPublication ()
292296 {
293- launch ();
297+ launch (ThreadingMode . SHARED );
294298
295299 final String channel = "aeron:udp?control=localhost:20000|control-mode=dynamic" ;
296300
@@ -323,7 +327,7 @@ void shouldNotHaveShortSendsWithMDCPublication()
323327 void shouldNotChangeConnectionStatusOnPublicationIfNormalSubscriberGoesAway (final String channel )
324328 throws InterruptedException
325329 {
326- launch ();
330+ launch (ThreadingMode . SHARED );
327331
328332 spy = client .addSubscription (spyForChannel (channel ), STREAM_ID );
329333 subscription = client .addSubscription (channel , STREAM_ID );
@@ -377,6 +381,64 @@ void shouldNotChangeConnectionStatusOnPublicationIfNormalSubscriberGoesAway(fina
377381 assertTrue (spy .isConnected ());
378382 }
379383
384+ @ ParameterizedTest
385+ @ MethodSource ("channels" )
386+ @ InterruptAfter (10 )
387+ void addingAndRemovingSpiesIsThreadSafe (final String channel ) throws InterruptedException
388+ {
389+ launch (ThreadingMode .DEDICATED );
390+
391+ final String spyChannel = spyForChannel (channel );
392+ spy = client .addSubscription (spyChannel , STREAM_ID );
393+ publication = client .addPublication (channel + "|ssc=true" , STREAM_ID );
394+ Tests .awaitConnected (publication );
395+ Tests .awaitConnected (spy );
396+
397+ final AtomicBoolean running = new AtomicBoolean (true );
398+ final Thread sender = new Thread (() ->
399+ {
400+ while (running .get ())
401+ {
402+ if (publication .offer (buffer ) < 0 )
403+ {
404+ Tests .yield ();
405+ }
406+ }
407+ });
408+ sender .start ();
409+
410+ final long [] spyRegistrationIds = new long [100 ];
411+ for (int i = 0 ; i < spyRegistrationIds .length ; i ++)
412+ {
413+ spyRegistrationIds [i ] = client .asyncAddSubscription (spyChannel , STREAM_ID );
414+ }
415+
416+ final Subscription [] spies = new Subscription [spyRegistrationIds .length ];
417+
418+ int count ;
419+ do
420+ {
421+ count = 0 ;
422+ for (int i = 0 ; i < spyRegistrationIds .length ; i ++)
423+ {
424+ if (null == spies [i ])
425+ {
426+ spies [i ] = client .getSubscription (spyRegistrationIds [i ]);
427+ }
428+ else
429+ {
430+ count ++;
431+ }
432+ }
433+ }
434+ while (spyRegistrationIds .length != count );
435+
436+ CloseHelper .closeAll (spies );
437+
438+ running .set (false );
439+ sender .join ();
440+ }
441+
380442 private void waitUntilFullConnectivity ()
381443 {
382444 while (!spy .isConnected () || !subscription .isConnected () || !publication .isConnected ())
0 commit comments