UNPKG

next

Version:

The React Framework

83 lines (82 loc) • 3.66 kB
import { NEXT_REQUEST_ID_HEADER } from '../components/app-router-headers'; import { InvariantError } from '../../shared/lib/invariant-error'; const pairs = new Map(); /** * Upper bound on the number of in-memory debug-channel pairs we retain, evicted * least-recently-used, bounding the live per-request map. * * A pair must outlive its stream's close so a late decode of the same response * (the primary decode plus stage extractions via `decodeStageUntilBoundary`, * which can run after the channel closed over the WebSocket) still finds the * buffered data. The cap only needs to exceed the pairs live or recently closed * at once (bounded by how many prefetch/navigation requests are in flight * together), so a few dozen leaves ample headroom even for the segment-heavy * bursts the Instant Navs DevTools capture can produce. */ const MAX_DEBUG_CHANNEL_PAIRS = 64; /** * Reclaim the least-recently-used debug-channel pairs once the map exceeds * `MAX_DEBUG_CHANNEL_PAIRS`. The map is iterated in insertion order and we * re-insert entries on access (see * `getOrCreateDebugChannelReadableWriterPair`), so the least-recently-used * pairs sit at the front. Evicting only ever affects future lookups for that * request id; consumers that already hold a tee branch keep reading * independently of the map. */ function evictExcessDebugChannelPairs() { while(pairs.size > MAX_DEBUG_CHANNEL_PAIRS){ const oldestRequestId = pairs.keys().next().value; if (oldestRequestId === undefined) { break; } pairs.delete(oldestRequestId); } } export function getOrCreateDebugChannelReadableWriterPair(requestId) { const existingPair = pairs.get(requestId); if (existingPair) { // Refresh the LRU recency of an already-known channel by re-inserting it at // the most-recent position, so a channel that's still being written to or // read from isn't evicted while a late consumer (e.g. a stage re-decode of // the same response) still needs it. pairs.delete(requestId); pairs.set(requestId, existingPair); return existingPair; } const { readable, writable } = new TransformStream(); const pair = { readable, writer: writable.getWriter() }; pairs.set(requestId, pair); // Retain the pair past its stream's close (see MAX_DEBUG_CHANNEL_PAIRS) and // bound the map by reclaiming the least-recently-used. evictExcessDebugChannelPairs(); // An errored stream rejects `writer.closed`. Observe the rejection so that it // does not surface as an unhandled rejection. pair.writer.closed.catch((error)=>{ console.debug('Debug channel writer closed with error', error); }); return pair; } export function createDebugChannel(requestHeaders) { let requestId; if (requestHeaders) { requestId = requestHeaders[NEXT_REQUEST_ID_HEADER] ?? undefined; if (!requestId) { throw new InvariantError(`Expected a ${JSON.stringify(NEXT_REQUEST_ID_HEADER)} request header.`); } } else { requestId = self.__next_r; if (!requestId) { throw new InvariantError(`Expected a request ID to be defined for the document via self.__next_r.`); } } const pair = getOrCreateDebugChannelReadableWriterPair(requestId); // Hand out a fresh tee branch per consumer and keep the remainder for the // next one (see the `readable` field doc above). const [branch, rest] = pair.readable.tee(); pair.readable = rest; return { readable: branch }; } //# sourceMappingURL=debug-channel.js.map