wip fixes to sendfile() blocking

This commit is contained in:
Jarred Sumner
2022-04-12 00:43:23 -07:00
parent a0f9a6ed60
commit 9db701ab41
7 changed files with 185 additions and 84 deletions

View File

@@ -47,7 +47,6 @@ const FetchEvent = WebCore.FetchEvent;
const js = @import("../../../jsc.zig").C;
const JSC = @import("../../../jsc.zig");
const JSError = @import("../base.zig").JSError;
const d = @import("../base.zig").d;
const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer;
const getAllocator = @import("../base.zig").getAllocator;
const JSValue = @import("../../../jsc.zig").JSValue;
@@ -82,6 +81,7 @@ const Fallback = Runtime.Fallback;
const MimeType = HTTP.MimeType;
const Blob = JSC.WebCore.Blob;
const BoringSSL = @import("boringssl");
const Arena = @import("../../../mimalloc_arena.zig").Arena;
const SendfileContext = struct {
fd: i32,
socket_fd: i32 = 0,
@@ -431,13 +431,20 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return struct {
const RequestContext = @This();
const App = uws.NewApp(ssl_enabled);
pub threadlocal var pool: ?*RequestContext.RequestContextStackAllocator = null;
pub threadlocal var pool_allocator: std.mem.Allocator = undefined;
server: *ThisServer,
resp: *App.Response,
/// thread-local default heap allocator
/// this prevents an extra pthread_getspecific() call which shows up in profiling
allocator: std.mem.Allocator,
req: *uws.Request,
url: string,
method: HTTP.Method,
aborted: bool = false,
has_marked_complete: bool = false,
response_jsvalue: JSC.JSValue = JSC.JSValue.zero,
response_ptr: ?*JSC.WebCore.Response = null,
blob: JSC.WebCore.Blob = JSC.WebCore.Blob{},
@@ -454,13 +461,75 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
/// When the response body is a temporary value
response_buf_owned: std.ArrayListUnmanaged(u8) = .{},
pub const RequestContextStackAllocator = std.heap.StackFallbackAllocator(@sizeOf(RequestContext) * 2048 + 4096);
// Pre-allocate up to 2048 requests
// use a bitset to track which ones are used
pub const RequestContextStackAllocator = struct {
buf: [2048]RequestContext = undefined,
unused: Set = undefined,
fallback_allocator: std.mem.Allocator = undefined,
pub const Set = std.bit_set.ArrayBitSet(usize, 2048);
pub fn get(this: *@This()) std.mem.Allocator {
this.unused = Set.initFull();
return std.mem.Allocator.init(this, alloc, resize, free);
}
fn alloc(self: *@This(), a: usize, b: u29, c: u29, d: usize) ![]u8 {
if (self.unused.findFirstSet()) |i| {
self.unused.unset(i);
return std.mem.asBytes(&self.buf[i]);
}
return try self.fallback_allocator.rawAlloc(a, b, c, d);
}
fn resize(
_: *@This(),
_: []u8,
_: u29,
_: usize,
_: u29,
_: usize,
) ?usize {
unreachable;
}
fn sliceContainsSlice(container: []u8, slice: []u8) bool {
return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and
(@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len);
}
fn free(
self: *@This(),
buf: []u8,
buf_align: u29,
return_address: usize,
) void {
_ = buf_align;
_ = return_address;
const bytes = std.mem.asBytes(&self.buf);
if (sliceContainsSlice(bytes, buf)) {
const index = if (bytes[0..buf.len].ptr != buf.ptr)
(@ptrToInt(buf.ptr) - @ptrToInt(bytes)) / @sizeOf(RequestContext)
else
@as(usize, 0);
if (comptime Environment.allow_assert) {
std.debug.assert(@intToPtr(*RequestContext, @ptrToInt(buf.ptr)) == &self.buf[index]);
std.debug.assert(!self.unused.isSet(index));
}
self.unused.set(index);
} else {
self.fallback_allocator.rawFree(buf, buf_align, return_address);
}
}
};
// TODO: support builtin compression
const can_sendfile = !ssl_enabled;
pub threadlocal var pool: *RequestContextStackAllocator = undefined;
pub fn setAbortHandler(this: *RequestContext) void {
if (this.has_abort_handler) return;
this.has_abort_handler = true;
@@ -497,6 +566,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
ctx.renderMissing();
return;
};
ctx.response_jsvalue = value;
JSC.C.JSValueProtect(ctx.server.globalThis.ref(), value.asObjectRef());
ctx.render(response);
}
@@ -551,7 +623,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.resp.writeStatus("500 Internal Server Error");
this.resp.writeHeader("content-type", MimeType.html.value);
var allocator = bun.default_allocator;
const allocator = this.allocator;
var fallback_container = allocator.create(Api.FallbackMessageContainer) catch unreachable;
defer allocator.destroy(fallback_container);
@@ -596,6 +668,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub fn onWritableResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
if (this.aborted) {
this.finalize();
return false;
}
return this.sendWritableBytes(this.response_buf_owned.items, write_offset, resp);
@@ -603,10 +676,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void {
this.* = .{
.allocator = server.allocator,
.resp = resp,
.req = req,
// this memory is owned by the Request object
.url = strings.append(bun.default_allocator, server.base_url_string_for_joining, req.url()) catch
.url = strings.append(this.allocator, server.base_url_string_for_joining, req.url()) catch
@panic("Out of memory while joining the URL path?"),
.method = HTTP.Method.which(req.method()) orelse .GET,
.server = server,
@@ -616,13 +690,19 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub fn onAbort(this: *RequestContext, _: *App.Response) void {
this.aborted = true;
this.finalizeWithoutDeinit();
this.markComplete();
}
pub fn markComplete(this: *RequestContext) void {
if (!this.has_marked_complete) this.server.onRequestComplete();
this.has_marked_complete = true;
}
// This function may be called multiple times
// so it's important that we can safely do that
pub fn finalizeWithoutDeinit(this: *RequestContext) void {
this.blob.detach();
this.request_body_buf.clearAndFree(bun.default_allocator);
this.request_body_buf.clearAndFree(this.allocator);
if (!this.response_jsvalue.isEmpty()) {
this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue);
@@ -656,14 +736,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.response_headers = null;
}
this.response_buf_owned.clearAndFree(bun.default_allocator);
this.response_buf_owned.clearAndFree(this.allocator);
}
pub fn finalize(this: *RequestContext) void {
var server = this.server;
this.finalizeWithoutDeinit();
std.debug.assert(server.pending_requests > 0);
this.markComplete();
server.request_pool_allocator.destroy(this);
server.onRequestComplete();
}
fn writeHeaders(
@@ -767,8 +846,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
pub fn onWritableBytes(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
if (this.aborted)
if (this.aborted) {
this.finalize();
return false;
}
var bytes = this.blob.sharedView();
return this.sendWritableBytes(bytes, write_offset, resp);
@@ -794,12 +875,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
fn onPrepareSendfile(this: *RequestContext, fd: i32, size: Blob.SizeType, err: ?JSC.SystemError, globalThis: *JSGlobalObject) void {
if (this.aborted) {
this.finalize();
return;
}
if (err) |system_error| {
if (this.aborted) {
this.finalize();
return;
}
if (system_error.errno == @enumToInt(std.os.E.NOENT)) {
this.runErrorHandlerWithStatusCode(system_error.toErrorInstance(globalThis), 404);
} else {
@@ -819,6 +900,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
};
if (this.aborted) {
_ = JSC.Node.Syscall.close(fd);
this.finalize();
return;
}
@@ -849,11 +931,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
pub fn doSendfile(this: *RequestContext, blob: Blob) void {
if (this.has_sendfile_ctx) return;
if (this.aborted) {
this.finalize();
return;
}
if (this.has_sendfile_ctx) return;
this.has_sendfile_ctx = true;
this.setAbortHandler();
@@ -865,6 +949,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void {
if (this.aborted) {
this.finalize();
return;
}
if (result == .err) {
this.runErrorHandler(result.err.toErrorInstance(this.server.globalThis));
return;
@@ -886,21 +975,25 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
pub fn doRenderWithBody(this: *RequestContext, value: *JSC.WebCore.Body.Value) void {
if (this.aborted) {
this.finalize();
return;
}
switch (value.*) {
.Error => {
const err = value.Error;
_ = value.use();
if (this.aborted) {
this.finalize();
return;
}
this.runErrorHandler(err);
return;
},
.Blob => {
this.blob = value.use();
if (this.aborted) {
this.finalize();
return;
}
if (this.blob.needsToReadFile()) {
this.req.setYield(false);
this.setAbortHandler();
@@ -932,6 +1025,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub fn doRender(this: *RequestContext) void {
if (this.aborted) {
this.finalize();
return;
}
var response = this.response_ptr.?;
@@ -968,7 +1062,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
) void {
if (this.resp.hasResponded()) return;
var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(bun.default_allocator);
var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(this.allocator);
defer exception_list.deinit();
if (!this.server.config.onError.isEmpty() and !this.has_called_error_handler) {
this.has_called_error_handler = true;
@@ -1094,14 +1188,17 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
pub fn resolveRequestBody(this: *RequestContext) void {
if (this.aborted)
if (this.aborted) {
this.finalize();
return;
}
if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
var bytes = this.request_body_buf.toOwnedSlice(bun.default_allocator);
var bytes = this.request_body_buf.toOwnedSlice(this.allocator);
var old = req.body;
req.body = .{
.Blob = if (bytes.len > 0)
Blob.init(bytes, bun.default_allocator, this.server.globalThis)
Blob.init(bytes, this.allocator, this.server.globalThis)
else
Blob.initEmpty(this.server.globalThis),
};
@@ -1113,12 +1210,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub fn onBodyChunk(this: *RequestContext, _: *App.Response, chunk: []const u8, last: bool) void {
if (this.aborted) return;
this.request_body_buf.appendSlice(bun.default_allocator, chunk) catch @panic("Out of memory while allocating request body");
this.request_body_buf.appendSlice(this.allocator, chunk) catch @panic("Out of memory while allocating request body");
if (last) {
if (JSC.JSValue.fromRef(this.request_js_object).as(Request) != null) {
uws.Loop.get().?.nextTick(*RequestContext, this, resolveRequestBody);
} else {
this.request_body_buf.deinit(bun.default_allocator);
this.request_body_buf.deinit(this.allocator);
this.request_body_buf = .{};
}
}
@@ -1154,7 +1251,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return;
}
this.request_body_buf.ensureTotalCapacityPrecise(bun.default_allocator, len) catch @panic("Out of memory while allocating request body buffer");
this.request_body_buf.ensureTotalCapacityPrecise(this.allocator, len) catch @panic("Out of memory while allocating request body buffer");
}
this.setAbortHandler();
@@ -1189,6 +1286,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
request_pool_allocator: std.mem.Allocator = undefined,
has_js_deinited: bool = false,
listen_callback: JSC.AnyTask = undefined,
allocator: std.mem.Allocator,
pub const Class = JSC.NewClass(
ThisServer,
@@ -1261,8 +1359,6 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
}
pub fn stop(this: *ThisServer) void {
this.next_tick_pending = true;
if (this.listener) |listener| {
listener.close();
this.listener = null;
@@ -1284,7 +1380,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
}
this.app.destroy();
bun.default_allocator.destroy(this);
const allocator = this.allocator;
allocator.destroy(this);
}
pub fn init(config: ServerConfig, globalThis: *JSGlobalObject) *ThisServer {
@@ -1294,9 +1391,19 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
.config = config,
.base_url_string_for_joining = strings.trim(config.base_url.href, "/"),
.vm = JSC.VirtualMachine.vm,
.allocator = Arena.getThreadlocalDefault(),
};
RequestContext.pool = bun.default_allocator.create(RequestContext.RequestContextStackAllocator) catch @panic("Out of memory!");
server.request_pool_allocator = RequestContext.pool.get();
if (RequestContext.pool == null) {
RequestContext.pool = server.allocator.create(RequestContext.RequestContextStackAllocator) catch @panic("Out of memory!");
RequestContext.pool.?.* = .{
.fallback_allocator = server.allocator,
};
server.request_pool_allocator = RequestContext.pool.?.get();
RequestContext.pool_allocator = server.request_pool_allocator;
} else {
server.request_pool_allocator = RequestContext.pool_allocator;
}
return server;
}
@@ -1386,7 +1493,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
this.pending_requests += 1;
defer this.pending_requests -= 1;
req.setYield(false);
var stack_fallback = std.heap.stackFallback(8096, bun.default_allocator);
var stack_fallback = std.heap.stackFallback(8096, this.allocator);
var allocator = stack_fallback.get();
var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable;
@@ -1431,7 +1538,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
if (strings.indexOfChar(url, ':')) |colon| {
url = url[0..colon];
}
editor.open(ctx.path, url, line, column, bun.default_allocator) catch Output.prettyErrorln("Failed to open editor", .{});
editor.open(ctx.path, url, line, column, this.allocator) catch Output.prettyErrorln("Failed to open editor", .{});
} else {
resp.writeStatus("500 Missing Editor :(");
resp.end("Please set your editor in bunfig.toml", false);
@@ -1446,7 +1553,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory");
ctx.create(this, req, resp);
var request_object = bun.default_allocator.create(JSC.WebCore.Request) catch unreachable;
var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable;
request_object.* = .{
.url = JSC.ZigString.init(ctx.url),
.method = ctx.method,
@@ -1464,32 +1571,34 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)};
ctx.request_js_object = args[0];
JSC.C.JSValueProtect(this.globalThis.ref(), args[0]);
ctx.response_jsvalue = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args);
const response_value = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args);
if (ctx.aborted) {
ctx.finalize();
return;
}
if (ctx.response_jsvalue.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) {
if (response_value.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) {
ctx.renderMissing();
return;
}
if (ctx.response_jsvalue.isError() or ctx.response_jsvalue.isAggregateError(this.globalThis) or ctx.response_jsvalue.isException(this.globalThis.vm())) {
ctx.runErrorHandler(ctx.response_jsvalue);
if (response_value.isError() or response_value.isAggregateError(this.globalThis) or response_value.isException(this.globalThis.vm())) {
ctx.runErrorHandler(response_value);
return;
}
JSC.C.JSValueProtect(this.globalThis.ref(), ctx.response_jsvalue.asObjectRef());
if (ctx.response_jsvalue.as(JSC.WebCore.Response)) |response| {
if (response_value.as(JSC.WebCore.Response)) |response| {
JSC.C.JSValueProtect(this.globalThis.ref(), response_value.asObjectRef());
ctx.response_jsvalue = response_value;
ctx.render(response);
return;
}
var wait_for_promise = false;
if (ctx.response_jsvalue.asPromise()) |promise| {
if (response_value.asPromise()) |promise| {
// If we immediately have the value available, we can skip the extra event loop tick
switch (promise.status(vm.global.vm())) {
.Pending => {},
@@ -1505,7 +1614,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
wait_for_promise = true;
// I don't think this case should happen
// But I'm uncertain
} else if (ctx.response_jsvalue.asInternalPromise()) |promise| {
} else if (response_value.asInternalPromise()) |promise| {
switch (promise.status(vm.global.vm())) {
.Pending => {},
.Fulfilled => {
@@ -1522,7 +1631,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
if (wait_for_promise) {
ctx.setAbortHandler();
ctx.response_jsvalue.then(
response_value.then(
this.globalThis,
RequestContext,
ctx,

View File

@@ -508,9 +508,14 @@ JSC__JSPromise* JSC__JSPromise__create(JSC__JSGlobalObject* arg0)
void JSC__JSValue___then(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, void* ctx, void (*ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, JSC__JSValue arg2, size_t arg3), void (*ArgFn4)(JSC__JSGlobalObject* arg0, void* arg1, JSC__JSValue arg2, size_t arg3))
{
globalObject->vm().drainMicrotasks();
auto* cell = JSC::JSValue::decode(JSValue0).asCell();
JSC::Strong<JSC::Unknown> promiseValue = { globalObject->vm(), cell };
JSC::JSNativeStdFunction* resolverFunction = JSC::JSNativeStdFunction::create(
globalObject->vm(), globalObject, 1, String(), [ctx, ArgFn3](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue {
globalObject->vm(), globalObject, 1, String(), [&promiseValue, ctx, ArgFn3](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue {
auto argCount = static_cast<uint16_t>(callFrame->argumentCount());
WTF::Vector<JSC::EncodedJSValue, 16> arguments;
arguments.reserveInitialCapacity(argCount);
if (argCount) {
@@ -520,11 +525,12 @@ void JSC__JSValue___then(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObjec
}
ArgFn3(globalObject, ctx, reinterpret_cast<JSC__JSValue>(arguments.data()), argCount);
return JSC::JSValue::encode(JSC::jsUndefined());
});
JSC::JSNativeStdFunction* rejecterFunction = JSC::JSNativeStdFunction::create(
globalObject->vm(), globalObject, 1, String(),
[ctx, ArgFn4](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue {
[&promiseValue, ctx, ArgFn4](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue {
auto argCount = static_cast<uint16_t>(callFrame->argumentCount());
WTF::Vector<JSC::EncodedJSValue, 16> arguments;
arguments.reserveInitialCapacity(argCount);
@@ -535,11 +541,10 @@ void JSC__JSValue___then(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObjec
}
ArgFn4(globalObject, ctx, reinterpret_cast<JSC__JSValue>(arguments.data()), argCount);
return JSC::JSValue::encode(JSC::jsUndefined());
});
globalObject->vm().drainMicrotasks();
auto* cell = JSC::JSValue::decode(JSValue0).asCell();
if (JSC::JSPromise* promise = JSC::jsDynamicCast<JSC::JSPromise*>(globalObject->vm(), cell)) {
promise->performPromiseThen(globalObject, resolverFunction, rejecterFunction, JSC::jsUndefined());
} else if (JSC::JSInternalPromise* promise = JSC::jsDynamicCast<JSC::JSInternalPromise*>(globalObject->vm(), cell)) {

View File

@@ -635,12 +635,8 @@ pub const VirtualMachine = struct {
this.tasks.ensureUnusedCapacity(add) catch unreachable;
{
var writable = std.mem.sliceAsBytes(this.tasks.writableSlice(0));
const readable = std.mem.sliceAsBytes(this.concurrent_tasks.readableSlice(0));
@memcpy(writable.ptr, readable.ptr, @minimum(writable.len, readable.len));
this.tasks.count += add;
this.concurrent_tasks.head = 0;
this.concurrent_tasks.count = 0;
this.tasks.writeAssumeCapacity(this.concurrent_tasks.readableSlice(0));
this.concurrent_tasks.discard(this.concurrent_tasks.count);
}
_ = this.pending_tasks_count.fetchAdd(add, .Monotonic);

View File

@@ -1756,13 +1756,13 @@ pub const Blob = struct {
const system_error_ = this.system_error;
var store = this.store;
bun.default_allocator.destroy(this);
if (system_error_) |err| {
cb(cb_ctx, -1, 0, err, globalThis);
} else {
cb(cb_ctx, fd, _size, null, globalThis);
}
store.deref();
bun.default_allocator.destroy(this);
}
fn _runAsync(this: *OpenAndStatFile) void {
@@ -1770,12 +1770,15 @@ pub const Blob = struct {
if (this.file_store.pathlike == .fd) {
this.opened_fd = this.file_store.pathlike.fd;
}
const fd =
if (this.opened_fd == null_fd)
const fd = if (this.opened_fd == null_fd)
this.getFd() catch return
else
this.opened_fd;
this.doWithFd(fd);
}
pub fn doWithFd(this: *OpenAndStatFile, fd: JSC.Node.FileDescriptor) void {
const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) {
.result => |result| result,
.err => |err| {