Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 feat: Add support for graceful shutdown timeout in ListenConfig #3220

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ksw2000
Copy link

@ksw2000 ksw2000 commented Nov 29, 2024

Description

Implement the graceful shutdown timeout in ListenConfig. This feature has been left idle as a TODO comment since September 2020.

Users can use the GracefulShutdownTimeout field in ListenConfig to set a time limit for the graceful shutdown. When a timeout occurs, a context.DeadlineExceeded error will be passed to the OnShutdownError defined in ListenConfig.

Additionally, in the original implementation, the OnShutdownSuccess function was called even when an error occurred, which is unexpected behavior.

Changes introduced

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • Documentation update (changes to documentation)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

Copy link

welcome bot commented Nov 29, 2024

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

Copy link
Contributor

coderabbitai bot commented Nov 29, 2024

Walkthrough

The pull request updates the Fiber package documentation to include a new configuration field, GracefulShutdownTimeout, in the ListenConfig section. This timeout specifies the duration for graceful server shutdowns. The documentation also details the Shutdown methods and adds examples for using the Listen method with TLS. Additionally, the listen.go file introduces the timeout feature in the gracefulShutdown method, and the listen_test.go file adds tests to validate the new behavior during shutdown scenarios.

Changes

File Change Summary
docs/api/fiber.md Updated documentation to include GracefulShutdownTimeout, detailed Shutdown methods, and examples for Listen with TLS.
listen.go Added GracefulShutdownTimeout field to ListenConfig struct; modified gracefulShutdown method to utilize this timeout and updated error handling.
listen_test.go Introduced Test_Listen_Graceful_Shutdown_Timeout to test graceful shutdown behavior with a specified timeout; includes multiple test scenarios. Updated existing test for timeout error handling.

Possibly related PRs

Suggested reviewers

  • ReneWerner87
  • sixcolors

Poem

🐰 In the land of Fiber, changes bloom,
New methods and docs, making room.
With graceful shutdowns, we now can play,
Configurations abound, brightening the day!
Hops of joy for tests that ensure,
Our app's behavior is safe and secure! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between dd0b528 and a15845d.

📒 Files selected for processing (1)
  • docs/api/fiber.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/api/fiber.md

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ksw2000 ksw2000 marked this pull request as ready for review November 29, 2024 08:45
@ksw2000 ksw2000 requested a review from a team as a code owner November 29, 2024 08:45
@ksw2000 ksw2000 requested review from gaby, sixcolors, ReneWerner87 and efectn and removed request for a team November 29, 2024 08:45
@ReneWerner87
Copy link
Member

@ksw2000 there is another PR for the shutdown, can you take a look at it and share your experience, hope it doesn't affect your feature
#3162

@ReneWerner87 ReneWerner87 added this to the v3 milestone Nov 29, 2024
@ReneWerner87
Copy link
Member

after your feature and the release for v3 we can adjust
https://github.com/gofiber/recipes/blob/master/graceful-shutdown/main.go

Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (4)
listen.go (1)

96-101: Documentation could be more descriptive

While the documentation explains the basic functionality, it could be enhanced with:

  1. A more detailed explanation of the shutdown process behavior when timeout occurs
  2. An example usage showing how to configure the timeout

Consider expanding the documentation like this:

-	// When the graceful shutdown begins, use this field to set the timeout
-	// duration. If the timeout is reached, OnShutdownError will be called.
-	// The default value is 0, which means the timeout setting is disabled.
+	// GracefulShutdownTimeout sets a time limit for graceful shutdown operations.
+	// When a shutdown begins, the server will wait for existing connections to complete
+	// within this duration. If the timeout is reached before all connections are closed:
+	// 1. A context.DeadlineExceeded error will be triggered
+	// 2. The error will be passed to OnShutdownError callback
+	// 3. Any remaining connections will be forcefully closed
+	//
+	// Example usage:
+	//  app.Listen(":3000", fiber.ListenConfig{
+	//    GracefulShutdownTimeout: 5 * time.Second,
+	//    OnShutdownError: func(err error) {
+	//      log.Printf("Shutdown error: %v", err)
+	//    },
+	//  })
+	//
+	// Default: 0 (timeout disabled)
docs/api/fiber.md (2)

Line range hint 379-391: Enhance Server Shutdown documentation section

The Server Shutdown section should be updated to reflect the new timeout functionality and provide clear examples.

Add comprehensive documentation about the shutdown behavior:

 ## Server Shutdown
 
 Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners and then waits indefinitely for all connections to return to idle before shutting down.
 
-ShutdownWithTimeout will forcefully close any active connections after the timeout expires.
+ShutdownWithTimeout will forcefully close any active connections after the specified timeout expires. This is equivalent to setting GracefulShutdownTimeout in ListenConfig.
 
 ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded.
 
 ```go
 func (app *App) Shutdown() error
 func (app *App) ShutdownWithTimeout(timeout time.Duration) error
 func (app *App) ShutdownWithContext(ctx context.Context) error

+```go title="Example with GracefulShutdownTimeout"
+app := fiber.New()
+
+// Configure graceful shutdown with a 5-second timeout
+app.Listen(":8080", fiber.ListenConfig{

  • GracefulShutdownTimeout: 5 * time.Second,
  • OnShutdownError: func(err error) {
  •    if err == context.DeadlineExceeded {
    
  •        log.Println("Graceful shutdown timed out")
    
  •    }
    
  • },
    +})
    +```

---

`111-111`: **Add historical context and behavior change notes**

To provide better context for users, consider adding a note about the behavioral changes.


Add a note about the behavioral change:

```diff
-| <Reference id="gracefulshutdowntimeout">GracefulShutdownTimeout</Reference>| `time.Duration`            | Field to set the timeout for graceful shutdown. Set to 0 to disable the timeout.                                                                | `0`   |
+| <Reference id="gracefulshutdowntimeout">GracefulShutdownTimeout</Reference>| `time.Duration`            | Field to set the timeout for graceful shutdown. Set to 0 to disable the timeout. When timeout occurs, OnShutdownError is called with context.DeadlineExceeded error, addressing a long-standing issue where OnShutdownSuccess was incorrectly called even when shutdown failed.                                                                | `0`   |
listen_test.go (1)

155-167: Consider refactoring duplicated code in the test setup and loops

The server readiness checks and test loops in Test_Listen_Graceful_Shutdown_Timeout are similar to those in Test_Listen_Graceful_Shutdown. Refactoring common code into helper functions can reduce duplication and improve maintainability.

Also applies to: 195-217

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1bfbb50 and 4fbfc17.

📒 Files selected for processing (3)
  • docs/api/fiber.md (1 hunks)
  • listen.go (3 hunks)
  • listen_test.go (1 hunks)
🔇 Additional comments (1)
listen.go (1)

481-491: 🛠️ Refactor suggestion

Address TODO comments and verify context implementation

The implementation looks solid, but there are two TODO comments about context implementation that need to be addressed.

Let's verify the context implementation in the codebase:

Consider refactoring the error handling to be more concise:

-	var err error
-
-	if cfg.GracefulShutdownTimeout != 0 {
-		err = app.ShutdownWithTimeout(cfg.GracefulShutdownTimeout) //nolint:contextcheck // TODO: Implement it
-	} else {
-		err = app.Shutdown() //nolint:contextcheck // TODO: Implement it
-	}
-
-	if err != nil {
-		cfg.OnShutdownError(err)
-		return
-	}
+	err := app.Shutdown()
+	if cfg.GracefulShutdownTimeout != 0 {
+		err = app.ShutdownWithTimeout(cfg.GracefulShutdownTimeout)
+	}
+	
+	if err != nil {
+		cfg.OnShutdownError(err)
+		return
+	}

docs/api/fiber.md Outdated Show resolved Hide resolved
listen_test.go Outdated Show resolved Hide resolved
listen_test.go Outdated Show resolved Hide resolved
@ksw2000
Copy link
Author

ksw2000 commented Nov 29, 2024

@ksw2000 there is another PR for the shutdown, can you take a look at it and share your experience, hope it doesn't affect your feature #3162

@ReneWerner87

PR #3162 improved the ShutdownWithContext function in app.go. I've reviewed the code and rerun the tests, and so far, I haven't found any conflicts.

@ReneWerner87
Copy link
Member

please always consider the comments of our ai, there are usually many, but this leads to an enormous increase in code quality

Copy link
Contributor

@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: 0

🧹 Outside diff range and nitpick comments (4)
listen_test.go (4)

118-152: LGTM! Consider using more descriptive variable names.

The test setup is well-structured with proper synchronization. However, consider renaming errs to serverErrors for better clarity about its purpose.

-errs := make(chan error)
+serverErrors := make(chan error)

154-167: Extract magic numbers into named constants.

The retry mechanism is well-implemented, but the magic numbers should be constants for better maintainability.

+const (
+    maxRetries = 10
+    retryDelay = 100 * time.Millisecond
+)

-for i := 0; i < 10; i++ {
+for i := 0; i < maxRetries; i++ {
    conn, err := ln.Dial()
    if err == nil {
        _ = conn
        break
    }
-    time.Sleep(100 * time.Millisecond)
+    time.Sleep(retryDelay)
-    if i == 9 {
+    if i == maxRetries-1 {
        t.Fatalf("Server did not become ready in time: %v", err)
    }
}

169-193: Consider adding edge cases to improve test coverage.

The test cases effectively cover the basic success and timeout scenarios. Consider adding these edge cases:

  1. Timeout exactly at the boundary (500ms)
  2. Multiple concurrent connections
  3. Zero timeout value
testCases := []struct {
    ExpectedErr             error
    ExpectedShutdownError   error
    ExpectedBody            string
    Time                    time.Duration
    ExpectedStatusCode      int
    ExpectedShutdownSuccess bool
}{
    // ... existing cases ...
+    {
+        Time:                    500 * time.Millisecond,
+        ExpectedBody:            "",
+        ExpectedStatusCode:      StatusOK,
+        ExpectedErr:             errors.New("connection closed"),
+        ExpectedShutdownError:   context.DeadlineExceeded,
+        ExpectedShutdownSuccess: false,
+    },
+    {
+        Time:                    0,
+        ExpectedBody:            "example.com",
+        ExpectedStatusCode:      StatusOK,
+        ExpectedErr:             nil,
+        ExpectedShutdownError:   nil,
+        ExpectedShutdownSuccess: false,
+    },
}

195-228: Consider enabling parallel test execution.

The test execution is well-structured with proper error handling and resource cleanup. Consider enabling parallel test execution to improve test performance.

func Test_Listen_Graceful_Shutdown_Timeout(t *testing.T) {
+    t.Parallel()
    var mu sync.Mutex
    // ... rest of the test
}

Note: Ensure all test dependencies are properly isolated before enabling parallel execution.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4fbfc17 and 58c0feb.

📒 Files selected for processing (2)
  • docs/api/fiber.md (1 hunks)
  • listen_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/api/fiber.md

Copy link
Contributor

@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: 0

🧹 Outside diff range and nitpick comments (2)
listen_test.go (2)

159-159: Add a comment explaining why the connection is not closed

The blank identifier usage could be clearer. Consider adding a comment explaining that we're intentionally keeping the connection open to test the timeout scenario.

-			_ = conn
+			// Intentionally keep connection open to test shutdown timeout
+			_ = conn

154-167: Consider extracting the server readiness check into a helper function

This readiness check logic is duplicated across multiple tests. Consider extracting it into a helper function to improve maintainability.

+func waitForServerReady(t *testing.T, ln *fasthttputil.InmemoryListener, keepConnOpen bool) {
+	for i := 0; i < 10; i++ {
+		conn, err := ln.Dial()
+		if err == nil {
+			if !keepConnOpen {
+				conn.Close()
+			}
+			return
+		}
+		time.Sleep(100 * time.Millisecond)
+		if i == 9 {
+			t.Fatalf("Server did not become ready in time: %v", err)
+		}
+	}
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 58c0feb and dd466cf.

📒 Files selected for processing (1)
  • listen_test.go (2 hunks)
🔇 Additional comments (2)
listen_test.go (2)

88-88: LGTM! Good improvement in error handling

The change from string error to fasthttputil.ErrInmemoryListenerClosed follows Go's best practices for error handling.


169-218: LGTM! Well-structured test cases and proper validation

The test implementation is thorough and well-structured:

  • Comprehensive test cases covering both success and timeout scenarios
  • Thread-safe state validation using mutex
  • Proper error handling using ErrorIs

@ReneWerner87
Copy link
Member

@gaby @efectn can you check pls

docs/api/fiber.md Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 0

🧹 Outside diff range and nitpick comments (1)
docs/api/fiber.md (1)

111-111: LGTM! Consider adding cross-references to related fields.

The documentation for GracefulShutdownTimeout is clear, comprehensive, and well-structured. It effectively explains the purpose, behavior, and default value of the field.

Consider adding cross-references to related fields like GracefulContext, OnShutdownError, and OnShutdownSuccess to help users understand how these fields work together. For example:

-| <Reference id="gracefulshutdowntimeout">GracefulShutdownTimeout</Reference>| `time.Duration`            | Specifies the maximum duration to wait for the server to gracefully shutdown. When the timeout is reached, the graceful shutdown process is interrupted and forcibly terminated, and the `context.DeadlineExceeded` error is passed to the `OnShutdownError` callback. Set to 0 (default) to disable the timeout and wait indefinitely. | `0`   |
+| <Reference id="gracefulshutdowntimeout">GracefulShutdownTimeout</Reference>| `time.Duration`            | Specifies the maximum duration to wait for the server to gracefully shutdown. When the timeout is reached, the graceful shutdown process is interrupted and forcibly terminated, and the `context.DeadlineExceeded` error is passed to the <Reference id="onshutdownerror">OnShutdownError</Reference> callback. Can be used in conjunction with <Reference id="gracefulcontext">GracefulContext</Reference> for more control over the shutdown process. Set to 0 (default) to disable the timeout and wait indefinitely. | `0`   |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between dd466cf and dd0b528.

📒 Files selected for processing (1)
  • docs/api/fiber.md (1 hunks)

@ksw2000 ksw2000 requested a review from efectn December 3, 2024 10:27
// duration. If the timeout is reached, OnShutdownError will be called.
// The default value is 0, which means the timeout setting is disabled.
//
// Default: 0
Copy link
Member

@gaby gaby Dec 3, 2024

Choose a reason for hiding this comment

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

I think default should be 10s. Having infinite will cause for example Docker to send a SIGKILL.

See: https://docs.docker.com/reference/cli/docker/container/stop/#time

Copy link
Member

Choose a reason for hiding this comment

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

Copy link

codecov bot commented Dec 3, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.79%. Comparing base (9a2ceb7) to head (a15845d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3220      +/-   ##
==========================================
+ Coverage   82.73%   82.79%   +0.06%     
==========================================
  Files         115      115              
  Lines       11287    11295       +8     
==========================================
+ Hits         9338     9352      +14     
+ Misses       1547     1543       -4     
+ Partials      402      400       -2     
Flag Coverage Δ
unittests 82.79% <100.00%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

// The default value is 0, which means the timeout setting is disabled.
//
// Default: 0
GracefulShutdownTimeout time.Duration `json:"graceful_shutdown_timeout"`
Copy link
Member

Choose a reason for hiding this comment

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

Rename this to ShutdownTimeout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants