23
scheduleMicrotask,
24
flushBuffered,
25
beginWriting,
26
+ writeChunk,
27
writeChunkAndReturn,
28
stringToChunk,
29
typedArrayToBinaryChunk,
561
const RENDER = 20;
562
const PRERENDER = 21;
563
564
+// Marker pushed before a [headerChunk, contentChunk] pair in
565
+// completedRegularChunks / completedDebugChunks to signal that the next two
566
+// entries must be written atomically — see emitTextChunk and
567
+// emitTypedArrayChunk for why, and flushCompletedChunks for how it's read.
568
+const NEXT_TWO_CHUNKS_ARE_ATOMIC: symbol = Symbol();
569
+
570
export type Request = {
571
status: 10 | 11 | 12 | 13 | 14,
572
type: 20 | 21,
583
pingedTasks: Array<Task>,
584
completedImportChunks: Array<Chunk>,
585
completedHintChunks: Array<Chunk>,
579
- completedRegularChunks: Array<Chunk | BinaryChunk>,
586
+ // Text and TypedArray rows are pushed as a NEXT_TWO_CHUNKS_ARE_ATOMIC
587
+ // sentinel followed by their [headerChunk, contentChunk] pair, so that
588
+ // flushCompletedChunks can write the pair atomically and never strand the
589
+ // content chunk on a backpressure break.
590
+ completedRegularChunks: Array<
591
+ Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
592
+ >,
593
completedErrorChunks: Array<Chunk>,
594
writtenSymbols: Map<symbol, number>,
595
writtenClientReferences: Map<ClientReferenceKey, number>,
607
abortTime: number,
608
// DEV-only
609
pendingDebugChunks: number,
597
- completedDebugChunks: Array<Chunk | BinaryChunk>,
610
+ // See completedRegularChunks for why some entries are preceded by the
611
+ // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel.
612
+ completedDebugChunks: Array<
613
+ Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
614
+ >,
615
debugDestination: null | Destination,
616
environmentName: () => string,
617
filterStackFrame: (
712
this.pingedTasks = pingedTasks;
713
this.completedImportChunks = ([]: Array<Chunk>);
714
this.completedHintChunks = ([]: Array<Chunk>);
698
- this.completedRegularChunks = ([]: Array<Chunk | BinaryChunk>);
715
+ this.completedRegularChunks = ([]: Array<
716
+ Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
717
+ >);
718
this.completedErrorChunks = ([]: Array<Chunk>);
719
this.writtenSymbols = new Map();
720
this.writtenClientReferences = new Map();
730
731
if (__DEV__) {
732
this.pendingDebugChunks = 0;
714
- this.completedDebugChunks = ([]: Array<Chunk>);
733
+ this.completedDebugChunks = ([]: Array<
734
+ Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
735
+ >);
736
this.debugDestination = null;
737
this.environmentName =
738
environmentName === undefined
4712
const binaryLength = byteLengthOfBinaryChunk(binaryChunk);
4713
const row = id.toString(16) + ':' + tag + binaryLength.toString(16) + ',';
4714
const headerChunk = stringToChunk(row);
4715
+ // Push a NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel before the header so that
4716
+ // flushCompletedChunks can write the header and binary chunks atomically.
4717
+ // Otherwise, if the destination's backpressure flips between the two writes,
4718
+ // the content chunk would be stranded at the front of the queue and the next
4719
+ // drain would emit Import or Hint chunks between the header and the content —
4720
+ // and the Flight Client would frame those intervening bytes as this row's
4721
+ // content.
4722
if (__DEV__ && debug) {
4695
- request.completedDebugChunks.push(headerChunk, binaryChunk);
4723
+ request.completedDebugChunks.push(
4724
+ NEXT_TWO_CHUNKS_ARE_ATOMIC,
4725
+ headerChunk,
4726
+ binaryChunk,
4727
+ );
4728
} else {
4697
- request.completedRegularChunks.push(headerChunk, binaryChunk);
4729
+ request.completedRegularChunks.push(
4730
+ NEXT_TWO_CHUNKS_ARE_ATOMIC,
4731
+ headerChunk,
4732
+ binaryChunk,
4733
+ );
4734
}
4735
}
4736
4755
const binaryLength = byteLengthOfChunk(textChunk);
4756
const row = id.toString(16) + ':T' + binaryLength.toString(16) + ',';
4757
const headerChunk = stringToChunk(row);
4758
+ // See emitTypedArrayChunk for why the pair is preceded by a sentinel.
4759
if (__DEV__ && debug) {
4723
- request.completedDebugChunks.push(headerChunk, textChunk);
4760
+ request.completedDebugChunks.push(
4761
+ NEXT_TWO_CHUNKS_ARE_ATOMIC,
4762
+ headerChunk,
4763
+ textChunk,
4764
+ );
4765
} else {
4725
- request.completedRegularChunks.push(headerChunk, textChunk);
4766
+ request.completedRegularChunks.push(
4767
+ NEXT_TWO_CHUNKS_ARE_ATOMIC,
4768
+ headerChunk,
4769
+ textChunk,
4770
+ );
4771
}
4772
}
4773
6059
const debugChunks = request.completedDebugChunks;
6060
let i = 0;
6061
for (; i < debugChunks.length; i++) {
6017
- request.pendingDebugChunks--;
6018
- const chunk = debugChunks[i];
6019
- writeChunkAndReturn(debugDestination, chunk);
6062
+ const item = debugChunks[i];
6063
+ if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) {
6064
+ if (i + 2 >= debugChunks.length) {
6065
+ throw new Error(
6066
+ 'A chunk pair is incomplete. This is a bug in React.',
6067
+ );
6068
+ }
6069
+ request.pendingDebugChunks -= 2;
6070
+ writeChunk(
6071
+ debugDestination,
6072
+ ((debugChunks[i + 1]: any): Chunk | BinaryChunk),
6073
+ );
6074
+ writeChunk(
6075
+ debugDestination,
6076
+ ((debugChunks[i + 2]: any): Chunk | BinaryChunk),
6077
+ );
6078
+ i += 2;
6079
+ } else {
6080
+ request.pendingDebugChunks--;
6081
+ writeChunk(debugDestination, ((item: any): Chunk | BinaryChunk));
6082
+ }
6083
}
6084
debugChunks.splice(0, i);
6085
} finally {
6127
const debugChunks = request.completedDebugChunks;
6128
i = 0;
6129
for (; i < debugChunks.length; i++) {
6067
- request.pendingDebugChunks--;
6068
- const chunk = debugChunks[i];
6069
- const keepWriting: boolean = writeChunkAndReturn(destination, chunk);
6130
+ const item = debugChunks[i];
6131
+ let keepWriting: boolean;
6132
+ if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) {
6133
+ if (i + 2 >= debugChunks.length) {
6134
+ throw new Error(
6135
+ 'A chunk pair is incomplete. This is a bug in React.',
6136
+ );
6137
+ }
6138
+ request.pendingDebugChunks -= 2;
6139
+ writeChunk(
6140
+ destination,
6141
+ ((debugChunks[i + 1]: any): Chunk | BinaryChunk),
6142
+ );
6143
+ keepWriting = writeChunkAndReturn(
6144
+ destination,
6145
+ ((debugChunks[i + 2]: any): Chunk | BinaryChunk),
6146
+ );
6147
+ i += 2;
6148
+ } else {
6149
+ request.pendingDebugChunks--;
6150
+ keepWriting = writeChunkAndReturn(
6151
+ destination,
6152
+ ((item: any): Chunk | BinaryChunk),
6153
+ );
6154
+ }
6155
if (!keepWriting) {
6156
request.destination = null;
6157
i++;
6165
const regularChunks = request.completedRegularChunks;
6166
i = 0;
6167
for (; i < regularChunks.length; i++) {
6083
- request.pendingChunks--;
6084
- const chunk = regularChunks[i];
6085
- const keepWriting: boolean = writeChunkAndReturn(destination, chunk);
6168
+ const item = regularChunks[i];
6169
+ let keepWriting: boolean;
6170
+ if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) {
6171
+ if (i + 2 >= regularChunks.length) {
6172
+ throw new Error(
6173
+ 'A chunk pair is incomplete. This is a bug in React.',
6174
+ );
6175
+ }
6176
+ request.pendingChunks -= 2;
6177
+ writeChunk(
6178
+ destination,
6179
+ ((regularChunks[i + 1]: any): Chunk | BinaryChunk),
6180
+ );
6181
+ keepWriting = writeChunkAndReturn(
6182
+ destination,
6183
+ ((regularChunks[i + 2]: any): Chunk | BinaryChunk),
6184
+ );
6185
+ i += 2;
6186
+ } else {
6187
+ request.pendingChunks--;
6188
+ keepWriting = writeChunkAndReturn(
6189
+ destination,
6190
+ ((item: any): Chunk | BinaryChunk),
6191
+ );
6192
+ }
6193
if (!keepWriting) {
6194
request.destination = null;
6195
i++;