From 158e39760b00bc8db649fa0943949246462322ba Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 4 Sep 2026 21:47:41 +0300 Subject: [PATCH] Core: invoke ready watcher even if discovery fails In async transport initialization (buildAsync) the ready watcher was never called when discovery failed: YdbDiscovery.waitReady throws IllegalStateException and the exception was swallowed by the executor, so the caller waited forever. Wrap waitReady in try/catch/finally so readyWatcher.run() is always executed, and log the discovery failure with its cause. Fixes #294 --- CHANGELOG.md | 3 ++ .../tech/ydb/core/impl/YdbTransportImpl.java | 9 ++++-- .../ydb/core/impl/YdbTransportImplTest.java | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c213392..84a62a8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.4.12 ## +* Core: fixed ready watcher invocation on discovery error + ## 2.4.11 ## * Table: fixed StackOverflow problem for the immediately released sessions diff --git a/core/src/main/java/tech/ydb/core/impl/YdbTransportImpl.java b/core/src/main/java/tech/ydb/core/impl/YdbTransportImpl.java index d0d8e1e4d..e3a38f3a8 100644 --- a/core/src/main/java/tech/ydb/core/impl/YdbTransportImpl.java +++ b/core/src/main/java/tech/ydb/core/impl/YdbTransportImpl.java @@ -83,8 +83,13 @@ public void startAsync(Runnable readyWatcher) { discovery.start(); if (readyWatcher != null) { scheduler.execute(() -> { - discovery.waitReady(-1); - readyWatcher.run(); + try { + discovery.waitReady(-1); + } catch (RuntimeException ex) { + logger.warn("Discovery failed during async transport initialization", ex); + } finally { + readyWatcher.run(); + } }); } } diff --git a/core/src/test/java/tech/ydb/core/impl/YdbTransportImplTest.java b/core/src/test/java/tech/ydb/core/impl/YdbTransportImplTest.java index e5fc1c5ab..5978a3988 100644 --- a/core/src/test/java/tech/ydb/core/impl/YdbTransportImplTest.java +++ b/core/src/test/java/tech/ydb/core/impl/YdbTransportImplTest.java @@ -186,6 +186,34 @@ public void asyncBuildGoodTest() { Assert.assertTrue(isReady.isDone()); } + @Test + public void asyncBuildDiscoveryErrorTest() { + MockedScheduler scheduler = new MockedScheduler(MockedClock.create(ZoneId.of("UTC"))); + + Mockito.when(discoveryChannel.newCall(Mockito.eq(DiscoveryServiceGrpc.getListEndpointsMethod()), Mockito.any())) + .thenReturn(MockedCall.unavailable()); + + CompletableFuture isReady = new CompletableFuture<>(); + + @SuppressWarnings("deprecation") + GrpcTransport transport = GrpcTransport.forConnectionString("grpc://mocked:2136/local") + .withSchedulerFactory(() -> scheduler) + .withDiscoveryTimeout(Duration.ofMillis(100)) + .withChannelFactoryBuilder(builder -> channelFactory) + .buildAsync(() -> isReady.complete(null)); + + Assert.assertNotNull(transport); + Assert.assertFalse(isReady.isDone()); + + // Run discovery task and ready-watcher task + scheduler.hasTasksCount(2).runNextTask().runNextTask(); + + // readyWatcher must be called even if discovery fails + Assert.assertTrue(isReady.isDone()); + + transport.close(); + } + @Test @SuppressWarnings("SleepWhileInLoop") public void asyncWaitingForReadyTest() throws Exception {