Compare commits

...

20 Commits

Author SHA1 Message Date
cirospaciari
f9c23893fd wip cleanup and close context before deinit 2024-05-17 12:19:23 -03:00
Jarred Sumner
3d906a6a53 Merge branch 'main' into ciro/fix-ssl-crash 2024-05-16 17:34:15 -07:00
Jarred Sumner
61e85a1aaf Merge branch 'main' into ciro/fix-ssl-crash 2024-05-16 17:27:20 -07:00
cirospaciari
007db52dde Apply formatting changes 2024-05-16 15:35:33 +00:00
cirospaciari
8128e74a24 cleanup 2 2024-05-16 12:33:58 -03:00
cirospaciari
ccf0691608 cleanup 2024-05-16 12:33:58 -03:00
cirospaciari
20979eee57 cleanup 2024-05-16 12:33:58 -03:00
cirospaciari
31876c9845 dont touch JS when finalizing socket 2024-05-16 12:33:58 -03:00
Jarred Sumner
b367efa86f Reduce duplication 2024-05-16 12:33:58 -03:00
Jarred Sumner
39af21ecbd Update websocket.test.js 2024-05-16 12:33:58 -03:00
Jarred Sumner
abd6be55e9 reduce duplication slightly 2024-05-16 12:33:58 -03:00
cirospaciari
6440fbb7af mark handshake state 2024-05-16 12:33:58 -03:00
cirospaciari
61e4dc44ec make more likely to reproduce 2024-05-16 12:33:58 -03:00
cirospaciari
ab3343c4a3 opsie 2024-05-16 12:33:58 -03:00
cirospaciari
f3e33b0049 add shutdown test 2024-05-16 12:33:58 -03:00
cirospaciari
e5abb2c223 progress 2024-05-16 12:33:58 -03:00
cirospaciari
e2d21cf37a drain on ssl shutdown 2024-05-16 12:33:58 -03:00
cirospaciari
f274daf642 revert 2024-05-16 12:33:58 -03:00
cirospaciari
860e7a9311 no crash anymore 2024-05-16 12:33:58 -03:00
cirospaciari
7847eb95b3 fix crash wip 2024-05-16 12:33:58 -03:00
4 changed files with 186 additions and 88 deletions

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// clang-format off
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
@@ -264,8 +264,61 @@ struct us_bun_verify_error_t us_socket_verify_error(int ssl, struct us_socket_t
return (struct us_bun_verify_error_t) { .error = 0, .code = NULL, .reason = NULL };
}
static struct us_socket_t * us_socket_context_on_open_dummy(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
return s;
}
static struct us_socket_t *us_socket_context_on_close_dummy(struct us_socket_t *s, int code, void *reason) {
return s;
}
static struct us_socket_t *us_socket_context_on_data_dummy(struct us_socket_t *s, char *data, int length){
return NULL; // null ends data loop
}
static struct us_socket_t *us_socket_context_on_writable_dummy(struct us_socket_t *s) {
return s;
}
static struct us_socket_t *us_socket_context_on_timeout_dummy(struct us_socket_t *s) {
return s;
}
static struct us_socket_t *us_socket_context_on_connect_error_dummy(struct us_socket_t *s, int code) {
return s;
}
static struct us_socket_t *us_socket_context_on_end_dummy(struct us_socket_t *s) {
return s;
}
static void us_socket_context_on_handshake_dummy(struct us_socket_t *s, int success, struct us_bun_verify_error_t verify_error, void* custom_data) {
return;
}
static struct us_socket_t *us_socket_context_on_long_timeout_dummy(struct us_socket_t *s) {
return s;
}
void us_socket_context_clean_callbacks(int ssl, struct us_socket_context_t *context) {
us_socket_context_on_open(ssl, context, us_socket_context_on_open_dummy);
us_socket_context_on_close(ssl, context, us_socket_context_on_close_dummy);
us_socket_context_on_data(ssl, context, us_socket_context_on_data_dummy);
us_socket_context_on_writable(ssl, context, us_socket_context_on_writable_dummy);
us_socket_context_on_timeout(ssl, context, us_socket_context_on_timeout_dummy);
us_socket_context_on_connect_error(ssl, context, us_socket_context_on_connect_error_dummy);
us_socket_context_on_end(ssl, context, us_socket_context_on_end_dummy);
us_socket_context_on_handshake(ssl, context, us_socket_context_on_handshake_dummy, NULL);
us_socket_context_on_long_timeout(ssl, context, us_socket_context_on_long_timeout_dummy);
}
void us_socket_context_free(int ssl, struct us_socket_context_t *context) {
/* We clean callbacks so we avoid UAF when closing/deiniting */
us_socket_context_clean_callbacks(ssl, context);
/* We are deiniting so we garantee that we are closed */
us_socket_context_close(ssl, context);
// TODO: if we are listening and we have some sockets in accept loop, we cannot deinit because we will have UAF
// we need to wait for all sockets to be closed before closing the context
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us again with SSL=false */

View File

@@ -180,6 +180,11 @@ int BIO_s_custom_read(BIO *bio, char *dst, int length) {
return length;
}
static void us_internal_ssl_on_socket_shutdown(struct us_internal_ssl_socket_t *s) {
s->received_ssl_shutdown = 1;
s->handshake_state = HANDSHAKE_COMPLETED;
}
struct us_internal_ssl_socket_t *ssl_on_open(struct us_internal_ssl_socket_t *s,
int is_client, char *ip,
int ip_length) {
@@ -345,7 +350,7 @@ void us_internal_update_handshake(struct us_internal_ssl_socket_t *s) {
int result = SSL_do_handshake(s->ssl);
if (SSL_get_shutdown(s->ssl) & SSL_RECEIVED_SHUTDOWN) {
s->received_ssl_shutdown = 1;
us_internal_ssl_on_socket_shutdown(s);
us_internal_ssl_socket_close(s, 0, NULL);
return;
}
@@ -398,6 +403,7 @@ ssl_on_end(struct us_internal_ssl_socket_t *s) {
return us_internal_ssl_socket_close(s, 0, NULL);
}
// this whole function needs a complete clean-up
struct us_internal_ssl_socket_t *ssl_on_data(struct us_internal_ssl_socket_t *s,
void *data, int length) {
@@ -429,7 +435,9 @@ struct us_internal_ssl_socket_t *ssl_on_data(struct us_internal_ssl_socket_t *s,
// two phase shutdown is complete here
/* Todo: this should also report some kind of clean shutdown */
return us_internal_ssl_socket_close(s, 0, NULL);
us_internal_ssl_on_socket_shutdown(s);
us_internal_ssl_socket_close(s, 0, NULL);
return NULL;
} else if (ret < 0) {
int err = SSL_get_error(s->ssl, ret);
@@ -442,7 +450,7 @@ struct us_internal_ssl_socket_t *ssl_on_data(struct us_internal_ssl_socket_t *s,
}
// no further processing of data when in shutdown state
return s;
return NULL;
}
// bug checking: this loop needs a lot of attention and clean-ups and
// check-ups
@@ -456,8 +464,8 @@ restart:
LIBUS_RECV_BUFFER_LENGTH - read);
// we need to check if we received a shutdown here
if (SSL_get_shutdown(s->ssl) & SSL_RECEIVED_SHUTDOWN) {
s->received_ssl_shutdown = 1;
// we will only close after we handle the data and errors
us_internal_ssl_on_socket_shutdown(s);
// process the data we might've received before
}
if (just_read <= 0) {
@@ -555,13 +563,9 @@ restart:
}
}
// we received the shutdown after reading so we close
if (s->received_ssl_shutdown) {
us_internal_ssl_socket_close(s, 0, NULL);
return NULL;
}
// trigger writable if we failed last write with want read
if (s->ssl_write_wants_read) {
// if ssl is shutdown, ignore this
if (s->ssl_write_wants_read && !s->received_ssl_shutdown) {
s->ssl_write_wants_read = 0;
// make sure to update context before we call (context can change if the
@@ -1693,7 +1697,7 @@ void *us_internal_ssl_socket_ext(struct us_internal_ssl_socket_t *s) {
int us_internal_ssl_socket_is_shut_down(struct us_internal_ssl_socket_t *s) {
return us_socket_is_shut_down(0, &s->s) ||
SSL_get_shutdown(s->ssl) & SSL_SENT_SHUTDOWN;
(SSL_get_shutdown(s->ssl) & SSL_SENT_SHUTDOWN);
}
void us_internal_ssl_socket_shutdown(struct us_internal_ssl_socket_t *s) {

View File

@@ -804,7 +804,7 @@ pub const Listener = struct {
.this_value = .zero,
.socket = socket,
.protos = listener.protos,
.owned_protos = false,
.flags = .{ .owned_protos = false },
};
if (listener.strong_data.get()) |default_data| {
const globalObject = listener.handlers.globalObject;
@@ -1140,20 +1140,26 @@ fn selectALPNCallback(
}
fn NewSocket(comptime ssl: bool) type {
const SocketFlags = packed struct(u8) {
detached: bool = false,
finalizing: bool = false,
is_active: bool = false,
owned_protos: bool = true,
authorized: bool = false,
// future flags
_reserved: u3 = 0,
};
return struct {
pub const Socket = uws.NewSocketHandler(ssl);
socket: Socket,
detached: bool = false,
flags: SocketFlags = .{},
wrapped: WrappedType = .none,
handlers: *Handlers,
this_value: JSC.JSValue = .zero,
poll_ref: Async.KeepAlive = Async.KeepAlive.init(),
is_active: bool = false,
last_4: [4]u8 = .{ 0, 0, 0, 0 },
authorized: bool = false,
connection: ?Listener.UnixOrHost = null,
protos: ?[]const u8,
owned_protos: bool = true,
server_name: ?[]const u8 = null,
// TODO: switch to something that uses `visitAggregate` and have the
@@ -1221,7 +1227,7 @@ fn NewSocket(comptime ssl: bool) type {
) void {
JSC.markBinding(@src());
log("onWritable", .{});
if (this.detached) return;
if (this.flags.detached) return;
const handlers = this.handlers;
const callback = handlers.onWritable;
if (callback == .zero) return;
@@ -1245,7 +1251,7 @@ fn NewSocket(comptime ssl: bool) type {
) void {
JSC.markBinding(@src());
log("onTimeout", .{});
if (this.detached) return;
if (this.flags.detached) return;
const handlers = this.handlers;
const callback = handlers.onTimeout;
@@ -1268,8 +1274,8 @@ fn NewSocket(comptime ssl: bool) type {
}
fn handleConnectError(this: *This, errno: c_int) void {
log("onConnectError({d})", .{errno});
if (this.detached) return;
this.detached = true;
if (this.flags.detached) return;
this.flags.detached = true;
defer this.markInactive();
const handlers = this.handlers;
@@ -1328,27 +1334,27 @@ fn NewSocket(comptime ssl: bool) type {
}
pub fn markActive(this: *This) void {
if (!this.is_active) {
if (!this.flags.is_active) {
this.handlers.markActive();
this.is_active = true;
this.flags.is_active = true;
this.has_pending_activity.store(true, .Release);
}
}
pub fn markInactive(this: *This) void {
if (this.is_active) {
if (!this.detached) {
if (this.flags.is_active) {
if (!this.flags.detached) {
// we have to close the socket before the socket context is closed
// otherwise we will get a segfault
// uSockets will defer closing the TCP socket until the next tick
if (!this.socket.isClosed()) {
this.detached = true;
this.flags.detached = true;
this.socket.close(0, null);
// onClose will call markInactive again
return;
}
}
this.is_active = false;
this.flags.is_active = false;
const vm = this.handlers.vm;
this.handlers.markInactive(ssl, this.socket.context(), this.wrapped);
@@ -1393,7 +1399,7 @@ fn NewSocket(comptime ssl: bool) type {
}
}
this.detached = false;
this.flags.detached = false;
this.socket = socket;
if (this.wrapped == .none) {
@@ -1426,7 +1432,7 @@ fn NewSocket(comptime ssl: bool) type {
});
if (result.toError()) |err| {
this.detached = true;
this.flags.detached = true;
defer this.markInactive();
if (!this.socket.isClosed()) {
log("Closing due to error", .{});
@@ -1453,7 +1459,7 @@ fn NewSocket(comptime ssl: bool) type {
pub fn onEnd(this: *This, socket: Socket) void {
JSC.markBinding(@src());
log("onEnd", .{});
if (this.detached) return;
if (this.flags.detached) return;
const handlers = this.handlers;
@@ -1485,11 +1491,11 @@ fn NewSocket(comptime ssl: bool) type {
pub fn onHandshake(this: *This, socket: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void {
log("onHandshake({d})", .{success});
JSC.markBinding(@src());
if (this.detached) return;
if (this.flags.detached) return;
const authorized = if (success == 1) true else false;
this.authorized = authorized;
this.flags.authorized = authorized;
const handlers = this.handlers;
var callback = handlers.onHandshake;
@@ -1548,8 +1554,11 @@ fn NewSocket(comptime ssl: bool) type {
pub fn onClose(this: *This, socket: Socket, err: c_int, _: ?*anyopaque) void {
JSC.markBinding(@src());
// We call close inside the finalizer, before this we always mark as finalizing
// this is to avoid calling the close/error callbacks and interaction with JS at this point
if (this.flags.finalizing) return;
log("onClose", .{});
this.detached = true;
this.flags.detached = true;
defer this.markInactive();
const handlers = this.handlers;
@@ -1579,7 +1588,7 @@ fn NewSocket(comptime ssl: bool) type {
pub fn onData(this: *This, socket: Socket, data: []const u8) void {
JSC.markBinding(@src());
log("onData({d})", .{data.len});
if (this.detached) return;
if (this.flags.detached) return;
const handlers = this.handlers;
const callback = handlers.onData;
@@ -1627,7 +1636,7 @@ fn NewSocket(comptime ssl: bool) type {
this: *This,
_: *JSC.JSGlobalObject,
) callconv(.C) JSValue {
if (!this.handlers.is_server or this.detached) {
if (!this.handlers.is_server or this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -1640,7 +1649,7 @@ fn NewSocket(comptime ssl: bool) type {
) callconv(.C) JSValue {
log("getReadyState()", .{});
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNumber(@as(i32, -1));
} else if (this.socket.isClosed()) {
return JSValue.jsNumber(@as(i32, 0));
@@ -1658,7 +1667,7 @@ fn NewSocket(comptime ssl: bool) type {
_: *JSC.JSGlobalObject,
) callconv(.C) JSValue {
log("getAuthorized()", .{});
return JSValue.jsBoolean(this.authorized);
return JSValue.jsBoolean(this.flags.authorized);
}
pub fn timeout(
this: *This,
@@ -1667,7 +1676,7 @@ fn NewSocket(comptime ssl: bool) type {
) callconv(.C) JSValue {
JSC.markBinding(@src());
const args = callframe.arguments(1);
if (this.detached) return JSValue.jsUndefined();
if (this.flags.detached) return JSValue.jsUndefined();
if (args.len == 0) {
globalObject.throw("Expected 1 argument, got 0", .{});
return .zero;
@@ -1690,7 +1699,7 @@ fn NewSocket(comptime ssl: bool) type {
) callconv(.C) JSValue {
JSC.markBinding(@src());
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNull();
}
@@ -1720,7 +1729,7 @@ fn NewSocket(comptime ssl: bool) type {
) callconv(.C) JSValue {
JSC.markBinding(@src());
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNumber(@as(i32, -1));
}
@@ -1741,7 +1750,7 @@ fn NewSocket(comptime ssl: bool) type {
this: *This,
_: *JSC.JSGlobalObject,
) callconv(.C) JSValue {
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -1752,7 +1761,7 @@ fn NewSocket(comptime ssl: bool) type {
this: *This,
globalThis: *JSC.JSGlobalObject,
) callconv(.C) JSValue {
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -1773,7 +1782,7 @@ fn NewSocket(comptime ssl: bool) type {
}
fn writeMaybeCorked(this: *This, buffer: []const u8, is_end: bool) i32 {
if (this.detached or this.socket.isShutdown() or this.socket.isClosed()) {
if (this.flags.detached or this.socket.isShutdown() or this.socket.isClosed()) {
return -1;
}
// we don't cork yet but we might later
@@ -1943,7 +1952,7 @@ fn NewSocket(comptime ssl: bool) type {
_: *JSC.CallFrame,
) callconv(.C) JSValue {
JSC.markBinding(@src());
if (!this.detached)
if (!this.flags.detached)
this.socket.flush();
return JSValue.jsUndefined();
@@ -1956,7 +1965,7 @@ fn NewSocket(comptime ssl: bool) type {
) callconv(.C) JSValue {
JSC.markBinding(@src());
const args = callframe.arguments(1);
if (!this.detached) {
if (!this.flags.detached) {
if (args.len > 0 and args.ptr[0].toBoolean()) {
this.socket.shutdownRead();
} else {
@@ -1978,7 +1987,7 @@ fn NewSocket(comptime ssl: bool) type {
log("end({d} args)", .{args.len});
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNumber(@as(i32, -1));
}
@@ -1997,7 +2006,7 @@ fn NewSocket(comptime ssl: bool) type {
pub fn ref(this: *This, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
JSC.markBinding(@src());
if (this.detached) return JSValue.jsUndefined();
if (this.flags.detached) return JSValue.jsUndefined();
this.poll_ref.ref(globalObject.bunVM());
return JSValue.jsUndefined();
}
@@ -2010,8 +2019,9 @@ fn NewSocket(comptime ssl: bool) type {
pub fn finalize(this: *This) callconv(.C) void {
log("finalize() {d}", .{@intFromPtr(this)});
if (!this.detached) {
this.detached = true;
this.flags.finalizing = true;
if (!this.flags.detached) {
this.flags.detached = true;
if (!this.socket.isClosed()) {
this.socket.close(0, null);
}
@@ -2021,7 +2031,7 @@ fn NewSocket(comptime ssl: bool) type {
this.poll_ref.unref(JSC.VirtualMachine.get());
// need to deinit event without being attached
if (this.owned_protos) {
if (this.flags.owned_protos) {
if (this.protos) |protos| {
this.protos = null;
default_allocator.free(protos);
@@ -2047,7 +2057,7 @@ fn NewSocket(comptime ssl: bool) type {
return .zero;
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2085,7 +2095,7 @@ fn NewSocket(comptime ssl: bool) type {
if (comptime ssl == false) {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2102,7 +2112,7 @@ fn NewSocket(comptime ssl: bool) type {
if (comptime ssl == false) {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2143,7 +2153,7 @@ fn NewSocket(comptime ssl: bool) type {
if (comptime ssl == false) {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2164,7 +2174,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2191,7 +2201,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2237,7 +2247,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2265,7 +2275,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsBoolean(false);
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsBoolean(false);
}
@@ -2297,7 +2307,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2385,7 +2395,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsNull();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNull();
}
@@ -2454,7 +2464,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
var result = JSValue.createEmptyObject(globalObject, 3);
@@ -2501,7 +2511,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2533,7 +2543,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2566,7 +2576,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsNull();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNull();
}
const ssl_ptr = @as(*BoringSSL.SSL, @ptrCast(this.socket.getNativeHandle()));
@@ -2657,7 +2667,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsNull();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsNull();
}
@@ -2680,7 +2690,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsBoolean(false);
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsBoolean(false);
}
@@ -2719,7 +2729,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2773,7 +2783,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -2836,7 +2846,7 @@ fn NewSocket(comptime ssl: bool) type {
this.server_name = slice;
}
if (this.detached) {
if (this.flags.detached) {
// will be attached onOpen
return JSValue.jsUndefined();
}
@@ -2871,7 +2881,7 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}
if (this.detached) {
if (this.flags.detached) {
return JSValue.jsUndefined();
}
@@ -3026,10 +3036,10 @@ fn NewSocket(comptime ssl: bool) type {
new_socket.startTLS(!this.handlers.is_server);
//detach and invalidate the old instance
this.detached = true;
if (this.is_active) {
this.flags.detached = true;
if (this.flags.is_active) {
const vm = this.handlers.vm;
this.is_active = false;
this.flags.is_active = false;
// will free handlers and the old_context when hits 0 active connections
// the connection can be upgraded inside a handler call so we need to garantee that it will be still alive
this.handlers.markInactive(ssl, old_context, this.wrapped);

View File

@@ -56,6 +56,40 @@ describe("WebSocket", () => {
await closed;
Bun.gc(true);
});
it("should handle shutdown properly", async () => {
using server = Bun.serve({
port: 0,
tls: COMMON_CERT,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("Upgrade failed :(", { status: 500 });
},
websocket: {
message(ws, message) {
// echo
ws.send(message);
},
open(ws) {},
},
});
const websockets = [];
for (let i = 0; i < 10_000; i++) {
const ws = new WebSocket(server.url.href, { tls: { rejectUnauthorized: false } });
const { promise, resolve, reject } = Promise.withResolvers();
ws.onopen = () => {
ws.send("message");
resolve();
};
ws.onerror = reject;
websockets.push(promise);
}
await Promise.all(websockets);
}, 60_000);
it("should connect many times over https", async () => {
using server = Bun.serve({
@@ -75,21 +109,18 @@ describe("WebSocket", () => {
open(ws) {},
},
});
{
for (let i = 0; i < 1000; i++) {
const ws = new WebSocket(server.url.href, { tls: { rejectUnauthorized: false } });
await new Promise((resolve, reject) => {
ws.onopen = resolve;
ws.onerror = reject;
});
var closed = new Promise((resolve, reject) => {
ws.onclose = resolve;
});
ws.close();
await closed;
for (let i = 0; i < 1000; i++) {
const ws = new WebSocket(server.url.href, { tls: { rejectUnauthorized: false } });
{
const { promise, resolve, reject } = Promise.withResolvers();
ws.onopen = resolve;
ws.onerror = reject;
await promise;
}
Bun.gc(true);
const { promise: closed, resolve: resolveOnClose } = Promise.withResolvers();
ws.onclose = resolveOnClose;
ws.close();
await closed;
}
});