|
15 | 15 | import java.util.concurrent.ExecutorService; |
16 | 16 | import java.util.concurrent.Executors; |
17 | 17 | import java.util.concurrent.Future; |
| 18 | +import java.util.concurrent.atomic.AtomicBoolean; |
18 | 19 | import java.util.function.BiConsumer; |
19 | 20 | import java.util.function.Consumer; |
20 | 21 | import java.util.function.Function; |
@@ -289,6 +290,78 @@ void shouldRunLambdasOnError() throws Exception { |
289 | 290 | verify(afterError, timeout(TIMEOUT)).accept(eq(errorFeatureProvider), any()); |
290 | 291 | } |
291 | 292 | } |
| 293 | + |
| 294 | + @Nested |
| 295 | + class GracefulShutdownBehavior { |
| 296 | + |
| 297 | + @Test |
| 298 | + @DisplayName("should complete shutdown successfully when executor terminates within timeout") |
| 299 | + void shouldCompleteShutdownSuccessfullyWhenExecutorTerminatesWithinTimeout() { |
| 300 | + FeatureProvider provider = createMockedProvider(); |
| 301 | + setFeatureProvider(provider); |
| 302 | + |
| 303 | + assertThatCode(() -> providerRepository.shutdown()).doesNotThrowAnyException(); |
| 304 | + |
| 305 | + verify(provider, timeout(TIMEOUT)).shutdown(); |
| 306 | + } |
| 307 | + |
| 308 | + @Test |
| 309 | + @DisplayName("should force shutdown when executor does not terminate within timeout") |
| 310 | + void shouldForceShutdownWhenExecutorDoesNotTerminateWithinTimeout() throws Exception { |
| 311 | + FeatureProvider provider = createMockedProvider(); |
| 312 | + AtomicBoolean wasInterrupted = new AtomicBoolean(false); |
| 313 | + doAnswer(invocation -> { |
| 314 | + try { |
| 315 | + Thread.sleep(TIMEOUT); |
| 316 | + } catch (InterruptedException e) { |
| 317 | + wasInterrupted.set(true); |
| 318 | + throw e; |
| 319 | + } |
| 320 | + return null; |
| 321 | + }).when(provider).shutdown(); |
| 322 | + |
| 323 | + setFeatureProvider(provider); |
| 324 | + |
| 325 | + assertThatCode(() -> providerRepository.shutdown()).doesNotThrowAnyException(); |
| 326 | + |
| 327 | + verify(provider, timeout(TIMEOUT)).shutdown(); |
| 328 | + // Verify that shutdownNow() interrupted the running shutdown task |
| 329 | + await().atMost(Duration.ofSeconds(1)) |
| 330 | + .untilAsserted(() -> assertThat(wasInterrupted.get()).isTrue()); |
| 331 | + } |
| 332 | + |
| 333 | + @Test |
| 334 | + @DisplayName("should handle interruption during shutdown gracefully") |
| 335 | + void shouldHandleInterruptionDuringShutdownGracefully() throws Exception { |
| 336 | + FeatureProvider provider = createMockedProvider(); |
| 337 | + setFeatureProvider(provider); |
| 338 | + |
| 339 | + Thread shutdownThread = new Thread(() -> { |
| 340 | + providerRepository.shutdown(); |
| 341 | + }); |
| 342 | + |
| 343 | + shutdownThread.start(); |
| 344 | + shutdownThread.interrupt(); |
| 345 | + shutdownThread.join(TIMEOUT); |
| 346 | + |
| 347 | + assertThat(shutdownThread.isAlive()).isFalse(); |
| 348 | + verify(provider, timeout(TIMEOUT)).shutdown(); |
| 349 | + } |
| 350 | + |
| 351 | + @Test |
| 352 | + @DisplayName("should not hang indefinitely on shutdown") |
| 353 | + void shouldNotHangIndefinitelyOnShutdown() { |
| 354 | + FeatureProvider provider = createMockedProvider(); |
| 355 | + setFeatureProvider(provider); |
| 356 | + |
| 357 | + await().alias("shutdown should complete within reasonable time") |
| 358 | + .atMost(Duration.ofSeconds(5)) |
| 359 | + .until(() -> { |
| 360 | + providerRepository.shutdown(); |
| 361 | + return true; |
| 362 | + }); |
| 363 | + } |
| 364 | + } |
292 | 365 | } |
293 | 366 |
|
294 | 367 | @Test |
|
0 commit comments