Skip to content

Commit 8f16b67

Browse files
committed
πŸ› [RUM-16985] Stabilize WASM error metadata
1 parent aded8c6 commit 8f16b67

5 files changed

Lines changed: 52 additions & 25 deletions

File tree

β€Žpackages/browser-core/src/domain/wasmModules/wasmModuleTracking.spec.tsβ€Ž

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,38 @@ describe('startWasmModuleTracking', () => {
7474
expect(getLoadedWasmModules()).toEqual([{ url: '<wasm-compile-bytes>', build_id: '' }])
7575
})
7676

77+
it('waits for module metadata before resolving streaming instantiation', async () => {
78+
const wasmModule = new Uint8Array([
79+
0, 97, 115, 109, 1, 0, 0, 0, 0, 11, 8, 98, 117, 105, 108, 100, 95, 105, 100, 0xab, 0xcd,
80+
])
81+
let resolveArrayBuffer!: (buffer: ArrayBuffer) => void
82+
const arrayBufferPromise = new Promise<ArrayBuffer>((resolve) => {
83+
resolveArrayBuffer = resolve
84+
})
85+
const response = {
86+
url: 'https://example.com/module.wasm',
87+
clone: () => ({ arrayBuffer: () => arrayBufferPromise }),
88+
} as Response
89+
const instantiateStreamingSpy = spyOn(WebAssembly, 'instantiateStreaming').and.resolveTo(
90+
{} as WebAssembly.WebAssemblyInstantiatedSource
91+
)
92+
93+
startWasmModuleTracking()
94+
let isResolved = false
95+
const instantiatePromise = WebAssembly.instantiateStreaming(response).then(() => {
96+
isResolved = true
97+
})
98+
await new Promise((resolve) => setTimeout(resolve))
99+
100+
expect(instantiateStreamingSpy).toHaveBeenCalled()
101+
expect(isResolved).toBe(false)
102+
103+
resolveArrayBuffer(wasmModule.buffer)
104+
await instantiatePromise
105+
106+
expect(getLoadedWasmModules()).toEqual([{ url: response.url, build_id: 'abcd' }])
107+
})
108+
77109
it('keeps hooks installed until every tracking client stops', () => {
78110
const originalCompile = WebAssembly.compile
79111
const stopFirstClient = startWasmModuleTracking()

β€Žpackages/browser-core/src/domain/wasmModules/wasmModuleTracking.tsβ€Ž

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,24 @@ function recordModuleFromView(url: string, view: ArrayBufferView): void {
5757
recordModule(url, view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength))
5858
}
5959

60-
// Extracts build_id from a Response without consuming it for the caller.
61-
// Returns the original response so the actual instantiation can proceed
62-
// without delay; build_id extraction races in parallel.
63-
function captureFromResponseAsync(response: Response): Response {
60+
// Extracts build_id from a Response clone without consuming it for the caller.
61+
// Streaming compilation and metadata extraction happen in parallel, but the
62+
// wrapper only resolves once both are done. This guarantees that an error
63+
// thrown immediately by an exported function can reference the loaded module.
64+
function captureFromResponse(response: Response): Promise<void> {
6465
const url = response.url || '<wasm-instantiate-streaming-no-url>'
65-
if (!registry.has(url)) {
66-
response
66+
if (registry.has(url)) {
67+
return Promise.resolve()
68+
}
69+
try {
70+
return response
6771
.clone()
6872
.arrayBuffer()
69-
.then((buf) => recordModule(url, buf))
73+
.then((buffer) => recordModule(url, buffer))
7074
.catch(() => undefined)
75+
} catch {
76+
return Promise.resolve()
7177
}
72-
return response
7378
}
7479

7580
export function startWasmModuleTracking(): () => void {
@@ -139,25 +144,19 @@ function installWasmModuleTracking(): () => void {
139144
if (origInstantiateStreaming) {
140145
WebAssembly.instantiateStreaming = function (source, importObject) {
141146
return Promise.resolve(source).then((response: Response) => {
142-
try {
143-
captureFromResponseAsync(response)
144-
} catch {
145-
// never block instantiation on capture failure
146-
}
147-
return origInstantiateStreaming.call(this, response, importObject)
147+
const capturePromise = captureFromResponse(response)
148+
return Promise.all([origInstantiateStreaming.call(this, response, importObject), capturePromise]).then(
149+
([result]) => result
150+
)
148151
})
149152
}
150153
}
151154

152155
if (origCompileStreaming) {
153156
WebAssembly.compileStreaming = function (source) {
154157
return Promise.resolve(source).then((response: Response) => {
155-
try {
156-
captureFromResponseAsync(response)
157-
} catch {
158-
// never block compilation on capture failure
159-
}
160-
return origCompileStreaming.call(this, response)
158+
const capturePromise = captureFromResponse(response)
159+
return Promise.all([origCompileStreaming.call(this, response), capturePromise]).then(([module]) => module)
161160
})
162161
}
163162
}

β€Žpackages/browser-logs/src/domain/createErrorFieldFromRawError.spec.tsβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ describe('createErrorFieldFromRawError', () => {
6161
],
6262
fingerprint: 'corge',
6363
handling: ErrorHandling.HANDLED,
64-
source_type: 'browser',
6564
})
6665
})
6766

β€Žpackages/browser-logs/src/domain/createErrorFieldFromRawError.tsβ€Ž

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export function createErrorFieldFromRawError(
2121
causes: rawError.causes,
2222
fingerprint: rawError.fingerprint,
2323
handling: rawError.handling,
24-
source_type: isWasm ? 'browser+wasm' : 'browser',
25-
...(isWasm ? { wasm_modules: getLoadedWasmModules() } : {}),
24+
...(isWasm ? { source_type: 'browser+wasm' as const, wasm_modules: getLoadedWasmModules() } : {}),
2625
}
2726
}

β€Žpackages/browser-logs/src/domain/runtimeError/runtimeErrorCollection.spec.tsβ€Ž

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ describe('runtime error collection', () => {
7070
handling: ErrorHandling.UNHANDLED,
7171
fingerprint: undefined,
7272
message: undefined,
73-
source_type: 'browser',
7473
},
7574
message: 'error!',
7675
status: StatusType.error,
@@ -125,7 +124,6 @@ describe('runtime error collection', () => {
125124
],
126125
fingerprint: undefined,
127126
message: undefined,
128-
source_type: 'browser',
129127
},
130128
message: 'High level error',
131129
status: StatusType.error,

0 commit comments

Comments
Β (0)