-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(core): Improve waiting for tracing channel bindings #21815
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
Open
mydea
wants to merge
7
commits into
develop
Choose a base branch
from
fn/tracing-channel-bindings
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+279
−75
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a7a671e
fix(core): Improve waiting for tracing channel bindings
mydea 886b7c5
changes...
mydea 2215c72
safe unref
mydea 688a5b6
ref a bit
mydea 113ee44
fixes
mydea 9e4a016
fix edge
mydea 35ef394
avoid reorder
mydea File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { getMainCarrier } from '../carrier'; | ||
| import type { Scope } from '../scope'; | ||
| import { _setSpanForScope } from '../utils/spanOnScope'; | ||
| import { safeUnref } from '../utils/timer'; | ||
| import { getAsyncContextStrategy } from './index'; | ||
| import type { TracingChannelBinding } from './types'; | ||
|
|
||
| /** | ||
| * Execute a callback whenever the tracing channel binding is available. | ||
| * If it is not available after retry, the callback is not executed. | ||
| */ | ||
| export function waitForTracingChannelBinding(callback: () => void, retries = 1): void { | ||
| const binding = getAsyncContextStrategy(getMainCarrier()).getTracingChannelBinding?.(); | ||
|
|
||
| if (binding) { | ||
| callback(); | ||
| return; | ||
| } | ||
|
|
||
| if (!retries) { | ||
| return; | ||
| } | ||
|
|
||
| // It is possible that the binding is not available yet when this is initially called | ||
| // This happens when users use a custom OTEL setup | ||
| // In this case, we wait for a tick and try again afterwards | ||
| // If it still fails, we bail and do nothing | ||
| // `safeUnref` so this retry timer never keeps the process alive on its own (Node server runtimes). | ||
| safeUnref( | ||
| setTimeout(() => { | ||
| waitForTracingChannelBinding(callback, retries - 1); | ||
| }, 1), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Build the default {@link TracingChannelBinding} shared by AsyncLocalStorage-based strategies. | ||
| * | ||
| * The ALS instance is supplied by the caller (kept as `unknown`). | ||
| * The binding clones the current scope, plants the span on it, and reuses the existing isolation scope. | ||
| * | ||
| * The OpenTelemetry strategy does not use this: its store value is an OTel context, not a | ||
| * `{ scope, isolationScope }` pair. | ||
| */ | ||
| export function _INTERNAL_createTracingChannelBinding( | ||
| asyncLocalStorage: unknown, | ||
| getScopes: () => { scope: Scope; isolationScope: Scope }, | ||
| ): TracingChannelBinding { | ||
| return { | ||
| asyncLocalStorage, | ||
| getStoreWithActiveSpan: span => { | ||
| const { scope, isolationScope } = getScopes(); | ||
| const activeScope = scope.clone(); | ||
| _setSpanForScope(activeScope, span); | ||
|
|
||
| return { scope: activeScope, isolationScope }; | ||
| }, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
packages/core/test/lib/asyncContext/tracing-channel-binding.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | ||
| import { getAsyncContextStrategy, setAsyncContextStrategy } from '../../../src/asyncContext'; | ||
| import { waitForTracingChannelBinding } from '../../../src/asyncContext/tracing-channel-binding'; | ||
| import type { TracingChannelBinding } from '../../../src/asyncContext/types'; | ||
| import { getMainCarrier } from '../../../src/carrier'; | ||
|
|
||
| const FAKE_BINDING: TracingChannelBinding = { | ||
| asyncLocalStorage: {}, | ||
| getStoreWithActiveSpan: () => ({}), | ||
| }; | ||
|
|
||
| /** Install an async context strategy whose `getTracingChannelBinding` is driven by `provider`. */ | ||
| function setBindingProvider(provider: (() => TracingChannelBinding | undefined) | undefined): void { | ||
| setAsyncContextStrategy({ | ||
| ...getAsyncContextStrategy(getMainCarrier()), | ||
| getTracingChannelBinding: provider, | ||
| }); | ||
| } | ||
|
|
||
| describe('waitForTracingChannelBinding', () => { | ||
| beforeEach(() => { | ||
| vi.useFakeTimers(); | ||
| setAsyncContextStrategy(undefined); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| setAsyncContextStrategy(undefined); | ||
| vi.useRealTimers(); | ||
| }); | ||
|
|
||
| it('runs the callback synchronously when the binding is already available', () => { | ||
| const getBinding = vi.fn(() => FAKE_BINDING); | ||
| setBindingProvider(getBinding); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback); | ||
|
|
||
| expect(callback).toHaveBeenCalledTimes(1); | ||
| // Resolved on the first attempt, so no retry should be scheduled. | ||
| expect(getBinding).toHaveBeenCalledTimes(1); | ||
| vi.runAllTimers(); | ||
| expect(getBinding).toHaveBeenCalledTimes(1); | ||
| expect(callback).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('retries on the next tick and runs the callback once the binding becomes available', () => { | ||
| const getBinding = vi.fn<[], TracingChannelBinding | undefined>(() => FAKE_BINDING); | ||
| getBinding.mockReturnValueOnce(undefined); | ||
| setBindingProvider(getBinding); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback); | ||
|
|
||
| // Not available on the first (synchronous) attempt. | ||
| expect(callback).not.toHaveBeenCalled(); | ||
|
|
||
| vi.advanceTimersByTime(1); | ||
|
|
||
| expect(callback).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('does not run the callback if the binding never becomes available (default single retry)', () => { | ||
| const getBinding = vi.fn(() => undefined); | ||
| setBindingProvider(getBinding); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); | ||
|
|
||
| vi.advanceTimersByTime(1); | ||
| expect(callback).not.toHaveBeenCalled(); | ||
|
|
||
| // The single retry is exhausted — no further attempts are scheduled. | ||
| expect(getBinding).toHaveBeenCalledTimes(2); | ||
| vi.runAllTimers(); | ||
| expect(getBinding).toHaveBeenCalledTimes(2); | ||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('does not retry when retries is 0', () => { | ||
| const getBinding = vi.fn(() => undefined); | ||
| setBindingProvider(getBinding); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback, 0); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); | ||
| expect(getBinding).toHaveBeenCalledTimes(1); | ||
|
|
||
| // No retry is scheduled when no retries remain. | ||
| vi.runAllTimers(); | ||
| expect(getBinding).toHaveBeenCalledTimes(1); | ||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('honors a custom retry count', () => { | ||
| const getBinding = vi.fn<[], TracingChannelBinding | undefined>(() => FAKE_BINDING); | ||
| getBinding.mockReturnValueOnce(undefined).mockReturnValueOnce(undefined).mockReturnValue(FAKE_BINDING); | ||
| setBindingProvider(getBinding); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback, 2); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); // attempt 1 (sync): undefined | ||
|
|
||
| vi.advanceTimersByTime(1); | ||
| expect(callback).not.toHaveBeenCalled(); // attempt 2: undefined | ||
|
|
||
| vi.advanceTimersByTime(1); | ||
| expect(callback).toHaveBeenCalledTimes(1); // attempt 3: available | ||
| }); | ||
|
|
||
| it('does nothing when the strategy exposes no `getTracingChannelBinding`', () => { | ||
| // The default (stack) strategy has no tracing-channel binding support. | ||
| setAsyncContextStrategy(undefined); | ||
|
|
||
| const callback = vi.fn(); | ||
| waitForTracingChannelBinding(callback, 0); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.