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: Improve and Optimize ShutdownWithContext Func #3162

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

Conversation

JIeJaitt
Copy link
Contributor

@JIeJaitt JIeJaitt commented Oct 10, 2024

Description

Optimize ShutdownWithContext function and elegant shutdown flow of fiber, streamline Listen structure

Changes introduced

  • By holding the mutex lock throughout the operation, any modifications to the app's state are protected. defer ensures that the hook function is executed only after it is unlocked. At this time, no other goroutine will access the shared resource at the same time, ensuring concurrency safety.
  • defer ensures that the hook function will be executed regardless of whether an error occurs in the function. It also avoids execution order issues and further ensures concurrency safety.
  • Using executeOnPreShutdownHooks and executeOnPostShutdownHooks Instead of OnShutdownSuccess and OnShutdownError

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

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.

yingjie.huang added 2 commits October 10, 2024 16:44
- Reorder mutex lock acquisition to the start of the function
- Early return if server is not running
- Use defer for executing shutdown hooks
- Simplify nil check for hooks
- Remove TODO comment

This commit improves the readability, robustness, and execution order
of the shutdown process. It ensures consistent state throughout the
shutdown and guarantees hook execution even in error cases.
- Add shutdown hook verification
- Implement better synchronization with channels
- Improve error handling and assertions
- Adjust timeouts for more consistent results
- Add server state check after shutdown attempt
- Include comments explaining expected behavior

This commit improves the comprehensiveness and reliability of the
ShutdownWithContext test, ensuring proper verification of shutdown
hooks, timeout behavior, and server state during long-running requests.
Copy link

welcome bot commented Oct 10, 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

@JIeJaitt JIeJaitt changed the title Jiejaitt feature/improve shutdown with context Feature: Complete TODO and Optimize ShutdownWithContext Func Oct 10, 2024
@gaby gaby added v3 and removed ✏️ Feature labels Oct 10, 2024
Copy link

codecov bot commented Oct 10, 2024

Codecov Report

Attention: Patch coverage is 91.17647% with 3 lines in your changes missing coverage. Please review.

Project coverage is 84.11%. Comparing base (f5b7a12) to head (e4de2c3).
Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
hooks.go 91.66% 2 Missing ⚠️
listen.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3162      +/-   ##
==========================================
+ Coverage   84.08%   84.11%   +0.02%     
==========================================
  Files         116      116              
  Lines       11551    11558       +7     
==========================================
+ Hits         9713     9722       +9     
+ Misses       1405     1404       -1     
+ Partials      433      432       -1     
Flag Coverage Δ
unittests 84.11% <91.17%> (+0.02%) ⬆️

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.

app_test.go Outdated
}()

<-clientDone
time.Sleep(100 * time.Millisecond)
Copy link
Member

Choose a reason for hiding this comment

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

why do we need a sleep here? i think it's arbitrary since we already wait for clientDone channel

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why do we need a sleep here? i think it's arbitrary since we already wait for clientDone channel

This sleep is to ensure that our server has started processing the request. Although we have confirmed that the client has sent the request through <-clientDone, this means that the request has been sent to the server's listening port, and there is no guarantee that the server's processing coroutine has been sent. Start processing the request, so I think sleep 100 * Millisecond is required here as well.

app.go Outdated Show resolved Hide resolved
@JIeJaitt JIeJaitt marked this pull request as ready for review October 12, 2024 15:32
@JIeJaitt JIeJaitt requested a review from a team as a code owner October 12, 2024 15:32
@JIeJaitt JIeJaitt requested review from gaby, sixcolors, ReneWerner87 and efectn and removed request for a team October 12, 2024 15:32
Copy link
Contributor

coderabbitai bot commented Oct 12, 2024

Walkthrough

The changes enhance the Fiber web framework by introducing a new method New for creating an instance of App with customizable configuration options. The Listen method now accepts an optional ListenConfig parameter for server customization. Additionally, the ShutdownWithContext method has been updated to clarify the execution of shutdown hooks and improve control flow during shutdown. Documentation has been expanded to provide detailed descriptions and examples for these new features, ensuring clarity for users.

Changes

File(s) Change Summary
docs/api/fiber.md Updated documentation to include new New method, detailed Config structure, and examples for app instantiation and server listening.
app.go Updated ShutdownWithContext method to check server state before shutdown and reorder hook execution.
app_test.go Enhanced tests for ShutdownWithContext, added atomic operations for shutdown hook verification, and refined error handling during shutdown.
hooks.go Restructured shutdown hook handling by introducing OnPreShutdownHandler and OnPostShutdownHandler, replacing the previous OnShutdownHandler. Updated execution methods for new hooks.
hooks_test.go Renamed Test_Hook_OnShutdown to Test_Hook_OnPreShutdown, added Test_Hook_OnPostShutdown, and updated assertions for shutdown hooks.
listen.go Removed OnShutdownError and OnShutdownSuccess fields from ListenConfig, centralizing error handling in hooks.
listen_test.go Restructured Test_Listen_Graceful_Shutdown into a new helper function testGracefulShutdown for better organization and clarity.

Possibly related PRs

  • 🔥 feat: Add support for graceful shutdown timeout in ListenConfig #3220: The changes in the main PR regarding the ShutdownWithContext method's error handling and execution order are directly related to the modifications in the retrieved PR, which also focuses on enhancing the shutdown process, including the introduction of a ShutdownTimeout feature that impacts how shutdown errors are managed.
  • 🧹 chore: Improve Performance of Fiber Router #3261: The changes in the main PR regarding the ShutdownWithContext method in the App struct are directly related to the modifications made to the same method in the retrieved PR, which also involves the App struct in the app.go file.
  • 🐛 fix: Improve naming convention for Context returning functions #3193: The changes in the main PR regarding the ShutdownWithContext method and its error handling are related to the modifications in the retrieved PR that involve renaming and clarifying context-related functions, particularly the UserContext() to Context() change, which impacts how contexts are managed during shutdown processes.

Suggested reviewers

  • sixcolors
  • gaby
  • ReneWerner87
  • efectn

Poem

In the garden of code, changes bloom bright,
With hooks that now dance, in the soft moonlight.
Shutdowns are smoother, no more race to the end,
Documentation's richer, our framework's new friend.
So hop with delight, let the updates take flight! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 187ad51 and e4de2c3.

📒 Files selected for processing (1)
  • listen_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
listen_test.go

75-75: Function Listener should pass the context parameter

(contextcheck)

🪛 GitHub Check: lint
listen_test.go

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)

🪛 GitHub Actions: golangci-lint
listen_test.go

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: unit (1.23.x, windows-latest)
  • GitHub Check: Compare
  • GitHub Check: repeated
🔇 Additional comments (2)
listen_test.go (2)

40-48: LGTM! Well-structured test organization.

The test has been nicely refactored into sub-tests with clear scenarios for basic shutdown and timeout cases.


50-152: LGTM! Well-designed test helper function.

The testGracefulShutdown function is well-structured with:

  • Clear setup of test scenarios
  • Table-driven test cases
  • Proper cleanup with defer statements
  • Comprehensive assertions
🧰 Tools
🪛 golangci-lint (1.62.2)

75-75: Function Listener should pass the context parameter

(contextcheck)

🪛 GitHub Check: lint

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)

🪛 GitHub Actions: golangci-lint

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

@gaby gaby changed the title Feature: Complete TODO and Optimize ShutdownWithContext Func feat: Complete TODO and Optimize ShutdownWithContext Func Oct 12, 2024
@gaby gaby changed the title feat: Complete TODO and Optimize ShutdownWithContext Func feat: Improve and Optimize ShutdownWithContext Func Oct 12, 2024
…ub.com:JIeJaitt/fiber into jiejaitt-feature/improve-shutdown-with-context
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 (1)
docs/api/fiber.md (1)

208-208: Approve with minor suggestions for improvement

The added description for ShutdownWithContext is informative and aligns well with the PR objectives. It provides important details about the method's behavior, especially regarding the execution of shutdown hooks. To enhance clarity and readability, consider the following suggestions:

  1. Add a comma after "including" for better sentence structure.
  2. Split the long sentence into two for improved readability.

Here's a suggested revision:

- ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded.Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors.
+ ShutdownWithContext shuts down the server, including by force if the context's deadline is exceeded. Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~208-~208: Possible missing comma found.
Context: ...es. ShutdownWithContext shuts down the server including by force if the context's dea...

(AI_HYDRA_LEO_MISSING_COMMA)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9dd3d94 and 98fbdff.

📒 Files selected for processing (3)
  • app.go (1 hunks)
  • app_test.go (1 hunks)
  • docs/api/fiber.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/api/fiber.md

[uncategorized] ~208-~208: Possible missing comma found.
Context: ...es. ShutdownWithContext shuts down the server including by force if the context's dea...

(AI_HYDRA_LEO_MISSING_COMMA)

🔇 Additional comments (1)
app.go (1)

846-855: Improved shutdown process with deferred hook execution

The changes to the ShutdownWithContext method enhance the shutdown process in several ways:

  1. Thread-safety is ensured by acquiring a mutex lock at the beginning of the method.
  2. A nil check for the server prevents potential panics if the server hasn't been initialized.
  3. The deferral of executeOnShutdownHooks guarantees that shutdown hooks are executed even if an error occurs during the shutdown process.

These improvements contribute to a more robust and reliable shutdown mechanism.

app_test.go Outdated
Comment on lines 897 to 900
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
shutdownErr <- app.ShutdownWithContext(ctx)
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure Proper Context Cancellation

The context created with context.WithTimeout should have its cancellation function deferred immediately to prevent potential resource leaks, even if an error occurs before the defer statement is reached.

Suggested Improvement

Move the defer cancel() directly after context creation.

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+	defer cancel()
	shutdownErr <- app.ShutdownWithContext(ctx)
-	defer cancel()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
shutdownErr <- app.ShutdownWithContext(ctx)
}()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
shutdownErr <- app.ShutdownWithContext(ctx)
}()

app_test.go Outdated
Comment on lines 913 to 917
case err := <-serverErr:
assert.NoError(t, err, "Server should have shut down without error")
default:
// Server is still running, which is expected as the long-running request prevented full shutdown
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid Skipping Server Error Handling

Using a default case in the select statement may cause the test to proceed without waiting for the server to shut down, potentially leading to flaky tests. It's important to handle the server error explicitly.

Suggested Fix: Remove the default Case

This ensures the test waits for serverErr to receive an error or completes the shutdown process.

	select {
	case err := <-serverErr:
		assert.NoError(t, err, "Server should have shut down without error")
-	default:
-		// Server is still running, which is expected as the long-running request prevented full shutdown
	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case err := <-serverErr:
assert.NoError(t, err, "Server should have shut down without error")
default:
// Server is still running, which is expected as the long-running request prevented full shutdown
}
case err := <-serverErr:
assert.NoError(t, err, "Server should have shut down without error")

app_test.go Outdated
Comment on lines 863 to 867
shutdownHookCalled := false
app.Hooks().OnShutdown(func() error {
shutdownHookCalled = true
return nil
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible Data Race on shutdownHookCalled Variable

The shutdownHookCalled boolean is accessed by multiple goroutines without proper synchronization, which could lead to a data race. In Go, variables shared between goroutines should be protected using synchronization mechanisms like channels, mutexes, or atomic operations.

Suggested Fix: Use an Atomic Variable

Replace shutdownHookCalled with an int32 and use atomic.StoreInt32 and atomic.LoadInt32 for thread-safe operations.

+import "sync/atomic"

-	shutdownHookCalled := false
+	var shutdownHookCalled int32

	app.Hooks().OnShutdown(func() error {
-		shutdownHookCalled = true
+		atomic.StoreInt32(&shutdownHookCalled, 1)
		return nil
	})

	// ...

-	assert.True(t, shutdownHookCalled, "Shutdown hook was not called")
+	assert.Equal(t, int32(1), atomic.LoadInt32(&shutdownHookCalled), "Shutdown hook was not called")

Committable suggestion was skipped due to low confidence.

@@ -205,7 +205,7 @@ Shutdown gracefully shuts down the server without interrupting any active connec

ShutdownWithTimeout will forcefully close any active connections after the timeout expires.

ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded.
ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors.
Copy link
Member

Choose a reason for hiding this comment

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

Not sure about this behavior. I think we should only execute them during successful shutdown. Maybe we can OnShutdownError hook. What do you think @gaby @ReneWerner87

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure either. Also not sure about the defer of the hook

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer to add Post Pre hook

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The add Post Pre hook is a good idea, but how do I tell which hooks are pre closed and which are post closed? @efectn

Copy link
Member

Choose a reason for hiding this comment

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

You can add new hook called PostShutdown, PreShutdown and replace the old one with them

Copy link
Member

Choose a reason for hiding this comment

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

sound good, @JIeJaitt can you do the suggested change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This means making a change below the type Hooks struct to add these two fields that mean PreShutdownHandler and PostShutdownHandler respectively , if I understand it correctly I'll try to implement it @ReneWerner87 @efectn @gaby

Copy link
Member

Choose a reason for hiding this comment

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

This means making a change below the type Hooks struct to add these two fields that mean PreShutdownHandler and PostShutdownHandler respectively , if I understand it correctly I'll try to implement it @ReneWerner87 @efectn @gaby

Yes it is. You can also add err parameter to PostShutdown hook, so that we can also get rid of OnShutdownError, OnShutdownSuccess properties of ListenerConfig.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This means making a change below the type Hooks struct to add these two fields that mean PreShutdownHandler and PostShutdownHandler respectively , if I understand it correctly I'll try to implement it @ReneWerner87 @efectn @gaby

Yes it is. You can also add err parameter to PostShutdown hook, so that we can also get rid of OnShutdownError, OnShutdownSuccess properties of ListenerConfig.

OK, I will press on with this idea to realize it again in my free time, by the way what is the v3 deadline as I am busy with my final exams these days.

Copy link
Member

Choose a reason for hiding this comment

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

This means making a change below the type Hooks struct to add these two fields that mean PreShutdownHandler and PostShutdownHandler respectively , if I understand it correctly I'll try to implement it @ReneWerner87 @efectn @gaby

Yes it is. You can also add err parameter to PostShutdown hook, so that we can also get rid of OnShutdownError, OnShutdownSuccess properties of ListenerConfig.

OK, I will press on with this idea to realize it again in my free time, by the way what is the v3 deadline as I am busy with my final exams these days.

We want to release v3 rc1 in the beginning of January. If you can't look at the PR this week, i can also look no problem

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)
app_test.go (1)

913-918: Consider removing commented code

The commented default case on line 917 should be removed as it's no longer needed.

-	// default:
-	// Server is still running, which is expected as the long-running request prevented full shutdown
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a32bddd and 21169dc.

📒 Files selected for processing (2)
  • app.go (1 hunks)
  • app_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app.go
🧰 Additional context used
📓 Learnings (1)
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🔇 Additional comments (6)
app_test.go (6)

24-24: LGTM: Import atomic package for thread-safe operations

The addition of the sync/atomic import is appropriate for implementing thread-safe operations in the test.


865-869: LGTM: Thread-safe shutdown hook tracking

The use of atomic.Int32 for tracking the shutdown hook call is a thread-safe approach, ensuring accurate state tracking in concurrent scenarios.


878-881: LGTM: Proper error handling with buffered channel

Using a buffered channel for server errors prevents potential goroutine leaks and ensures errors are properly captured.


885-891: LGTM: Proper client request setup with synchronization

The client request is properly set up with error handling and synchronization using a channel to signal completion.


894-896: Consider removing the sleep after clientDone

The sleep after the clientDone channel might be unnecessary since we already have synchronization through the channel.

Based on the past review comments and learnings, the clientDone channel is sufficient for synchronization, making the sleep redundant.


898-911: LGTM: Robust shutdown handling with timeout

The shutdown handling is well-implemented with:

  • Proper context timeout
  • Deferred context cancellation
  • Clear error assertions for timeout scenarios

@JIeJaitt JIeJaitt changed the title 🔥 feat: Improve and Optimize ShutdownWithContext Func WIP: 🔥 feat: Improve and Optimize ShutdownWithContext Func Dec 10, 2024
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: 2

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

105-111: Remove commented-out code

The TODO comment and commented-out OnShutdown code should be removed since it's being replaced by the new pre/post shutdown hooks.

-// TODO:To be deleted, replaced by OnPreShutdown and OnPostShutdown
-// OnShutdown is a hook to execute user functions after Shutdown.
-// func (h *Hooks) OnShutdown(handler ...OnShutdownHandler) {
-// 	h.app.mutex.Lock()
-// 	h.onShutdown = append(h.onShutdown, handler...)
-// 	h.app.mutex.Unlock()
-// }

223-229: Consider adding error return values to hook execution methods

The hook execution methods currently log errors but don't return them. Consider returning an error to allow callers to handle failures appropriately.

-func (h *Hooks) executeOnPreShutdownHooks() {
+func (h *Hooks) executeOnPreShutdownHooks() error {
+	var lastErr error
 	for _, v := range h.onPreShutdown {
 		if err := v(); err != nil {
 			log.Errorf("failed to call pre shutdown hook: %v", err)
+			lastErr = err
 		}
 	}
+	return lastErr
}

-func (h *Hooks) executeOnPostShutdownHooks(err error) {
+func (h *Hooks) executeOnPostShutdownHooks(err error) error {
+	var lastErr error
 	for _, v := range h.onPostShutdown {
 		if err := v(err); err != nil {
 			log.Errorf("failed to call post shutdown hook: %v", err)
+			lastErr = err
 		}
 	}
+	return lastErr
}

Also applies to: 231-235

app_test.go (1)

918-919: Remove commented-out code

The commented-out default case should be removed as it's no longer needed.

-	// default:
-	// Server is still running, which is expected as the long-running request prevented full shutdown
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between be64a51 and 87b2aab.

📒 Files selected for processing (4)
  • app.go (1 hunks)
  • app_test.go (2 hunks)
  • hooks.go (5 hunks)
  • hooks_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • hooks_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • app.go
🧰 Additional context used
📓 Learnings (1)
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🔇 Additional comments (4)
hooks.go (2)

14-16: LGTM: Clear type definitions for shutdown hooks

The new pre and post shutdown handler types are well-defined and follow a logical separation of concerns.


32-34: LGTM: Consistent hook storage in Hooks struct

The hook storage fields follow the same pattern as other hooks in the struct.

app_test.go (2)

879-882: LGTM: Proper error handling for server shutdown

The serverErr channel is correctly used to capture and verify server errors.


895-897: 🛠️ Refactor suggestion

Remove redundant sleep

Based on the learnings from efectn, the time.Sleep is redundant since we already wait for the clientDone channel.

 <-clientDone
-// Sleep to ensure the server has started processing the request
-time.Sleep(100 * time.Millisecond)

Likely invalid or redundant comment.

hooks.go Outdated
func (h *Hooks) executeOnPostShutdownHooks(err error) {
for _, v := range h.onPostShutdown {
if err := v(err); err != nil {
log.Errorf("failed to call pre shutdown hook: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix error message in log

The error message incorrectly states "pre shutdown hook" when it should be "post shutdown hook".

-			log.Errorf("failed to call pre shutdown hook: %v", err)
+			log.Errorf("failed to call post shutdown hook: %v", err)

Also applies to: 234-234

app_test.go Outdated
Comment on lines 865 to 871
var shutdownHookCalled atomic.Int32
// TODO: add test
// app.Hooks().OnShutdown(func() error {
// shutdownHookCalled.Store(1)
// return nil
// })

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Uncomment and update shutdown hook test

The TODO comment and commented-out shutdown hook test should be uncommented and updated to use the new pre/post shutdown hooks.

 var shutdownHookCalled atomic.Int32
-// TODO: add test
-// app.Hooks().OnShutdown(func() error {
-// 	shutdownHookCalled.Store(1)
-// 	return nil
-// })
+app.Hooks().OnPreShutdown(func() error {
+	shutdownHookCalled.Store(1)
+	return nil
+})

Committable suggestion skipped: line range outside the PR's diff.

@JIeJaitt JIeJaitt changed the title WIP: 🔥 feat: Improve and Optimize ShutdownWithContext Func 🔥 feat: Improve and Optimize ShutdownWithContext Func Dec 10, 2024
@JIeJaitt JIeJaitt marked this pull request as draft December 10, 2024 13:04
@JIeJaitt
Copy link
Contributor Author

@efectn @ReneWerner87 @gaby I have completed the general revision, now can review my revision ideas, the rest of the lint problem and the content of the new document to add, and so on the big brothers review after I write, to prevent me from writing problems need to repeatedly modify the document!

@JIeJaitt JIeJaitt marked this pull request as ready for review December 11, 2024 09:23
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: 2

🧹 Outside diff range and nitpick comments (10)
hooks_test.go (3)

238-238: Rename Unused Parameter err to Enhance Readability

The parameter err in the closure is not used, which may cause confusion. Renaming it to _ clarifies that it is intentionally unused.

Apply this diff:

-	app.Hooks().OnPostShutdown(func(err error) error {
+	app.Hooks().OnPostShutdown(func(_ error) error {
 		execution = append(execution, 1)
 		return nil
 	})
🧰 Tools
🪛 GitHub Check: lint

[failure] 238-238:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

🪛 golangci-lint (1.62.2)

[warning] 238-238: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


243-243: Rename Unused Parameter err to Enhance Readability

The parameter err in the closure is not used. Consider renaming it to _ to indicate that it is intentionally unused.

Apply this diff:

-	app.Hooks().OnPostShutdown(func(err error) error {
+	app.Hooks().OnPostShutdown(func(_ error) error {
 		execution = append(execution, 2)
 		return nil
 	})
🧰 Tools
🪛 GitHub Check: lint

[failure] 243-243:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

🪛 golangci-lint (1.62.2)

[warning] 243-243: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


263-263: Rename Unused Parameter err in Closure

The parameter err is not used within this closure. Renaming it to _ will make it clear that the error parameter is intentionally unused.

Apply this diff:

-	app.Hooks().OnPostShutdown(func(err error) error {
+	app.Hooks().OnPostShutdown(func(_ error) error {
 		return hookErr
 	})
🧰 Tools
🪛 GitHub Check: lint

[failure] 263-263:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

🪛 golangci-lint (1.62.2)

[warning] 263-263: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)

listen_test.go (6)

50-50: Add t.Helper() to Helper Function

The function testGracefulShutdown is a test helper function. Adding t.Helper() at the beginning of the function improves test diagnostics by marking it as a helper.

Apply this diff:

 func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
+	t.Helper()
 	var mu sync.Mutex
 	var shutdown bool
🧰 Tools
🪛 GitHub Check: lint

[failure] 50-50:
test helper function should start from t.Helper() (thelper)

🪛 golangci-lint (1.62.2)

50-50: test helper function should start from t.Helper()

(thelper)


62-62: Rename Unused Parameter err in Closure

The parameter err in the closure is not used. Renaming it to _ clarifies that it's intentionally unused.

Apply this diff:

-app.hooks.OnPostShutdown(func(err error) error {
+app.hooks.OnPostShutdown(func(_ error) error {
 	mu.Lock()
 	defer mu.Unlock()
 	shutdown = true
 	return nil
 })
🧰 Tools
🪛 GitHub Check: lint

[failure] 62-62:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

🪛 golangci-lint (1.62.2)

[warning] 62-62: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


83-83: Check Error Return Value of conn.Close()

The error returned by conn.Close() is not checked. Failing to handle potential errors might lead to resource leaks or unnoticed issues.

Apply this diff:

	conn, err := ln.Dial()
	if err == nil {
-		conn.Close()
+		err := conn.Close()
+		if err != nil {
+			t.Errorf("Error closing connection: %v", err)
+		}
 		return true
 	}
🧰 Tools
🪛 GitHub Check: lint

[failure] 83-83:
Error return value of conn.Close is not checked (errcheck)

🪛 golangci-lint (1.62.2)

83-83: Error return value of conn.Close is not checked

(errcheck)


120-120: Avoid Unnecessary Copy of Loop Variable

In Go 1.22 and later, it is unnecessary to create a new variable tc inside the loop. The loop variable is no longer reused across iterations, making the copy redundant.

Apply this diff:

	for _, tc := range testCases {
-		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			time.Sleep(tc.waitTime)
🧰 Tools
🪛 golangci-lint (1.62.2)

120-120: The copy of the 'for' variable "tc" can be deleted (Go 1.22+)

(copyloopvar)


134-134: Use require.NoError Instead of assert.NoError

In test code, using require.NoError is preferred over assert.NoError when the test cannot proceed if there's an error. It fails the test immediately, preventing further assertions on invalid data.

Apply this diff:

-	assert.NoError(t, err)
+	require.NoError(t, err)
🧰 Tools
🪛 golangci-lint (1.62.2)

134-134: require-error: for error assertions use require

(testifylint)


145-145: Use require.NoError Instead of assert.NoError

To ensure the test fails immediately if there is an error, use require.NoError instead of assert.NoError.

Apply this diff:

-	assert.NoError(t, <-errs)
+	require.NoError(t, <-errs)
🧰 Tools
🪛 golangci-lint (1.62.2)

145-145: require-error: for error assertions use require

(testifylint)

listen.go (1)

485-489: Architectural improvement: Centralized shutdown notification via hooks

The change from direct callback functions to using the hooks system is a good architectural improvement. This centralization makes the shutdown process more maintainable and consistent.

Consider documenting the hook execution order in the package documentation to help users understand when their hooks will be called during shutdown.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 485-485: listen.go#L485
Added line #L485 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 87b2aab and 6aeb048.

📒 Files selected for processing (6)
  • app.go (1 hunks)
  • app_test.go (2 hunks)
  • hooks.go (5 hunks)
  • hooks_test.go (1 hunks)
  • listen.go (1 hunks)
  • listen_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app.go
🧰 Additional context used
📓 Learnings (1)
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🪛 GitHub Check: codecov/patch
listen.go

[warning] 485-485: listen.go#L485
Added line #L485 was not covered by tests

hooks.go

[warning] 207-208: hooks.go#L207-L208
Added lines #L207 - L208 were not covered by tests

🪛 GitHub Check: lint
hooks_test.go

[failure] 217-217:
Error return value of app.Listen is not checked (errcheck)


[failure] 228-228:
comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error (errorlint)


[failure] 238-238:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 243-243:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 259-259:
unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 263-263:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

listen_test.go

[failure] 50-50:
test helper function should start from t.Helper() (thelper)


[failure] 62-62:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 73-73:
Function Listener should pass the context parameter (contextcheck)


[failure] 83-83:
Error return value of conn.Close is not checked (errcheck)

🪛 golangci-lint (1.62.2)
hooks_test.go

217-217: Error return value of app.Listen is not checked

(errcheck)


228-228: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


[warning] 238-238: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 243-243: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 259-259: unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 263-263: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)

listen_test.go

50-50: test helper function should start from t.Helper()

(thelper)


[warning] 62-62: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


73-73: Function Listener should pass the context parameter

(contextcheck)


83-83: Error return value of conn.Close is not checked

(errcheck)


93-93: fieldalignment: struct with 64 pointer bytes could be 40

(govet)


120-120: The copy of the 'for' variable "tc" can be deleted (Go 1.22+)

(copyloopvar)


134-134: require-error: for error assertions use require

(testifylint)


145-145: require-error: for error assertions use require

(testifylint)

🔇 Additional comments (4)
app_test.go (1)

895-897: Consider Removing Unnecessary Sleep to Improve Test Reliability

Using time.Sleep(100 * time.Millisecond) after receiving clientDone might lead to flaky tests or unnecessary delays. Since the clientDone channel ensures that the client has sent the request, consider implementing a synchronization mechanism to confirm that the server has started processing the request instead of relying on time.Sleep.

hooks.go (1)

207-208: 🛠️ Refactor suggestion

Increase Test Coverage for Error Handling in executeOnPreShutdownHooks

The error handling code within executeOnPreShutdownHooks is not currently covered by tests. To ensure that errors returned by pre-shutdown hooks are properly handled and logged, consider adding test cases that include pre-shutdown hooks which return errors. This will improve the robustness and reliability of the shutdown process.

Would you like assistance in generating the necessary tests to cover this error handling code?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 207-208: hooks.go#L207-L208
Added lines #L207 - L208 were not covered by tests

listen.go (2)

485-489: Verify hook execution order

The hooks system needs to guarantee consistent execution order during shutdown.

Let's verify the hook execution implementation:

✅ Verification successful

Hook execution order is properly maintained

The implementation guarantees consistent execution order during shutdown:

  • Hooks are stored in a slice (onPostShutdown) that maintains insertion order
  • Registration is thread-safe using app-level mutex lock/unlock
  • Hooks are executed sequentially in the order they were registered
  • Error handling is properly implemented without breaking execution order
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check hooks implementation
ast-grep --pattern 'func (h *Hooks) executeOnPostShutdownHooks($_) {
  $$$
}'

# Check for potential race conditions in hooks execution
rg -A 5 "sync\.Mutex.*executeOnPostShutdownHooks"

Length of output: 418


Script:

#!/bin/bash
# Check how hooks are registered and stored
ast-grep --pattern 'type Hooks struct {
  $$$
}'

# Check hook registration methods
ast-grep --pattern 'func (h *Hooks) OnPostShutdown($_) {
  $$$
}'

# Look for hook registration calls
rg -A 2 "OnPostShutdown" 

Length of output: 3150

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 485-485: listen.go#L485
Added line #L485 was not covered by tests


485-485: Add test coverage for error path

The static analysis indicates that line 485 (error path) lacks test coverage.

Let's verify the test coverage and existing tests:

Would you like me to help create a test case that covers the error path during shutdown?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 485-485: listen.go#L485
Added line #L485 was not covered by tests

hooks_test.go Outdated
t.Fatal("hook was not called")
}

if receivedErr != expectedErr {
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 11, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use errors.Is for Error Comparison

Comparing errors using != may fail when errors are wrapped. To accurately check for specific errors, use errors.Is instead.

Apply this diff to improve error comparison:

-	if receivedErr != expectedErr {
+	if !errors.Is(receivedErr, expectedErr) {
 		t.Fatalf("hook received wrong error: want %v, got %v", expectedErr, receivedErr)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if receivedErr != expectedErr {
if !errors.Is(receivedErr, expectedErr) {
🧰 Tools
🪛 GitHub Check: lint

[failure] 228-228:
comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error (errorlint)

🪛 golangci-lint (1.62.2)

228-228: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)

Copy link
Member

Choose a reason for hiding this comment

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

You can use require methods instead

Copy link
Contributor

Choose a reason for hiding this comment

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

@efectn, you're right, using the require methods simplifies the test assertions. You can modify the test code to:

require.ErrorIs(t, receivedErr, expectedErr)

or

require.Equal(t, expectedErr, receivedErr)

This leverages the testify package's assertion functions for cleaner and more expressive tests.


✏️ Learnings added
Learnt from: efectn
PR: gofiber/fiber#3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.698Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

hooks_test.go Outdated
})

go func() {
_ = app.Listen(":0")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check Error Return Value of app.Listen

The error returned by app.Listen(":0") is not checked. Ignoring errors can lead to unexpected behaviors or missed errors during testing. Please handle the error appropriately to ensure that any issues during the listening process are captured.

Apply this diff to handle the error:

-	_ = app.Listen(":0")
+	err := app.Listen(":0")
+	require.NoError(t, err, "app.Listen failed")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_ = app.Listen(":0")
err := app.Listen(":0")
require.NoError(t, err, "app.Listen failed")
🧰 Tools
🪛 GitHub Check: lint

[failure] 217-217:
Error return value of app.Listen is not checked (errcheck)

🪛 golangci-lint (1.62.2)

217-217: Error return value of app.Listen is not checked

(errcheck)

@@ -900,16 +900,18 @@ func (app *App) ShutdownWithTimeout(timeout time.Duration) error {
//
// ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout to something else than 0.
func (app *App) ShutdownWithContext(ctx context.Context) error {
if app.hooks != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Here we can define executeOnPostShutdownHooks hook with defer as well as PreShutdownHook

Copy link
Contributor Author

@JIeJaitt JIeJaitt Dec 17, 2024

Choose a reason for hiding this comment

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

Here we can define executeOnPostShutdownHooks hook with defer as well as PreShutdownHook

Is that what you mean?I think PreShutdownHook and PostShutdownHook set outside the function and then executed at ShutdownWithContext

func (app *App) ShutdownWithContext(ctx context.Context) error {
	app.mutex.Lock()
	defer app.mutex.Unlock()

	if app.server == nil {
		return ErrNotRunning
	}

	// Execute the Shutdown hook
	if app.hooks != nil {
		app.hooks.executeOnPreShutdownHooks()
	}
	defer app.hooks.executeOnPostShutdownHooks(nil)

	return app.server.ShutdownWithContext(ctx)
}

Copy link
Member

Choose a reason for hiding this comment

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

Here we can define executeOnPostShutdownHooks hook with defer as well as PreShutdownHook

Is that what you mean?I think PreShutdownHook and PostShutdownHook set outside the function and then executed at ShutdownWithContext

func (app *App) ShutdownWithContext(ctx context.Context) error {
	app.mutex.Lock()
	defer app.mutex.Unlock()

	if app.server == nil {
		return ErrNotRunning
	}

	// Execute the Shutdown hook
	if app.hooks != nil {
		app.hooks.executeOnPreShutdownHooks()
	}
	defer app.hooks.executeOnPostShutdownHooks(nil)

	return app.server.ShutdownWithContext(ctx)
}

You can do it like:

func (app *App) ShutdownWithContext(ctx context.Context) error {
	app.mutex.Lock()
	defer app.mutex.Unlock()

	var err error

	if app.server == nil {
		return ErrNotRunning
	}

	// Execute the Shutdown hook
	app.hooks.executeOnPreShutdownHooks()
	defer app.hooks.executeOnPostShutdownHooks(err)

	err = app.server.ShutdownWithContext(ctx)
	return err
}

So that, we won't need to add another additional check to listen.go and this is going to work even if you use app.Shutdown() separately. However, this hook wouldn't work if app.Listen() hadn't been started in another goroutine. Maybe we can also add a note to indicate it -> https://github.com/valyala/fasthttp/blob/master/server.go#L1830

@ReneWerner87
Copy link
Member

@JIeJaitt can you update the code with the last ideas

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

🧹 Nitpick comments (6)
docs/api/fiber.md (1)

233-233: Improve documentation clarity and fix grammar.

The sentence structure could be improved for better readability and comprehension.

Apply this diff to enhance clarity and fix grammar:

-ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors.
+ShutdownWithContext shuts down the server, including by force, if the context's deadline is exceeded. Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~233-~233: Possible missing comma found.
Context: ...es. ShutdownWithContext shuts down the server including by force if the context's dea...

(AI_HYDRA_LEO_MISSING_COMMA)

app_test.go (2)

942-942: Replace sleep with proper synchronization.

Using time.Sleep for synchronization can make tests flaky. Consider using a channel or sync.WaitGroup to properly synchronize the server startup.

Here's how you could improve it:

+    serverReady := make(chan struct{})
     go func() {
+        defer close(serverReady)
         _ = app.Listener(ln)
     }()
-    time.Sleep(100 * time.Millisecond)
+    <-serverReady

930-935: Improve hook error handling.

The err parameter in the post-shutdown hook is unused. Either use it to verify the shutdown error or rename it to _ to indicate it's intentionally ignored.

Apply this diff:

-app.Hooks().OnPostShutdown(func(err error) error {
+app.Hooks().OnPostShutdown(func(_ error) error {
     hookMutex.Lock()
     hookOrder = append(hookOrder, "post")
     hookMutex.Unlock()
     return nil
 })
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 930-930: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)

🪛 GitHub Check: lint

[failure] 930-930:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

listen_test.go (3)

62-67: Consider using the error parameter or rename it to _.

The error parameter in the OnPostShutdown hook is unused.

-app.hooks.OnPostShutdown(func(err error) error {
+app.hooks.OnPostShutdown(func(_ error) error {
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 62-62: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


83-83: Check error from conn.Close.

The error from conn.Close() should be checked.

-conn.Close()
+if err := conn.Close(); err != nil {
+    t.Logf("error closing connection: %v", err)
+}
🧰 Tools
🪛 golangci-lint (1.62.2)

83-83: Error return value of conn.Close is not checked

(errcheck)


134-134: Use require instead of assert for error checks.

For error assertions, use require instead of assert to ensure the test fails immediately if the assertion fails.

-assert.NoError(t, err)
+require.NoError(t, err)

-assert.NoError(t, <-errs)
+require.NoError(t, <-errs)

Also applies to: 145-145

🧰 Tools
🪛 golangci-lint (1.62.2)

134-134: require-error: for error assertions use require

(testifylint)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6aeb048 and 41487b5.

📒 Files selected for processing (5)
  • app.go (2 hunks)
  • app_test.go (2 hunks)
  • docs/api/fiber.md (1 hunks)
  • listen.go (1 hunks)
  • listen_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app.go
🧰 Additional context used
🧠 Learnings (1)
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🪛 GitHub Check: codecov/patch
listen.go

[warning] 500-500: listen.go#L500
Added line #L500 was not covered by tests

🪛 golangci-lint (1.62.2)
app_test.go

897-897: Error return value of app.Listener is not checked

(errcheck)


[warning] 930-930: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


939-939: Error return value of app.Listener is not checked

(errcheck)


961-961: Error return value of app.Listener is not checked

(errcheck)


968-968: Error return value of ln.Dial is not checked

(errcheck)


969-969: Error return value of conn.Write is not checked

(errcheck)

listen_test.go

50-50: test helper function should start from t.Helper()

(thelper)


[warning] 62-62: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


73-73: Function Listener should pass the context parameter

(contextcheck)


83-83: Error return value of conn.Close is not checked

(errcheck)


93-93: fieldalignment: struct with 64 pointer bytes could be 40

(govet)


120-120: The copy of the 'for' variable "tc" can be deleted (Go 1.22+)

(copyloopvar)


134-134: require-error: for error assertions use require

(testifylint)


145-145: require-error: for error assertions use require

(testifylint)

🪛 GitHub Check: lint
app_test.go

[failure] 897-897:
Error return value of app.Listener is not checked (errcheck)


[failure] 930-930:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 939-939:
Error return value of app.Listener is not checked (errcheck)


[failure] 961-961:
Error return value of app.Listener is not checked (errcheck)


[failure] 968-968:
Error return value of ln.Dial is not checked (errcheck)


[failure] 969-969:
Error return value of conn.Write is not checked (errcheck)

🪛 GitHub Actions: golangci-lint
app_test.go

[error] 897-897: Error return value of app.Listener is not checked (errcheck)

🪛 LanguageTool
docs/api/fiber.md

[uncategorized] ~233-~233: Possible missing comma found.
Context: ...es. ShutdownWithContext shuts down the server including by force if the context's dea...

(AI_HYDRA_LEO_MISSING_COMMA)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: unit (1.23.x, macos-13)
  • GitHub Check: unit (1.23.x, windows-latest)
  • GitHub Check: Compare
  • GitHub Check: Analyse
  • GitHub Check: repeated
🔇 Additional comments (4)
docs/api/fiber.md (1)

233-234: Documentation accurately reflects implementation changes.

The documentation effectively captures the key changes to the ShutdownWithContext method, including the guaranteed execution of shutdown hooks through deferred calls and improved error handling.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~233-~233: Possible missing comma found.
Context: ...es. ShutdownWithContext shuts down the server including by force if the context's dea...

(AI_HYDRA_LEO_MISSING_COMMA)

app_test.go (1)

883-914: Well-structured test case for successful shutdown!

The test case is well-organized with clear setup, execution, and verification steps. It effectively tests the basic shutdown functionality with a fast request.

🧰 Tools
🪛 golangci-lint (1.62.2)

897-897: Error return value of app.Listener is not checked

(errcheck)

🪛 GitHub Check: lint

[failure] 897-897:
Error return value of app.Listener is not checked (errcheck)

🪛 GitHub Actions: golangci-lint

[error] 897-897: Error return value of app.Listener is not checked (errcheck)

listen.go (1)

500-504: Add test coverage for the error case.

The changes look good and align with the PR objectives to improve the shutdown process. However, line 500 is not covered by tests.

Please add a test case to cover the error scenario in the shutdown process. I can help you generate the test code if needed.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 500-500: listen.go#L500
Added line #L500 was not covered by tests

listen_test.go (1)

41-47: LGTM! Well-structured test cases.

The use of sub-tests improves test organization and readability, covering both basic shutdown and shutdown with timeout scenarios.

app_test.go Outdated
Comment on lines 895 to 898
go func() {
serverStarted <- true
_ = app.Listener(ln)
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for critical operations.

Several error returns from critical operations are not checked:

  • app.Listener(ln) calls on lines 897, 939, and 961
  • Network operations on lines 968-969

These errors could affect test reliability if they fail silently.

Apply this diff to add error handling:

 go func() {
     serverStarted <- true
-    _ = app.Listener(ln)
+    if err := app.Listener(ln); err != nil {
+        t.Errorf("Failed to start listener: %v", err)
+    }
 }()

 // In other goroutines...
 go func() {
-    _ = app.Listener(ln)
+    if err := app.Listener(ln); err != nil {
+        t.Errorf("Failed to start listener: %v", err)
+    }
 }()

 go func() {
-    conn, _ := ln.Dial()
-    _, _ = conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
+    conn, err := ln.Dial()
+    if err != nil {
+        t.Errorf("Failed to dial: %v", err)
+        return
+    }
+    if _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")); err != nil {
+        t.Errorf("Failed to write: %v", err)
+    }
 }()

Also applies to: 937-940, 959-962, 967-970

🧰 Tools
🪛 golangci-lint (1.62.2)

897-897: Error return value of app.Listener is not checked

(errcheck)

🪛 GitHub Check: lint

[failure] 897-897:
Error return value of app.Listener is not checked (errcheck)

🪛 GitHub Actions: golangci-lint

[error] 897-897: Error return value of app.Listener is not checked (errcheck)

}

// go test -run Test_Listen_Graceful_Shutdown_Timeout
func Test_Listen_Graceful_Shutdown_Timeout(t *testing.T) {
func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add t.Helper() call.

Since this is a test helper function, add t.Helper() as the first statement to improve test failure reporting.

 func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
+    t.Helper()
     var mu sync.Mutex
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
t.Helper()
var mu sync.Mutex
// ... rest of the function body ...
}
🧰 Tools
🪛 golangci-lint (1.62.2)

50-50: test helper function should start from t.Helper()

(thelper)

Comment on lines 73 to 78
errs <- app.Listener(ln, ListenConfig{
DisableStartupMessage: true,
GracefulContext: ctx,
ShutdownTimeout: 500 * time.Millisecond,
OnShutdownSuccess: func() {
mu.Lock()
shutdownSuccess = true
mu.Unlock()
},
OnShutdownError: func(err error) {
mu.Lock()
shutdownTimeoutError = err
mu.Unlock()
},
ShutdownTimeout: shutdownTimeout,
})
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Pass context to Listener.

The Listener function should receive the context parameter for proper shutdown handling.

-errs <- app.Listener(ln, ListenConfig{
+errs <- app.Listener(ln, ListenConfig{
     DisableStartupMessage: true,
     GracefulContext:       ctx,
     ShutdownTimeout:       shutdownTimeout,
 })

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 golangci-lint (1.62.2)

73-73: Function Listener should pass the context parameter

(contextcheck)

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: 2

🧹 Nitpick comments (3)
app_test.go (3)

883-916: Enhance test assertions for shutdown state.

The test case verifies basic shutdown behavior but could be improved by:

  1. Adding assertions to verify the server state after shutdown.
  2. Adding a timeout to prevent the test from hanging if shutdown fails.
 t.Run("successful shutdown", func(t *testing.T) {
     t.Parallel()
     app := New()
+    isShutdown := false
+    app.Hooks().OnPostShutdown(func(err error) error {
+        isShutdown = true
+        return nil
+    })

     // Fast request that should complete
     app.Get("/", func(c Ctx) error {
         return c.SendString("OK")
     })

     ln := fasthttputil.NewInmemoryListener()
     serverStarted := make(chan bool, 1)

     go func() {
         serverStarted <- true
         if err := app.Listener(ln); err != nil {
             t.Errorf("Failed to start listener: %v", err)
         }
     }()

     <-serverStarted

     // Execute normal request
     conn, err := ln.Dial()
     require.NoError(t, err)
     _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
     require.NoError(t, err)

     // Shutdown with sufficient timeout
     ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
     defer cancel()

     err = app.ShutdownWithContext(ctx)
     require.NoError(t, err, "Expected successful shutdown")
+    require.True(t, isShutdown, "Server should be in shutdown state")
 })

932-937: Address unused parameter in OnPostShutdown hook.

The err parameter in the OnPostShutdown hook is unused. Consider using _ to explicitly ignore it.

-app.Hooks().OnPostShutdown(func(err error) error {
+app.Hooks().OnPostShutdown(func(_ error) error {
     hookMutex.Lock()
     hookOrder = append(hookOrder, "post")
     hookMutex.Unlock()
     return nil
 })
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 932-932: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)

🪛 GitHub Check: lint

[failure] 932-932:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)

🪛 GitHub Actions: golangci-lint

[warning] 932-932: Parameter 'err' seems to be unused, consider removing or renaming it as _.


939-944: Improve error handling for app.Listener.

The error from app.Listener should be handled more gracefully.

 ln := fasthttputil.NewInmemoryListener()
+serverErr := make(chan error, 1)
 go func() {
     if err := app.Listener(ln); err != nil {
-        t.Errorf("Failed to start listener: %v", err)
+        serverErr <- fmt.Errorf("failed to start listener: %w", err)
+        return
     }
+    serverErr <- nil
 }()
+
+select {
+case err := <-serverErr:
+    if err != nil {
+        t.Fatal(err)
+    }
+case <-time.After(100 * time.Millisecond):
+    // Server started successfully
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41487b5 and e5a1ef5.

📒 Files selected for processing (1)
  • app_test.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🪛 golangci-lint (1.62.2)
app_test.go

[warning] 932-932: unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _

(revive)


965-965: Error return value of app.Listener is not checked

(errcheck)

🪛 GitHub Check: lint
app_test.go

[failure] 932-932:
unused-parameter: parameter 'err' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 965-965:
Error return value of app.Listener is not checked (errcheck)

🪛 GitHub Actions: golangci-lint
app_test.go

[warning] 932-932: Parameter 'err' seems to be unused, consider removing or renaming it as _.

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: unit (1.23.x, macos-13)
  • GitHub Check: unit (1.23.x, macos-latest)
  • GitHub Check: unit (1.23.x, windows-latest)
  • GitHub Check: Compare
  • GitHub Check: Analyse
  • GitHub Check: repeated

app_test.go Outdated
Comment on lines 964 to 966
go func() {
_ = app.Listener(ln)
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for app.Listener.

The error from app.Listener should be handled properly.

+    serverErr := make(chan error, 1)
     go func() {
-        _ = app.Listener(ln)
+        if err := app.Listener(ln); err != nil {
+            serverErr <- fmt.Errorf("failed to start listener: %w", err)
+            return
+        }
+        serverErr <- nil
     }()
+
+    select {
+    case err := <-serverErr:
+        if err != nil {
+            t.Fatal(err)
+        }
+    case <-time.After(100 * time.Millisecond):
+        // Server started successfully
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
go func() {
_ = app.Listener(ln)
}()
serverErr := make(chan error, 1)
go func() {
if err := app.Listener(ln); err != nil {
serverErr <- fmt.Errorf("failed to start listener: %w", err)
return
}
serverErr <- nil
}()
select {
case err := <-serverErr:
if err != nil {
t.Fatal(err)
}
case <-time.After(100 * time.Millisecond):
// Server started successfully
}
🧰 Tools
🪛 golangci-lint (1.62.2)

965-965: Error return value of app.Listener is not checked

(errcheck)

🪛 GitHub Check: lint

[failure] 965-965:
Error return value of app.Listener is not checked (errcheck)

Comment on lines +982 to +983
time.Sleep(100 * time.Millisecond) // Wait for request to start

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace sleep with proper synchronization.

Using time.Sleep for synchronization can lead to flaky tests. Consider using a channel to signal when the request has started processing.

+    requestStarted := make(chan struct{})
     app.Get("/", func(c Ctx) error {
+        close(requestStarted)
         time.Sleep(2 * time.Second)
         return c.SendString("OK")
     })

     // ... connection setup ...

-    time.Sleep(100 * time.Millisecond) // Wait for request to start
+    select {
+    case <-requestStarted:
+        // Request has started processing
+    case <-time.After(time.Second):
+        t.Fatal("Request did not start in time")
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
time.Sleep(100 * time.Millisecond) // Wait for request to start
// Add a channel to signal when the request has started processing
requestStarted := make(chan struct{})
app.Get("/", func(c Ctx) error {
// Signal that the handler has started processing the request
close(requestStarted)
time.Sleep(2 * time.Second)
return c.SendString("OK")
})
// ... connection setup ...
// Instead of using a fixed sleep, wait for the request to start
select {
case <-requestStarted:
// Request has started processing
case <-time.After(time.Second):
t.Fatal("Request did not start in time")
}

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: 2

♻️ Duplicate comments (3)
listen_test.go (1)

74-80: ⚠️ Potential issue

Pass context to Listener function.

The static analysis tools and pipeline failures indicate that the context parameter should be passed to the Listener function.

 go func() {
-    errs <- app.Listener(ln, ListenConfig{
+    errs <- app.Listener(ln, ListenConfig{
         DisableStartupMessage: true,
         GracefulContext:       ctx,
         ShutdownTimeout:       shutdownTimeout,
     })
 }()
🧰 Tools
🪛 golangci-lint (1.62.2)

75-75: Function Listener should pass the context parameter

(contextcheck)

🪛 GitHub Check: lint

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)

🪛 GitHub Actions: golangci-lint

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

hooks_test.go (2)

216-220: ⚠️ Potential issue

Handle error from app.Listen properly.

The error from app.Listen should be properly handled using the require package.

Apply this diff:

 go func() {
-    if err := app.Listen(":0"); err != nil {
-        t.Errorf("Failed to start listener: %v", err)
-    }
+    require.NoError(t, app.Listen(":0"))
 }()

230-232: ⚠️ Potential issue

Use require.ErrorIs for error comparison.

Replace manual error comparison with require.ErrorIs for better handling of wrapped errors.

Apply this diff:

-if !errors.Is(receivedErr, expectedErr) {
-    t.Fatalf("hook received wrong error: want %v, got %v", expectedErr, receivedErr)
-}
+require.ErrorIs(t, receivedErr, expectedErr, "hook received wrong error")
🧹 Nitpick comments (4)
listen_test.go (2)

64-69: Consider adding error handling for OnPostShutdown hook.

The hook function should handle potential errors that might occur during the shutdown process.

 app.hooks.OnPostShutdown(func(_ error) error {
     mu.Lock()
     defer mu.Unlock()
     shutdown = true
+    if err := someCleanupFunction(); err != nil {
+        return fmt.Errorf("shutdown cleanup failed: %w", err)
+    }
     return nil
 })

129-145: Consider using test cleanup for resource management.

While the current implementation using defer for request and response cleanup is good, consider using t.Cleanup() for better test isolation.

 t.Run(tc.name, func(t *testing.T) {
+    t.Cleanup(func() {
+        fasthttp.ReleaseRequest(req)
+        fasthttp.ReleaseResponse(resp)
+    })
     time.Sleep(tc.waitTime)

     req := fasthttp.AcquireRequest()
-    defer fasthttp.ReleaseRequest(req)
     req.SetRequestURI("http://example.com")

     resp := fasthttp.AcquireResponse()
-    defer fasthttp.ReleaseResponse(resp)
hooks_test.go (2)

184-200: Consider adding more test cases for pre-shutdown hooks.

The test verifies basic functionality but could be enhanced with:

  • Error handling scenarios
  • Multiple hooks execution order
  • Edge cases (e.g., nil hooks, panic recovery)

Here's a suggested addition:

 func Test_Hook_OnPrehutdown(t *testing.T) {
+	t.Run("successful execution", func(t *testing.T) {
     t.Parallel()
     app := New()
     // ... existing test code ...
+	})
+
+	t.Run("error handling", func(t *testing.T) {
+		app := New()
+		app.Hooks().OnPreShutdown(func() error {
+			return errors.New("shutdown error")
+		})
+		require.Error(t, app.Shutdown())
+	})
+
+	t.Run("multiple hooks order", func(t *testing.T) {
+		app := New()
+		order := make([]int, 0)
+		app.Hooks().OnPreShutdown(func() error {
+			order = append(order, 1)
+			return nil
+		})
+		app.Hooks().OnPreShutdown(func() error {
+			order = append(order, 2)
+			return nil
+		})
+		require.NoError(t, app.Shutdown())
+		require.Equal(t, []int{1, 2}, order)
+	})
 }

226-228: Use require.True for boolean assertions.

Replace manual assertion with require.True for better test readability and error messages.

Apply this diff:

-if !hookCalled {
-    t.Fatal("hook was not called")
-}
+require.True(t, hookCalled, "hook was not called")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5a1ef5 and bab3038.

📒 Files selected for processing (3)
  • app_test.go (2 hunks)
  • hooks_test.go (1 hunks)
  • listen_test.go (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
hooks_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
app_test.go (1)
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
🪛 golangci-lint (1.62.2)
listen_test.go

75-75: Function Listener should pass the context parameter

(contextcheck)

🪛 GitHub Check: lint
listen_test.go

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)

🪛 GitHub Actions: golangci-lint
listen_test.go

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: unit (1.23.x, macos-13)
  • GitHub Check: unit (1.23.x, windows-latest)
  • GitHub Check: Analyse
  • GitHub Check: Compare
  • GitHub Check: repeated
🔇 Additional comments (5)
listen_test.go (3)

40-48: LGTM! Well-structured test organization.

The test has been nicely restructured into sub-tests with clear names and separation of concerns.


50-51: LGTM! t.Helper() is correctly added.

The test helper function correctly includes t.Helper() as the first statement.


97-104: LGTM! Well-structured test cases.

The table-driven test approach with clear test case definitions improves readability and maintainability.

Also applies to: 106-123

app_test.go (2)

883-916: LGTM! Well-structured test with proper synchronization and error handling.

The test effectively verifies successful shutdown with:

  • Proper error handling for the listener
  • Server startup synchronization using channels
  • Sufficient timeout duration for shutdown

918-952: LGTM! Well-implemented hook execution test with proper synchronization.

The test effectively verifies hook execution with:

  • Thread-safe hook order tracking using mutex
  • Clear verification of hook execution order
  • Proper error handling for the listener

Comment on lines +954 to +991
t.Run("timeout with long running request", func(t *testing.T) {
t.Parallel()
app := New()

app.Get("/", func(c Ctx) error {
time.Sleep(2 * time.Second)
return c.SendString("OK")
})

ln := fasthttputil.NewInmemoryListener()
go func() {
if err := app.Listener(ln); err != nil {
t.Errorf("Failed to start listener: %v", err)
}
}()

time.Sleep(100 * time.Millisecond)

// Start long request
go func() {
conn, err := ln.Dial()
if err != nil {
t.Errorf("Failed to dial: %v", err)
return
}
if _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")); err != nil {
t.Errorf("Failed to write: %v", err)
}
}()

time.Sleep(100 * time.Millisecond) // Wait for request to start

ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
shutdownErr <- app.ShutdownWithContext(ctx)
}()

select {
case <-time.After(5 * time.Second):
t.Fatal("idle connections not closed on shutdown")
case err := <-shutdownErr:
if err == nil || !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("unexpected err %v. Expecting %v", err, context.DeadlineExceeded)
}
}
err := app.ShutdownWithContext(ctx)
require.ErrorIs(t, err, context.DeadlineExceeded)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace sleep-based synchronization with channels for test reliability.

The test correctly verifies timeout behavior, but using time.Sleep for synchronization can make tests flaky. Consider using channels to synchronize the request start, similar to how clientDone is used in other tests.

Apply this diff to improve test reliability:

 app.Get("/", func(c Ctx) error {
+    requestStarted <- struct{}{}
     time.Sleep(2 * time.Second)
     return c.SendString("OK")
 })

 ln := fasthttputil.NewInmemoryListener()
+requestStarted := make(chan struct{})
 go func() {
     if err := app.Listener(ln); err != nil {
         t.Errorf("Failed to start listener: %v", err)
     }
 }()

-time.Sleep(100 * time.Millisecond)
+select {
+case <-requestStarted:
+    // Request has started processing
+case <-time.After(time.Second):
+    t.Fatal("Request did not start in time")
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +202 to 272
func Test_Hook_OnPostShutdown(t *testing.T) {
t.Run("should execute post shutdown hook with error", func(t *testing.T) {
app := New()

hookCalled := false
var receivedErr error
expectedErr := errors.New("test shutdown error")

app.Hooks().OnPostShutdown(func(err error) error {
hookCalled = true
receivedErr = err
return nil
})

go func() {
if err := app.Listen(":0"); err != nil {
t.Errorf("Failed to start listener: %v", err)
}
}()

time.Sleep(100 * time.Millisecond)

app.hooks.executeOnPostShutdownHooks(expectedErr)

if !hookCalled {
t.Fatal("hook was not called")
}

if !errors.Is(receivedErr, expectedErr) {
t.Fatalf("hook received wrong error: want %v, got %v", expectedErr, receivedErr)
}
})

t.Run("should execute multiple hooks in order", func(t *testing.T) {
app := New()

execution := make([]int, 0)

app.Hooks().OnPostShutdown(func(_ error) error {
execution = append(execution, 1)
return nil
})

app.Hooks().OnPostShutdown(func(_ error) error {
execution = append(execution, 2)
return nil
})

app.hooks.executeOnPostShutdownHooks(nil)

if len(execution) != 2 {
t.Fatalf("expected 2 hooks to execute, got %d", len(execution))
}

if execution[0] != 1 || execution[1] != 2 {
t.Fatal("hooks executed in wrong order")
}
})

t.Run("should handle hook error", func(_ *testing.T) {
app := New()
hookErr := errors.New("hook error")

app.Hooks().OnPostShutdown(func(_ error) error {
return hookErr
})

// Should not panic
app.hooks.executeOnPostShutdownHooks(nil)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add synchronization to prevent race conditions.

The test uses goroutines and shared variables without proper synchronization, which could lead to race conditions.

Consider using a channel or WaitGroup to properly synchronize the test:

 func Test_Hook_OnPostShutdown(t *testing.T) {
     t.Run("should execute post shutdown hook with error", func(t *testing.T) {
         app := New()
+        ready := make(chan struct{})
 
         hookCalled := false
         var receivedErr error
         expectedErr := errors.New("test shutdown error")
 
         app.Hooks().OnPostShutdown(func(err error) error {
             hookCalled = true
             receivedErr = err
             return nil
         })
 
         go func() {
+            defer close(ready)
             require.NoError(t, app.Listen(":0"))
         }()
 
-        time.Sleep(100 * time.Millisecond)
+        <-ready
 
         app.hooks.executeOnPostShutdownHooks(expectedErr)
         // ... rest of the test
     })

Committable suggestion skipped: line range outside the PR's diff.

@JIeJaitt
Copy link
Contributor Author

JIeJaitt commented Feb 11, 2025

@ReneWerner87 Hi, I have updated the code as per the latest discussion. But I'm having a little go lint glitch. The Listener entry doesn't pass ctx, but go-lint lets me pass ctx, which is weird!
image
image

To solve this problem, you may have to change the signature of the Listener method so that it receives the context parameter directly. This seems to be a long-standing problem.

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

♻️ Duplicate comments (1)
listen_test.go (1)

74-80: ⚠️ Potential issue

Pass context to Listener function.

The static analysis tool correctly identifies that the context parameter should be passed to the Listener function.

-errs <- app.Listener(ln, ListenConfig{
+errs <- app.Listener(ln, ListenConfig{
     DisableStartupMessage: true,
     GracefulContext:       ctx,
     ShutdownTimeout:       shutdownTimeout,
 })
🧰 Tools
🪛 golangci-lint (1.62.2)

75-75: Function Listener should pass the context parameter

(contextcheck)

🪛 GitHub Check: lint

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)

🪛 GitHub Actions: golangci-lint

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

🧹 Nitpick comments (1)
listen_test.go (1)

97-104: Consider optimizing struct field alignment.

The testCase struct's field alignment could be optimized to reduce memory usage.

 type testCase struct {
-    name               string
-    expectedErr        error
-    waitTime           time.Duration
-    expectedStatusCode int
-    closeConnection    bool
-    expectedBody       string
+    expectedErr        error           // 8 bytes
+    waitTime           time.Duration   // 8 bytes
+    name               string          // 16 bytes
+    expectedBody       string          // 16 bytes
+    expectedStatusCode int             // 4 bytes
+    closeConnection    bool            // 1 byte
+    _                  [3]byte         // padding
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

97-97: fieldalignment: struct with 64 pointer bytes could be 40

(govet)

🪛 GitHub Check: lint

[failure] 97-97:
fieldalignment: struct with 64 pointer bytes could be 40 (govet)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bab3038 and 187ad51.

📒 Files selected for processing (1)
  • listen_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
listen_test.go

75-75: Function Listener should pass the context parameter

(contextcheck)


97-97: fieldalignment: struct with 64 pointer bytes could be 40

(govet)

🪛 GitHub Check: lint
listen_test.go

[failure] 75-75:
Function Listener should pass the context parameter (contextcheck)


[failure] 97-97:
fieldalignment: struct with 64 pointer bytes could be 40 (govet)

🪛 GitHub Actions: golangci-lint
listen_test.go

[error] 75-75: Function Listener should pass the context parameter (contextcheck)

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: unit (1.23.x, macos-13)
  • GitHub Check: unit (1.23.x, macos-latest)
  • GitHub Check: unit (1.23.x, windows-latest)
  • GitHub Check: repeated
  • GitHub Check: Compare
  • GitHub Check: Analyse
🔇 Additional comments (5)
listen_test.go (5)

40-48: LGTM! Well-structured test organization.

The test has been nicely organized into sub-tests with clear scenarios for basic shutdown and timeout cases.


50-51: LGTM! Helper function follows best practices.

The helper function correctly implements t.Helper() as the first statement.


82-91: LGTM! Robust server readiness check.

The implementation uses require.Eventually for a reliable server readiness check with proper error handling for connection closure.


106-123: LGTM! Well-structured test cases.

The table-driven test cases are well-organized with clear expectations and comprehensive test scenarios.


125-146: LGTM! Thorough test execution.

The test execution is well-implemented with proper resource cleanup using defer and clear assertions.

@ReneWerner87
Copy link
Member

@JIeJaitt
image
problem is that the context is created outside in another golang function
previously both were within the same scope

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