2626import static org .junit .jupiter .api .Assertions .assertNotNull ;
2727import static org .junit .jupiter .api .Assertions .assertTrue ;
2828
29+ import java .lang .System .Logger ;
30+ import java .lang .System .Logger .Level ;
2931import java .lang .management .ManagementFactory ;
3032import java .lang .reflect .Constructor ;
3133import java .util .concurrent .CompletableFuture ;
34+ import java .util .concurrent .CompletionException ;
35+ import java .util .concurrent .CountDownLatch ;
3236import java .util .concurrent .ForkJoinPool ;
3337import java .util .concurrent .SubmissionPublisher ;
3438import java .util .concurrent .TimeUnit ;
4549import org .apache .ignite .client .fakes .FakeIgniteQueryProcessor ;
4650import org .apache .ignite .client .fakes .FakeIgniteTables ;
4751import org .apache .ignite .internal .client .ClientMetricSource ;
52+ import org .apache .ignite .internal .client .ReliableChannel ;
4853import org .apache .ignite .internal .client .TcpIgniteClient ;
4954import org .apache .ignite .internal .metrics .AbstractMetricSource ;
5055import org .apache .ignite .internal .metrics .MetricSet ;
5156import org .apache .ignite .internal .testframework .BaseIgniteAbstractTest ;
5257import org .apache .ignite .internal .testframework .IgniteTestUtils ;
5358import org .apache .ignite .internal .testframework .WithSystemProperty ;
5459import org .apache .ignite .lang .ErrorGroups .Sql ;
60+ import org .apache .ignite .lang .LoggerFactory ;
5561import org .apache .ignite .table .DataStreamerItem ;
5662import org .apache .ignite .table .Table ;
5763import org .apache .ignite .table .Tuple ;
5864import org .junit .jupiter .api .AfterEach ;
5965import org .junit .jupiter .api .Test ;
6066import org .junit .jupiter .params .ParameterizedTest ;
6167import org .junit .jupiter .params .provider .ValueSource ;
68+ import org .mockito .Mockito ;
6269
6370/**
6471 * Tests client-side metrics (see also server-side metrics tests in {@link ServerMetricsTest}).
@@ -143,10 +150,27 @@ public void testHandshakesFailed() {
143150 }
144151
145152 @ Test
146- public void testHandshakesFailedTimeout () throws InterruptedException {
147- AtomicInteger counter = new AtomicInteger ();
153+ public void testHandshakesFailedTimeout () {
154+ // Record handshake timeout logs.
155+ // These logs are sent after the timeout is detected by the timeout task and after the metric manager is updated.
156+ // Therefore it's safe to way for them.
157+ // Checkout: org.apache.ignite.internal.client.TcpClientChannel.handshakeAsync
158+ CountDownLatch latch = new CountDownLatch (1 );
159+ HandshakeTimeoutLoggerListener handshakeTimeoutListener = new HandshakeTimeoutLoggerListener (latch );
160+
148161 Function <Integer , Boolean > shouldDropConnection = requestIdx -> false ;
149- Function <Integer , Integer > responseDelay = idx -> counter .incrementAndGet () < 3 ? 600 : 0 ;
162+ // Blocks until a timeout was observed.
163+ Function <Integer , Integer > responseDelay = idx -> {
164+ try {
165+ latch .await ();
166+ } catch (InterruptedException e ) {
167+ Thread .currentThread ().interrupt ();
168+ throw new RuntimeException ("Interrupted while waiting for handshake timeout latch" , e );
169+ }
170+
171+ return 0 ;
172+ };
173+
150174 server = new TestServer (
151175 1000 ,
152176 new FakeIgnite (),
@@ -157,13 +181,14 @@ public void testHandshakesFailedTimeout() throws InterruptedException {
157181 null ,
158182 null
159183 );
184+
160185 client = clientBuilder ()
161186 .connectTimeout (100 )
187+ .loggerFactory (handshakeTimeoutListener )
162188 .build ();
163189
164- assertTrue (
165- IgniteTestUtils .waitForCondition (() -> metrics ().handshakesFailedTimeout () >= 1 , 200 , 6_000 ),
166- () -> "handshakesFailedTimeout: " + metrics ().handshakesFailedTimeout ());
190+ long numObservedTimeouts = handshakeTimeoutListener .count ();
191+ assertThat ("handshakesFailedTimeout" , metrics ().handshakesFailedTimeout (), greaterThanOrEqualTo (numObservedTimeouts ));
167192 }
168193
169194 @ SuppressWarnings ("resource" )
@@ -407,4 +432,52 @@ private Builder clientBuilder() {
407432 private ClientMetricSource metrics () {
408433 return ((TcpIgniteClient ) client ).metrics ();
409434 }
435+
436+ /** This logger factory intercepts and counts Handshake Timeout messages. Can be generalised in the future for other messages. */
437+ private static class HandshakeTimeoutLoggerListener implements LoggerFactory {
438+ private final CountDownLatch latch ;
439+
440+ private final AtomicInteger counter ;
441+
442+ HandshakeTimeoutLoggerListener (CountDownLatch latch ) {
443+ this .latch = latch ;
444+ this .counter = new AtomicInteger (0 );
445+ }
446+
447+ @ Override
448+ public Logger forName (String name ) {
449+ Logger base = System .getLogger (name );
450+ if (ReliableChannel .class .getName ().equals (name )) {
451+ Logger tracker = Mockito .mock (
452+ base .getClass (),
453+ Mockito .withSettings ()
454+ .spiedInstance (base )
455+ .defaultAnswer (Mockito .CALLS_REAL_METHODS )
456+ .stubOnly ()
457+ );
458+
459+ Mockito .doAnswer (inv -> {
460+ Throwable err = inv .getArgument (2 );
461+ if (err instanceof CompletionException && err .getCause () instanceof IgniteClientConnectionException ) {
462+ IgniteClientConnectionException ex = (IgniteClientConnectionException ) err .getCause ();
463+ if (ex .getMessage ().startsWith ("Handshake timeout" )) {
464+ // Updates the timeout counter and releases the responses so that we can connect to the server.
465+ counter .getAndIncrement ();
466+ latch .countDown ();
467+ }
468+ }
469+
470+ return inv .callRealMethod ();
471+ }).when (tracker ).log (Mockito .eq (Level .WARNING ), Mockito .anyString (), Mockito .any (Throwable .class ));
472+
473+ return tracker ;
474+ } else {
475+ return base ;
476+ }
477+ }
478+
479+ int count () {
480+ return this .counter .get ();
481+ }
482+ }
410483}
0 commit comments