diff options
-rw-r--r-- | src/bun.js/api/bun/socket.zig | 170 | ||||
-rw-r--r-- | src/bun.js/net.exports.js | 4 | ||||
-rw-r--r-- | test/bun.js/socket/node-net.test.ts | 14 | ||||
-rw-r--r-- | test/bun.js/socket/socket.test.ts | 39 | ||||
-rw-r--r-- | test/bun.js/tcp-server.test.ts | 11 |
5 files changed, 143 insertions, 95 deletions
diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 319d1de37..f3a1d2646 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -224,7 +224,7 @@ pub const SocketConfig = struct { const port_value = opts.get(globalObject, "port") orelse JSValue.zero; if (port_value.isEmptyOrUndefinedOrNull() or !port_value.isNumber() or port_value.toInt64() > std.math.maxInt(u16) or port_value.toInt64() < 0) { - exception.* = JSC.toInvalidArguments("Expected \"port\" to be a number between 0 and 65432", .{}, globalObject).asObjectRef(); + exception.* = JSC.toInvalidArguments("Expected \"port\" to be a number between 0 and 65535", .{}, globalObject).asObjectRef(); return null; } @@ -530,9 +530,13 @@ pub const Listener = struct { var this_socket = listener.handlers.vm.allocator.create(Socket) catch @panic("Out of memory"); this_socket.* = Socket{ .handlers = &listener.handlers, - .this_value = listener.strong_data.get() orelse JSValue.zero, + .this_value = .zero, .socket = socket, }; + if (listener.strong_data.get()) |default_data| { + const globalObject = listener.handlers.globalObject; + Socket.dataSetCached(this_socket.getThisValue(globalObject), globalObject, default_data); + } socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, this_socket); socket.timeout(120000); } @@ -722,10 +726,12 @@ pub const Listener = struct { tls.* = .{ .handlers = handlers_ptr, - .this_value = default_data, + .this_value = .zero, .socket = undefined, }; + TLSSocket.dataSetCached(tls.getThisValue(globalObject), globalObject, default_data); + tls.doConnect(connection, socket_context) catch { handlers_ptr.unprotect(); socket_context.deinit(true); @@ -742,10 +748,12 @@ pub const Listener = struct { tcp.* = .{ .handlers = handlers_ptr, - .this_value = default_data, + .this_value = .zero, .socket = undefined, }; + TCPSocket.dataSetCached(tcp.getThisValue(globalObject), globalObject, default_data); + tcp.doConnect(connection, socket_context) catch { handlers_ptr.unprotect(); socket_context.deinit(false); @@ -836,22 +844,21 @@ fn NewSocket(comptime ssl: bool) type { _: Socket, ) void { JSC.markBinding(@src()); + log("onWritable", .{}); if (this.detached) return; - var handlers = this.handlers; + + const handlers = this.handlers; const callback = handlers.onWritable; - if (callback == .zero) { - return; - } + if (callback == .zero) return; - const this_value = this.getThisValue(handlers.globalObject); - const result = callback.callWithThis(handlers.globalObject, this_value, &[_]JSValue{ + const globalObject = handlers.globalObject; + const this_value = this.getThisValue(globalObject); + const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, }); - if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(handlers.globalObject)) { - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } + if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } pub fn onTimeout( @@ -859,42 +866,57 @@ fn NewSocket(comptime ssl: bool) type { _: Socket, ) void { JSC.markBinding(@src()); + log("onTimeout", .{}); if (this.detached) return; this.detached = true; defer this.markInactive(); - var handlers = this.handlers; + + const handlers = this.handlers; this.poll_ref.unref(handlers.vm); - var globalObject = handlers.globalObject; - const callback = handlers.onTimeout; - if (callback == .zero) { - return; - } + const callback = handlers.onTimeout; + if (callback == .zero) return; + const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, }); if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } pub fn onConnectError(this: *This, _: Socket, errno: c_int) void { JSC.markBinding(@src()); - log("onConnectError({d}", .{errno}); + log("onConnectError({d})", .{ errno }); + if (this.detached) return; this.detached = true; defer this.markInactive(); - var handlers = this.handlers; + + const handlers = this.handlers; this.poll_ref.unref(handlers.vm); - var err = JSC.SystemError{ + + const callback = handlers.onConnectError; + if (callback == .zero) return; + + const globalObject = handlers.globalObject; + const this_value = this.getThisValue(globalObject); + const err = JSC.SystemError{ .errno = errno, .message = ZigString.init("Failed to connect"), .syscall = ZigString.init("connect"), }; - _ = handlers.rejectPromise(err.toErrorInstance(handlers.globalObject)); + const err_value = err.toErrorInstance(globalObject); + const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ + this_value, + err_value, + }); + + _ = handlers.rejectPromise(err_value); + if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); + } } pub fn markActive(this: *This) void { @@ -929,26 +951,23 @@ fn NewSocket(comptime ssl: bool) type { this.detached = false; this.socket = socket; socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, this); - var handlers = this.handlers; - const old_this_value = this.this_value; - this.this_value = .zero; - const this_value = this.getThisValue(handlers.globalObject); - if (old_this_value != .zero) { - This.dataSetCached(this_value, handlers.globalObject, old_this_value); - } + const handlers = this.handlers; + const callback = handlers.onOpen; + + const globalObject = handlers.globalObject; + const this_value = this.getThisValue(globalObject); this.markActive(); handlers.resolvePromise(this_value); - if (handlers.onOpen == .zero and old_this_value == .zero) - return; + if (callback == .zero) return; - const result = handlers.onOpen.callWithThis(handlers.globalObject, this_value, &[_]JSValue{ + const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, }); - if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(handlers.globalObject)) { + if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { this.detached = true; defer this.markInactive(); if (!this.socket.isClosed()) { @@ -957,15 +976,8 @@ fn NewSocket(comptime ssl: bool) type { log("Already closed", .{}); } - if (handlers.rejectPromise(this_value)) { - return; - } - - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } - - return; + if (handlers.rejectPromise(result)) return; + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } @@ -985,22 +997,21 @@ fn NewSocket(comptime ssl: bool) type { log("onEnd", .{}); this.detached = true; defer this.markInactive(); - var handlers = this.handlers; - const callback = handlers.onEnd; - if (callback == .zero) { - return; - } + const handlers = this.handlers; + this.poll_ref.unref(handlers.vm); + + const callback = handlers.onEnd; + if (callback == .zero) return; - const this_value = this.getThisValue(handlers.globalObject); - const result = callback.callWithThis(handlers.globalObject, this_value, &[_]JSValue{ + const globalObject = handlers.globalObject; + const this_value = this.getThisValue(globalObject); + const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, }); - if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(handlers.globalObject)) { - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } + if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } @@ -1009,56 +1020,45 @@ fn NewSocket(comptime ssl: bool) type { log("onClose", .{}); this.detached = true; defer this.markInactive(); - var handlers = this.handlers; + + const handlers = this.handlers; this.poll_ref.unref(handlers.vm); const callback = handlers.onClose; - var globalObject = handlers.globalObject; - - if (callback == .zero) { - return; - } + if (callback == .zero) return; + var globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); - const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, JSValue.jsNumber(@as(i32, err)), }); if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } pub fn onData(this: *This, _: Socket, data: []const u8) void { JSC.markBinding(@src()); - if (comptime Environment.allow_assert) { - log("onData({d})", .{data.len}); - } - + log("onData({d})", .{ data.len }); if (this.detached) return; - var handlers = this.handlers; - // const encoding = handlers.encoding; - const callback = handlers.onData; - if (callback == .zero) { - return; - } - const output_value = JSC.ArrayBuffer.create(handlers.globalObject, data, .Uint8Array); + const handlers = this.handlers; + const callback = handlers.onData; + if (callback == .zero) return; - const this_value = this.getThisValue(handlers.globalObject); - const result = callback.callWithThis(handlers.globalObject, this_value, &[_]JSValue{ + const globalObject = handlers.globalObject; + const this_value = this.getThisValue(globalObject); + const output_value = JSC.ArrayBuffer.create(globalObject, data, .Uint8Array); + // const encoding = handlers.encoding; + const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, output_value, }); - if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(handlers.globalObject)) { - if (handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result })) { - return; - } + if (!result.isEmptyOrUndefinedOrNull() and result.isAnyError(globalObject)) { + _ = handlers.callErrorHandler(this_value, &[_]JSC.JSValue{ this_value, result }); } } diff --git a/src/bun.js/net.exports.js b/src/bun.js/net.exports.js index 47e4b68db..183fa86e1 100644 --- a/src/bun.js/net.exports.js +++ b/src/bun.js/net.exports.js @@ -60,6 +60,10 @@ const { Duplex } = import.meta.require("node:stream"); export class Socket extends Duplex { static #Handlers = { close: Socket.#Close, + connectError(socket, error) { + const self = socket.data; + self.emit("error", error); + }, data(socket, buffer) { const self = socket.data; self.bytesRead += buffer.length; diff --git a/test/bun.js/socket/node-net.test.ts b/test/bun.js/socket/node-net.test.ts index 47c9964cf..d19644bd3 100644 --- a/test/bun.js/socket/node-net.test.ts +++ b/test/bun.js/socket/node-net.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "bun:test"; -import { isIP, isIPv4, isIPv6, Socket } from "net"; +import { connect, isIP, isIPv4, isIPv6, Socket } from "net"; it("should support net.isIP()", () => { expect(isIP("::1")).toBe(6); @@ -173,3 +173,15 @@ describe("net.Socket write", () => { afterAll(() => server.stop()); }); + +it("should handle connection error", done => { + var data = {}; + connect(55555, () => { + done(new Error("Should not have connected")); + }).on("error", error => { + expect(error).toBeDefined(); + expect(error.name).toBe("SystemError"); + expect(error.message).toBe("Failed to connect"); + done(); + }); +}); diff --git a/test/bun.js/socket/socket.test.ts b/test/bun.js/socket/socket.test.ts index aff001c75..7336306fa 100644 --- a/test/bun.js/socket/socket.test.ts +++ b/test/bun.js/socket/socket.test.ts @@ -1,6 +1,6 @@ import { expect, it } from "bun:test"; import { bunExe } from "../bunExe"; -import { spawn } from "bun"; +import { connect, spawn } from "bun"; it("should keep process alive only when active", async () => { const { exited, stdout, stderr } = spawn({ @@ -32,3 +32,40 @@ it("should keep process alive only when active", async () => { "[Client] CLOSED", ]); }); + +it("should handle connection error", done => { + var data = {}; + connect({ + data, + hostname: "localhost", + port: 55555, + socket: { + connectError(socket, error) { + expect(socket).toBeDefined(); + expect(socket.data).toBe(data); + expect(error).toBeDefined(); + expect(error.name).toBe("SystemError"); + expect(error.message).toBe("Failed to connect"); + done(); + }, + data() { + done(new Error("Unexpected data()")); + }, + drain() { + done(new Error("Unexpected drain()")); + }, + close() { + done(new Error("Unexpected close()")); + }, + end() { + done(new Error("Unexpected end()")); + }, + error() { + done(new Error("Unexpected error()")); + }, + open() { + done(new Error("Unexpected open()")); + }, + }, + }); +}); diff --git a/test/bun.js/tcp-server.test.ts b/test/bun.js/tcp-server.test.ts index 65dd069a6..3f008a16b 100644 --- a/test/bun.js/tcp-server.test.ts +++ b/test/bun.js/tcp-server.test.ts @@ -96,17 +96,12 @@ it("echo server 1 on 1", async () => { await Promise.all([prom, clientProm, serverProm]); server.stop(); server = serverData = clientData = undefined; - Bun.gc(true); })(); +}); +it("should not leak memory", () => { // Tell the garbage collector for sure that we're done with the sockets - await new Promise((resolve, reject) => { - setTimeout(() => { - Bun.gc(true); - resolve(undefined); - }, 1); - }); - + Bun.gc(true); // assert we don't leak the sockets // we expect 1 because that's the prototype / structure expect(JSC.heapStats().objectTypeCounts.TCPSocket).toBe(1); |