Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 28, 2025

Description

Adds tests to verify that Relic's HTTP server allows in-flight requests to complete gracefully during shutdown rather than cutting connections immediately.

Tests added:

  • Single in-flight request completes successfully when server.close() is called
  • Multiple concurrent in-flight requests all complete during shutdown
  • Multi-isolate server graceful shutdown behavior
  • New request rejection after close begins

Implementation details:

  • Uses Completers for deterministic synchronization in single-isolate tests (avoids timing-based flakiness)
  • Multi-isolate test uses delay-based handler since Completers cannot cross isolate boundaries via SendPort.send()
  • Tests use a serverClosed flag to prevent double-close in tearDown (calling close() twice on a multi-isolate server causes a hang)
  • Uses (future1, future2).wait pattern consistently to await multiple futures simultaneously without leaving un-awaited futures across async gaps

Related Issues

Pre-Launch Checklist

Please ensure that your PR meets the following requirements before submitting:

  • This update focuses on a single feature or bug fix. (For multiple fixes, please submit separate PRs.)
  • I have read and followed the Dart Style Guide and formatted the code using dart format.
  • I have referenced at least one issue this PR fixes or is related to.
  • I have updated/added relevant documentation (doc comments with ///), ensuring consistency with existing project documentation.
  • I have added new tests to verify the changes.
  • All existing and new tests pass successfully.
  • I have documented any breaking changes below.

Breaking Changes

  • Includes breaking changes.
  • No breaking changes.

Additional Notes

Test results: 4 passing (total suite: 3376 tests passing).

The tests confirm that Dart's HttpServer.close(force: false) does wait for in-flight requests to complete—Relic's current implementation handles graceful shutdown correctly.

Original prompt

This section details on the original issue you should resolve

<issue_title>Add test to verify graceful shutdown of in-flight requests</issue_title>
<issue_description>### Problem to Solve

Currently, there is a concern that the HTTP server may cut open connections immediately during shutdown rather than allowing in-flight requests to complete gracefully. The Dart SDK's HTTP server documentation suggests it should close gracefully, but testing has shown that calling close() seems to cut open connections immediately, even without the force parameter.

We need to ensure that Relic's server shutdown behavior is tested and verified to handle in-flight requests properly.

Proposal

Add a test to Relic to ensure that in-flight requests are not terminated immediately during server shutdown, but are allowed to finish up to a timeout duration.

The test should:

  1. Start a Relic HTTP server
  2. Initiate one or more long-running requests (e.g., requests that take several seconds to complete)
  3. Trigger server shutdown while requests are in-flight
  4. Verify that the in-flight requests complete successfully before the server fully shuts down
  5. Verify that the shutdown respects a maximum timeout duration

Use Case

This is critical for production deployments where:

  • Users have active HTTP requests when a server restart or deployment occurs
  • Rolling updates need to ensure zero downtime
  • Long-running API calls (e.g., file uploads, data processing endpoints) should not be abruptly terminated
  • Graceful shutdown is expected as part of container orchestration (Kubernetes, Docker, etc.)

Example scenario: A user is uploading a large file when a deployment is triggered. The current request should be allowed to complete (up to a reasonable timeout) rather than being cut off mid-upload.

Alternatives

Alternative approaches considered:

  1. Document the current behavior and accept immediate connection termination - rejected because this leads to poor user experience
  2. Implement custom connection tracking outside of the Dart SDK's HTTP server - could be done if the SDK doesn't support graceful shutdown, but should first verify the actual behavior with tests
  3. Use a reverse proxy (like nginx) to handle graceful shutdown - adds complexity and doesn't solve the underlying issue

The proposed test will help determine if the issue is with Relic's implementation or the Dart SDK itself, and guide the appropriate solution.

Additional context

This issue originated from a discussion about observed behavior where the Dart SDK's HTTP server appeared to contradict its own documentation regarding graceful shutdown. The test will provide concrete evidence of the actual shutdown behavior and serve as a regression test once the issue is resolved.

Related concerns:

  • SIGINT/SIGTERM signal handling
  • The behavior of HttpServer.close(force: false)
  • Maximum timeout duration for graceful shutdown (needs to be configurable)</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

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


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

Copilot AI and others added 2 commits November 28, 2025 14:00
Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
Copilot AI changed the title [WIP] Add test to verify graceful shutdown of in-flight requests test: Add test to verify graceful shutdown of in-flight requests Nov 28, 2025
Copilot AI requested a review from nielsenko November 28, 2025 14:04
@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.99%. Comparing base (c75273d) to head (768de12).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #292   +/-   ##
=======================================
  Coverage   91.99%   91.99%           
=======================================
  Files          97       97           
  Lines        3662     3662           
  Branches     1881     1881           
=======================================
  Hits         3369     3369           
  Misses        293      293           

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nielsenko
Copy link
Collaborator

@copilot Please use Completers to avoid timing issues

…behavior

Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot Please use Completers to avoid timing issues

Refactored the tests to use Completers for deterministic synchronization instead of timing-based delays. Single-isolate tests now use _createSignalingHandler which accepts onRequestStarted callback and canComplete completer. Note: Multi-isolate test still uses delay-based approach since Completers cannot cross isolate boundaries via SendPort.send(). Fixed in 2c79a8c.

@nielsenko
Copy link
Collaborator

@copilot No need to skip the 'when server.close() is called, then new requests are not accepted after close begins' test?!

…er approach

Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot No need to skip the 'when server.close() is called, then new requests are not accepted after close begins' test?!

You're right - the Completer-based approach eliminates the timing dependency. Removed the skip and the test now passes. Fixed in 232fcf5.

@nielsenko
Copy link
Collaborator

@copilot Address PR review

Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com>
@nielsenko
Copy link
Collaborator

@coderabbitai Create two follow-up issues:

  1. Calling close twice should not hang on multi-isolate relic servers. If this is the case, then it is a bug.
  2. Add configurable timeout on close, so that in-flight requests cannot keep the server alive forever. This might not actually be an issue for serverpod cloud, as they will likely force entire container down after a while, but still makes for a more complete api.

@nielsenko
Copy link
Collaborator

@coderabbitai Create the issues in serverpod/relic repo.

Copy link
Collaborator

@nielsenko nielsenko left a comment

Choose a reason for hiding this comment

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

LGTM now

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create first issue about double-close hang bug
gh issue create \
  --repo serverpod/relic \
  --title "Fix: Calling close() twice hangs multi-isolate servers" \
  --body "## Problem

Calling \`close()\` twice on a multi-isolate Relic server causes the application to hang. This was discovered during testing of graceful shutdown behavior.

## Current Behavior

When \`server.close()\` is called twice on a multi-isolate server configuration, the second call causes the server to hang indefinitely instead of returning immediately or throwing an appropriate error.

## Expected Behavior

- The second call to \`close()\` should either:
  - Return immediately (idempotent behavior)
  - Throw a clear error indicating the server is already closed
  - Complete without hanging

## Context

This issue was identified in PR #292 during graceful shutdown testing. A workaround was implemented using a \`serverClosed\` flag in tests to prevent calling \`close()\` twice in tearDown.

## Related

- PR: https://github.com/serverpod/relic/pull/292
- Reported by: @nielsenko" \
  --assignee "nielsenko"

echo "---"

# Create second issue about configurable timeout
gh issue create \
  --repo serverpod/relic \
  --title "Feature: Add configurable timeout for graceful shutdown" \
  --body "## Feature Request

Add a configurable timeout parameter to the server's \`close()\` method to limit how long in-flight requests can keep the server alive during graceful shutdown.

## Motivation

Currently, in-flight requests can potentially keep the server alive indefinitely during shutdown. While this may not be an issue in environments like Serverpod Cloud (where containers are force-terminated after a timeout), having a configurable timeout would make the API more complete and give users better control over shutdown behavior.

## Proposed Solution

Add a timeout parameter to the \`close()\` method or server configuration that:
- Allows in-flight requests to complete within the specified duration
- Forcefully terminates remaining connections after the timeout expires
- Provides sensible default behavior

## Use Case

This is particularly useful for:
- Self-hosted deployments where container orchestration behavior may vary
- Scenarios where long-running requests should not delay server shutdown indefinitely
- Production environments requiring predictable shutdown timing

## Context

This enhancement was suggested during testing of graceful shutdown behavior in PR #292, which verified that in-flight requests complete successfully during shutdown.

## Related

- PR: https://github.com/serverpod/relic/pull/292
- Requested by: @nielsenko" \
  --assignee "nielsenko"

Length of output: 95


@nielsenko: I've successfully created both follow-up issues in the serverpod/relic repository:

  1. Issue Fix: Calling close() twice hangs multi-isolate servers #293: Fix: Calling close() twice hangs multi-isolate servers

    • Documents the bug where calling close() twice on a multi-isolate server causes it to hang
    • Assigned to you
  2. Issue Feature: Add force parameter to close method for immediate shutdown #294: Feature: Add configurable timeout for graceful shutdown

    • Proposes adding a configurable timeout to limit how long in-flight requests can keep the server alive during shutdown
    • Assigned to you

Both issues reference PR #292 and include all the relevant context from our discussion.


🧠 Learnings used
Learnt from: nielsenko
Repo: serverpod/relic PR: 216
File: lib/src/router/relic_app.dart:47-49
Timestamp: 2025-10-22T11:25:39.264Z
Learning: In the serverpod/relic repository, validation of the `noOfIsolates` parameter should be handled in the `RelicServer` constructor (lib/src/relic_server.dart), not in `RelicApp.run` (lib/src/router/relic_app.dart).

Learnt from: CR
Repo: serverpod/relic PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T16:21:09.310Z
Learning: Applies to lib/**/*.dart : Ensure WebSocket handling includes proper lifecycle management (e.g., ping/pong for connection health)

Learnt from: nielsenko
Repo: serverpod/relic PR: 48
File: lib/src/handler/handler.dart:59-67
Timestamp: 2025-04-25T07:39:38.915Z
Learning: Nielsenko prefers using switch statements with pattern matching over if statements when working with sealed classes in Dart, as they provide exhaustiveness checking at compile time and can be more concise.

Learnt from: CR
Repo: serverpod/relic PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-09T16:21:09.310Z
Learning: Applies to test/**/*.dart : Provide clear, descriptive test titles; prefer single responsibility per test unless related assertions improve clarity

@nielsenko nielsenko marked this pull request as ready for review December 1, 2025 11:19
@serverpod serverpod deleted a comment from coderabbitai bot Dec 1, 2025
Copy link

@christerswahn christerswahn left a comment

Choose a reason for hiding this comment

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

Had one question, otherwise looks good!

@nielsenko nielsenko requested a review from a team December 1, 2025 17:10
Copy link
Contributor

@SandPod SandPod left a comment

Choose a reason for hiding this comment

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

🤖 👍

@nielsenko nielsenko merged commit 1f056a7 into main Dec 2, 2025
27 checks passed
@nielsenko nielsenko deleted the copilot/add-graceful-shutdown-test branch December 2, 2025 18:36
@nielsenko nielsenko mentioned this pull request Dec 2, 2025
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add test to verify graceful shutdown of in-flight requests Allow CacheControl header to be set per file

4 participants