-
Notifications
You must be signed in to change notification settings - Fork 703
Sample Strategy #1313
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
base: main
Are you sure you want to change the base?
Sample Strategy #1313
Conversation
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.
Implements SAMPLE strategy with abort signal support. Good implementation but has a critical type bug and missing error handling.
const cancelOthers = currentTarget.strategy?.cancelOthers; | ||
|
||
// v1 limitation: do not support sampling when request body is a ReadableStream | ||
if (request instanceof ReadableStream) { |
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.
🐛 Bug Fix
Issue: Type confusion bug - request instanceof ReadableStream
will always be false. The request
parameter is a Request
object, not a ReadableStream
. This check should verify if the request body is a stream.
Fix: Check the request body type instead of the request object type
Impact: Prevents incorrect rejection of streaming requests, ensuring proper validation
if (request instanceof ReadableStream) { | |
// v1 limitation: do not support sampling when request body is a ReadableStream | |
if (request.body instanceof ReadableStream) { |
const isSuccess = | ||
(Array.isArray(onStatusCodes) && | ||
!onStatusCodes.includes(resp?.status)) || | ||
(!onStatusCodes && resp?.ok) || | ||
gatewayException; |
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.
🐛 Bug Fix
Issue: Logic error in success condition - the boolean OR operator |
should be logical OR ||
. This causes incorrect type coercion and wrong success evaluation.
Fix: Use logical OR operator for proper boolean evaluation
Impact: Fixes success detection logic to properly identify successful responses
const isSuccess = | |
(Array.isArray(onStatusCodes) && | |
!onStatusCodes.includes(resp?.status)) || | |
(!onStatusCodes && resp?.ok) || | |
gatewayException; | |
const isSuccess = | |
(Array.isArray(onStatusCodes) && | |
!onStatusCodes.includes(resp?.status)) || | |
(!onStatusCodes && resp?.ok) || | |
gatewayException; |
for (const ctl of controllers) { | ||
try { | ||
ctl.abort(); | ||
} catch {} |
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.
🛠️ Code Refactor
Issue: Empty catch block silently ignores abort errors, making debugging difficult
Fix: Add minimal error logging for abort operations
Impact: Improves debugging capability while maintaining non-blocking behavior
} catch {} | |
} catch (e) { | |
// Ignore abort errors but log for debugging | |
console.debug('Abort controller error:', e); | |
} |
winnerResolved = true; | ||
resolveWinner(resp); | ||
if (cancelOthers) { | ||
for (const ctl of controllers) { | ||
try { | ||
ctl.abort(); | ||
} catch {} | ||
} | ||
} |
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.
🛠️ Code Refactor
Issue: Duplicate abort controller cleanup logic - same code block appears twice (lines 782-788 and 812-818)
Fix: Extract abort cleanup into a reusable function
Impact: Reduces code duplication and improves maintainability
winnerResolved = true; | |
resolveWinner(resp); | |
if (cancelOthers) { | |
for (const ctl of controllers) { | |
try { | |
ctl.abort(); | |
} catch {} | |
} | |
} | |
const cleanupControllers = () => { | |
if (cancelOthers) { | |
for (const ctl of controllers) { | |
try { | |
ctl.abort(); | |
} catch (e) { | |
console.debug('Abort controller error:', e); | |
} | |
} | |
} | |
}; | |
winnerResolved = true; | |
resolveWinner(resp); | |
cleanupControllers(); |
if (externalAbortSignal) { | ||
if (externalAbortSignal.aborted) { | ||
controller.abort(); | ||
} else { | ||
externalAbortSignal.addEventListener('abort', () => controller.abort()); | ||
} | ||
} |
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.
🐛 Bug Fix
Issue: Race condition - external abort signal could be triggered between the aborted
check and event listener registration
Fix: Register event listener first, then check abort status
Impact: Prevents missed abort signals and ensures reliable cancellation
if (externalAbortSignal) { | |
if (externalAbortSignal.aborted) { | |
controller.abort(); | |
} else { | |
externalAbortSignal.addEventListener('abort', () => controller.abort()); | |
} | |
} | |
if (externalAbortSignal) { | |
externalAbortSignal.addEventListener('abort', () => controller.abort()); | |
if (externalAbortSignal.aborted) { | |
controller.abort(); | |
} | |
} |
Clean simplification removing hardcoded port configuration. Code change is straightforward with no critical issues detected. |
Description
New Gateway strategy: Sample
Quick info:
The sample strategy fans out a single gateway request to multiple targets in parallel and returns the first-success response. It’s useful for shaving tail latency, probing multiple providers at once, and improving reliability without waiting for sequential fallbacks.
How it works
strategy.mode = "sample"
are invoked concurrently.strategy.on_status_codes
is set: first response whose HTTP status is NOT in that list wins. Gateway exceptions also count as a win to break out early.strategy.cancel_others = true
, once the winner is decided, all in-flight sibling requests are aborted.false
, non-winners are allowed to finish silently.Current limitations
on_status_codes
thoughtfully to prevent fast-fail responses from “winning.”Configuration
mode
: must be"sample"
.cancel_others
: optional boolean; aborts losers when true.on_status_codes
: optional array; statuses to treat as “fail” for winner selection.Minimal example
With winner policy guardrails
Request example (curl)
Observability
cancel_others=true
aborted targets show abort errors; withfalse
, they complete normally.Type of Change
How Has This Been Tested?
Screenshots (if applicable)
Checklist
Related Issues