Skip to content

Commit 80f3886

Browse files
committed
Encode owner in the error payload in dev and use it as the Error's Task
1 parent 288d428 commit 80f3886

File tree

4 files changed

+62
-24
lines changed

4 files changed

+62
-24
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,11 +3181,27 @@ function resolveErrorDev(
31813181
'An error occurred in the Server Components render but no message was provided',
31823182
),
31833183
);
3184-
const rootTask = getRootTask(response, env);
3185-
if (rootTask != null) {
3186-
error = rootTask.run(callStack);
3184+
3185+
let ownerTask: null | ConsoleTask = null;
3186+
if (errorInfo.owner != null) {
3187+
const ownerRef = errorInfo.owner.slice(1);
3188+
// TODO: This is not resilient to the owner loading later in an Error like a debug channel.
3189+
// The whole error serialization should probably go through the regular model at least for DEV.
3190+
const owner = getOutlinedModel(response, ownerRef, {}, '', createModel);
3191+
if (owner !== null) {
3192+
ownerTask = initializeFakeTask(response, owner);
3193+
}
3194+
}
3195+
3196+
if (ownerTask === null) {
3197+
const rootTask = getRootTask(response, env);
3198+
if (rootTask != null) {
3199+
error = rootTask.run(callStack);
3200+
} else {
3201+
error = callStack();
3202+
}
31873203
} else {
3188-
error = callStack();
3204+
error = ownerTask.run(callStack);
31893205
}
31903206

31913207
(error: any).name = name;

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,12 +1941,14 @@ function createChildReconciler(
19411941
throwFiber.return = returnFiber;
19421942
if (__DEV__) {
19431943
const debugInfo = (throwFiber._debugInfo = currentDebugInfo);
1944-
// Conceptually the error's owner/task should ideally be captured when the
1945-
// Error constructor is called but neither console.createTask does this,
1946-
// nor do we override them to capture our `owner`. So instead, we use the
1947-
// nearest parent as the owner/task of the error. This is usually the same
1948-
// thing when it's thrown from the same async component but not if you await
1949-
// a promise started from a different component/task.
1944+
// Conceptually the error's owner should ideally be captured when the
1945+
// Error constructor is called but we don't override them to capture our
1946+
// `owner`. So instead, we use the nearest parent as the owner/task of the
1947+
// error. This is usually the same thing when it's thrown from the same
1948+
// async component but not if you await a promise started from a different
1949+
// component/task.
1950+
// In newer Chrome, Error constructor does capture the Task which is what
1951+
// is logged by reportError. In that case this debugTask isn't used.
19501952
throwFiber._debugOwner = returnFiber._debugOwner;
19511953
throwFiber._debugTask = returnFiber._debugTask;
19521954
if (debugInfo != null) {

packages/react-server/src/ReactFlightServer.js

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ function serializeDebugThenable(
864864
const x = thenable.reason;
865865
// We don't log these errors since they didn't actually throw into Flight.
866866
const digest = '';
867-
emitErrorChunk(request, id, digest, x, true);
867+
emitErrorChunk(request, id, digest, x, true, null);
868868
return ref;
869869
}
870870
}
@@ -916,7 +916,7 @@ function serializeDebugThenable(
916916
}
917917
// We don't log these errors since they didn't actually throw into Flight.
918918
const digest = '';
919-
emitErrorChunk(request, id, digest, reason, true);
919+
emitErrorChunk(request, id, digest, reason, true, null);
920920
enqueueFlush(request);
921921
},
922922
);
@@ -964,7 +964,7 @@ function emitRequestedDebugThenable(
964964
}
965965
// We don't log these errors since they didn't actually throw into Flight.
966966
const digest = '';
967-
emitErrorChunk(request, id, digest, reason, true);
967+
emitErrorChunk(request, id, digest, reason, true, null);
968968
enqueueFlush(request);
969969
},
970970
);
@@ -2759,7 +2759,7 @@ function serializeClientReference(
27592759
request.pendingChunks++;
27602760
const errorId = request.nextChunkId++;
27612761
const digest = logRecoverableError(request, x, null);
2762-
emitErrorChunk(request, errorId, digest, x, false);
2762+
emitErrorChunk(request, errorId, digest, x, false, null);
27632763
return serializeByValueID(errorId);
27642764
}
27652765
}
@@ -2808,7 +2808,7 @@ function serializeDebugClientReference(
28082808
request.pendingDebugChunks++;
28092809
const errorId = request.nextChunkId++;
28102810
const digest = logRecoverableError(request, x, null);
2811-
emitErrorChunk(request, errorId, digest, x, true);
2811+
emitErrorChunk(request, errorId, digest, x, true, null);
28122812
return serializeByValueID(errorId);
28132813
}
28142814
}
@@ -3049,7 +3049,7 @@ function serializeDebugBlob(request: Request, blob: Blob): string {
30493049
}
30503050
function error(reason: mixed) {
30513051
const digest = '';
3052-
emitErrorChunk(request, id, digest, reason, true);
3052+
emitErrorChunk(request, id, digest, reason, true, null);
30533053
enqueueFlush(request);
30543054
// $FlowFixMe should be able to pass mixed
30553055
reader.cancel(reason).then(noop, noop);
@@ -3249,7 +3249,14 @@ function renderModel(
32493249
emitPostponeChunk(request, errorId, postponeInstance);
32503250
} else {
32513251
const digest = logRecoverableError(request, x, task);
3252-
emitErrorChunk(request, errorId, digest, x, false);
3252+
emitErrorChunk(
3253+
request,
3254+
errorId,
3255+
digest,
3256+
x,
3257+
false,
3258+
__DEV__ ? task.debugOwner : null,
3259+
);
32533260
}
32543261
if (wasReactNode) {
32553262
// We'll replace this element with a lazy reference that throws on the client
@@ -4067,7 +4074,8 @@ function emitErrorChunk(
40674074
id: number,
40684075
digest: string,
40694076
error: mixed,
4070-
debug: boolean,
4077+
debug: boolean, // DEV-only
4078+
owner: ?ReactComponentInfo, // DEV-only
40714079
): void {
40724080
let errorInfo: ReactErrorInfo;
40734081
if (__DEV__) {
@@ -4099,7 +4107,9 @@ function emitErrorChunk(
40994107
message = 'An error occurred but serializing the error message failed.';
41004108
stack = [];
41014109
}
4102-
errorInfo = {digest, name, message, stack, env};
4110+
const ownerRef =
4111+
owner == null ? null : outlineComponentInfo(request, owner);
4112+
errorInfo = {digest, name, message, stack, env, owner: ownerRef};
41034113
} else {
41044114
errorInfo = {digest};
41054115
}
@@ -4199,7 +4209,7 @@ function emitDebugChunk(
41994209
function outlineComponentInfo(
42004210
request: Request,
42014211
componentInfo: ReactComponentInfo,
4202-
): void {
4212+
): string {
42034213
if (!__DEV__) {
42044214
// These errors should never make it into a build so we don't need to encode them in codes.json
42054215
// eslint-disable-next-line react-internal/prod-error-codes
@@ -4208,9 +4218,10 @@ function outlineComponentInfo(
42084218
);
42094219
}
42104220

4211-
if (request.writtenDebugObjects.has(componentInfo)) {
4221+
const existingRef = request.writtenDebugObjects.get(componentInfo);
4222+
if (existingRef !== undefined) {
42124223
// Already written
4213-
return;
4224+
return existingRef;
42144225
}
42154226

42164227
if (componentInfo.owner != null) {
@@ -4265,6 +4276,7 @@ function outlineComponentInfo(
42654276
request.writtenDebugObjects.set(componentInfo, ref);
42664277
// We also store this in the main dedupe set so that it can be referenced by inline React Elements.
42674278
request.writtenObjects.set(componentInfo, ref);
4279+
return ref;
42684280
}
42694281

42704282
function emitIOInfoChunk(
@@ -5456,7 +5468,14 @@ function erroredTask(request: Request, task: Task, error: mixed): void {
54565468
emitPostponeChunk(request, task.id, postponeInstance);
54575469
} else {
54585470
const digest = logRecoverableError(request, error, task);
5459-
emitErrorChunk(request, task.id, digest, error, false);
5471+
emitErrorChunk(
5472+
request,
5473+
task.id,
5474+
digest,
5475+
error,
5476+
false,
5477+
__DEV__ ? task.debugOwner : null,
5478+
);
54605479
}
54615480
request.abortableTasks.delete(task);
54625481
callOnAllReadyIfReady(request);
@@ -6031,7 +6050,7 @@ export function abort(request: Request, reason: mixed): void {
60316050
const errorId = request.nextChunkId++;
60326051
request.fatalError = errorId;
60336052
request.pendingChunks++;
6034-
emitErrorChunk(request, errorId, digest, error, false);
6053+
emitErrorChunk(request, errorId, digest, error, false, null);
60356054
abortableTasks.forEach(task => abortTask(task, request, errorId));
60366055
scheduleWork(() => finishAbort(request, abortableTasks, errorId));
60376056
}

packages/shared/ReactTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ export type ReactErrorInfoDev = {
228228
+message: string,
229229
+stack: ReactStackTrace,
230230
+env: string,
231+
+owner?: null | string,
231232
};
232233

233234
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)