|
2 | 2 | /** @import { AsyncLocalStorage } from 'node:async_hooks' */ |
3 | 3 | /** @import { RenderContext } from '#server' */ |
4 | 4 |
|
5 | | -import { deferred } from '../shared/utils.js'; |
| 5 | +import { deferred, noop } from '../shared/utils.js'; |
6 | 6 | import * as e from './errors.js'; |
7 | 7 |
|
8 | 8 | /** @type {Promise<void> | null} */ |
@@ -56,14 +56,26 @@ export async function with_render_context(fn) { |
56 | 56 |
|
57 | 57 | /** @type {AsyncLocalStorage<RenderContext | null> | null} */ |
58 | 58 | let als = null; |
| 59 | +/** @type {Promise<void> | null} */ |
| 60 | +let als_import = null; |
59 | 61 |
|
60 | | -export async function init_render_context() { |
61 | | - if (als !== null) return; |
62 | | - try { |
63 | | - // @ts-ignore -- we don't include node types in the production build |
64 | | - const { AsyncLocalStorage } = await import('node:async_hooks'); |
65 | | - als = new AsyncLocalStorage(); |
66 | | - } catch {} |
| 62 | +/** |
| 63 | + * |
| 64 | + * @returns {Promise<void>} |
| 65 | + */ |
| 66 | +export function init_render_context() { |
| 67 | + // It's important the right side of this assignment can run a maximum of one time |
| 68 | + // otherwise it's possible for a very, very well-timed race condition to assign to `als` |
| 69 | + // at the beginning of a render, and then another render to assign to it again, which causes |
| 70 | + // the first render's second half to use a new instance of `als` which doesn't have its |
| 71 | + // context anymore. |
| 72 | + // @ts-ignore -- we don't include node types in the production build |
| 73 | + als_import ??= import('node:async_hooks') |
| 74 | + .then((hooks) => { |
| 75 | + als = new hooks.AsyncLocalStorage(); |
| 76 | + }) |
| 77 | + .then(noop, noop); |
| 78 | + return als_import; |
67 | 79 | } |
68 | 80 |
|
69 | 81 | // this has to be a function because rollup won't treeshake it if it's a constant |
|
0 commit comments