Skip to content

Commit 4bd48b9

Browse files
committed
Make JS source-map symbolication race-safe and run it in parallel with native
Refactor the source-map worker to return position-keyed resolutions instead of full table snapshots. The main thread applies the response against the current shared state via applySourceMapSymbolicationResponse, allocating sourceMapInfo rows, interning names, and deduping URLs against whatever native symbolication has committed by then. With per-funcIndex/per-frameIndex idempotency (skip rows already populated) and JS funcs/frames being insulated from native symbolication, the worker no longer needs to wait for native symbolication to finish. finalizeProfileView now runs source-map fetch, native symbolication, and the source-map worker all in parallel.
1 parent b2ae292 commit 4bd48b9

4 files changed

Lines changed: 319 additions & 173 deletions

File tree

src/actions/receive-profile.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -248,19 +248,19 @@ export function finalizeProfileView(
248248
}
249249
}
250250

251-
// Fetch source maps for all JS sources with a sourceMapURL, then apply JS
252-
// symbolication. Fetching runs in parallel with native symbolication, but
253-
// the worker is only dispatched after native symbolication has committed
254-
// its Redux changes, so neither clobbers the other's funcTable updates.
255-
// Requires WebChannel version 7+.
251+
// Fetch source maps for all JS sources with a sourceMapURL, then run the
252+
// source-map worker. Runs fully in parallel with native symbolication:
253+
// native only touches funcs/frames belonging to library resources (JS
254+
// funcs aren't in those sets), and the JS apply step reads current
255+
// shared state at dispatch time so it composes with whatever native
256+
// has committed by then. Requires WebChannel version 7+.
256257
let sourceMapSymbolicationPromise: Promise<void> | null = null;
257258
if (browserConnection !== null && browserConnection.supportsGetSourceMap) {
258-
// Fetch source maps concurrently with native symbolication. Once both
259-
// have completed, apply JS symbolication on top of the final state.
260-
sourceMapSymbolicationPromise = Promise.all([
261-
doResolveSourceMaps(profile, browserConnection, dispatch),
262-
symbolicationPromise,
263-
]).then(([{ resolvedSourceMaps, compiledSources }]) =>
259+
sourceMapSymbolicationPromise = doResolveSourceMaps(
260+
profile,
261+
browserConnection,
262+
dispatch
263+
).then(({ resolvedSourceMaps, compiledSources }) =>
264264
dispatch(doSourceMapSymbolication(resolvedSourceMaps, compiledSources))
265265
);
266266
}

src/actions/source-map-symbolication.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
import { getRawProfileSharedData } from 'firefox-profiler/selectors';
6+
import { applySourceMapSymbolicationResponse } from 'firefox-profiler/profile-logic/source-map-symbolication';
67

78
import type {
89
WorkerInput,
@@ -49,16 +50,29 @@ export function doSourceMapSymbolication(
4950
dispatch({ type: 'START_SOURCE_MAP_SYMBOLICATION' });
5051
const result = await _runSourceMapWorker(input);
5152
switch (result.type) {
52-
case 'success':
53+
case 'success': {
54+
// Apply against the current shared state (not the snapshot the worker
55+
// received), so concurrent worker runs compose instead of stomping
56+
// each other's results.
57+
const currentShared = getRawProfileSharedData(getState());
58+
const applied = applySourceMapSymbolicationResponse(
59+
currentShared,
60+
result.response
61+
);
62+
if (applied === null) {
63+
dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' });
64+
break;
65+
}
5366
dispatch({
5467
type: 'BULK_SOURCE_MAP_SYMBOLICATION',
55-
newFuncTable: result.newFuncTable,
56-
newFrameTable: result.newFrameTable,
57-
newSourceLocationTable: result.newSourceLocationTable,
58-
newSources: result.newSources,
59-
newStringArray: result.newStringArray,
68+
newFuncTable: applied.newFuncTable,
69+
newFrameTable: applied.newFrameTable,
70+
newSourceLocationTable: applied.newSourceLocationTable,
71+
newSources: applied.newSources,
72+
newStringArray: applied.newStringArray,
6073
});
6174
break;
75+
}
6276
case 'error':
6377
console.warn('Source map worker error:', result.message);
6478
dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' });
@@ -79,26 +93,14 @@ export function doSourceMapSymbolication(
7993
*
8094
* Debugging tip: to step through symbolication from the page DevTools, paste
8195
* the snippet below over this function body. It runs the same core on the
82-
* main thread (blocking, debug only).
96+
* main thread (blocking, debug only). The worker no longer mutates its
97+
* input, so no defensive cloning is needed here.
8398
*
8499
* const { runSourceMapSymbolicationCore } = await import(
85100
* 'firefox-profiler/profile-logic/source-map-symbolication'
86101
* );
87-
* const sources = {
88-
* length: input.sources.length,
89-
* id: input.sources.id.slice(),
90-
* filename: input.sources.filename.slice(),
91-
* startLine: input.sources.startLine.slice(),
92-
* startColumn: input.sources.startColumn.slice(),
93-
* sourceMapURL: input.sources.sourceMapURL.slice(),
94-
* content: input.sources.content.slice(),
95-
* };
96-
* const stringArray = input.stringArray.slice();
97102
* const wasmUrl = new URL('/mappings.wasm', window.location.href).href;
98-
* return runSourceMapSymbolicationCore(
99-
* { ...input, sources, stringArray },
100-
* wasmUrl
101-
* );
103+
* return runSourceMapSymbolicationCore(input, wasmUrl);
102104
*/
103105
function _runSourceMapWorker(input: WorkerInput): Promise<WorkerOutput> {
104106
return new Promise((resolve) => {

0 commit comments

Comments
 (0)