Skip to content

fix(sse): remove race condition in cache metrics tracking - #758

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.2.8from
tombii:fix/cache-metrics-race-condition
Mar 29, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.2.8from
tombii:fix/cache-metrics-race-condition

Conversation

@tombii

@tombii tombii commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up fix for PR #752 which was merged before this critical fix was included.

Changes

  • Remove in-memory metrics tracking (, , )
  • Cache metrics now computed on-the-fly from table (single source of truth)
  • Eliminates race condition where concurrent requests could overwrite each other's metrics
  • Removes duplicate metric tracking logic in streaming/non-streaming paths

Addresses Code Review Issues

From the original PR review bots:

  1. CRITICAL - Race condition in metrics updates: Fixed by removing read-modify-write pattern entirely
  2. WARNING - Duplicate metric tracking logic: Fixed by removing redundant tracking calls

Testing

All cache-related unit tests pass. Two pre-existing test failures unrelated to this change:

    • CodexExecutor test (pre-existing)
    • Auto-update test (pre-existing)

- Remove in-memory metrics tracking (currentMetrics, trackCacheMetrics, updateCacheMetrics)
- Cache metrics now computed on-the-fly from usage_history table (single source of truth)
- Fixes CRITICAL issue from code review: concurrent requests overwriting metrics
- Fixes WARNING: duplicate metric tracking logic in streaming/non-streaming paths

Ref: PR cheaper-inference#752 (merged before this fix was included)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request removes cache metrics tracking and persistence from the handleChatCore function and adds usage saving for streaming responses. The review feedback suggests cleaning up resulting dead code in utility files and refactoring duplicated usage-saving logic into a helper function to improve maintainability.

type CacheControlMetrics,
} from "../utils/cacheControlPolicy.ts";
import { getCacheMetrics, updateCacheMetrics } from "@/lib/db/settings.ts";
import { shouldPreserveCacheControl, recordCacheHit } from "../utils/cacheControlPolicy.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Now that trackCacheMetrics and updateCacheMetrics are no longer used here, the corresponding functions trackCacheMetrics and updateCacheTokenMetrics in open-sse/utils/cacheControlPolicy.ts appear to be dead code. Please consider removing them to improve code maintainability.

Comment on lines +1614 to +1629
saveRequestUsage({
provider: provider || "unknown",
model: model || "unknown",
tokens: streamUsage,
status: String(streamStatus || 200),
success: streamStatus === 200,
latencyMs: Date.now() - startTime,
timeToFirstTokenMs: ttft,
errorCode: null,
timestamp: new Date().toISOString(),
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
apiKeyName: apiKeyInfo?.name || undefined,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This saveRequestUsage call is very similar to the one for non-streaming responses around line 1450. To improve maintainability and reduce code duplication, consider refactoring the common logic into a helper function.

For example, a helper could create the base object with common properties like provider, model, timestamp, etc., which can then be spread and extended in both call sites.

function getBaseUsageData() {
    return {
        provider: provider || "unknown",
        model: model || "unknown",
        errorCode: null,
        timestamp: new Date().toISOString(),
        connectionId: connectionId || undefined,
        apiKeyId: apiKeyInfo?.id || undefined,
        apiKeyName: apiKeyInfo?.name || undefined,
    };
}

@kilo-code-bot

kilo-code-bot Bot commented Mar 29, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 CRITICAL Issue Found | Recommendation: Revert or hotfix immediately

⚠️ This PR appears to be already merged. The issue below is now live on main.

CRITICAL Issues

File Line Issue
open-sse/handlers/chatCore.ts 1558 Runtime ReferenceError: updateCacheMetrics undefined, currentMetrics not defined

Issue Details

open-sse/handlers/chatCore.ts:1558

The PR removes the trackCacheMetrics import and updateCacheMetrics import, plus removes the currentMetrics variable declaration. However, the usage of these removed symbols at line 1558 was not removed:

// Line 1558 - still references removed symbols
updateCacheMetrics(currentMetrics).catch((err) => {
  • updateCacheMetrics is no longer imported (import on line 47 only has getCacheMetrics)
  • currentMetrics variable no longer exists (was removed along with trackCacheMetrics block around line 701)

Impact: All non-streaming chat completions that reach line 1558 will throw a ReferenceError: updateCacheMetrics is not defined at runtime.

Fix required: Either:

  1. Remove the updateCacheMetrics(currentMetrics) call entirely, OR
  2. Add back the import and ensure currentMetrics is defined before this line

Since updateCacheMetrics in src/lib/db/settings.ts is now a no-op (line 650: // No-op: metrics are now computed from usage_history on-the-fly), the simplest fix is to remove the call entirely.


Files Reviewed (1 file)

  • open-sse/handlers/chatCore.ts - 1 critical issue

Additional Notes

The streaming path also had a similar updateCacheMetrics call that was removed (previously around line 1677), but that removal appears complete. The non-streaming path at line 1558 was missed.

@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.2.8 March 29, 2026 15:48
@diegosouzapw
diegosouzapw merged commit 89bd31c into cheaper-inference:release/v3.2.8 Mar 29, 2026
1 check passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thank you for the contribution! This fix has been merged into release v3.2.8.

diegosouzapw added a commit that referenced this pull request Mar 29, 2026
* chore(release): v3.2.8 — Docker auto-update UI and cache analytics fixes

* fix(sse): remove race condition in cache metrics tracking (#758)

- Remove in-memory metrics tracking (currentMetrics, trackCacheMetrics, updateCacheMetrics)
- Cache metrics now computed on-the-fly from usage_history table (single source of truth)
- Fixes CRITICAL issue from code review: concurrent requests overwriting metrics
- Fixes WARNING: duplicate metric tracking logic in streaming/non-streaming paths

Ref: PR #752 (merged before this fix was included)

* fix: handle allRateLimited credentials & forward extra body keys in embeddings/images routes (#757)

* fix: handle allRateLimited credentials in embeddings and images routes

When getProviderCredentials() returns an allRateLimited object (truthy,
but without apiKey/accessToken), the embeddings and images routes
incorrectly passed it to handlers as valid credentials. The handlers
then sent upstream requests without Authorization headers, causing
401 errors from providers (e.g. NVIDIA NIM).

This only manifested under concurrent requests: a chat/completions
call could trigger rate limiting on a provider account, and a
simultaneous embeddings request would receive the allRateLimited
sentinel — but treat it as valid credentials.

The chat pipeline already handled this case correctly. This commit
adds the same allRateLimited guard to all affected routes:
- POST /v1/embeddings
- POST /v1/providers/{provider}/embeddings
- POST /v1/images/generations
- POST /v1/providers/{provider}/images/generations

Also adds a defense-in-depth guard in the embeddings handler itself:
if no auth token is available for a non-local provider, return 401
immediately instead of sending an unauthenticated request upstream.

Made-with: Cursor

* fix(embeddings): forward extra body keys to upstream providers

The embeddings handler only forwarded model, input, dimensions, and
encoding_format to upstream providers, silently dropping any additional
fields. This broke asymmetric embedding APIs (e.g. NVIDIA NIM
nv-embedqa-e5-v5) that require input_type, and other providers
expecting user or truncate parameters.

Add a KNOWN_FIELDS exclusion set and forward all unrecognized body
keys to the upstream request, matching the passthrough pattern used
by the chat pipeline's DefaultExecutor.transformRequest().

Made-with: Cursor

* fix(auth): redirect and unconditional 401 on disabled requireLogin + fix test cases

* fix(build): remove legacy proxy.ts causing Next.js build collision

* fix(build): revert middleware.ts rename to proxy.ts because of Next.js Edge constraints

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: tombii <tombii@users.noreply.github.com>
Co-authored-by: Gorchakov-Pressure <117600961+Gorchakov-Pressure@users.noreply.github.com>
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…r-inference#755)

* chore(release): v3.2.8 — Docker auto-update UI and cache analytics fixes

* fix(sse): remove race condition in cache metrics tracking (cheaper-inference#758)

- Remove in-memory metrics tracking (currentMetrics, trackCacheMetrics, updateCacheMetrics)
- Cache metrics now computed on-the-fly from usage_history table (single source of truth)
- Fixes CRITICAL issue from code review: concurrent requests overwriting metrics
- Fixes WARNING: duplicate metric tracking logic in streaming/non-streaming paths

Ref: PR cheaper-inference#752 (merged before this fix was included)

* fix: handle allRateLimited credentials & forward extra body keys in embeddings/images routes (cheaper-inference#757)

* fix: handle allRateLimited credentials in embeddings and images routes

When getProviderCredentials() returns an allRateLimited object (truthy,
but without apiKey/accessToken), the embeddings and images routes
incorrectly passed it to handlers as valid credentials. The handlers
then sent upstream requests without Authorization headers, causing
401 errors from providers (e.g. NVIDIA NIM).

This only manifested under concurrent requests: a chat/completions
call could trigger rate limiting on a provider account, and a
simultaneous embeddings request would receive the allRateLimited
sentinel — but treat it as valid credentials.

The chat pipeline already handled this case correctly. This commit
adds the same allRateLimited guard to all affected routes:
- POST /v1/embeddings
- POST /v1/providers/{provider}/embeddings
- POST /v1/images/generations
- POST /v1/providers/{provider}/images/generations

Also adds a defense-in-depth guard in the embeddings handler itself:
if no auth token is available for a non-local provider, return 401
immediately instead of sending an unauthenticated request upstream.

Made-with: Cursor

* fix(embeddings): forward extra body keys to upstream providers

The embeddings handler only forwarded model, input, dimensions, and
encoding_format to upstream providers, silently dropping any additional
fields. This broke asymmetric embedding APIs (e.g. NVIDIA NIM
nv-embedqa-e5-v5) that require input_type, and other providers
expecting user or truncate parameters.

Add a KNOWN_FIELDS exclusion set and forward all unrecognized body
keys to the upstream request, matching the passthrough pattern used
by the chat pipeline's DefaultExecutor.transformRequest().

Made-with: Cursor

* fix(auth): redirect and unconditional 401 on disabled requireLogin + fix test cases

* fix(build): remove legacy proxy.ts causing Next.js build collision

* fix(build): revert middleware.ts rename to proxy.ts because of Next.js Edge constraints

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: tombii <tombii@users.noreply.github.com>
Co-authored-by: Gorchakov-Pressure <117600961+Gorchakov-Pressure@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…r-inference#755)

* chore(release): v3.2.8 — Docker auto-update UI and cache analytics fixes

* fix(sse): remove race condition in cache metrics tracking (cheaper-inference#758)

- Remove in-memory metrics tracking (currentMetrics, trackCacheMetrics, updateCacheMetrics)
- Cache metrics now computed on-the-fly from usage_history table (single source of truth)
- Fixes CRITICAL issue from code review: concurrent requests overwriting metrics
- Fixes WARNING: duplicate metric tracking logic in streaming/non-streaming paths

Ref: PR cheaper-inference#752 (merged before this fix was included)

* fix: handle allRateLimited credentials & forward extra body keys in embeddings/images routes (cheaper-inference#757)

* fix: handle allRateLimited credentials in embeddings and images routes

When getProviderCredentials() returns an allRateLimited object (truthy,
but without apiKey/accessToken), the embeddings and images routes
incorrectly passed it to handlers as valid credentials. The handlers
then sent upstream requests without Authorization headers, causing
401 errors from providers (e.g. NVIDIA NIM).

This only manifested under concurrent requests: a chat/completions
call could trigger rate limiting on a provider account, and a
simultaneous embeddings request would receive the allRateLimited
sentinel — but treat it as valid credentials.

The chat pipeline already handled this case correctly. This commit
adds the same allRateLimited guard to all affected routes:
- POST /v1/embeddings
- POST /v1/providers/{provider}/embeddings
- POST /v1/images/generations
- POST /v1/providers/{provider}/images/generations

Also adds a defense-in-depth guard in the embeddings handler itself:
if no auth token is available for a non-local provider, return 401
immediately instead of sending an unauthenticated request upstream.

Made-with: Cursor

* fix(embeddings): forward extra body keys to upstream providers

The embeddings handler only forwarded model, input, dimensions, and
encoding_format to upstream providers, silently dropping any additional
fields. This broke asymmetric embedding APIs (e.g. NVIDIA NIM
nv-embedqa-e5-v5) that require input_type, and other providers
expecting user or truncate parameters.

Add a KNOWN_FIELDS exclusion set and forward all unrecognized body
keys to the upstream request, matching the passthrough pattern used
by the chat pipeline's DefaultExecutor.transformRequest().

Made-with: Cursor

* fix(auth): redirect and unconditional 401 on disabled requireLogin + fix test cases

* fix(build): remove legacy proxy.ts causing Next.js build collision

* fix(build): revert middleware.ts rename to proxy.ts because of Next.js Edge constraints

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: tombii <tombii@users.noreply.github.com>
Co-authored-by: Gorchakov-Pressure <117600961+Gorchakov-Pressure@users.noreply.github.com>
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.

2 participants