fix: set ConnectionClosed flag before closing stream on cancellation - #3733
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR prevents panic during context cancellation in stream handling by establishing flag-before-close ordering: ChangesStream Cancellation Safety
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Comment |
|
tejas ghatte seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Confidence Score: 5/5Safe to merge — the changes are narrow, well-tested, and address a real stream teardown crash with no API surface changes. Both changes are targeted: reordering two lines to fix a flag-visibility race, and replacing a silent (0, nil) return with a proper error. The new test deterministically reproduces the race condition and the defer ordering in the test is correct. No existing callers are broken since ErrStreamClosed/ErrStreamIdleTimeout were already the established error values for this path. No files require special attention. Important Files Changed
Reviews (1): Last reviewed commit: "fix: set ConnectionClosed flag before cl..." | Re-trigger Greptile |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/providers/utils/idle_timeout_reader_test.go (1)
426-488: ⚡ Quick winAdd a companion regression for the
done+ cancelled-context path.This test only drives the
<-ctx.Done()branch. The PR also changes the<-donebranch inSetupStreamCancellation, and that ordering bug is subtle enough that it’s worth pinning with a second test as well.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/utils/idle_timeout_reader_test.go` around lines 426 - 488, Add a second regression test mirroring TestSetupStreamCancellation_NoPanicOnCancelledContext but exercising the "<-done" branch: create a test (e.g., TestSetupStreamCancellation_NoPanicOnDoneClose) that uses NewIdleTimeoutReader, SetupStreamCancellation and the same synced panic body, spawn a Read() goroutine that recovers and reports panics/errors, then trigger the done path by closing the body's done channel (instead of cancelling the context) and assert that Read does not re-panic and returns ErrStreamClosed; reference the same helper symbols (NewIdleTimeoutReader, SetupStreamCancellation, the synced panic body fields like allowReturn/done) so the subtle ordering bug for the done-branch is pinned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@core/providers/utils/idle_timeout_reader_test.go`:
- Around line 426-488: Add a second regression test mirroring
TestSetupStreamCancellation_NoPanicOnCancelledContext but exercising the
"<-done" branch: create a test (e.g.,
TestSetupStreamCancellation_NoPanicOnDoneClose) that uses NewIdleTimeoutReader,
SetupStreamCancellation and the same synced panic body, spawn a Read() goroutine
that recovers and reports panics/errors, then trigger the done path by closing
the body's done channel (instead of cancelling the context) and assert that Read
does not re-panic and returns ErrStreamClosed; reference the same helper symbols
(NewIdleTimeoutReader, SetupStreamCancellation, the synced panic body fields
like allowReturn/done) so the subtle ordering bug for the done-branch is pinned.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: caf8c37c-252c-4a13-8f8d-6e8638b04738
📒 Files selected for processing (2)
core/providers/utils/idle_timeout_reader_test.gocore/providers/utils/utils.go
Merge activity
|
…3733) ## Summary Fixes a race condition in `SetupStreamCancellation` where `BifrostContextKeyConnectionClosed` was set *after* calling `Close()` on the body stream. Because `Close()` immediately unblocks any in-progress `Read` (which may panic due to a force-closed connection), the recover block in `idleTimeoutReader.Read` could run before the flag was set — causing it to re-panic instead of returning `ErrStreamClosed`. Additionally, `idleTimeoutReader.Read` was returning `(0, nil)` when the connection was already marked closed, which is incorrect. It now returns the appropriate closed-stream error via `closedReadError()`. ## Changes - `SetupStreamCancellation` now sets `BifrostContextKeyConnectionClosed` **before** calling `Close()` or `CloseWithError()` in all four call sites, eliminating the race window where a panicking `Read` could recover before the flag was visible. - `idleTimeoutReader.Read` now returns `r.closedReadError()` instead of `(0, nil)` when the connection is already closed, ensuring callers receive a meaningful error rather than a silent empty read. - Added `syncedPanicBody`, a deterministic test helper that reproduces the exact race: `Close()` triggers a panic in `Read` and then blocks, holding `SetupStreamCancellation` inside `Close()` so the flag is guaranteed to be unset when the recover block runs under the unfixed code. - Added `TestSetupStreamCancellation_NoPanicOnCancelledContext` which fails against the unfixed code and passes after the fix. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) ## How to test ```sh go test ./core/providers/utils/... -race -count=1 -run TestSetupStreamCancellation_NoPanicOnCancelledContext go test ./core/providers/utils/... -race -count=1 ``` The new test should pass without any detected data races or re-panics. ## Breaking changes - [x] No ## Security considerations None. This is a stability fix for stream teardown on context cancellation. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## ✨ Features
- **Azure v1 API Migration** — Migrated Azure provider to the v1 API:
removed the `api-version` query parameter and the
`/openai/deployments/{model}/...` URL pattern in favor of
`/openai/v1/{operation}`; the `api_version` field has been dropped from
`AzureKeyConfig` (#3661, #3756)
- **EnvVar Support for OTEL & Prometheus Configs** — `CollectorURL`,
`MetricsEndpoint`, headers, push gateway URL, and basic auth credentials
can now be sourced from environment variables (e.g.,
`env.OTEL_COLLECTOR_URL`); added a new `ConfigMarshallerPlugin`
interface that lets plugins control storage/redaction round-trips
(#3651)
- **OTel Extra Header Forwarding** — `x-bf-eh-*` extra headers forwarded
to upstream providers are now also emitted on the request span under
`gen_ai.request.extra_header.*` for end-to-end tracing (#3730)
- **OTel Semantic Conventions** — Aligned OTel attribute keys with the
OpenTelemetry GenAI spec (canonical `gen_ai.*` and new `bifrost.*`
attributes); legacy attributes are retained in parallel to avoid
breaking existing dashboards (#3732)
- **VK Quota with Provider Configs** — `GetVirtualKeyQuotaByValue` and
the `getVirtualKeyQuota` HTTP response now include `provider_configs`
with their budgets and rate limits (#3721)
- **MCP Temp Token Non-Auth Toggle** — Added
`mcp_enable_temp_token_auth` client config flag to gate short-lived MCP
token minting for non-authenticated users (#3720)
- **Responses Stream in JSON Parser** — `jsonparser` plugin now handles
OpenAI Responses API streaming (`ResponsesStreamRequest`) in addition to
chat completions (#3749)
- **Session API Rework** — Logout now calls both the password-based
session logout and OAuth logout endpoints and resets all RTK Query cache
state (#3698)
## 🐞 Fixed
- **Streaming Latency for Observability** — Deferred root span
termination to the trace completer callback for streaming requests so
request latency is no longer inflated by header-flush time (#3762)
- **Stream Cancellation Race** — Set `BifrostContextKeyConnectionClosed`
before closing the stream and short-circuit `idleTimeoutReader.Read`
when the connection is already closed to avoid panics and hangs on
cancellation (#3733)
- **Bedrock Cache Points** — Strip cache points from Bedrock requests
for models that do not support prompt caching (e.g., GLM, Llama) to
avoid Converse API errors (#3754)
- **Bedrock Empty Text Blocks** — Skip empty/nil text blocks during
Bedrock response conversion to avoid invalid messages (#3747)
- **Bedrock Reasoning + Tools** — Preserve reasoning content blocks on
assistant turns that also contain tool calls in the Bedrock chat
converter (#3690)
- **Bedrock Search Content & Video** — Restored search content and video
parts that were being dropped from Bedrock-native passthrough requests
(#3729)
- **Structured Output Stop Reason** — Fixed an incorrect `tool_calls`
finish reason when structured output is combined with extended-thinking
tools (#3685)
- **Gemini Tool Schema Passthrough** — Forward full tool parameter
schemas via `parametersJsonSchema` instead of the lossy `parameters`
form; corrected tool response role to `user`; resolved structured output
+ tools conflict (#3761)
- **Anthropic Stop Reason & Tool Versions** — Normalized stop reason
mapping (`end_turn` to `stop`, `tool_use` to `tool_calls`, `max_tokens`
to `length`) and upgraded `text_editor_20250124`/`str_replace_editor` to
`text_editor_20250728` for computer-use tools (#3761)
- **Azure Endpoint Redaction** — Fixed a panic when
`AzureKeyConfig.Endpoint` is a literal value rather than an env
reference (#3761)
- **Auth Middleware Path Match** — Match temp-token auth middleware
whitelist against the request path only, not the full URI with query
parameters (#3737)
- **Governance Blocked Models UI** — Restored the missing Blocked Models
create/edit UI in the VK provider config sheet (#3750)
- **Logging Plugin Cleanup Drain** — Fixed a shutdown race where
`batchWriter` could drop in-flight log entries; `Cleanup` now drains
both the recovered batch and remaining queue within a 30-second budget
(#3717)
- **Model Rankings Empty Entries** — Excluded entries with empty `model`
values from model rankings matview queries so blank rows no longer
surface in the UI (#3758)
- **User Filter Duplicates** — Recreated `mv_filter_users` matview to
require non-empty `user_name`, eliminating duplicate filter dropdown
entries (#3764)
- **User Filter Display Name** — Use `user_name` instead of `user_id` as
the display label for users in logging filters (#3691)
- **Large Numeric ID Precision** — Preserve large numeric IDs in URL
search params by skipping JSON parsing for plain strings (#3692)
## 🔧 Refactors & Chores
- **Error Propagation for GetAvailable\* APIs** — `GetAvailable*`
methods on `LoggerPlugin`/`LogManager` now return wrapped errors instead
of silently logging and returning empty slices (#3759)
- **Governance Blocklist Matching** — Use `slices.Contains` for VK
blocked-model matching for clearer code with identical semantics (#3727)
- **Exported `ResolvePeriod`** — Renamed `resolvePeriod` to
`ResolvePeriod` so external packages can reuse the period parsing
(#3763)
## 📚 Docs
- **OTEL Env Var Documentation** — Documented `env.VAR_NAME` support for
`collector_url`, `metrics_endpoint`, and headers in OTEL/Prometheus
plugin docs
- **OTEL OSS Features & Examples** — Added OTEL documentation to the OSS
features list with usage examples (#3731)
- **Anthropic Auth Recommendation** — Recommend `ANTHROPIC_AUTH_TOKEN`
over `ANTHROPIC_CUSTOM_HEADERS` for Claude Code authentication (#3686)
## Summary
This PR releases Bifrost OSS `v1.5.5` and Enterprise `v1.4.4`, bumping all module pins from `v1.5.12`/`v1.3.12` to `v1.5.13`/`v1.3.13` across core, framework, and all plugins. It also hardens the Docker manifest shell scripts, expands CI egress allowlists, and updates documentation to reflect the new SCIM-based user provisioning feature.
## Changes
- **Module version bumps**: All `go.mod`/`go.sum` files updated from `core v1.5.12` → `v1.5.13`, `framework v1.3.12` → `v1.3.13`, and all plugin versions incremented accordingly (`compat`, `governance`, `jsonparser`, `logging`, `maxim`, `mocker`, `otel`, `prompts`, `semanticcache`, `telemetry`).
- **Docker manifest scripts**: Added `#!/usr/bin/env bash` shebang and `set -euo pipefail` to `create-docker-manifest.sh` and `create-docker-manifest-ubi9.sh`; quoted all variable expansions and switched `jq -r` to `jq -er` to fail on null digests.
- **CI egress allowlist**: Added `production.cloudfront.docker.com:443` to Docker-related job allowlists, and added `_https._tcp.dl.google.com:443` and `motd.ubuntu.com:443` to the Ubuntu package job allowlist.
- **Changelog files**: Cleared per-module `changelog.md` files (content moved into the new versioned docs). Added `docs/changelogs/v1.5.5.mdx` and `docs/changelogs/ent-v1.4.4.mdx` with full release notes, and registered both in `docs/docs.json`.
- **Documentation**: Replaced the SSO Integration link with a User Provisioning (SCIM) link in both `README.md` and `transports/README.md`.
- **Enterprise v1.4.4 highlights** (documented): Kafka and Google Cloud Pub/Sub observability sinks, chunked streaming with a 100 MB inter-node message ceiling, BigQuery custom labels via env vars using the new `ConfigMarshallerPlugin` interface, temporary access token expiry extensions, and a multi-node cluster integration harness.
- **OSS v1.5.5 highlights** (documented): Azure v1 API migration, env-var support for OTel/Prometheus configs, OTel extra-header forwarding and semantic-convention alignment, virtual key quota including provider configs, Responses API streaming in `jsonparser`, and a batch of Bedrock, Gemini, Anthropic, Azure, and logging plugin fixes.
## Type of change
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [x] Documentation
- [x] Chore/CI
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
- [x] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [x] Docs
## How to test
```sh
# Core/Transports
go version
go test ./...
# Verify Docker manifest scripts exit on error
bash -n .github/workflows/scripts/create-docker-manifest.sh
bash -n .github/workflows/scripts/create-docker-manifest-ubi9.sh
```
Validate that the new changelog pages (`changelogs/v1.5.5` and `changelogs/ent-v1.4.4`) render correctly in the docs site.
## Screenshots/Recordings
N/A
## Breaking changes
- [x] Yes
- [ ] No
The Azure provider no longer accepts `api_version` in `AzureKeyConfig` and has migrated to the `/openai/v1/{operation}` URL pattern. See the [v1.4.0 Migration Guide](https://docs.getbifrost.ai/enterprise/migration-guides/v1.4.0) for full details.
## Related issues
#3661, #3756, #3651, #3730, #3732, #3754, #3747, #3690, #3729, #3685, #3733, #3761, #3735, #3721, #3720, #3749, #3698, #3762, #3750, #3727, #3717, #3759, #3758, #3764, #3691, #3692, #3737, #3763
## Security considerations
- The `ConfigMarshallerPlugin` interface redacts secrets (OTel collector URLs, Prometheus push gateway credentials, BigQuery labels) at config storage time and rehydrates them at load time, preventing plaintext secret persistence.
- Docker manifest scripts now use `set -euo pipefail`, preventing silent failures that could result in malformed or missing image manifests being pushed.
## Checklist
- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

Summary
Fixes a race condition in
SetupStreamCancellationwhereBifrostContextKeyConnectionClosedwas set after callingClose()on the body stream. BecauseClose()immediately unblocks any in-progressRead(which may panic due to a force-closed connection), the recover block inidleTimeoutReader.Readcould run before the flag was set — causing it to re-panic instead of returningErrStreamClosed.Additionally,
idleTimeoutReader.Readwas returning(0, nil)when the connection was already marked closed, which is incorrect. It now returns the appropriate closed-stream error viaclosedReadError().Changes
SetupStreamCancellationnow setsBifrostContextKeyConnectionClosedbefore callingClose()orCloseWithError()in all four call sites, eliminating the race window where a panickingReadcould recover before the flag was visible.idleTimeoutReader.Readnow returnsr.closedReadError()instead of(0, nil)when the connection is already closed, ensuring callers receive a meaningful error rather than a silent empty read.syncedPanicBody, a deterministic test helper that reproduces the exact race:Close()triggers a panic inReadand then blocks, holdingSetupStreamCancellationinsideClose()so the flag is guaranteed to be unset when the recover block runs under the unfixed code.TestSetupStreamCancellation_NoPanicOnCancelledContextwhich fails against the unfixed code and passes after the fix.Type of change
Affected areas
How to test
The new test should pass without any detected data races or re-panics.
Breaking changes
Security considerations
None. This is a stability fix for stream teardown on context cancellation.
Checklist
docs/contributing/README.mdand followed the guidelines