diff options
author | 2023-03-29 05:37:15 -0700 | |
---|---|---|
committer | 2023-03-29 09:37:15 -0300 | |
commit | 767fdfbb97704405cb6442fab243c0896d82819f (patch) | |
tree | 059732206fdcf41a3c57f51ead631f9742c19758 | |
parent | f0def8c77072869f3ce6bc081651ff4d8c97f66a (diff) | |
download | bun-767fdfbb97704405cb6442fab243c0896d82819f.tar.gz bun-767fdfbb97704405cb6442fab243c0896d82819f.tar.zst bun-767fdfbb97704405cb6442fab243c0896d82819f.zip |
Fixes #2499 (#2501)
* Fixes #2499
* This needs to be quoted
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/api/schema.js | 632 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 14 | ||||
-rw-r--r-- | src/deps/libuwsockets.cpp | 20 | ||||
-rw-r--r-- | test/regression/issue/02499-repro.ts | 21 | ||||
-rw-r--r-- | test/regression/issue/02499.test.ts | 95 |
5 files changed, 462 insertions, 320 deletions
diff --git a/src/api/schema.js b/src/api/schema.js index fde187ce0..cc25603b3 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -1,90 +1,90 @@ const Loader = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - 9: 9, - 10: 10, - jsx: 1, - js: 2, - ts: 3, - tsx: 4, - css: 5, - file: 6, - json: 7, - toml: 8, - wasm: 9, - napi: 10, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "9": 9, + "10": 10, + "jsx": 1, + "js": 2, + "ts": 3, + "tsx": 4, + "css": 5, + "file": 6, + "json": 7, + "toml": 8, + "wasm": 9, + "napi": 10, }; const LoaderKeys = { - 1: "jsx", - 2: "js", - 3: "ts", - 4: "tsx", - 5: "css", - 6: "file", - 7: "json", - 8: "toml", - 9: "wasm", - 10: "napi", - jsx: "jsx", - js: "js", - ts: "ts", - tsx: "tsx", - css: "css", - file: "file", - json: "json", - toml: "toml", - wasm: "wasm", - napi: "napi", + "1": "jsx", + "2": "js", + "3": "ts", + "4": "tsx", + "5": "css", + "6": "file", + "7": "json", + "8": "toml", + "9": "wasm", + "10": "napi", + "jsx": "jsx", + "js": "js", + "ts": "ts", + "tsx": "tsx", + "css": "css", + "file": "file", + "json": "json", + "toml": "toml", + "wasm": "wasm", + "napi": "napi", }; const FrameworkEntryPointType = { - 1: 1, - 2: 2, - 3: 3, - client: 1, - server: 2, - fallback: 3, + "1": 1, + "2": 2, + "3": 3, + "client": 1, + "server": 2, + "fallback": 3, }; const FrameworkEntryPointTypeKeys = { - 1: "client", - 2: "server", - 3: "fallback", - client: "client", - server: "server", - fallback: "fallback", + "1": "client", + "2": "server", + "3": "fallback", + "client": "client", + "server": "server", + "fallback": "fallback", }; const StackFrameScope = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - Eval: 1, - Module: 2, - Function: 3, - Global: 4, - Wasm: 5, - Constructor: 6, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "Eval": 1, + "Module": 2, + "Function": 3, + "Global": 4, + "Wasm": 5, + "Constructor": 6, }; const StackFrameScopeKeys = { - 1: "Eval", - 2: "Module", - 3: "Function", - 4: "Global", - 5: "Wasm", - 6: "Constructor", - Eval: "Eval", - Module: "Module", - Function: "Function", - Global: "Global", - Wasm: "Wasm", - Constructor: "Constructor", + "1": "Eval", + "2": "Module", + "3": "Function", + "4": "Global", + "5": "Wasm", + "6": "Constructor", + "Eval": "Eval", + "Module": "Module", + "Function": "Function", + "Global": "Global", + "Wasm": "Wasm", + "Constructor": "Constructor", }; function decodeStackFrame(bb) { @@ -332,40 +332,40 @@ function encodeJSException(message, bb) { bb.writeByte(0); } const FallbackStep = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - ssr_disabled: 1, - create_vm: 2, - configure_router: 3, - configure_defines: 4, - resolve_entry_point: 5, - load_entry_point: 6, - eval_entry_point: 7, - fetch_event_handler: 8, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "ssr_disabled": 1, + "create_vm": 2, + "configure_router": 3, + "configure_defines": 4, + "resolve_entry_point": 5, + "load_entry_point": 6, + "eval_entry_point": 7, + "fetch_event_handler": 8, }; const FallbackStepKeys = { - 1: "ssr_disabled", - 2: "create_vm", - 3: "configure_router", - 4: "configure_defines", - 5: "resolve_entry_point", - 6: "load_entry_point", - 7: "eval_entry_point", - 8: "fetch_event_handler", - ssr_disabled: "ssr_disabled", - create_vm: "create_vm", - configure_router: "configure_router", - configure_defines: "configure_defines", - resolve_entry_point: "resolve_entry_point", - load_entry_point: "load_entry_point", - eval_entry_point: "eval_entry_point", - fetch_event_handler: "fetch_event_handler", + "1": "ssr_disabled", + "2": "create_vm", + "3": "configure_router", + "4": "configure_defines", + "5": "resolve_entry_point", + "6": "load_entry_point", + "7": "eval_entry_point", + "8": "fetch_event_handler", + "ssr_disabled": "ssr_disabled", + "create_vm": "create_vm", + "configure_router": "configure_router", + "configure_defines": "configure_defines", + "resolve_entry_point": "resolve_entry_point", + "load_entry_point": "load_entry_point", + "eval_entry_point": "eval_entry_point", + "fetch_event_handler": "fetch_event_handler", }; function decodeProblems(bb) { @@ -517,76 +517,76 @@ function encodeFallbackMessageContainer(message, bb) { bb.writeByte(0); } const ResolveMode = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - disable: 1, - lazy: 2, - dev: 3, - bundle: 4, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "disable": 1, + "lazy": 2, + "dev": 3, + "bundle": 4, }; const ResolveModeKeys = { - 1: "disable", - 2: "lazy", - 3: "dev", - 4: "bundle", - disable: "disable", - lazy: "lazy", - dev: "dev", - bundle: "bundle", + "1": "disable", + "2": "lazy", + "3": "dev", + "4": "bundle", + "disable": "disable", + "lazy": "lazy", + "dev": "dev", + "bundle": "bundle", }; const Platform = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - browser: 1, - node: 2, - bun: 3, - bun_macro: 4, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "browser": 1, + "node": 2, + "bun": 3, + "bun_macro": 4, }; const PlatformKeys = { - 1: "browser", - 2: "node", - 3: "bun", - 4: "bun_macro", - browser: "browser", - node: "node", - bun: "bun", - bun_macro: "bun_macro", + "1": "browser", + "2": "node", + "3": "bun", + "4": "bun_macro", + "browser": "browser", + "node": "node", + "bun": "bun", + "bun_macro": "bun_macro", }; const CSSInJSBehavior = { - 1: 1, - 2: 2, - 3: 3, - facade: 1, - facade_onimportcss: 2, - auto_onimportcss: 3, + "1": 1, + "2": 2, + "3": 3, + "facade": 1, + "facade_onimportcss": 2, + "auto_onimportcss": 3, }; const CSSInJSBehaviorKeys = { - 1: "facade", - 2: "facade_onimportcss", - 3: "auto_onimportcss", - facade: "facade", - facade_onimportcss: "facade_onimportcss", - auto_onimportcss: "auto_onimportcss", + "1": "facade", + "2": "facade_onimportcss", + "3": "auto_onimportcss", + "facade": "facade", + "facade_onimportcss": "facade_onimportcss", + "auto_onimportcss": "auto_onimportcss", }; const JSXRuntime = { - 1: 1, - 2: 2, - 3: 3, - automatic: 1, - classic: 2, - solid: 3, + "1": 1, + "2": 2, + "3": 3, + "automatic": 1, + "classic": 2, + "solid": 3, }; const JSXRuntimeKeys = { - 1: "automatic", - 2: "classic", - 3: "solid", - automatic: "automatic", - classic: "classic", - solid: "solid", + "1": "automatic", + "2": "classic", + "3": "solid", + "automatic": "automatic", + "classic": "classic", + "solid": "solid", }; function decodeJSX(bb) { @@ -914,28 +914,28 @@ function encodeJavascriptBundleContainer(message, bb) { bb.writeByte(0); } const ScanDependencyMode = { - 1: 1, - 2: 2, - app: 1, - all: 2, + "1": 1, + "2": 2, + "app": 1, + "all": 2, }; const ScanDependencyModeKeys = { - 1: "app", - 2: "all", - app: "app", - all: "all", + "1": "app", + "2": "all", + "app": "app", + "all": "all", }; const ModuleImportType = { - 1: 1, - 2: 2, - import: 1, - require: 2, + "1": 1, + "2": 2, + "import": 1, + "require": 2, }; const ModuleImportTypeKeys = { - 1: "import", - 2: "require", - import: "import", - require: "require", + "1": "import", + "2": "require", + "import": "import", + "require": "require", }; function decodeModuleImportRecord(bb) { @@ -1086,20 +1086,20 @@ function encodeLoaderMap(message, bb) { } } const DotEnvBehavior = { - 1: 1, - 2: 2, - 3: 3, - disable: 1, - prefix: 2, - load_all: 3, + "1": 1, + "2": 2, + "3": 3, + "disable": 1, + "prefix": 2, + "load_all": 3, }; const DotEnvBehaviorKeys = { - 1: "disable", - 2: "prefix", - 3: "load_all", - disable: "disable", - prefix: "prefix", - load_all: "load_all", + "1": "disable", + "2": "prefix", + "3": "load_all", + "disable": "disable", + "prefix": "prefix", + "load_all": "load_all", }; function decodeEnvConfig(bb) { @@ -1905,16 +1905,16 @@ function encodeTransformOptions(message, bb) { bb.writeByte(0); } const SourceMapMode = { - 1: 1, - 2: 2, - inline_into_file: 1, - external: 2, + "1": 1, + "2": 2, + "inline_into_file": 1, + "external": 2, }; const SourceMapModeKeys = { - 1: "inline_into_file", - 2: "external", - inline_into_file: "inline_into_file", - external: "external", + "1": "inline_into_file", + "2": "external", + "inline_into_file": "inline_into_file", + "external": "external", }; function decodeFileHandle(bb) { @@ -2133,52 +2133,52 @@ function encodeScannedImport(message, bb) { } } const ImportKind = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - entry_point: 1, - stmt: 2, - require: 3, - dynamic: 4, - require_resolve: 5, - at: 6, - url: 7, - internal: 8, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "entry_point": 1, + "stmt": 2, + "require": 3, + "dynamic": 4, + "require_resolve": 5, + "at": 6, + "url": 7, + "internal": 8, }; const ImportKindKeys = { - 1: "entry_point", - 2: "stmt", - 3: "require", - 4: "dynamic", - 5: "require_resolve", - 6: "at", - 7: "url", - 8: "internal", - entry_point: "entry_point", - stmt: "stmt", - require: "require", - dynamic: "dynamic", - require_resolve: "require_resolve", - at: "at", - url: "url", - internal: "internal", + "1": "entry_point", + "2": "stmt", + "3": "require", + "4": "dynamic", + "5": "require_resolve", + "6": "at", + "7": "url", + "8": "internal", + "entry_point": "entry_point", + "stmt": "stmt", + "require": "require", + "dynamic": "dynamic", + "require_resolve": "require_resolve", + "at": "at", + "url": "url", + "internal": "internal", }; const TransformResponseStatus = { - 1: 1, - 2: 2, - success: 1, - fail: 2, + "1": 1, + "2": 2, + "success": 1, + "fail": 2, }; const TransformResponseStatusKeys = { - 1: "success", - 2: "fail", - success: "success", - fail: "fail", + "1": "success", + "2": "fail", + "success": "success", + "fail": "fail", }; function decodeOutputFile(bb) { @@ -2256,28 +2256,28 @@ function encodeTransformResponse(message, bb) { } } const MessageLevel = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - err: 1, - warn: 2, - note: 3, - info: 4, - debug: 5, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "err": 1, + "warn": 2, + "note": 3, + "info": 4, + "debug": 5, }; const MessageLevelKeys = { - 1: "err", - 2: "warn", - 3: "note", - 4: "info", - 5: "debug", - err: "err", - warn: "warn", - note: "note", - info: "info", - debug: "debug", + "1": "err", + "2": "warn", + "3": "note", + "4": "info", + "5": "debug", + "err": "err", + "warn": "warn", + "note": "note", + "info": "info", + "debug": "debug", }; function decodeLocation(bb) { @@ -2508,72 +2508,72 @@ function encodeLog(message, bb) { } } const Reloader = { - 1: 1, - 2: 2, - 3: 3, - disable: 1, - live: 2, - fast_refresh: 3, + "1": 1, + "2": 2, + "3": 3, + "disable": 1, + "live": 2, + "fast_refresh": 3, }; const ReloaderKeys = { - 1: "disable", - 2: "live", - 3: "fast_refresh", - disable: "disable", - live: "live", - fast_refresh: "fast_refresh", + "1": "disable", + "2": "live", + "3": "fast_refresh", + "disable": "disable", + "live": "live", + "fast_refresh": "fast_refresh", }; const WebsocketMessageKind = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - welcome: 1, - file_change_notification: 2, - build_success: 3, - build_fail: 4, - manifest_success: 5, - manifest_fail: 6, - resolve_file: 7, - file_change_notification_with_hint: 8, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "welcome": 1, + "file_change_notification": 2, + "build_success": 3, + "build_fail": 4, + "manifest_success": 5, + "manifest_fail": 6, + "resolve_file": 7, + "file_change_notification_with_hint": 8, }; const WebsocketMessageKindKeys = { - 1: "welcome", - 2: "file_change_notification", - 3: "build_success", - 4: "build_fail", - 5: "manifest_success", - 6: "manifest_fail", - 7: "resolve_file", - 8: "file_change_notification_with_hint", - welcome: "welcome", - file_change_notification: "file_change_notification", - build_success: "build_success", - build_fail: "build_fail", - manifest_success: "manifest_success", - manifest_fail: "manifest_fail", - resolve_file: "resolve_file", - file_change_notification_with_hint: "file_change_notification_with_hint", + "1": "welcome", + "2": "file_change_notification", + "3": "build_success", + "4": "build_fail", + "5": "manifest_success", + "6": "manifest_fail", + "7": "resolve_file", + "8": "file_change_notification_with_hint", + "welcome": "welcome", + "file_change_notification": "file_change_notification", + "build_success": "build_success", + "build_fail": "build_fail", + "manifest_success": "manifest_success", + "manifest_fail": "manifest_fail", + "resolve_file": "resolve_file", + "file_change_notification_with_hint": "file_change_notification_with_hint", }; const WebsocketCommandKind = { - 1: 1, - 2: 2, - 3: 3, - build: 1, - manifest: 2, - build_with_file_path: 3, + "1": 1, + "2": 2, + "3": 3, + "build": 1, + "manifest": 2, + "build_with_file_path": 3, }; const WebsocketCommandKindKeys = { - 1: "build", - 2: "manifest", - 3: "build_with_file_path", - build: "build", - manifest: "manifest", - build_with_file_path: "build_with_file_path", + "1": "build", + "2": "manifest", + "3": "build_with_file_path", + "build": "build", + "manifest": "manifest", + "build_with_file_path": "build_with_file_path", }; function decodeWebsocketMessage(bb) { diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 3cfce400e..ed26ea3a7 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -692,6 +692,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pending_promises_for_abort: u8 = 0, has_marked_complete: bool = false, + has_marked_pending: bool = false, + response_jsvalue: JSC.JSValue = JSC.JSValue.zero, response_protected: bool = false, response_ptr: ?*JSC.WebCore.Response = null, @@ -835,7 +837,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } - if (!ctx.resp.hasResponded()) { + if (!ctx.resp.hasResponded() and !ctx.has_marked_pending) { ctx.renderMissing(); return; } @@ -930,6 +932,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } + this.has_marked_pending = true; this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity }; this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); this.setAbortHandler(); @@ -948,11 +951,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.response_buf_owned.items.len, this.shouldCloseConnection(), )) { + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); this.setAbortHandler(); return; } - this.finalize(); } @@ -1320,6 +1323,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (!this.sendfile.has_set_on_writable) { this.sendfile.has_set_on_writable = true; + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableSendfile, this); } @@ -1352,6 +1356,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.finalize(); return true; } else { + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableBytes, this); return true; } @@ -1365,6 +1370,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.response_buf_owned.items.len = 0; this.finalize(); } else { + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); } @@ -1885,7 +1891,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } // The user returned something that wasn't a promise or a promise with a response - if (!ctx.resp.hasResponded()) ctx.renderMissing(); + if (!ctx.resp.hasResponded() and !ctx.has_marked_pending) ctx.renderMissing(); } pub fn handleResolveStream(req: *RequestContext) void { @@ -2139,6 +2145,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } else { // when it's the last one, we just want to know if it's done if (stream.isDone()) { + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this); } } @@ -2394,6 +2401,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp bytes.len, this.shouldCloseConnection(), )) { + this.has_marked_pending = true; this.resp.onWritable(*RequestContext, onWritableBytes, this); // given a blob, we might not have set an abort handler yet this.setAbortHandler(); diff --git a/src/deps/libuwsockets.cpp b/src/deps/libuwsockets.cpp index bacab2cbf..cbf399f20 100644 --- a/src/deps/libuwsockets.cpp +++ b/src/deps/libuwsockets.cpp @@ -1025,11 +1025,15 @@ extern "C" if (ssl) { uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res; + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); uwsRes->end(std::string_view(data, length), close_connection); } else { uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res; + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); uwsRes->end(std::string_view(data, length), close_connection); } } @@ -1039,11 +1043,15 @@ extern "C" if (ssl) { uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res; + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); uwsRes->endWithoutBody(std::nullopt, close_connection); } else { uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res; + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); uwsRes->endWithoutBody(std::nullopt, close_connection); } } @@ -1514,12 +1522,22 @@ extern "C" { uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res; auto pair = uwsRes->tryEnd(std::string_view(bytes, len), total_len, close); + if (pair.first) { + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); + } + return pair.first; } else { uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res; auto pair = uwsRes->tryEnd(std::string_view(bytes, len), total_len, close); + if (pair.first) { + uwsRes->getHttpResponseData()->onWritable = nullptr; + uwsRes->onAborted(nullptr); + } + return pair.first; } } @@ -1551,4 +1569,4 @@ extern "C" return uwsRes->getNativeHandle(); } } -}
\ No newline at end of file +} diff --git a/test/regression/issue/02499-repro.ts b/test/regression/issue/02499-repro.ts new file mode 100644 index 000000000..3c50e53e5 --- /dev/null +++ b/test/regression/issue/02499-repro.ts @@ -0,0 +1,21 @@ +const server = Bun.serve({ + port: 0, + async fetch(req) { + console.log(await req.json()); + return new Response(); + }, +}); +console.log( + JSON.stringify({ + hostname: server.hostname, + port: server.port, + }), +); + +(async function () { + for await (let line of console) { + if (line === "--CLOSE--") { + process.exit(0); + } + } +})(); diff --git a/test/regression/issue/02499.test.ts b/test/regression/issue/02499.test.ts new file mode 100644 index 000000000..da114d95d --- /dev/null +++ b/test/regression/issue/02499.test.ts @@ -0,0 +1,95 @@ +import { expect, it } from "bun:test"; +import { bunExe, bunEnv } from "../../harness.js"; +import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs"; +import { tmpdir } from "os"; +import { dirname, join } from "path"; +import { sleep, spawn, spawnSync, which } from "bun"; + +// https://github.com/oven-sh/bun/issues/2499 +it("onAborted() and onWritable are not called after receiving an empty response body due to a promise rejection", async testDone => { + var timeout = AbortSignal.timeout(10_000); + timeout.onabort = e => { + testDone(new Error("Test timed out, which means it failed")); + }; + + const body = new FormData(); + body.append("hey", "hi"); + + // We want to test that the server isn't keeping the connection open in a + // zombie-like state when an error occurs due to an unhandled rejected promise + // + // At the time of writing, this can only happen when: + // - development mode is enabled + // - the server didn't send the complete response body in one send() + // - renderMissing() is called + // + // In that case, it finalizes the response in the middle of an incomplete body + // + // On an M1, this reproduces 1 out of every 4 calls to this function + // It's inherently going to be flaky without simulating system calls or overriding libc + // + // So to make sure we catch it + // 1) Run this test 40 times + // 2) Set a timeout for this test of 10 seconds. + // + // In debug builds, this test should complete in 1-2 seconds. + for (let i = 0; i < 40; i++) { + let bunProcess; + try { + bunProcess = spawn({ + cmd: [bunExe(), "run", join(import.meta.dir, "./02499-repro.ts")], + stdin: "pipe", + stderr: "ignore", + stdout: "pipe", + env: bunEnv, + }); + + const reader = bunProcess.stdout?.getReader(); + let hostname, port; + { + const chunks = []; + var decoder = new TextDecoder(); + while (!hostname && !port) { + // @ts-expect-error TODO + var { value, done } = await reader?.read(); + if (done) break; + if (chunks.length > 0) { + chunks.push(value); + } + try { + if (chunks.length > 0) { + value = Buffer.concat(chunks); + } + + ({ hostname, port } = JSON.parse(decoder.decode(value).trim())); + } catch { + chunks.push(value); + } + } + } + + try { + await fetch(`http://${hostname}:${port}/upload`, { + body, + keepalive: false, + method: "POST", + timeout: true, + signal: timeout, + }); + } catch (e) {} + + bunProcess.stdin?.write("--CLOSE--"); + await bunProcess.stdin?.flush(); + await bunProcess.stdin?.end(); + expect(await bunProcess.exited).toBe(0); + } catch (e) { + timeout.onabort = () => {}; + testDone(e); + throw e; + } finally { + bunProcess?.kill(9); + } + } + timeout.onabort = () => {}; + testDone(); +}); |