Skip to content

Conversation

@novoj
Copy link

@novoj novoj commented Dec 27, 2025

Added parameters to enable setting the quiet period and timeout for graceful shutdown of worker groups, both globally and through the ClientFactoryBuilder.

Make use of graceful shutdown settings on worker groups on the server side.

Refs: #5813

Motivation:

This pull request is continuation of a discussion in less ambitious PR #6528 and tries to materialize @jrhee17 proposals.

Modifications:

Graceful shutdown support for client worker groups:

  • Added a new WORKER_GROUP_GRACEFUL_SHUTDOWN option to ClientFactoryOptions, enabling configuration of graceful shutdown for the worker EventLoopGroup when the ClientFactory is closed. Includes a new getter method and integration with the shutdown process. [1] [2] [3]
  • Extended ClientFactoryBuilder with overloaded workerGroup methods that accept a GracefulShutdown parameter, allowing users to customize shutdown behavior when creating client factories.

Refactoring and generalization of graceful shutdown classes:

  • Moved and renamed GracefulShutdown, GracefulShutdownBuilder, GracefulShutdownSupport, and DefaultGracefulShutdown from the server package to the common package, making them usable for both server and client. Removed server-specific logic (such as exception handling for pending requests) to generalize the API. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]

Configuration and flag enhancements:

  • Added support for configuring the default graceful shutdown strategy for worker groups via the Flags and FlagsProvider classes, with a new flag and default implementation. [1] [2] [3]

Dependency and import updates:

  • Updated imports across affected files to reflect the move of graceful shutdown classes from the server to the common package. [1] [2] [3] [4]

Result:

…`EventLoopGroup`s

Added parameters to enable setting the quiet period and timeout for graceful shutdown of worker groups, both globally and through the `ClientFactoryBuilder`.

Make use of graceful shutdown settings on worker groups on the server side.

Refs: line#5813
Copilot AI review requested due to automatic review settings December 27, 2025 19:26
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds configurable GracefulShutdown for EventLoopGroups and related wiring: moves GracefulShutdown to common, removes per-request toException on GracefulShutdown, introduces GracefulShutdownExceptionFactory, exposes flags/options/providers, and applies quietPeriod/timeout when shutting down worker and boss groups for server and client factories.

Changes

Cohort / File(s) Summary
GracefulShutdown core API & builder
core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java, core/src/main/java/com/linecorp/armeria/common/DefaultGracefulShutdown.java, core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java
Relocated to common; removed per-request toException API; DefaultGracefulShutdown now only holds quietPeriod and timeout; builder no longer accepts per-request exception function.
New server-side exception factory
core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java
Added unstable functional interface to produce an optional Throwable per ServiceRequestContext/HttpRequest during shutdown.
Server config, builder, and runtime
core/src/main/java/com/linecorp/armeria/server/ServerConfig.java, .../DefaultServerConfig.java, .../UpdatableServerConfig.java, .../ServerBuilder.java, .../Server.java, .../HttpServerHandler.java
Added worker/boss graceful-shutdown fields/accessors and a graceful-shutdown exception factory; ServerBuilder gains workerGroup(...) overloads accepting GracefulShutdown and gracefulShutdown(...) accepting the factory; Server and HttpServerHandler use configured quietPeriod/timeout and factory when shutting down or terminating requests.
Client-side wiring
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java, .../ClientFactoryOptions.java, .../HttpClientFactory.java
Added workerGroup overloads that accept GracefulShutdown and new WORKER_GROUP_GRACEFUL_SHUTDOWN option; HttpClientFactory.closeAsync uses configured quietPeriod/timeout when shutting down worker group.
Flags & providers
core/src/main/java/com/linecorp/armeria/common/Flags.java, .../FlagsProvider.java, .../DefaultFlagsProvider.java
Added Flags getter and FlagsProvider method for workerGroupGracefulShutdown; DefaultFlagsProvider returns default GracefulShutdown (2s quiet, 15s timeout).
Tests
core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java, .../server/CustomGracefulShutDownTest.java, .../server/GracefulShutdownBuilderTest.java, .../server/ServerBuilderTest.java
Added and adapted tests for new GracefulShutdown and GracefulShutdownExceptionFactory APIs; removed assertions and usages tied to the removed toException/per-request behavior.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Operator as Operator (user code)
  participant ServerBuilder as ServerBuilder
  participant Server as Server
  participant Worker as EventLoopGroup (worker)
  participant Boss as EventLoopGroup (boss)
  participant Handler as HttpServerHandler

  note right of ServerBuilder `#DDEBF7`: configure GracefulShutdown & factory
  Operator->>ServerBuilder: builder.gracefulShutdown(gs, factory)
  Operator->>ServerBuilder: builder.workerGroup(..., gsWorker)
  Operator->>ServerBuilder: builder.build()
  ServerBuilder->>Server: construct(config with gs, gsWorker, gsBoss, factory)
  note right of Server `#E2F0D9`: running...
  Operator->>Server: stop()
  Server->>Worker: shutdownGracefully(quietMillis, timeoutMillis)
  Server->>Boss: shutdownGracefully(quietMillis, timeoutMillis)
  alt pending requests during shutdown
    Server->>Handler: gracefulShutdownExceptionFactory.createThrowableForContext(ctx, req)
    Handler->>Operator: request terminated with produced Throwable (if any)
  end
  Worker->>Server: terminationComplete
  Boss->>Server: terminationComplete
  Server->>Operator: stopped
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • trustin
  • ikhoon
  • minwoox

Poem

🐰 A quiet hop, a softer end of day,
Worker and boss tuck threads away,
Quiet periods hum, timeouts hum too,
I nudge a config — swift teardown anew,
Hooray for graceful hops! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately reflects the main objective: enabling customization of graceful shutdown delays for Netty EventLoopGroups on both client and server sides.
Description check ✅ Passed The PR description clearly explains the changes made, including client-side worker-group graceful shutdown configuration, generalization of graceful shutdown classes from server to common package, server-side enhancements, and configuration/flag updates.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #5813: enables customization of graceful shutdown delays for EventLoopGroups, provides APIs in both ClientFactoryBuilder and ServerBuilder, supports configurations for external/disabled/immediate/custom graceful shutdown via GracefulShutdown, and generalizes the implementation for client and server use.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing customizable graceful shutdown for EventLoopGroups. Test additions validate the new functionality. No out-of-scope modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09bb8b1 and ec355b5.

📒 Files selected for processing (1)
  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (1)
core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java (1)

326-339: LGTM! API migration correctly implemented.

The method has been successfully updated to use the new GracefulShutdownExceptionFactory API:

  • Line 332 correctly calls createThrowableForContext(ctx, req) through the factory.
  • Lines 334-336 accurately reference the new method in the log message and include the factory instance as suggested in previous reviews.
  • Exception handling provides appropriate fallback behavior (returns null, which triggers defaultCause usage in the caller).

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java (1)

24-28: Javadoc is now incomplete after package relocation.

The Javadoc states this "Configures the graceful shutdown behavior of a {@link Server}" but now that GracefulShutdown is in the common package and used for ClientFactory worker groups as well, this description is incomplete.

Consider updating to reflect the broader usage:

 /**
- * Configures the graceful shutdown behavior of a {@link Server}.
+ * Configures the graceful shutdown behavior of worker groups for both
+ * {@link com.linecorp.armeria.server.Server Server} and
+ * {@link com.linecorp.armeria.client.ClientFactory ClientFactory}.
  */
core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java (1)

75-81: Worker-group graceful shutdown option is wired correctly

  • WORKER_GROUP_GRACEFUL_SHUTDOWN is defined with a default from Flags.workerGroupGracefulShutdown(), so existing code that does not override it still has a deterministic policy.
  • The workerGroupGracefulShutdown() accessor simply returns this option and is marked @UnstableApi, which is appropriate for a newly introduced tuning knob.

You might optionally consider documenting in the accessor Javadoc that it defaults to the Flags.workerGroupGracefulShutdown() value, for discoverability, but functionally this looks good.

Also applies to: 756-763

core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (3)

566-572: Consider breaking long method signature for readability.

The method signature spans a single long line. For consistency with other overloads in this file (e.g., line 537), consider breaking the parameters across multiple lines.

🔎 Proposed formatting
-    public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop, GracefulShutdown gracefulShutdown) {
+    public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop,
+                                      GracefulShutdown gracefulShutdown) {

574-585: Javadoc missing description for gracefulShutdown parameter.

The Javadoc for workerGroup(int numThreads, GracefulShutdown gracefulShutdown) doesn't document the gracefulShutdown parameter. Consider adding a @param tag for consistency with the three-argument overload at lines 557-565.

🔎 Proposed Javadoc addition
     /**
      * Uses a newly created {@link EventLoopGroup} with the specified number of threads for
      * performing socket I/O and running {@link Service#serve(ServiceRequestContext, Request)}.
      * The worker {@link EventLoopGroup} will be shut down when the {@link Server} stops.
      *
      * @param numThreads the number of event loop threads
+     * @param gracefulShutdown the {@link GracefulShutdown} configuration for the worker {@link EventLoopGroup}
      */

1007-1010: Parameter name toException is inconsistent with field name.

The parameter is named toException, but the field it sets is named gracefulShutdownExceptionFactory. Consider renaming for consistency:

🔎 Proposed fix
     @UnstableApi
     public ServerBuilder gracefulShutdownExceptionFactory(
-            GracefulShutdownExceptionFactory toException
+            GracefulShutdownExceptionFactory gracefulShutdownExceptionFactory
     ) {
-        gracefulShutdownExceptionFactory = requireNonNull(toException, "toException");
+        this.gracefulShutdownExceptionFactory = requireNonNull(gracefulShutdownExceptionFactory, "gracefulShutdownExceptionFactory");
         return this;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4811bd and f4d64ba.

📒 Files selected for processing (24)
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java
  • core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultGracefulShutdown.java
  • core/src/main/java/com/linecorp/armeria/common/Flags.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java
  • core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java
  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java
  • core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandler.java
  • core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandlerBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerPipelineConfigurator.java
  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/ServerConfig.java
  • core/src/main/java/com/linecorp/armeria/server/UpdatableServerConfig.java
  • core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java
  • core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java
  • core/src/test/java/com/linecorp/armeria/server/GracefulShutdownBuilderTest.java
  • core/src/test/java/com/linecorp/armeria/server/GracefulShutdownSupportTest.java
  • core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerPipelineConfigurator.java
  • core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java
  • core/src/main/java/com/linecorp/armeria/server/UpdatableServerConfig.java
  • core/src/main/java/com/linecorp/armeria/common/Flags.java
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
  • core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java
  • core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandlerBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/ServerConfig.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultGracefulShutdown.java
  • core/src/test/java/com/linecorp/armeria/server/GracefulShutdownSupportTest.java
  • core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java
  • core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandler.java
  • core/src/test/java/com/linecorp/armeria/server/GracefulShutdownBuilderTest.java
  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java
🧬 Code graph analysis (8)
core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)
  • UnstableApi (28-111)
core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandlerBuilder.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/server/ServerConfig.java (2)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)
  • UnstableApi (28-111)
core/src/main/java/com/linecorp/armeria/common/ShuttingDownException.java (1)
  • UnstableApi (27-47)
core/src/test/java/com/linecorp/armeria/server/GracefulShutdownSupportTest.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandler.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/server/Server.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java (2)
core/src/main/java/com/linecorp/armeria/common/Flags.java (1)
  • Flags (106-1907)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)
  • UnstableApi (28-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CodeQL analysis (java)
  • GitHub Check: Agent
  • GitHub Check: Summary
🔇 Additional comments (26)
core/src/test/java/com/linecorp/armeria/server/GracefulShutdownSupportTest.java (1)

39-39: LGTM!

The import update correctly reflects the package relocation of GracefulShutdownSupport from server to common.

core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandlerBuilder.java (1)

18-18: LGTM!

The import update correctly reflects the package relocation of GracefulShutdownSupport to the common package.

core/src/main/java/com/linecorp/armeria/server/HttpServerPipelineConfigurator.java (1)

49-49: LGTM!

The import update correctly reflects the package relocation of GracefulShutdownSupport to the common package.

core/src/main/java/com/linecorp/armeria/server/Http2ServerConnectionHandler.java (1)

21-21: LGTM!

The import update correctly reflects the package relocation of GracefulShutdownSupport to the common package.

core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java (1)

467-477: LGTM!

The graceful shutdown integration is well implemented:

  • Correctly retrieves the GracefulShutdown configuration from options
  • Properly converts Duration values to milliseconds
  • Preserves the existing error-logging behavior via the listener
core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java (2)

48-48: LGTM!

The import update correctly reflects the package relocation of GracefulShutdownSupport to the common package.


333-338: LGTM!

The refactoring to use the factory-based exception creation pattern is cleaner and aligns with the broader graceful shutdown configuration changes. The factory approach allows for more flexible exception customization per-context.

core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java (2)

323-348: Client worker-group graceful shutdown test covers config and defaults well

The gracefulShutdown() test correctly:

  • Asserts that a custom GracefulShutdown instance configured via workerGroup(1, workerGroupGracefulShutdown) is propagated into ClientFactoryOptions.
  • Verifies that when unset, workerGroupGracefulShutdown and http2GracefulShutdownTimeoutMillis fall back to the corresponding Flags values.

No issues from a correctness or coverage perspective.


350-383: HTTP/2 graceful shutdown timeout tests look correct and complete

The http2GracefulShutdownTimeoutMillis() test:

  • Validates both Duration and millisecond-based setters.
  • Confirms 0 disables the timeout.
  • Ensures negative values are rejected with an IllegalArgumentException and an informative message fragment.

This aligns with the builder’s validation logic and expected semantics.

core/src/main/java/com/linecorp/armeria/common/DefaultGracefulShutdown.java (1)

29-60: Value-type implementation for GracefulShutdown is consistent

The constructor, accessors, equals, and hashCode correctly treat quietPeriod and timeout as the defining state. No functional or equality/ordering issues spotted.

core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java (2)

824-846: gracefulShutdownExceptionFactory test matches new factory semantics

The test:

  • Verifies the default factory produces a ShuttingDownException.
  • Verifies a custom factory via gracefulShutdownExceptionFactory(...) is used instead.

This is the right level of coverage for the new factory hook. Passing null for (ctx, req) is acceptable as long as the factory contract allows @Nullable parameters, which seems consistent with internal usage.


848-892: Server graceful shutdown configuration test is sound

The gracefulShutdown() test:

  • Confirms that custom GracefulShutdown instances for server-wide, boss group, and worker group are all propagated into ServerConfig.
  • Validates that, when unset, all three default to GracefulShutdown.disabled().

This accurately exercises the new configuration plumbing without introducing resource-management issues (servers are not started).

core/src/test/java/com/linecorp/armeria/server/GracefulShutdownBuilderTest.java (1)

26-68: GracefulShutdownBuilder tests remain aligned with the builder contract

Updating the import to com.linecorp.armeria.common.GracefulShutdown matches the new package. The tests for invalid values, default (0/0), and custom durations still reflect the current builder behavior, so coverage remains appropriate.

core/src/main/java/com/linecorp/armeria/server/Server.java (2)

504-622: GracefulShutdownSupport integration during start is coherent

  • gracefulShutdownSupport is initialized once in doStart() based on config().gracefulShutdown().quietPeriod(), with createDisabled() used for the zero-quiet-period case.
  • The instance is then consistently injected into HttpServerPipelineConfigurator and metric setup.

The use of a volatile field plus local copies in methods avoids concurrency pitfalls, and the disabled vs enabled behavior is cleanly separated via GracefulShutdownSupport.createDisabled().


624-657: Server stop now cleanly honors configurable worker/boss graceful shutdown

  • doStop() correctly:
    • Waits for GracefulShutdownSupport.completedQuietPeriod() when enabled, using a dedicated scheduled executor with a hard timeout driven by config.gracefulShutdown().timeout().
    • Once past the quiet period, closes server channels and accepted sockets, then:
      • Shuts down the worker group only if config.shutdownWorkerGroupOnStop() is true, using config.workerGroupGracefulShutdown()’s quiet period and timeout.
      • Shuts down all boss groups using config.bossGroupGracefulShutdown()’s quiet period and timeout, and waits on their terminationFuture()s.

This matches the new configuration model and preserves the previous ordering (worker before boss). I don’t see correctness or lifecycle issues here.

Also applies to: 689-727

core/src/main/java/com/linecorp/armeria/common/Flags.java (1)

464-466: New workerGroupGracefulShutdown flag follows existing flag patterns

  • WORKER_GROUP_GRACEFUL_SHUTDOWN is initialized via getValue(FlagsProvider::workerGroupGracefulShutdown, ...), consistent with other flags, so SPI/DefaultFlagsProvider overrides will work as expected.
  • workerGroupGracefulShutdown() simply returns the cached instance and is annotated @UnstableApi, which matches the new, advanced configuration nature.

Javadoc stating that the default disables graceful shutdown is consistent with a default GracefulShutdown of quietPeriod=0 and timeout=0.

Also applies to: 1736-1745

core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java (1)

23-43: LGTM!

The new GracefulShutdownExceptionFactory functional interface is well-designed:

  • Properly annotated with @UnstableApi as required for new public APIs
  • Clear Javadoc explaining the purpose and return value semantics
  • @Nullable return type correctly allows null to fall back to ShuttingDownException
core/src/main/java/com/linecorp/armeria/server/ServerConfig.java (1)

266-288: LGTM!

The new ServerConfig interface methods are well-defined:

  • All three accessors (workerGroupGracefulShutdown(), bossGroupGracefulShutdown(), gracefulShutdownExceptionFactory()) are properly annotated with @UnstableApi
  • Javadocs clearly describe behavior, including the null-return semantics for the exception factory
core/src/main/java/com/linecorp/armeria/server/UpdatableServerConfig.java (1)

254-267: LGTM!

The delegate implementations correctly forward calls to the underlying DefaultServerConfig, maintaining the consistent delegation pattern used throughout this class.

core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java (1)

47-65: LGTM!

The test refactoring correctly exercises the new gracefulShutdownExceptionFactory() API:

  • Conditionally configures the factory only when testing custom exceptions
  • Validates both the HTTP status and the response cause type
  • Covers both default (ShuttingDownException) and custom (AnticipatedException) code paths
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)

17-110: LGTM!

The relocation to com.linecorp.armeria.common and API simplification are well-executed:

  • Package move appropriately reflects that GracefulShutdown is now used by both client and server components
  • Javadoc updates to "client or server" accurately reflect the broader scope
  • The toExceptionFunction removal aligns with the new GracefulShutdownExceptionFactory pattern in the server package
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (1)

2516-2518: Correctly wiring new graceful shutdown parameters.

The new graceful shutdown fields are properly passed to DefaultServerConfig, enabling the full configuration flow.

core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java (4)

97-99: New fields for graceful shutdown configuration.

The three new fields are properly declared and will store the per-group graceful shutdown settings and exception factory.


196-199: Proper null-checking in constructor.

The requireNonNull checks ensure the new graceful shutdown parameters are always provided, preventing potential NPEs.


608-621: LGTM!

The new getter implementations correctly return the stored configuration values, following the established pattern in this class.


731-738: LGTM!

The toString method updates properly include the new graceful shutdown fields for debugging visibility, following the existing formatting conventions.

Also applies to: 835-838

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds comprehensive support for customizing the graceful shutdown delay of Netty EventLoopGroups for both client and server components. It materializes proposals from issue #5813 by moving graceful shutdown configuration classes from the server package to the common package, making them reusable across the codebase.

Key changes:

  • Moved GracefulShutdown, GracefulShutdownBuilder, GracefulShutdownSupport, and DefaultGracefulShutdown from server to common package for broader reusability
  • Added GracefulShutdownExceptionFactory interface to separate exception generation logic from graceful shutdown configuration
  • Extended ServerBuilder and ClientFactoryBuilder with new overloaded methods accepting GracefulShutdown parameters for worker and boss groups
  • Added global configuration support via Flags and FlagsProvider for default worker group graceful shutdown behavior

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java Moved from server package to common package; removed server-specific toException method
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java Moved from server package; updated documentation to reflect client/server usage; removed toExceptionFunction method
core/src/main/java/com/linecorp/armeria/common/DefaultGracefulShutdown.java Moved from server package; removed exception function field
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java Moved from server package to common; changed visibility to public
core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java New interface for creating shutdown exceptions in server contexts
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java Added overloaded workerGroup methods with GracefulShutdown parameter and bossGroupGracefulShutdown/gracefulShutdownExceptionFactory configuration methods
core/src/main/java/com/linecorp/armeria/server/ServerConfig.java Added getter methods for worker/boss group graceful shutdown and exception factory
core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java Implemented new configuration fields and updated constructor/toString
core/src/main/java/com/linecorp/armeria/server/Server.java Updated shutdown logic to use configured graceful shutdown settings
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java Added overloaded workerGroup methods with GracefulShutdown parameter
core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java Added WORKER_GROUP_GRACEFUL_SHUTDOWN option and getter method
core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java Updated to use graceful shutdown configuration when closing worker group
core/src/main/java/com/linecorp/armeria/common/Flags.java Added workerGroupGracefulShutdown() method for global default configuration
core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java Added workerGroupGracefulShutdown() default method
Test files Updated imports and tests to reflect moved classes and new API
Comments suppressed due to low confidence (3)

core/src/main/java/com/linecorp/armeria/common/GracefulShutdown.java:26

  • The class-level documentation states "Configures the graceful shutdown behavior of a {@link Server}." However, this interface is now in the common package and is used for both server and client shutdown (as evidenced by its usage in ClientFactoryOptions and ServerConfig). The documentation should be updated to reflect that it can be used for both client and server graceful shutdown configuration.
    core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java:50
  • The documentation states "Sets the quiet period to wait for active requests to go end before shutting down." This should be "to end" instead of "go end" for proper grammar.
    core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java:63
  • The documentation states "Sets the quiet period millis to wait for active requests to go end before shutting down." This should be "to end" instead of "go end" for proper grammar.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (1)

576-587: Missing @UnstableApi annotation and @param javadoc for gracefulShutdown.

This new public API method needs the @UnstableApi annotation per coding guidelines. Additionally, the gracefulShutdown parameter is missing from the javadoc.

🔎 Proposed fix
     /**
      * Uses a newly created {@link EventLoopGroup} with the specified number of threads for
      * performing socket I/O and running {@link Service#serve(ServiceRequestContext, Request)}.
      * The worker {@link EventLoopGroup} will be shut down when the {@link Server} stops.
      *
      * @param numThreads the number of event loop threads
+     * @param gracefulShutdown the {@link GracefulShutdown} configuration for the worker {@link EventLoopGroup}
      */
+    @UnstableApi
     public ServerBuilder workerGroup(int numThreads, GracefulShutdown gracefulShutdown) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4d64ba and 02f102d.

📒 Files selected for processing (3)
  • core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
🧬 Code graph analysis (2)
core/src/main/java/com/linecorp/armeria/server/Server.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownSupport.java (1)
  • GracefulShutdownSupport (30-160)
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)
  • UnstableApi (28-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (6)
core/src/main/java/com/linecorp/armeria/server/Server.java (3)

63-64: LGTM!

The imports for GracefulShutdown and GracefulShutdownSupport from the common package are correctly added to support the new configurable graceful shutdown behavior.


655-661: LGTM!

The worker group shutdown now correctly uses the configurable GracefulShutdown parameters from config.workerGroupGracefulShutdown(), passing quietPeriod and timeout in milliseconds. This aligns with the PR objective to allow customization of the graceful shutdown delay.


680-687: LGTM!

The boss group shutdown correctly retrieves the GracefulShutdown configuration from config.bossGroupGracefulShutdown() and applies the configured quietPeriod and timeout to each boss group. The implementation is consistent with the worker group shutdown pattern.

core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (3)

227-230: LGTM!

The new fields are correctly initialized with GracefulShutdown.disabled() as the default, and the gracefulShutdownExceptionFactory has a sensible default that returns ShuttingDownException.get().


991-999: LGTM!

The bossGroupGracefulShutdown method is correctly annotated with @UnstableApi and has appropriate javadoc documentation.


2522-2524: LGTM!

The buildServerConfig method is correctly updated to pass the new graceful shutdown configuration parameters (gracefulShutdown, workerGroupGracefulShutdown, bossGroupGracefulShutdown, gracefulShutdownExceptionFactory) to the DefaultServerConfig constructor.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the initiative 👍 left some nit comments but overall looks good to me.

* when the {@link ClientFactory} is closed
*/
public ClientFactoryBuilder workerGroup(
EventLoopGroup workerGroup, boolean shutdownOnClose, GracefulShutdown gracefulShutdown) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional) What do you think of validating if shutdownOnClose == true since GracefulShutdown won't have meaning unless the workerGroup is actually closed?

e.g.

        if (!shutdownOnClose) {
            checkArgument(gracefulShutdown == GracefulShutdown.disabled(),
                          "GracefulShutdown is not supported when shutdownOnClose is false");
        }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, this makes sense.


GracefulShutdownBuilder() {}

static Duration validateNonNegative(Duration duration, String fieldName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; can be private

Suggested change
static Duration validateNonNegative(Duration duration, String fieldName) {
private static Duration validateNonNegative(Duration duration, String fieldName) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

*/
@UnstableApi
default GracefulShutdown workerGroupGracefulShutdown() {
return GracefulShutdown.disabled();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To allow users to override FlagsProvider#workerGroupGracefulShutdown using FlagsProvider#priority, what do you think of returning null here and returning the default GracefulShutdown at DefaultFlagsProvider?

Suggested change
return GracefulShutdown.disabled();
return null;

Also, to keep backwards compatibility, what do you think of returning netty's default as the default value?

https://github.com/netty/netty/blob/11938b53522a578b7e113e4fe85baa560e7aa841/common/src/main/java/io/netty/util/concurrent/AbstractEventExecutor.java#L39-L40

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will update it like this, but I wonder if this check would be still valid then:

if (!shutdownOnClose) {
            checkArgument(gracefulShutdown == GracefulShutdown.disabled(),
                          "GracefulShutdown is not supported when shutdownOnClose is false");
        }

@@ -27,17 +27,17 @@
* Keeps track of pending requests to allow shutdown to happen after a fixed quiet period passes
* after the last pending request.
*/
abstract class GracefulShutdownSupport {
public abstract class GracefulShutdownSupport {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Is there a reason this class has been moved to the common package?

If not, what do you think of reverting this change as I don't think there is a need to expose public classes that aren't directly beneficial to users?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it was not. I moved all related classes to common at once. But you're right this one is used only from the server package, so it could and should be left there.

* response.
*/
@UnstableApi
public ServerBuilder gracefulShutdownExceptionFactory(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think specifying gracefulShutdownExceptionFactory only makes sense when ServerBuilder#gracefulShutdown is specified.

What do you think of adding a single overloaded method which also accepts gracefulShutdownExceptionFactory?

e.g.

    /**
     * Sets the {@link GracefulShutdown} configuration.
     * If not set, {@link GracefulShutdown#disabled()} is used.
     */
    @UnstableApi
    public ServerBuilder gracefulShutdown(GracefulShutdown gracefulShutdown, GracefulShutdownExceptionFactory toException) {
        this.gracefulShutdown = requireNonNull(gracefulShutdown, "gracefulShutdown");
        this.toException = requireNonNull(toException, "toException");
        return this;
    }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought this factory would be used when either graceful shutdown takes place (i.e. Armeria internal one, worker group, boss group). But I can't find a place that would confirm this hypothesis - so it probably only relates to internal Armeria graceful shutdown and could be combined this way. I'll rework this.

Comment on lines 227 to 228
private GracefulShutdown workerGroupGracefulShutdown = GracefulShutdown.disabled();
private GracefulShutdown bossGroupGracefulShutdown = GracefulShutdown.disabled();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Would it make sense to use the default from Flags here?

e.g.

Suggested change
private GracefulShutdown workerGroupGracefulShutdown = GracefulShutdown.disabled();
private GracefulShutdown bossGroupGracefulShutdown = GracefulShutdown.disabled();
private GracefulShutdown workerGroupGracefulShutdown = Flags.workerGroupGracefulShutdown();
private GracefulShutdown bossGroupGracefulShutdown = Flags.workerGroupGracefulShutdown();

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't answer this since I'm not fully familiar with the Flags purpose. I guess it would mean that by single system property I could set defaults to both groups, which could be both handy and misleading (single configuration option for two toggles). For now, I leave current implementation in place, but will hapilly change that if you insist on the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I'm mostly concerned with avoiding behavioral breaking changes since previously shutdownGracefully was called for the workerGroup and the future was waited on when the Server actually closed. I think it's not uncommon for user-code to have long-living tasks that may continue running after server connections have been drained.

workerShutdownFuture = config.workerGroup().shutdownGracefully();

What do you think of using the Flags value at least for workerGroupGracefulShutdown then?

…`EventLoopGroup`s

Incorporation of comments from the review.

Refs: line#5813
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (3)

1009-1009: Minor grammar correction needed.

"an {@link Throwable}" should be "a {@link Throwable}" since "Throwable" starts with a consonant sound.

🔎 Proposed fix
-     * @param toException the function that returns an {@link Throwable} to terminate a pending request when
+     * @param toException the function that returns a {@link Throwable} to terminate a pending request when

559-584: Missing @UnstableApi annotation and parameter documentation.

This new public method requires the @UnstableApi annotation per project coding guidelines. Additionally, the gracefulShutdown parameter is missing its @param javadoc entry.

🔎 Proposed fix
     /**
      * Sets the worker {@link EventLoopGroup} which is responsible for performing socket I/O and running
      * {@link Service#serve(ServiceRequestContext, Request)}.
      * If not set, {@linkplain CommonPools#workerGroup() the common worker group} is used.
      *
      * @param workerGroup the worker {@link EventLoopGroup}
      * @param shutdownOnStop whether to shut down the worker {@link EventLoopGroup}
      *                       when the {@link Server} stops
+     * @param gracefulShutdown the {@link GracefulShutdown} configuration to be used when the worker
+     *                         {@link EventLoopGroup} is being shut down
      */
     @UnstableApi
     public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop, GracefulShutdown gracefulShutdown) {

586-600: Missing @UnstableApi annotation and parameter documentation.

This new public method requires the @UnstableApi annotation per project coding guidelines. Additionally, the gracefulShutdown parameter is missing its @param javadoc entry.

🔎 Proposed fix
     /**
      * Uses a newly created {@link EventLoopGroup} with the specified number of threads for
      * performing socket I/O and running {@link Service#serve(ServiceRequestContext, Request)}.
      * The worker {@link EventLoopGroup} will be shut down when the {@link Server} stops.
      *
      * @param numThreads the number of event loop threads
+     * @param gracefulShutdown the {@link GracefulShutdown} configuration to be used when the worker
+     *                         {@link EventLoopGroup} is being shut down
      */
     @UnstableApi
     public ServerBuilder workerGroup(int numThreads, GracefulShutdown gracefulShutdown) {
🧹 Nitpick comments (5)
core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java (2)

47-67: Consider adding a comment to explain the conditional configuration.

The refactored server construction correctly supports testing both default and custom graceful shutdown exception paths. The conditional logic appropriately applies the custom exception factory only when AnticipatedException is the expected cause. However, a brief comment explaining why different configuration paths are needed would improve maintainability.

💡 Suggested comment for clarity
+        // Configure graceful shutdown with custom exception factory only when testing AnticipatedException
         if (AnticipatedException.class.isAssignableFrom(expectedCause)) {
             serverBuilder.gracefulShutdown(
                     gracefulShutdown,
                     (ctx, req) -> new AnticipatedException()
             );
         } else {
             serverBuilder.gracefulShutdown(gracefulShutdown);
         }

85-99: Variable naming could be more descriptive.

The variables defaultError and customError distinguish between using the framework's default ShuttingDownException versus a custom AnticipatedException. However, both variables represent custom GracefulShutdown configurations (with non-default timeout values), which makes the "default" label potentially misleading.

💡 Consider more descriptive names
-            final GracefulShutdown customError =
+            final GracefulShutdown withCustomException =
                     GracefulShutdown.builder()
                                     .quietPeriod(Duration.ofMillis(500))
                                     .timeout(Duration.ofMillis(500))
                                     .build();

-            final GracefulShutdown defaultError =
+            final GracefulShutdown withDefaultException =
                     GracefulShutdown.builder()
                                     .quietPeriod(Duration.ofMillis(200))
                                     .timeout(Duration.ofMillis(200))
                                     .build();
             return Stream.of(
-                    Arguments.of(defaultError, ShuttingDownException.class, HttpStatus.SERVICE_UNAVAILABLE),
-                    Arguments.of(customError, AnticipatedException.class, HttpStatus.BAD_GATEWAY));
+                    Arguments.of(withDefaultException, ShuttingDownException.class, HttpStatus.SERVICE_UNAVAILABLE),
+                    Arguments.of(withCustomException, AnticipatedException.class, HttpStatus.BAD_GATEWAY));
core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java (1)

542-548: Optional: cache the default GracefulShutdown instance instead of rebuilding

The implementation of workerGroupGracefulShutdown() is correct, but it constructs a new GracefulShutdown on every call. If this accessor is used frequently, you could avoid repeated allocations by caching a single static instance, e.g.:

Optional caching tweak
-    @Override
-    public GracefulShutdown workerGroupGracefulShutdown() {
-        return GracefulShutdown.builder()
-                               .quietPeriod(Duration.ofSeconds(2L))
-                               .timeout(Duration.ofSeconds(15L))
-                               .build();
-    }
+    private static final GracefulShutdown DEFAULT_WORKER_GROUP_GRACEFUL_SHUTDOWN =
+            GracefulShutdown.builder()
+                            .quietPeriod(Duration.ofSeconds(2L))
+                            .timeout(Duration.ofSeconds(15L))
+                            .build();
+
+    @Override
+    public GracefulShutdown workerGroupGracefulShutdown() {
+        return DEFAULT_WORKER_GROUP_GRACEFUL_SHUTDOWN;
+    }

Purely a micro-optimization; behavior is already fine as-is.

core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java (1)

169-207: Optional: make gracefulShutdown null‑handling explicit for the shutdownOnClose == false case

The new overloads look good overall: they’re annotated with @UnstableApi, validate gracefulShutdown when shutdownOnClose is true, and guard against configuring a non‑disabled strategy when shutdownOnClose is false.

One minor clarity tweak: when shutdownOnClose is false, a null gracefulShutdown will currently fail the checkArgument(gracefulShutdown == GracefulShutdown.disabled(), …) check, but the error message doesn’t clearly indicate that null is invalid.

If you want a more explicit failure mode, you could require non‑null up front and keep the existing consistency check:

Optional null-handling refinement
    @UnstableApi
    public ClientFactoryBuilder workerGroup(
            EventLoopGroup workerGroup, boolean shutdownOnClose, GracefulShutdown gracefulShutdown) {
        option(ClientFactoryOptions.WORKER_GROUP, requireNonNull(workerGroup, "workerGroup"));
        option(ClientFactoryOptions.SHUTDOWN_WORKER_GROUP_ON_CLOSE, shutdownOnClose);
-        if (shutdownOnClose) {
-            option(ClientFactoryOptions.WORKER_GROUP_GRACEFUL_SHUTDOWN,
-                   requireNonNull(gracefulShutdown, "gracefulShutdown"));
-        } else {
+        gracefulShutdown = requireNonNull(gracefulShutdown, "gracefulShutdown");
+        if (shutdownOnClose) {
+            option(ClientFactoryOptions.WORKER_GROUP_GRACEFUL_SHUTDOWN, gracefulShutdown);
+        } else {
             // disabled graceful shutdown is default, therefore we check developer didn't set anything else
             // because it wouldn't be used and might indicate a mistake
             checkArgument(gracefulShutdown == GracefulShutdown.disabled(),
                           "GracefulShutdown makes no sense when shutdownOnClose is false");
         }
        return this;
    }

Behavior is already correct; this just tightens the API contract for callers.

core/src/main/java/com/linecorp/armeria/server/GracefulShutdownSupport.java (1)

32-45: Public GracefulShutdownSupport API is coherent; consider optional validation for external callers

Making GracefulShutdownSupport public with @UnstableApi, adding the create(...) / createDisabled() factories, and exposing the core lifecycle methods (inc, dec, pendingResponses, isShuttingDown, completedQuietPeriod) gives a consistent, testable surface without changing the underlying logic.

One optional hardening idea: create(Duration quietPeriod, ...) currently accepts any Duration, including negative values, which would result in a quiet period that is effectively always “completed.” If you expect external callers to use these factories directly (rather than going through GracefulShutdown/GracefulShutdownBuilder), you could reuse the same non‑negative validation as in GracefulShutdownBuilder:

Optional validation tweak
-    public static GracefulShutdownSupport create(Duration quietPeriod, Executor blockingTaskExecutor) {
-        return create(quietPeriod, blockingTaskExecutor, Ticker.systemTicker());
+    public static GracefulShutdownSupport create(Duration quietPeriod, Executor blockingTaskExecutor) {
+        requireNonNull(quietPeriod, "quietPeriod");
+        // mirror GracefulShutdownBuilder's non-negative requirement
+        if (quietPeriod.isNegative()) {
+            throw new IllegalArgumentException("quietPeriod: " + quietPeriod + " (expected: >= 0)");
+        }
+        return create(quietPeriod, blockingTaskExecutor, Ticker.systemTicker());
     }

If you’re confident all current usages already pass validated durations, this can be deferred.

Also applies to: 49-68, 73-79, 80-91, 96-147

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02f102d and c62f5e9.

📒 Files selected for processing (11)
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java
  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownSupport.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
  • core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java
  • core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java
  • core/src/main/java/com/linecorp/armeria/server/Server.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/GracefulShutdownSupport.java
  • core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java
  • core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
  • core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
🧬 Code graph analysis (1)
core/src/main/java/com/linecorp/armeria/server/GracefulShutdownSupport.java (1)
core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)
  • UnstableApi (28-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (5)
core/src/test/java/com/linecorp/armeria/server/CustomGracefulShutDownTest.java (1)

34-34: LGTM: Import reflects API relocation.

The import correctly uses the relocated GracefulShutdown class from the common package, aligning with the PR's objective to share this API between client and server components.

core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java (1)

325-337: GracefulShutdownExceptionFactory integration and logging look correct

The new use of config.gracefulShutdownExceptionFactory().createThrowableForContext(ctx, req) and the updated warning message cleanly switch over from the old API while keeping failures contained and falling back to the default shutdown cause. No changes needed here.

core/src/main/java/com/linecorp/armeria/server/Server.java (1)

655-686: GracefulShutdown configuration is correctly applied to worker and boss groups

Reading GracefulShutdown from config and passing its quiet period and timeout into shutdownGracefully(...) for both worker and boss groups fits the new API and keeps externally managed worker groups untouched when shutdownWorkerGroupOnStop() is false. The shutdown sequencing and termination listeners remain consistent with the previous behavior.

core/src/main/java/com/linecorp/armeria/server/GracefulShutdownExceptionFactory.java (1)

1-43: GracefulShutdownExceptionFactory API is well-scoped and matches server usage

The new @UnstableApi functional interface cleanly encapsulates shutdown-specific exception creation with a nullable return, and its contract aligns with how HttpServerHandler.shutdownError() consumes it. No changes needed here.

core/src/main/java/com/linecorp/armeria/common/GracefulShutdownBuilder.java (1)

42-47: GracefulShutdownBuilder validation and semantics look solid

The new validateNonNegative(...) helper, updated Javadoc, and the simplified DefaultGracefulShutdown(quietPeriod, timeout) construction together provide clear semantics: non‑negative durations only, with timeout >= quietPeriod, and support for both client and server use cases. No further changes needed here.

Also applies to: 49-53, 62-65, 72-76, 85-88, 107-110

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall to me - left some minor comments

* {@link EventLoopGroup} is being shut down, makes sense only if {@link #shutdownWorkerGroupOnStop} is true
*/
@UnstableApi
public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop, GracefulShutdown gracefulShutdown) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, it probably makes sense that shutdownOnStop isn't accepted at all since setting gracefulShutdown implies that the workerGroup will be shutdown. (it may be nice to indicate this in the javadocs though) Let me know what you think.

Suggested change
public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop, GracefulShutdown gracefulShutdown) {
public ServerBuilder workerGroup(EventLoopGroup workerGroup, GracefulShutdown gracefulShutdown) {

Ditto for the equivalent method in ClientFactoryBuilder

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented.

*/
abstract class GracefulShutdownSupport {
@UnstableApi
public abstract class GracefulShutdownSupport {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind also reverting the public modifiers in this class as well? I guess it's fine to just completely revert the changes in this class

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I should have reverted this automatically yesterday. My bad.

*/
@Nullable
@UnstableApi
default GracefulShutdown workerGroupGracefulShutdown() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to other reviewers: I think it's fine to skip the SystemPropertyFlagsProvider implementation since we will need to define a schema for parsing GracefulShutdown which I think is out of scope of this PR.

Comment on lines 227 to 228
private GracefulShutdown workerGroupGracefulShutdown = GracefulShutdown.disabled();
private GracefulShutdown bossGroupGracefulShutdown = GracefulShutdown.disabled();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I'm mostly concerned with avoiding behavioral breaking changes since previously shutdownGracefully was called for the workerGroup and the future was waited on when the Server actually closed. I think it's not uncommon for user-code to have long-living tasks that may continue running after server connections have been drained.

workerShutdownFuture = config.workerGroup().shutdownGracefully();

What do you think of using the Flags value at least for workerGroupGracefulShutdown then?

@jrhee17 jrhee17 added this to the 1.35.0 milestone Dec 30, 2025
…`EventLoopGroup`s

Reverted changes since the GracefulShutdownSupport was not needed to be moved and its API opened.

Refs: line#5813
…`EventLoopGroup`s

Reusing settings from worker group graceful shutdown from flags (and thus environment settings).

Refs: line#5813
…`EventLoopGroup`s

Removed unnecessary (implied) parameter.

Refs: line#5813
…`EventLoopGroup`s

Removed unnecessary (implied) parameter.

Refs: line#5813
@novoj
Copy link
Author

novoj commented Dec 30, 2025

@jrhee17 I changed the workerGroupGracefulShutdown = GracefulShutdown.disabled(); to Flags as you suggested, but couldn't reacted to your comment directly.

So this round of review comments should be sorted out.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me, thanks for your patience 👍

ctx, config.gracefulShutdown(), e);
logger.warn(
"{} Unexpected exception from gracefulShutdownExceptionFactory.createThrowableForContext(): "
+ "{}", ctx, config.gracefulShutdown(), e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; since the exception factory is no longer tied with gracefulShutdown

Suggested change
+ "{}", ctx, config.gracefulShutdown(), e);
+ "{}", ctx, config.gracefulShutdownExceptionFactory(), e);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

…`EventLoopGroup`s

Fixed log parameter that better relates to the probable issue.

Refs: line#5813
*/

package com.linecorp.armeria.server;
package com.linecorp.armeria.common;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this class is annotated with @UnstableApi, it cannot be moved to a different package. This is to ensure we maintain backward compatibility for as many users as possible.

How about adding a new one and deprecating the old one?
We also need to convert the old GracefulShutdown to the new one in the ServerBuilder:

@UnstableApi
public ServerBuilder gracefulShutdown(com.linecorp.armeria.server.GracefulShutdown gracefulShutdown) {
    // convert to common.GracefulShutdown and GracefulShutdownExceptionFactory
    return this;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original proposal was to move the class and incur a breaking change:

  • I wasn't able to find many (if any) cases where the server.GracefulShutdown construct was actually used
  • My thought was there wouldn't be many servers per application/repository - since server.GracefulShutdown is usually configured once per server I didn't think the breaking change would affect multiple points within each codebase.

Since this is a balance of maintaining two sets of classes vs. incurring breaking changes, I guess this is somewhat subjective and could go either way.

I'm fine with either approach

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a team discussion, we have decided to introduce a breaking change for the following reasons:

  • The feature was recently introduced and, based on a GitHub search, has limited adoption.
  • Maintaining two separate GracefulShutdown implementations is confusing for users and adds unnecessary complexity.

@novoj Sorry for confusing you. 😉

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay. I didn't react because I anticipated there might be a discussion about it. Both approaches have their benefits and disadvantages.

*/
@UnstableApi
@FunctionalInterface
public interface GracefulShutdownExceptionFactory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just ShutdownExceptionFactory?

* throwable should be thrown in this context
*/
@Nullable
Throwable createThrowableForContext(ServiceRequestContext serviceRequestContext, HttpRequest httpRequest);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just createException?

@ikhoon
Copy link
Contributor

ikhoon commented Jan 2, 2026

I think the current API has a few issues:

  • This API requires users to provide an EventLoopGroup and a GracefulShutdown as a pair, and this is so inconvenient to implement internally.
  • It also makes it impossible to control shutdown behavior by overriding EventLoopGroup.shutdownGracefully() without using GracefulShutdown.
  • A single EventLoopGroup may be shared in multiple places, and the behavior of GracefulShutdown may not be applied consistently.

As an alternative, without changing the existing server/client builders, what if we provide factory methods in EventLoopGroups or an EventLoopGroupBuilder so that users can easily configure quietPeriod and timeout when creating an EventLoopGroup?

EventLoopGroups
  .builder()
  .numThreads(...) 
  .gracefulShutdown(quietPeriod, timeout)
  ...
  .build();

One downside of this approach is that bossGroup is created internally and has no customization point.

final EventLoopGroup bossGroup = EventLoopGroups.newEventLoopGroup(1, r -> {
final FastThreadLocalThread thread = new FastThreadLocalThread(r, bossThreadName(port));
thread.setDaemon(false);
return thread;
});

We could fix this by provide an extension point in ServerBuilder to create bossGroup.

Server
  .builder()
  .bossGroupFactory(bossThreadName -> {
      EventLoopGroups.newEventLoopGroup(1, ...);
  });

Sorry for changing the overall direction, but I believe this approach will be more flexible.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @novoj! Although I left a comment about the direction but I'm okay with the current approach. We may improve this feature in the follow-up work.

@jrhee17
Copy link
Contributor

jrhee17 commented Jan 2, 2026

I think the current API has a few issues:

Adding more context, I didn't want to make you go through an entire rewrite of the current changeset - hence I volunteered to apply the changes explained by @ikhoon.
The current plan is to first go ahead and merge this PR, and then apply a separate PR written by me. (we'll be releasing next week, so I'm hoping I can make that deadline although there are no guarantees)

You're more than welcome to continue work based on the direction proposed by @ikhoon though, let me know what you think.

@novoj
Copy link
Author

novoj commented Jan 2, 2026

I could try implementing the third approach, but I'm unsure about your idea of baking the quiet period and timeout inside the EventLoopGroup. EventLoopGroup creation ends in com.linecorp.armeria.common.util.TransportType#newEventLoopGroup, which doesn't accept these arguments and forks into multiple implementations/specific classes instantiation. It would be difficult to propagate it there. I could wrap the result in a separate instance, probably a com.linecorp.armeria.internal.common.stream.EventExecutorWrapper, where these settings would be captured. When the non-parametric io.netty.util.concurrent.EventExecutorGroup#shutdownGracefully() method is called, it would take these settings into account. When the method io.netty.util.concurrent.EventExecutorGroup#shutdownGracefully(long, long, java.util.concurrent.TimeUnit) is called, the input arguments should take precedence. But what if the plain EventLoopGroup#close is called? Should the graceful settings be used?

Perhaps you had a completely different approach in mind that I'm missing?

What is your opinion on this matter? I'd need more guidance to continue on this issue.

@jrhee17
Copy link
Contributor

jrhee17 commented Jan 5, 2026

I could wrap the result in a separate instance, probably a com.linecorp.armeria.internal.common.stream.EventExecutorWrapper, where these settings would be captured. When the non-parametric io.netty.util.concurrent.EventExecutorGroup#shutdownGracefully() method is called, it would take these settings into account. When the method io.netty.util.concurrent.EventExecutorGroup#shutdownGracefully(long, long, java.util.concurrent.TimeUnit) is called, the input arguments should take precedence.

Right, this matches what I was imagining as well.

But what if the plain EventLoopGroup#close is called? Should the graceful settings be used?

It seems like the contract of #close is to wait until the executor is closed from the javadocs.
Hence, I think it's fine that #close is left as-is.

This method waits until all tasks have completed execution and the executor has terminated.

@novoj
Copy link
Author

novoj commented Jan 5, 2026

I understand. I can create a third implementation that follows this new plan. However, it will probably take me more than a week since the last two pull requests were made during my vacation, and now I only have weekends and evenings, which are rather scarce. So I'd suggest removing a planned milestone and assign new one when I'm finished.

@minwoox minwoox modified the milestones: 1.35.0, 1.36.0 Jan 5, 2026
@minwoox
Copy link
Contributor

minwoox commented Jan 5, 2026

Yeah, we don't have to hurry. Thanks a lot, @novoj, for your work. 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide a way to customize the graceful shutdown delay of Netty EventLoopGroups

4 participants