-
Notifications
You must be signed in to change notification settings - Fork 642
feat: add ping for sse server #80
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
Conversation
WalkthroughThe update introduces two new fields, Changes
Tip ⚡💬 Agentic Chat (Pro Plan, General Availability)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
🧹 Nitpick comments (2)
server/sse.go (2)
127-138
: Good implementation of option functionsThe implementation of
WithKeepAliveInterval
andWithKeepAlive
follows the existing pattern in the codebase. I like howWithKeepAliveInterval
automatically enables keep-alive when setting the interval.Consider adding documentation comments for these exported functions to explain their purpose and behavior.
+// WithKeepAliveInterval sets the interval for keep-alive pings and enables the keep-alive feature func WithKeepAliveInterval(keepAliveInterval time.Duration) SSEOption { return func(s *SSEServer) { s.keepAlive = true s.keepAliveInterval = keepAliveInterval } } +// WithKeepAlive enables or disables the keep-alive ping mechanism func WithKeepAlive(keepAlive bool) SSEOption { return func(s *SSEServer) { s.keepAlive = keepAlive } }
266-282
: Well-implemented keep-alive mechanismThe keep-alive implementation correctly:
- Only runs when the feature is enabled
- Uses a ticker with the configured interval
- Properly cleans up resources with
defer ticker.Stop()
- Handles termination conditions through multiple select cases
- Formats ping events according to SSE standards
Consider adding validation for very short keep-alive intervals that could impact performance, and consider handling the case where the event queue might be full (similar to the check in
SendEventToSession
).// Start keep alive : ping if s.keepAlive { + // Ensure minimum interval to prevent performance issues + if s.keepAliveInterval < 100 * time.Millisecond { + s.keepAliveInterval = 100 * time.Millisecond + } go func() { ticker := time.NewTicker(s.keepAliveInterval) defer ticker.Stop() for { select { case <-ticker.C: - session.eventQueue <- fmt.Sprintf("event: ping\ndata: %s\n\n", time.Now().Format(time.RFC3339)) + select { + case session.eventQueue <- fmt.Sprintf("event: ping\ndata: %s\n\n", time.Now().Format(time.RFC3339)): + // Event queued successfully + case <-session.done: + return + default: + // Queue is full, could log this + } case <-session.done: return case <-r.Context().Done(): return } } }() }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/sse.go
(5 hunks)
🔇 Additional comments (4)
server/sse.go (4)
13-13
: Great addition of the time packageThe addition of the time package is necessary to support the new keep-alive functionality with timer operations and duration types.
65-66
: Well-structured fields for the keep-alive mechanismThese fields clearly define the configuration parameters for the SSE keep-alive feature. The boolean toggle and configurable interval provide good flexibility.
154-155
: Sensible default valuesSetting keep-alive to disabled by default with a 10-second interval is a good approach. It allows users to opt into the feature while having reasonable defaults when they do.
266-282
: Consider adding tests for the keep-alive functionalityThis PR introduces important functionality for maintaining connection stability, but I don't see tests for this new feature. Consider adding tests to verify:
- Pings are sent at the expected intervals when enabled
- No pings are sent when the feature is disabled
- The option functions correctly configure the server
Do you need assistance with creating tests for this feature?
@ezynda3 Could you please verify this pr/ merge this? For running sse behind Load Balancers, this is critical. |
@kingster I am on holiday until the 10th, so I am not sure if I will have a spare moment to review this until then. If I do, I will definitely have a look. 🙏 |
i have tested it with our prod env, If you need it, you can try it directly with my repo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
server/sse.go (2)
137-142
: Consider making behavior more explicit
WithKeepAliveInterval
implicitly enables keep-alive by settings.keepAlive = true
. This implicit behavior might not be immediately obvious to users.Consider either:
- Renaming to
WithEnabledKeepAliveInterval
to make it clear that this also enables the feature, or- Adding a comment that documents this behavior
func WithKeepAliveInterval(keepAliveInterval time.Duration) SSEOption { return func(s *SSEServer) { + // Setting an interval implicitly enables keep-alive s.keepAlive = true s.keepAliveInterval = keepAliveInterval } }
286-286
: Consider making the ping format customizableThe current implementation uses a fixed format for ping messages (
:ping - <timestamp>
). In some scenarios, it might be beneficial to allow customization of this format.While not necessary for the current requirements, a future enhancement could allow for customizable ping formats:
// Add to SSEServer struct + pingFormat string // Add new option +func WithPingFormat(format string) SSEOption { + return func(s *SSEServer) { + s.pingFormat = format + } +} // Initialize in NewSSEServer + pingFormat: ":ping - %s\n\n", // Use in ping message - session.eventQueue <- fmt.Sprintf(":ping - %s\n\n", time.Now().Format(time.RFC3339)) + session.eventQueue <- fmt.Sprintf(s.pingFormat, time.Now().Format(time.RFC3339))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/sse.go
(5 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
server/sse.go (2)
server/server.go (1)
MCPServer
(138-152)examples/custom_context/main.go (1)
MCPServer
(100-102)
🔇 Additional comments (6)
server/sse.go (6)
13-13
: LGTM: Import of time packageThe time package is correctly imported to support the new keep-alive functionality.
66-67
: LGTM: New fields for keep-alive functionalityThe addition of
keepAlive
andkeepAliveInterval
fields to the SSEServer struct properly supports the implementation of ping messages for SSE connections.
144-148
: LGTM: Explicit keep-alive optionThis option function provides a clear way to enable or disable the keep-alive functionality.
165-166
: LGTM: Default values for keep-alive configurationThe default values are sensible: keep-alive is disabled by default, and the interval is set to 10 seconds (a reasonable default for keeping connections alive behind load balancers).
278-294
: LGTM: Keep-alive ping implementationThis implementation follows good practices:
- It only runs when keep-alive is enabled
- Uses a ticker for precise timing
- Properly cleans up resources with
defer ticker.Stop()
- Handles termination scenarios (session done, request context done)
- Uses the standard SSE comment format (
:ping
) for the keep-alive messageThe implementation will effectively keep connections open behind load balancers that might otherwise close idle connections.
296-296
: LGTM: Message endpoint constructionThis change correctly builds the message endpoint URL with the session ID.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
server/sse.go (1)
286-291
: Good implementation of non-blocking ping with error logging.The implementation now properly handles the case where the event queue is full by using a non-blocking channel send with the
select
statement and logging warnings for dropped pings.
🧹 Nitpick comments (3)
server/sse.go (3)
137-142
: Consider separating configuration concerns.The
WithKeepAliveInterval
function automatically enables keep-alive by settings.keepAlive = true
. This could cause unexpected behavior if a user only wants to configure the interval without enabling the feature yet.Consider separating these concerns:
func WithKeepAliveInterval(keepAliveInterval time.Duration) SSEOption { return func(s *SSEServer) { - s.keepAlive = true s.keepAliveInterval = keepAliveInterval } }
278-299
: Consider adding validation for keepAliveInterval.The implementation doesn't validate the keepAliveInterval value. Very short intervals could cause excessive network traffic or overload the client, while very long intervals might not be effective at keeping connections alive with some load balancers.
Consider adding validation in the WithKeepAliveInterval function:
func WithKeepAliveInterval(keepAliveInterval time.Duration) SSEOption { return func(s *SSEServer) { + // Ensure reasonable limits for the interval + if keepAliveInterval < time.Second { + keepAliveInterval = time.Second // Minimum 1 second + } else if keepAliveInterval > 2 * time.Minute { + keepAliveInterval = 2 * time.Minute // Maximum 2 minutes + } s.keepAlive = true s.keepAliveInterval = keepAliveInterval } }
301-302
: Unused variable.The
messageEndpoint
variable is computed but not used in the code that follows. This appears to be leftover code from a refactoring.Remove the unused variable:
- messageEndpoint := fmt.Sprintf("%s?sessionId=%s", s.CompleteMessageEndpoint(), sessionID) -
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/sse.go
(5 hunks)
🔇 Additional comments (4)
server/sse.go (4)
13-13
: Good addition of required time package.Adding the time package is necessary for the new keep-alive functionality using time.Duration.
66-67
: Well-structured addition of keep-alive configuration fields.The new fields are well-named and follow the existing code style. Having separate fields for the flag and interval provides good flexibility.
144-148
: Good implementation of WithKeepAlive option.The implementation correctly toggles the keep-alive flag without modifying other settings.
165-166
: Good default values for keep-alive settings.The defaults are sensible: keep-alive is disabled by default, and the 10-second interval is a reasonable default when enabled.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this 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 comments (1)
server/sse.go (1)
3-17
: 🛠️ Refactor suggestionAdd log package to imports.
The recommended implementation for the keep-alive functionality uses
log.Printf()
for error handling, but the "log" package is not imported.import ( "context" "encoding/json" "fmt" + "log" "net/http" "net/http/httptest" "net/url" "strings" "sync" "sync/atomic" "time" "github.com/google/uuid" "github.com/mark3labs/mcp-go/mcp" )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/sse.go
(5 hunks)
🧰 Additional context used
🪛 golangci-lint (1.64.8)
server/sse.go
278-278: expected statement, found 'import'
(typecheck)
🔇 Additional comments (3)
server/sse.go (3)
66-67
: Properly added keep-alive configuration fields to SSEServer struct.These fields will enable the server to maintain active connections by sending periodic ping messages, which is essential when running behind load balancers.
144-148
: LGTM - WithKeepAlive option is well implemented.This option allows users to explicitly enable or disable the keep-alive feature.
165-166
: LGTM - Good default values for keep-alive fields.Setting
keepAlive
to false by default ensures backward compatibility, and the 10-second interval is a reasonable default for keep-alive messages.
There was a problem hiding this 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 (2)
server/sse.go (2)
137-142
: Inconsistent behavior in WithKeepAliveInterval method.The method automatically sets
keepAlive
to true even if the user has explicitly disabled it withWithKeepAlive(false)
. This creates an unintuitive API behavior where the order of option application affects the outcome.func WithKeepAliveInterval(keepAliveInterval time.Duration) SSEOption { return func(s *SSEServer) { - s.keepAlive = true s.keepAliveInterval = keepAliveInterval } }
277-294
:⚠️ Potential issueAdd error handling for dropped keep-alive pings.
The current implementation doesn't handle the case when the event queue is full, unlike how notification events are handled elsewhere in the code. Additionally, there's a comment that appears to be debug/example output that should be removed.
if s.keepAlive { go func() { ticker := time.NewTicker(s.keepAliveInterval) defer ticker.Stop() for { select { case <-ticker.C: - //: ping - 2025-03-27 07:44:38.682659+00:00 - session.eventQueue <- fmt.Sprintf(":ping - %s\n\n", time.Now().Format(time.RFC3339)) + select { + case session.eventQueue <- fmt.Sprintf(":ping - %s\n\n", time.Now().Format(time.RFC3339)): + // Ping sent successfully + default: + // Queue is full, could log this or take appropriate action + } case <-session.done: return case <-r.Context().Done(): return } } }() }
🧹 Nitpick comments (1)
server/sse.go (1)
296-296
: Remove extra blank line.This extra blank line doesn't serve any purpose and should be removed for consistency.
} - // Send the initial endpoint event
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/sse.go
(5 hunks)
🔇 Additional comments (4)
server/sse.go (4)
66-67
: New fields added for SSE keep-alive functionality.These new fields enable the server to send periodic "ping" events to prevent connection timeout, which is essential when running behind load balancers as mentioned in the PR objectives.
144-148
: LGTM: WithKeepAlive implementation is correct.The implementation simply sets the keepAlive field to the provided value, giving users control over whether to enable or disable the keep-alive functionality.
165-167
: LGTM: Appropriate default values for keep-alive configuration.The default settings (keepAlive: false, keepAliveInterval: 10 seconds) are reasonable. Keep-alive is disabled by default to maintain backward compatibility, and the 10-second interval is a common standard for keep-alive mechanisms.
3-17
:❓ Verification inconclusive
Consider adding import for log package if implementing error logging.
If you decide to add error logging for dropped keep-alive pings as suggested in other comments, you'll need to import the "log" package.
🏁 Script executed:
#!/bin/bash # Check if log package is used elsewhere in the codebase rg "import\s+\([^)]*\"log\"" server/Length of output: 36
Action: Verify Error Logging Implementation
The current scan confirms that no file in theserver/
directory is importing the"log"
package. If you plan to implement error logging (for instance, to log dropped keep-alive pings), please remember to add this import. If error logging isn’t required at this time, you can safely ignore this suggestion.To further verify, please run the following command and share its output if needed:
#!/bin/bash # Search for any "log" package import across all Go files to confirm whether it's used elsewhere. rg --type=go 'import\s*\(.*?"log"' .
@lcgash - I just sent #169 that updates the way keep alive works to follow the spec's Base Protocol -> Utilities -> Ping definition. Mind taking a look? |
Same with #78.
Summary by CodeRabbit