Skip to content

Commit d00a8c2

Browse files
author
cod1k
committed
Refactor and rename utilities for execution context handling
Renamed `cloneExecutionContext` to `copyExecutionContext` and updated its implementation for improved binding logic and symbol handling. Moved `makeFlushLock` to `utils/flushLock` and adjusted imports accordingly. Added tests to validate `copyExecutionContext` behaviors and ensure robustness.
1 parent 4d46261 commit d00a8c2

File tree

9 files changed

+84
-32
lines changed

9 files changed

+84
-32
lines changed

packages/cloudflare/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ClientOptions, Options, ServerRuntimeClientOptions } from '@sentry/core';
22
import { applySdkMetadata, ServerRuntimeClient } from '@sentry/core';
3-
import type { makeFlushLock } from './flush';
43
import type { CloudflareTransportOptions } from './transport';
4+
import type { makeFlushLock } from './utils/flushLock';
55

66
/**
77
* The Sentry Cloudflare SDK Client.

packages/cloudflare/src/durableobject.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { isInstrumented, markAsInstrumented } from './instrument';
1818
import { getFinalOptions } from './options';
1919
import { wrapRequestHandler } from './request';
2020
import { init } from './sdk';
21-
import { cloneExecutionContext } from './utils/cloneExecutionContext';
21+
import { copyExecutionContext } from './utils/copyExecutionContext';
2222

2323
type MethodWrapperOptions = {
2424
spanName?: string;
@@ -196,7 +196,7 @@ export function instrumentDurableObjectWithSentry<
196196
construct(target, [ctx, env]) {
197197
setAsyncLocalStorageAsyncContextStrategy();
198198

199-
const context = cloneExecutionContext(ctx)
199+
const context = copyExecutionContext(ctx)
200200

201201
const options = getFinalOptions(optionsCallback(env), env);
202202

packages/cloudflare/src/handler.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { getFinalOptions } from './options';
1414
import { wrapRequestHandler } from './request';
1515
import { addCloudResourceContext } from './scope-utils';
1616
import { init } from './sdk';
17-
import { cloneExecutionContext } from './utils/cloneExecutionContext';
17+
import { copyExecutionContext } from './utils/copyExecutionContext';
1818

1919
/**
2020
* Wrapper for Cloudflare handlers.
@@ -41,7 +41,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
4141
const [request, env, ctx] = args;
4242

4343
const options = getFinalOptions(optionsCallback(env), env);
44-
const context = cloneExecutionContext(ctx)
44+
const context = copyExecutionContext(ctx)
4545
args[2] = context;
4646

4747
return wrapRequestHandler({ options, request, context }, () => target.apply(thisArg, args));
@@ -75,7 +75,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
7575
handler.scheduled = new Proxy(handler.scheduled, {
7676
apply(target, thisArg, args: Parameters<ExportedHandlerScheduledHandler<Env>>) {
7777
const [event, env, ctx] = args;
78-
const context = cloneExecutionContext(ctx)
78+
const context = copyExecutionContext(ctx)
7979
args[2] = context;
8080
return withIsolationScope(isolationScope => {
8181
const options = getFinalOptions(optionsCallback(env), env);
@@ -120,7 +120,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
120120
handler.email = new Proxy(handler.email, {
121121
apply(target, thisArg, args: Parameters<EmailExportedHandler<Env>>) {
122122
const [emailMessage, env, ctx] = args;
123-
const context = cloneExecutionContext(ctx)
123+
const context = copyExecutionContext(ctx)
124124
args[2] = context;
125125
return withIsolationScope(isolationScope => {
126126
const options = getFinalOptions(optionsCallback(env), env);
@@ -163,7 +163,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
163163
handler.queue = new Proxy(handler.queue, {
164164
apply(target, thisArg, args: Parameters<ExportedHandlerQueueHandler<Env, QueueHandlerMessage>>) {
165165
const [batch, env, ctx] = args;
166-
const context = cloneExecutionContext(ctx)
166+
const context = copyExecutionContext(ctx)
167167
args[2] = context;
168168

169169
return withIsolationScope(isolationScope => {
@@ -215,7 +215,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
215215
handler.tail = new Proxy(handler.tail, {
216216
apply(target, thisArg, args: Parameters<ExportedHandlerTailHandler<Env>>) {
217217
const [, env, ctx] = args;
218-
const context = cloneExecutionContext(ctx)
218+
const context = copyExecutionContext(ctx)
219219
args[2] = context;
220220

221221
return withIsolationScope(async isolationScope => {

packages/cloudflare/src/sdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
} from '@sentry/core';
1313
import type { CloudflareClientOptions, CloudflareOptions } from './client';
1414
import { CloudflareClient } from './client';
15-
import { makeFlushLock } from './flush';
1615
import { fetchIntegration } from './integrations/fetch';
1716
import { setupOpenTelemetryTracer } from './opentelemetry/tracer';
1817
import { makeCloudflareTransport } from './transport';
18+
import { makeFlushLock } from './utils/flushLock';
1919
import { defaultStackParser } from './vendor/stacktrace';
2020

2121
/** Get the default integrations for the Cloudflare SDK. */

packages/cloudflare/src/utils/cloneExecutionContext.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { type ExecutionContext } from '@cloudflare/workers-types';
2+
3+
const kBound = Symbol.for('kBound');
4+
5+
/**
6+
* Clones the given execution context by creating a shallow copy while ensuring the binding of specific methods.
7+
*
8+
* @param {ExecutionContext|void} ctx - The execution context to clone. Can be void.
9+
* @return {ExecutionContext|void} A cloned execution context with bound methods, or the original void value if no context was provided.
10+
*/
11+
export function copyExecutionContext<T extends ExecutionContext | void>(ctx: T): T {
12+
if (!ctx) return ctx;
13+
return Object.assign({}, ctx, {
14+
...('waitUntil' in ctx && { waitUntil: copyBound(ctx, 'waitUntil') }),
15+
...('passThroughOnException' in ctx && { passThroughOnException: copyBound(ctx, 'passThroughOnException') }),
16+
});
17+
}
18+
19+
function copyBound<T extends object, K extends keyof T>(obj: T, method: K): T[K] {
20+
const method_impl = obj[method];
21+
if (typeof method_impl !== 'function') return method_impl;
22+
if ((method_impl as T[K] & { [kBound]?: boolean })[kBound]) return method_impl;
23+
24+
const bound = method_impl.bind(obj);
25+
return Object.defineProperty(bound, kBound, { value: true, enumerable: false });
26+
}

packages/cloudflare/src/flush.ts renamed to packages/cloudflare/src/utils/flushLock.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import type { ExecutionContext } from '@cloudflare/workers-types';
2-
import { createPromiseResolver } from './utils/makePromiseResolver';
2+
import { createPromiseResolver } from './makePromiseResolver';
33

44
type FlushLock = {
55
readonly ready: Promise<void>;
66
readonly finalize: () => Promise<void>;
77
};
8-
type Lockable<T> = T & { [kFlushLock]?: FlushLock };
8+
type MaybeLockable<T extends object> = T & { [kFlushLock]?: FlushLock };
99

1010
const kFlushLock = Symbol.for('kFlushLock');
1111

12-
function getInstrumentedLock<T>(o: Lockable<T>): FlushLock | undefined {
12+
function getInstrumentedLock<T extends object>(o: MaybeLockable<T>): FlushLock | undefined {
1313
return o[kFlushLock];
1414
}
1515

16-
function storeInstrumentedLock<T>(o: Lockable<T>, lock: FlushLock): void {
16+
function storeInstrumentedLock<T extends object>(o: MaybeLockable<T>, lock: FlushLock): void {
1717
o[kFlushLock] = lock;
1818
}
1919

2020
/**
2121
* Enhances the given execution context by wrapping its `waitUntil` method with a proxy
22-
* to monitor pending tasks, and provides a flusher function to ensure all tasks
22+
* to monitor pending tasks and provides a flusher function to ensure all tasks
2323
* have been completed before executing any subsequent logic.
2424
*
2525
* @param {ExecutionContext} context - The execution context to be enhanced. If no context is provided, the function returns undefined.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { type ExecutionContext } from '@cloudflare/workers-types';
2+
import { type Mocked, describe, expect, it, vi } from 'vitest';
3+
import { copyExecutionContext } from '../src/utils/copyExecutionContext';
4+
5+
describe('Copy of the execution context', () => {
6+
describe.for<keyof ExecutionContext>(['waitUntil', 'passThroughOnException'])('%s', method => {
7+
it('Was not bound more than once', async () => {
8+
const context = makeExecutionContextMock();
9+
const copy = copyExecutionContext(context);
10+
const copy_of_copy = copyExecutionContext(copy);
11+
12+
expect(copy[method]).toBe(copy_of_copy[method]);
13+
});
14+
it('Copied method is bound to the original', async () => {
15+
const context = makeExecutionContextMock();
16+
const copy = copyExecutionContext(context);
17+
18+
expect(copy[method]()).toBe(context);
19+
});
20+
});
21+
22+
it('No side effects', async () => {
23+
const context = makeExecutionContextMock();
24+
expect(() => copyExecutionContext(Object.freeze(context))).not.toThrow(
25+
/Cannot define property \w+, object is not extensible/,
26+
);
27+
});
28+
it('Respects symbols', async () => {
29+
const s = Symbol('test');
30+
const context = makeExecutionContextMock<ExecutionContext & { [s]: unknown }>();
31+
context[s] = {};
32+
const copy = copyExecutionContext(context);
33+
expect(copy[s]).toBe(context[s]);
34+
});
35+
});
36+
37+
function makeExecutionContextMock<T extends ExecutionContext>() {
38+
return {
39+
waitUntil: vi.fn().mockReturnThis(),
40+
passThroughOnException: vi.fn().mockReturnThis(),
41+
} as unknown as Mocked<T>;
42+
}

packages/cloudflare/test/flush.test.ts renamed to packages/cloudflare/test/flush-lock.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type ExecutionContext } from '@cloudflare/workers-types';
22
import { describe, expect, it, vi } from 'vitest';
3-
import { makeFlushLock } from '../src/flush';
3+
import { makeFlushLock } from '../src/utils/flushLock';
44
import { createPromiseResolver } from '../src/utils/makePromiseResolver';
55

66
describe('Flush buffer test', () => {

0 commit comments

Comments
 (0)