462
onFatalError: mixed => void,
463
// Profiling-only
464
timeOrigin: number,
465
+ abortTime: number,
466
// DEV-only
467
completedDebugChunks: Array<Chunk | BinaryChunk>,
468
environmentName: () => string,
614
// $FlowFixMe[prop-missing]
615
performance.timeOrigin,
616
);
617
+ this.abortTime = -0.0;
618
} else {
619
timeOrigin = 0;
620
}
852
request.abortableTasks.delete(newTask);
853
if (enableHalt && request.type === PRERENDER) {
854
haltTask(newTask, request);
855
+ finishHaltedTask(newTask, request);
856
} else {
857
const errorId: number = (request.fatalError: any);
858
abortTask(newTask, request, errorId);
859
+ finishAbortedTask(newTask, request, errorId);
860
}
861
return newTask.id;
862
}
2045
node: AsyncSequence,
2046
visited: Set<AsyncSequence | ReactDebugInfo>,
2047
cutOff: number,
2044
-): null | PromiseNode | IONode {
2048
+): void | null | PromiseNode | IONode {
2049
if (visited.has(node)) {
2050
// It's possible to visit them same node twice when it's part of both an "awaited" path
2051
// and a "previous" path. This also gracefully handles cycles which would be a bug.
2054
visited.add(node);
2055
// First visit anything that blocked this sequence to start in the first place.
2056
if (node.previous !== null && node.end > request.timeOrigin) {
2053
- // We ignore the return value here because if it wasn't awaited in user space, then we don't log it.
2054
- // It also means that it can just have been part of a previous component's render.
2057
+ // We ignore the returned io nodes here because if it wasn't awaited in user space,
2058
+ // then we don't log it. It also means that it can just have been part of a previous
2059
+ // component's render.
2060
// TODO: This means that some I/O can get lost that was still blocking the sequence.
2056
- visitAsyncNode(request, task, node.previous, visited, cutOff);
2061
+ const ioNode = visitAsyncNode(
2062
+ request,
2063
+ task,
2064
+ node.previous,
2065
+ visited,
2066
+ cutOff,
2067
+ );
2068
+ if (ioNode === undefined) {
2069
+ // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2070
+ // further aborted nodes.
2071
+ return undefined;
2072
+ }
2073
}
2074
switch (node.tag) {
2075
case IO_NODE: {
2089
let match = null;
2090
if (awaited !== null) {
2091
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
2076
- if (ioNode !== null) {
2092
+ if (ioNode === undefined) {
2093
+ // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2094
+ // further aborted nodes.
2095
+ return undefined;
2096
+ } else if (ioNode !== null) {
2097
// This Promise was blocked on I/O. That's a signal that this Promise is interesting to log.
2098
// We don't log it yet though. We return it to be logged by the point where it's awaited.
2099
// The ioNode might be another PromiseNode in the case where none of the AwaitNode had
2110
} else {
2111
match = node;
2112
}
2113
+ } else if (request.status === ABORTING) {
2114
+ if (node.start < request.abortTime && node.end > request.abortTime) {
2115
+ // We aborted this render. If this Promise spanned the abort time it was probably the
2116
+ // Promise that was aborted. This won't necessarily have I/O associated with it but
2117
+ // it's a point of interest.
2118
+ if (filterStackTrace(request, node.stack).length > 0) {
2119
+ match = node;
2120
+ }
2121
+ }
2122
}
2123
}
2124
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
2141
let match = null;
2142
if (awaited !== null) {
2143
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
2115
- if (ioNode !== null) {
2144
+ if (ioNode === undefined) {
2145
+ // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2146
+ // further aborted nodes.
2147
+ return undefined;
2148
+ } else if (ioNode !== null) {
2149
const startTime: number = node.start;
2150
const endTime: number = node.end;
2151
if (endTime <= request.timeOrigin) {
2178
// If this await was fully filtered out, then it was inside third party code
2179
// such as in an external library. We return the I/O node and try another await.
2180
match = ioNode;
2181
+ } else if (
2182
+ request.status === ABORTING &&
2183
+ startTime > request.abortTime
2184
+ ) {
2185
+ // This was awaited after aborting so we skip it.
2186
} else {
2187
// We found a user space await.
2188
2208
// Mark the end time of the await. If we're aborting then we don't emit this
2209
// to signal that this never resolved inside this render.
2210
markOperationEndTime(request, task, endTime);
2211
+ if (request.status === ABORTING) {
2212
+ // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2213
+ // further aborted nodes.
2214
+ match = undefined;
2215
+ }
2216
}
2217
}
2218
}
2249
visited.add(alreadyForwardedDebugInfo);
2250
}
2251
const awaitedNode = visitAsyncNode(request, task, node, visited, task.time);
2209
- if (awaitedNode !== null) {
2252
+ if (awaitedNode === undefined) {
2253
+ // Undefined is used as a signal that we found an aborted await and that's good enough
2254
+ // anything derived from that aborted node might be irrelevant.
2255
+ } else if (awaitedNode !== null) {
2256
// Nothing in user space (unfiltered stack) awaited this.
2257
serializeIONode(request, awaitedNode, awaitedNode.promise);
2258
request.pendingChunks++;
4148
const endTime =
4149
ioNode.tag === UNRESOLVED_PROMISE_NODE
4150
? // Mark the end time as now. It's arbitrary since it's not resolved but this
4105
- // marks when we stopped trying.
4106
- performance.now()
4151
+ // marks when we called abort and therefore stopped trying.
4152
+ request.abortTime
4153
: ioNode.end;
4154
4155
request.pendingChunks++;
4962
thenable = (model: any);
4963
} else if (model.$$typeof === REACT_LAZY_TYPE) {
4964
const payload = model._payload;
4919
- const init = model._init;
4920
- try {
4921
- init(payload);
4922
- } catch (x) {
4923
- if (
4924
- typeof x === 'object' &&
4925
- x !== null &&
4926
- typeof x.then === 'function'
4927
- ) {
4928
- thenable = (x: any);
4929
- }
4965
+ if (typeof payload.then === 'function') {
4966
+ thenable = payload;
4967
}
4968
}
4969
if (thenable !== null) {
4992
advanceTaskTime(request, task, task.time);
4993
emitDebugChunk(request, task.id, asyncInfo);
4994
} else {
4995
+ // We have a resolved Promise. Its debug info can include both awaited data and rejected
4996
+ // promises after the abort.
4997
emitAsyncSequence(request, task, sequence, debugInfo, null, null);
4998
}
4999
}
5044
}
5045
// This is like advanceTaskTime() but always emits a timing chunk even if it doesn't advance.
5046
// This ensures that the end time of the previous entry isn't implied to be the start of the next one.
5008
- if (request.status === ABORTING) {
5047
+ if (request.status === ABORTING && timestamp > request.abortTime) {
5048
// If we're aborting then we don't emit any end times that happened after.
5049
return;
5050
}
5261
// When aborting a prerener with halt semantics we don't emit
5262
// anything into the slot for a task that aborts, it remains unresolved
5263
haltTask(task, request);
5264
+ finishHaltedTask(task, request);
5265
} else {
5266
// Otherwise we emit an error chunk into the task slot.
5267
const errorId: number = (request.fatalError: any);
5268
abortTask(task, request, errorId);
5269
+ finishAbortedTask(task, request, errorId);
5270
}
5271
return;
5272
}
5350
}
5351
5352
function abortTask(task: Task, request: Request, errorId: number): void {
5312
- if (task.status === RENDERING) {
5313
- // This task will be aborted by the render
5353
+ if (task.status !== PENDING) {
5354
+ // If this is already completed/errored we don't abort it.
5355
+ // If currently rendering it will be aborted by the render
5356
return;
5357
}
5358
task.status = ABORTED;
5359
+}
5360
+
5361
+function finishAbortedTask(
5362
+ task: Task,
5363
+ request: Request,
5364
+ errorId: number,
5365
+): void {
5366
+ if (task.status !== ABORTED) {
5367
+ return;
5368
+ }
5369
forwardDebugInfoFromAbortedTask(request, task);
5370
// Track when we aborted this task as its end time.
5371
if (enableProfilerTimer && enableComponentPerformanceTrack) {
5372
if (task.timed) {
5321
- markOperationEndTime(request, task, performance.now());
5373
+ markOperationEndTime(request, task, request.abortTime);
5374
}
5375
}
5376
// Instead of emitting an error per task.id, we emit a model that only
5381
}
5382
5383
function haltTask(task: Task, request: Request): void {
5332
- if (task.status === RENDERING) {
5333
- // this task will be halted by the render
5384
+ if (task.status !== PENDING) {
5385
+ // If this is already completed/errored we don't abort it.
5386
+ // If currently rendering it will be aborted by the render
5387
return;
5388
}
5389
task.status = ABORTED;
5390
+}
5391
+
5392
+function finishHaltedTask(task: Task, request: Request): void {
5393
+ if (task.status !== ABORTED) {
5394
+ return;
5395
+ }
5396
forwardDebugInfoFromAbortedTask(request, task);
5397
// We don't actually emit anything for this task id because we are intentionally
5398
// leaving the reference unfulfilled.
5577
request.destination = null;
5578
}
5579
5580
+function finishHalt(request: Request, abortedTasks: Set<Task>): void {
5581
+ try {
5582
+ abortedTasks.forEach(task => finishHaltedTask(task, request));
5583
+ const onAllReady = request.onAllReady;
5584
+ onAllReady();
5585
+ if (request.destination !== null) {
5586
+ flushCompletedChunks(request, request.destination);
5587
+ }
5588
+ } catch (error) {
5589
+ logRecoverableError(request, error, null);
5590
+ fatalError(request, error);
5591
+ }
5592
+}
5593
+
5594
+function finishAbort(
5595
+ request: Request,
5596
+ abortedTasks: Set<Task>,
5597
+ errorId: number,
5598
+): void {
5599
+ try {
5600
+ abortedTasks.forEach(task => finishAbortedTask(task, request, errorId));
5601
+ const onAllReady = request.onAllReady;
5602
+ onAllReady();
5603
+ if (request.destination !== null) {
5604
+ flushCompletedChunks(request, request.destination);
5605
+ }
5606
+ } catch (error) {
5607
+ logRecoverableError(request, error, null);
5608
+ fatalError(request, error);
5609
+ }
5610
+}
5611
+
5612
export function abort(request: Request, reason: mixed): void {
5613
+ // We define any status below OPEN as OPEN equivalent
5614
+ if (request.status > OPEN) {
5615
+ return;
5616
+ }
5617
try {
5523
- // We define any status below OPEN as OPEN equivalent
5524
- if (request.status <= OPEN) {
5525
- request.status = ABORTING;
5526
- request.cacheController.abort(reason);
5527
- callOnAllReadyIfReady(request);
5618
+ request.status = ABORTING;
5619
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
5620
+ request.abortTime = performance.now();
5621
}
5622
+ request.cacheController.abort(reason);
5623
const abortableTasks = request.abortableTasks;
5624
if (abortableTasks.size > 0) {
5625
if (enableHalt && request.type === PRERENDER) {
5626
// When prerendering with halt semantics we simply halt the task
5627
// and leave the reference unfulfilled.
5628
abortableTasks.forEach(task => haltTask(task, request));
5535
- abortableTasks.clear();
5536
- callOnAllReadyIfReady(request);
5629
+ scheduleWork(() => finishHalt(request, abortableTasks));
5630
} else if (
5631
enablePostpone &&
5632
typeof reason === 'object' &&
5642
request.pendingChunks++;
5643
emitPostponeChunk(request, errorId, postponeInstance);
5644
abortableTasks.forEach(task => abortTask(task, request, errorId));
5552
- abortableTasks.clear();
5553
- callOnAllReadyIfReady(request);
5645
+ scheduleWork(() => finishAbort(request, abortableTasks, errorId));
5646
} else {
5647
const error =
5648
reason === undefined
5664
request.pendingChunks++;
5665
emitErrorChunk(request, errorId, digest, error, false);
5666
abortableTasks.forEach(task => abortTask(task, request, errorId));
5575
- abortableTasks.clear();
5576
- callOnAllReadyIfReady(request);
5667
+ scheduleWork(() => finishAbort(request, abortableTasks, errorId));
5668
+ }
5669
+ } else {
5670
+ const onAllReady = request.onAllReady;
5671
+ onAllReady();
5672
+ if (request.destination !== null) {
5673
+ flushCompletedChunks(request, request.destination);
5674
}
5578
- }
5579
- if (request.destination !== null) {
5580
- flushCompletedChunks(request, request.destination);
5675
}
5676
} catch (error) {
5677
logRecoverableError(request, error, null);