Skip to content

Commit

Permalink
core: refactor how libuv companion objects are allocated
Browse files Browse the repository at this point in the history
All native bindings are allocated and attached to the JS object in an
opaque.

When what we attach is a native binding to a libuv handle there is a
tricky situation: uv_close() is not synchronous, which means that we
need to delay freeing the native object until the close callback was
called. This is a problem when objects are freed as part of freeing the
JS engine, since by the time the close callback is called the JS context
and runtime have been freed.

In order to avoid this problem, allocate the memory for these
"companion" objects with the tjs__ family of functions, which don't
depend on the JS context.

This also allows us to refactor TJSFreeRuntime to a much more obvious
flow. There is no need to walk and close all libuv handles, since
they'll all be closed when the JS engine is terminated.
  • Loading branch information
saghul committed Jun 20, 2024
1 parent 07b1873 commit 8daf2b8
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 81 deletions.
5 changes: 3 additions & 2 deletions src/curl-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "curl-utils.h"

#include "mem.h"
#include "utils.h"
#include "version.h"

Expand Down Expand Up @@ -150,7 +151,7 @@ typedef struct {
static void uv__poll_close_cb(uv_handle_t *handle) {
tjs_curl_poll_ctx_t *poll_ctx = handle->data;
CHECK_NOT_NULL(poll_ctx);
free(poll_ctx);
tjs__free(poll_ctx);
}

static void uv__poll_cb(uv_poll_t *handle, int status, int events) {
Expand Down Expand Up @@ -182,7 +183,7 @@ static int curl__handle_socket(CURL *easy, curl_socket_t s, int action, void *us
tjs_curl_poll_ctx_t *poll_ctx;
if (!socketp) {
// Initialize poll handle.
poll_ctx = malloc(sizeof(*poll_ctx));
poll_ctx = tjs__malloc(sizeof(*poll_ctx));
if (!poll_ctx)
return -1;
CHECK_EQ(uv_poll_init_socket(&qrt->loop, &poll_ctx->poll, s), 0);
Expand Down
17 changes: 8 additions & 9 deletions src/fswatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
* THE SOFTWARE.
*/

#include "mem.h"
#include "private.h"
#include "utils.h"

typedef struct {
JSContext *ctx;
JSRuntime *rt;
uv_fs_event_t handle;
JSValue callback;
int closed;
Expand All @@ -45,7 +45,7 @@ static void uv__fsevent_close_cb(uv_handle_t *handle) {
if (fw) {
fw->closed = 1;
if (fw->finalized)
js_free_rt(fw->rt, fw);
tjs__free(fw);
}
}

Expand All @@ -60,7 +60,7 @@ static void tjs_fswatch_finalizer(JSRuntime *rt, JSValue val) {
JS_FreeValueRT(rt, fw->callback);
fw->finalized = 1;
if (fw->closed)
js_free_rt(rt, fw);
tjs__free(fw);
else
maybe_close(fw);
}
Expand Down Expand Up @@ -128,7 +128,7 @@ static void uv__fs_event_cb(uv_fs_event_t *handle, const char *filename, int eve
if (status != 0)
return;

// libuv could set both, if we get rename, ignroe change.
// libuv could set both, if we get rename, ignore change.

JSValue event;
if (events & UV_RENAME) {
Expand Down Expand Up @@ -168,33 +168,32 @@ static JSValue tjs_fs_watch(JSContext *ctx, JSValue this_val, int argc, JSValue
return JS_EXCEPTION;
}

TJSFsWatch *fw = js_mallocz(ctx, sizeof(*fw));
TJSFsWatch *fw = tjs__mallocz(sizeof(*fw));
if (!fw) {
JS_FreeCString(ctx, path);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

int r = uv_fs_event_init(tjs_get_loop(ctx), &fw->handle);
if (r != 0) {
JS_FreeCString(ctx, path);
JS_FreeValue(ctx, obj);
js_free(ctx, fw);
tjs__free(fw);
return JS_ThrowInternalError(ctx, "couldn't initialize handle");
}

r = uv_fs_event_start(&fw->handle, uv__fs_event_cb, path, UV_FS_EVENT_RECURSIVE);
if (r != 0) {
JS_FreeCString(ctx, path);
JS_FreeValue(ctx, obj);
js_free(ctx, fw);
tjs__free(fw);
return tjs_throw_errno(ctx, r);
}

JS_FreeCString(ctx, path);

fw->ctx = ctx;
fw->rt = JS_GetRuntime(ctx);
fw->handle.data = fw;
fw->callback = JS_DupValue(ctx, argv[1]);

Expand Down
8 changes: 8 additions & 0 deletions src/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ void *tjs__malloc(size_t size) {
#endif
}

void *tjs__mallocz(size_t size) {
#ifdef TJS__HAS_MIMALLOC
return mi_calloc(1, size);
#else
return calloc(1, size);
#endif
}

void *tjs__calloc(size_t count, size_t size) {
#ifdef TJS__HAS_MIMALLOC
return mi_calloc(count, size);
Expand Down
1 change: 1 addition & 0 deletions src/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

size_t tjs__malloc_usable_size(const void *ptr);
void *tjs__malloc(size_t size);
void *tjs__mallocz(size_t size);
void *tjs__calloc(size_t count, size_t size);
void tjs__free(void *ptr);
void *tjs__realloc(void *ptr, size_t size);
Expand Down
13 changes: 6 additions & 7 deletions src/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* THE SOFTWARE.
*/

#include "mem.h"
#include "private.h"
#include "utils.h"

Expand All @@ -33,7 +34,6 @@ static JSClassID tjs_process_class_id;

typedef struct {
JSContext *ctx;
JSRuntime *rt;
JSValue obj;
bool closed;
bool finalized;
Expand All @@ -52,7 +52,7 @@ static void uv__close_cb(uv_handle_t *handle) {
CHECK_NOT_NULL(p);
p->closed = true;
if (p->finalized)
js_free_rt(p->rt, p);
tjs__free(p);
}

static void maybe_close(TJSProcess *p) {
Expand All @@ -69,7 +69,7 @@ static void tjs_process_finalizer(JSRuntime *rt, JSValue val) {
JS_FreeValueRT(rt, p->stdio[2]);
p->finalized = true;
if (p->closed)
js_free_rt(rt, p);
tjs__free(p);
else
maybe_close(p);
}
Expand Down Expand Up @@ -184,14 +184,13 @@ static JSValue tjs_spawn(JSContext *ctx, JSValue this_val, int argc, JSValue *ar
if (JS_IsException(obj))
return obj;

TJSProcess *p = js_mallocz(ctx, sizeof(*p));
TJSProcess *p = tjs__mallocz(sizeof(*p));
if (!p) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

p->ctx = ctx;
p->rt = JS_GetRuntime(ctx);
p->process.data = p;

TJS_ClearPromise(ctx, &p->status.result);
Expand Down Expand Up @@ -451,7 +450,7 @@ static JSValue tjs_spawn(JSContext *ctx, JSValue this_val, int argc, JSValue *ar
JS_FreeValue(ctx, p->stdio[0]);
JS_FreeValue(ctx, p->stdio[1]);
JS_FreeValue(ctx, p->stdio[2]);
js_free(ctx, p);
tjs__free(p);
JS_FreeValue(ctx, obj);
ret = JS_EXCEPTION;
cleanup:
Expand Down
15 changes: 7 additions & 8 deletions src/signals.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
* THE SOFTWARE.
*/

#include "mem.h"
#include "private.h"
#include "utils.h"


typedef struct {
JSContext *ctx;
JSRuntime *rt;
int closed;
int finalized;
uv_signal_t handle;
Expand All @@ -44,7 +44,7 @@ static void uv__signal_close_cb(uv_handle_t *handle) {
if (sh) {
sh->closed = 1;
if (sh->finalized)
js_free_rt(sh->rt, sh);
tjs__free(sh);
}
}

Expand All @@ -59,7 +59,7 @@ static void tjs_signal_handler_finalizer(JSRuntime *rt, JSValue val) {
JS_FreeValueRT(rt, sh->func);
sh->finalized = 1;
if (sh->closed)
js_free_rt(rt, sh);
tjs__free(sh);
else
maybe_close(sh);
}
Expand Down Expand Up @@ -97,29 +97,28 @@ static JSValue tjs_signal(JSContext *ctx, JSValue this_val, int argc, JSValue *a
if (JS_IsException(obj))
return obj;

TJSSignalHandler *sh = js_mallocz(ctx, sizeof(*sh));
TJSSignalHandler *sh = tjs__mallocz(sizeof(*sh));
if (!sh) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

int r = uv_signal_init(tjs_get_loop(ctx), &sh->handle);
if (r != 0) {
JS_FreeValue(ctx, obj);
js_free(ctx, sh);
tjs__free(sh);
return JS_ThrowInternalError(ctx, "couldn't initialize Signal handle");
}

r = uv_signal_start(&sh->handle, uv__signal_cb, sig_num);
if (r != 0) {
JS_FreeValue(ctx, obj);
js_free(ctx, sh);
tjs__free(sh);
return tjs_throw_errno(ctx, r);
}
uv_unref((uv_handle_t *) &sh->handle);

sh->ctx = ctx;
sh->rt = JS_GetRuntime(ctx);
sh->sig_num = sig_num;
sh->handle.data = sh;
sh->func = JS_DupValue(ctx, func);
Expand Down
27 changes: 11 additions & 16 deletions src/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* THE SOFTWARE.
*/

#include "mem.h"
#include "private.h"
#include "utils.h"

Expand All @@ -36,7 +37,6 @@ static JSValue tjs_new_tcp(JSContext *ctx, int af);

typedef struct {
JSContext *ctx;
JSRuntime *rt;
int closed;
int finalized;
union {
Expand Down Expand Up @@ -83,7 +83,7 @@ static void uv__stream_close_cb(uv_handle_t *handle) {
CHECK_NOT_NULL(s);
s->closed = 1;
if (s->finalized)
js_free_rt(s->rt, s);
tjs__free(s);
}

static void maybe_close(TJSStream *s) {
Expand Down Expand Up @@ -400,12 +400,7 @@ static JSValue tjs_stream_set_blocking(JSContext *ctx, TJSStream *s, int argc, J

static JSValue tjs_init_stream(JSContext *ctx, JSValue obj, TJSStream *s) {
s->ctx = ctx;
s->rt = JS_GetRuntime(ctx);
s->closed = 0;
s->finalized = 0;

s->h.handle.data = s;

s->read.b.tarray = JS_UNDEFINED;
s->read.b.data = NULL;
s->read.b.len = 0;
Expand Down Expand Up @@ -468,16 +463,16 @@ static JSValue tjs_new_tcp(JSContext *ctx, int af) {
if (JS_IsException(obj))
return obj;

s = js_mallocz(ctx, sizeof(*s));
s = tjs__mallocz(sizeof(*s));
if (!s) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

r = uv_tcp_init_ex(tjs_get_loop(ctx), &s->h.tcp, af);
if (r != 0) {
JS_FreeValue(ctx, obj);
js_free(ctx, s);
tjs__free(s);
return JS_ThrowInternalError(ctx, "couldn't initialize TCP handle");
}

Expand Down Expand Up @@ -651,16 +646,16 @@ static JSValue tjs_tty_constructor(JSContext *ctx, JSValue new_target, int argc,
if (JS_IsException(obj))
return obj;

s = js_mallocz(ctx, sizeof(*s));
s = tjs__mallocz(sizeof(*s));
if (!s) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

r = uv_tty_init(tjs_get_loop(ctx), &s->h.tty, fd, readable);
if (r != 0) {
JS_FreeValue(ctx, obj);
js_free(ctx, s);
tjs__free(s);
return JS_ThrowInternalError(ctx, "couldn't initialize TTY handle");
}

Expand Down Expand Up @@ -733,16 +728,16 @@ JSValue tjs_new_pipe(JSContext *ctx) {
if (JS_IsException(obj))
return obj;

s = js_mallocz(ctx, sizeof(*s));
s = tjs__mallocz(sizeof(*s));
if (!s) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
return JS_ThrowOutOfMemory(ctx);
}

r = uv_pipe_init(tjs_get_loop(ctx), &s->h.pipe, 0);
if (r != 0) {
JS_FreeValue(ctx, obj);
js_free(ctx, s);
tjs__free(s);
return JS_ThrowInternalError(ctx, "couldn't initialize Pipe handle");
}

Expand Down
Loading

0 comments on commit 8daf2b8

Please sign in to comment.