|
12 | 12 | import org.opensearch.common.network.NetworkService; |
13 | 13 | import org.opensearch.common.settings.Settings; |
14 | 14 | import org.opensearch.common.util.concurrent.OpenSearchExecutors; |
| 15 | +import org.opensearch.core.common.transport.BoundTransportAddress; |
15 | 16 | import org.opensearch.core.common.transport.TransportAddress; |
16 | 17 | import org.opensearch.test.OpenSearchTestCase; |
17 | 18 | import org.opensearch.transport.grpc.ssl.NettyGrpcClient; |
@@ -324,6 +325,209 @@ public void testExecutorCountSettingsValidation() { |
324 | 325 | expectThrows(IllegalArgumentException.class, () -> { new Netty4GrpcServerTransport(invalidSettings, services, networkService); }); |
325 | 326 | } |
326 | 327 |
|
| 328 | + public void testStartFailureTriggersCleanup() { |
| 329 | + // Create a transport that will fail to start |
| 330 | + Settings settingsWithInvalidPort = Settings.builder() |
| 331 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), "999999") // Invalid port |
| 332 | + .build(); |
| 333 | + |
| 334 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settingsWithInvalidPort, services, networkService); |
| 335 | + |
| 336 | + // Start should fail |
| 337 | + expectThrows(Exception.class, transport::start); |
| 338 | + |
| 339 | + // Resources should be cleaned up after failure - the implementation calls doStop() in the finally block |
| 340 | + ExecutorService executor = transport.getGrpcExecutorForTesting(); |
| 341 | + EventLoopGroup bossGroup = transport.getBossEventLoopGroupForTesting(); |
| 342 | + EventLoopGroup workerGroup = transport.getWorkerEventLoopGroupForTesting(); |
| 343 | + |
| 344 | + // Resources may still exist but should be shutdown |
| 345 | + if (executor != null) { |
| 346 | + assertTrue("Executor should be shutdown after failed start", executor.isShutdown()); |
| 347 | + } |
| 348 | + if (bossGroup != null) { |
| 349 | + assertTrue("Boss group should be shutdown after failed start", bossGroup.isShutdown()); |
| 350 | + } |
| 351 | + if (workerGroup != null) { |
| 352 | + assertTrue("Worker group should be shutdown after failed start", workerGroup.isShutdown()); |
| 353 | + } |
| 354 | + |
| 355 | + // Close should still be safe to call |
| 356 | + transport.close(); |
| 357 | + } |
| 358 | + |
| 359 | + public void testInterruptedShutdownHandling() throws InterruptedException { |
| 360 | + Settings settings = createSettings(); |
| 361 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 362 | + |
| 363 | + transport.start(); |
| 364 | + |
| 365 | + // Interrupt the current thread to test interrupt handling |
| 366 | + Thread.currentThread().interrupt(); |
| 367 | + |
| 368 | + // Stop should handle the interrupt gracefully |
| 369 | + transport.stop(); |
| 370 | + |
| 371 | + // Clear interrupt status |
| 372 | + Thread.interrupted(); |
| 373 | + |
| 374 | + transport.close(); |
| 375 | + } |
| 376 | + |
| 377 | + public void testInvalidHostBinding() { |
| 378 | + // Test with invalid bind host to trigger host resolution error |
| 379 | + Settings settings = Settings.builder() |
| 380 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), OpenSearchTestCase.getPortRange()) |
| 381 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_BIND_HOST.getKey(), "invalid.host.that.does.not.exist") |
| 382 | + .build(); |
| 383 | + |
| 384 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 385 | + |
| 386 | + // Start should fail due to host resolution failure |
| 387 | + expectThrows(Exception.class, transport::start); |
| 388 | + |
| 389 | + transport.close(); |
| 390 | + } |
| 391 | + |
| 392 | + public void testPublishPortResolutionFailure() { |
| 393 | + // Create settings that will cause publish port resolution to fail |
| 394 | + Settings settings = Settings.builder() |
| 395 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), "0") // Dynamic port |
| 396 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_PUBLISH_PORT.getKey(), "65536") // Invalid publish port |
| 397 | + .build(); |
| 398 | + |
| 399 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 400 | + |
| 401 | + // Start should fail due to publish port resolution |
| 402 | + expectThrows(Exception.class, transport::start); |
| 403 | + |
| 404 | + transport.close(); |
| 405 | + } |
| 406 | + |
| 407 | + public void testMultipleBindAddresses() { |
| 408 | + // Test binding to multiple localhost addresses |
| 409 | + Settings settings = Settings.builder() |
| 410 | + .put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), OpenSearchTestCase.getPortRange()) |
| 411 | + .putList(Netty4GrpcServerTransport.SETTING_GRPC_BIND_HOST.getKey(), "127.0.0.1", "localhost") |
| 412 | + .build(); |
| 413 | + |
| 414 | + try (Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService)) { |
| 415 | + transport.start(); |
| 416 | + |
| 417 | + BoundTransportAddress boundAddress = transport.getBoundAddress(); |
| 418 | + assertNotNull("Bound address should not be null", boundAddress); |
| 419 | + assertTrue("Should have at least one bound address", boundAddress.boundAddresses().length > 0); |
| 420 | + |
| 421 | + transport.stop(); |
| 422 | + } |
| 423 | + } |
| 424 | + |
| 425 | + public void testShutdownTimeoutHandling() throws InterruptedException { |
| 426 | + Settings settings = createSettings(); |
| 427 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 428 | + |
| 429 | + transport.start(); |
| 430 | + |
| 431 | + // Get references to the thread pools |
| 432 | + ExecutorService executor = transport.getGrpcExecutorForTesting(); |
| 433 | + EventLoopGroup bossGroup = transport.getBossEventLoopGroupForTesting(); |
| 434 | + EventLoopGroup workerGroup = transport.getWorkerEventLoopGroupForTesting(); |
| 435 | + |
| 436 | + assertNotNull("Executor should be created", executor); |
| 437 | + assertNotNull("Boss group should be created", bossGroup); |
| 438 | + assertNotNull("Worker group should be created", workerGroup); |
| 439 | + |
| 440 | + // Normal shutdown should work |
| 441 | + transport.stop(); |
| 442 | + |
| 443 | + // Verify everything is shutdown |
| 444 | + assertTrue("Executor should be shutdown", executor.isShutdown()); |
| 445 | + assertTrue("Boss group should be shutdown", bossGroup.isShutdown()); |
| 446 | + assertTrue("Worker group should be shutdown", workerGroup.isShutdown()); |
| 447 | + |
| 448 | + transport.close(); |
| 449 | + } |
| 450 | + |
| 451 | + public void testResourceCleanupOnClose() { |
| 452 | + Settings settings = createSettings(); |
| 453 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 454 | + |
| 455 | + transport.start(); |
| 456 | + transport.stop(); |
| 457 | + |
| 458 | + // doClose should handle cleanup gracefully even if resources are already shutdown |
| 459 | + transport.close(); |
| 460 | + |
| 461 | + // Multiple closes should be safe |
| 462 | + transport.close(); |
| 463 | + } |
| 464 | + |
| 465 | + public void testPortRangeHandling() { |
| 466 | + // Test with a port range |
| 467 | + Settings settings = Settings.builder().put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), "9300-9400").build(); |
| 468 | + |
| 469 | + try (Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService)) { |
| 470 | + transport.start(); |
| 471 | + |
| 472 | + BoundTransportAddress boundAddress = transport.getBoundAddress(); |
| 473 | + assertNotNull("Bound address should not be null", boundAddress); |
| 474 | + |
| 475 | + int actualPort = boundAddress.publishAddress().getPort(); |
| 476 | + assertTrue("Port should be in range 9300-9400", actualPort >= 9300 && actualPort <= 9400); |
| 477 | + |
| 478 | + transport.stop(); |
| 479 | + } |
| 480 | + } |
| 481 | + |
| 482 | + public void testGracefulShutdownWithException() { |
| 483 | + // Test that exceptions during shutdown don't prevent cleanup |
| 484 | + Settings settings = createSettings(); |
| 485 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 486 | + |
| 487 | + transport.start(); |
| 488 | + |
| 489 | + // Simulate an interruption during shutdown to test exception handling paths |
| 490 | + ExecutorService executor = transport.getGrpcExecutorForTesting(); |
| 491 | + EventLoopGroup bossGroup = transport.getBossEventLoopGroupForTesting(); |
| 492 | + EventLoopGroup workerGroup = transport.getWorkerEventLoopGroupForTesting(); |
| 493 | + |
| 494 | + assertNotNull("Executor should be created", executor); |
| 495 | + assertNotNull("Boss group should be created", bossGroup); |
| 496 | + assertNotNull("Worker group should be created", workerGroup); |
| 497 | + |
| 498 | + // Force shutdown to test the interrupt handling code paths |
| 499 | + executor.shutdownNow(); |
| 500 | + bossGroup.shutdownGracefully(0, 0, java.util.concurrent.TimeUnit.MILLISECONDS); |
| 501 | + workerGroup.shutdownGracefully(0, 0, java.util.concurrent.TimeUnit.MILLISECONDS); |
| 502 | + |
| 503 | + // Now call stop - it should handle the already shutdown resources gracefully |
| 504 | + transport.stop(); |
| 505 | + transport.close(); |
| 506 | + |
| 507 | + // Verify everything is still properly shutdown |
| 508 | + assertTrue("Executor should be shutdown", executor.isShutdown()); |
| 509 | + assertTrue("Boss group should be shutdown", bossGroup.isShutdown()); |
| 510 | + assertTrue("Worker group should be shutdown", workerGroup.isShutdown()); |
| 511 | + } |
| 512 | + |
| 513 | + public void testCloseWithNullResources() { |
| 514 | + // Test that close() handles null resources gracefully |
| 515 | + Settings settings = createSettings(); |
| 516 | + Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(settings, services, networkService); |
| 517 | + |
| 518 | + // Don't start the transport, so resources should be null |
| 519 | + assertNull("Boss group should be null before start", transport.getBossEventLoopGroupForTesting()); |
| 520 | + assertNull("Worker group should be null before start", transport.getWorkerEventLoopGroupForTesting()); |
| 521 | + assertNull("Executor should be null before start", transport.getGrpcExecutorForTesting()); |
| 522 | + |
| 523 | + // Close should handle null resources gracefully |
| 524 | + transport.close(); |
| 525 | + |
| 526 | + // Multiple closes should be safe |
| 527 | + transport.close(); |
| 528 | + transport.close(); |
| 529 | + } |
| 530 | + |
327 | 531 | private static Settings createSettings() { |
328 | 532 | return Settings.builder().put(Netty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), OpenSearchTestCase.getPortRange()).build(); |
329 | 533 | } |
|
0 commit comments