quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 9c7f95e72913c686d29d286b6cf69873e9edbb52
parent 764e417aeeeaa1b2f214d6d870147f7f5316d6fe
Author: Florian Dold <dold@taler.net>
Date:   Sun,  9 Aug 2026 21:19:17 +0200

wallet: make runtime shutdown deterministic

Diffstat:
Mqtart.c | 1+
Mquickjs/quickjs-libc.c | 31+++++++++++++++++++++++++++++++
Mquickjs/quickjs-libc.h | 1+
Mtaler_wallet_core_lib.c | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mtaler_wallet_core_lib.h | 6++++--
5 files changed, 261 insertions(+), 18 deletions(-)

diff --git a/qtart.c b/qtart.c @@ -565,6 +565,7 @@ int main(int argc, char **argv) return 0; fail: js_std_free_handlers(rt); + js_curl_http_client_destroy(http_impl); JS_FreeContext(ctx); JS_FreeRuntime(rt); return 1; diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c @@ -210,6 +210,8 @@ typedef struct JSThreadState { // used to provided fairer scheduling of event reactions unsigned int poll_iteration_count; + BOOL stop_requested; + struct list_head http_requests; #ifndef NO_HTTP @@ -3312,6 +3314,13 @@ static int js_os_poll(JSContext *ctx) int http_poll_fd_index; #endif + pthread_mutex_lock(&ts->host_pipe->mutex); + BOOL stop_requested = ts->stop_requested; + pthread_mutex_unlock(&ts->host_pipe->mutex); + if (stop_requested) { + return -1; + } + /* only check signals in the main thread */ if (!ts->is_worker_thread && unlikely(os_pending_signals != 0)) { @@ -4837,6 +4846,28 @@ js_os_post_message_from_host(JSContext *ctx, const char *msg_str) return -1; } +int +js_std_request_stop(JSContext *ctx) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); + JSHostMessagePipe *hp; + uint8_t ch = '\0'; + + if (!ts || !ts->host_pipe) { + return -1; + } + hp = ts->host_pipe; + pthread_mutex_lock(&hp->mutex); + if (!ts->stop_requested) { + ts->stop_requested = TRUE; + while (write(hp->write_fd, &ch, 1) < 0 && errno == EINTR) { + /* retry */ + } + } + pthread_mutex_unlock(&hp->mutex); + return 0; +} + static JSValue js_os_simulateHostMessage(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { diff --git a/quickjs/quickjs-libc.h b/quickjs/quickjs-libc.h @@ -66,6 +66,7 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, void *cls); int js_os_post_message_from_host(JSContext *ctx, const char *msg_str); +int js_std_request_stop(JSContext *ctx); #ifndef NO_HTTP void js_os_set_http_impl(JSRuntime *rt, struct JSHttpClientImplementation *impl); diff --git a/taler_wallet_core_lib.c b/taler_wallet_core_lib.c @@ -32,6 +32,16 @@ extern const uint8_t qjsc_wallet_core[]; extern const uint32_t qjsc_wallet_core_size; static JSClassID js_wallet_instance_handle_id; +static pthread_once_t wallet_class_id_once = PTHREAD_ONCE_INIT; + +static JSClassDef js_wallet_instance_handle_class = { + .class_name = "TalerWalletInstanceHandle", +}; + +static void init_wallet_class_id(void) +{ + JS_NewClassID(&js_wallet_instance_handle_id); +} struct HostMessage { struct list_head link; @@ -42,11 +52,22 @@ struct TALER_WALLET_Instance { // Mutex that is locked while initializing the handle pthread_mutex_t handle_mutex; + pthread_cond_t state_changed; JSRuntime *rt; JSContext *ctx; pthread_t wallet_thread; + int wallet_thread_valid; + + enum { + WALLET_CREATED, + WALLET_STARTING, + WALLET_RUNNING, + WALLET_STOPPING, + WALLET_STOPPED, + WALLET_FAILED, + } state; TALER_WALLET_MessageHandlerFn handler_f; void *handler_cls; @@ -55,6 +76,7 @@ struct TALER_WALLET_Instance void *log_handler_cls; struct JSHttpClientImplementation *http_impl; + int owns_http_impl; }; @@ -116,6 +138,13 @@ TALER_WALLET_send_request (struct TALER_WALLET_Instance *twi, { int ret; pthread_mutex_lock(&twi->handle_mutex); + while (twi->state == WALLET_STARTING) { + pthread_cond_wait(&twi->state_changed, &twi->handle_mutex); + } + if (twi->state != WALLET_RUNNING || !twi->ctx) { + pthread_mutex_unlock(&twi->handle_mutex); + return -1; + } ret = js_os_post_message_from_host(twi->ctx, msg); pthread_mutex_unlock(&twi->handle_mutex); return ret; @@ -137,11 +166,20 @@ struct TALER_WALLET_Instance * TALER_WALLET_create(void) { struct TALER_WALLET_Instance *wh; - wh = malloc(sizeof (*wh)); - memset(wh, 0, sizeof *wh); + wh = calloc(1, sizeof (*wh)); + if (!wh) { + return NULL; + } if (0 != pthread_mutex_init(&wh->handle_mutex, NULL)) { - abort(); + free(wh); + return NULL; + } + if (0 != pthread_cond_init(&wh->state_changed, NULL)) { + pthread_mutex_destroy(&wh->handle_mutex); + free(wh); + return NULL; } + wh->state = WALLET_CREATED; return wh; } @@ -156,6 +194,9 @@ static JSValue js_native_log(JSContext *ctx, const char *msg = NULL; uint32_t level = 0; wh = JS_GetOpaque(func_data[0], js_wallet_instance_handle_id); + if (!wh) { + return JS_ThrowInternalError(ctx, "invalid wallet log context"); + } if (NULL != wh->log_handler_f) { JS_ToUint32(ctx, &level, argv[0]); tag = JS_ToCString(ctx, argv[1]); @@ -175,10 +216,15 @@ static void * run(void *cls) { struct TALER_WALLET_Instance *wh = cls; + int handlers_initialized = 0; wh->rt = JS_NewRuntime(); + if (!wh->rt) { + goto fail; + } js_std_init_handlers(wh->rt); + handlers_initialized = 1; if (wh->http_impl) { js_os_set_http_impl(wh->rt, wh->http_impl); @@ -190,11 +236,14 @@ run(void *cls) if (!wh->ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); - pthread_mutex_unlock(&wh->handle_mutex); - return NULL; + goto fail; } - JS_NewClassID(&js_wallet_instance_handle_id); + if (0 != pthread_once(&wallet_class_id_once, init_wallet_class_id) || + 0 != JS_NewClass(wh->rt, js_wallet_instance_handle_id, + &js_wallet_instance_handle_class)) { + goto fail; + } JS_SetHostPromiseRejectionTracker(wh->rt, js_std_promise_rejection_tracker, @@ -208,11 +257,16 @@ run(void *cls) JSValue data; data = JS_NewObjectClass(wh->ctx, js_wallet_instance_handle_id); + if (JS_IsException(data)) { + goto fail; + } JS_SetOpaque(data, wh); global_obj = JS_GetGlobalObject(wh->ctx); // We could also try to add this to "os" or "tart", but we are lazy. JS_SetPropertyStr(wh->ctx, global_obj, "__nativeLog", JS_NewCFunctionData(wh->ctx, js_native_log, 3, 0, 1, &data)); + JS_FreeValue(wh->ctx, global_obj); + JS_FreeValue(wh->ctx, data); } // fprintf(stderr, "qtart: loading JS code\n"); @@ -223,12 +277,50 @@ run(void *cls) js_os_set_host_message_handler(wh->ctx, wallet_host_message_handler, wh); - pthread_mutex_unlock(&wh->handle_mutex); + if (0 != eval_buf(wh->ctx, "installNativeWalletListener()", + "<talerwalletcore>")) { + goto fail; + } - eval_buf(wh->ctx, "installNativeWalletListener()", "<talerwalletcore>"); + pthread_mutex_lock(&wh->handle_mutex); + wh->state = WALLET_RUNNING; + pthread_cond_broadcast(&wh->state_changed); + pthread_mutex_unlock(&wh->handle_mutex); js_std_loop(wh->ctx); + pthread_mutex_lock(&wh->handle_mutex); + wh->state = WALLET_STOPPING; + pthread_cond_broadcast(&wh->state_changed); + pthread_mutex_unlock(&wh->handle_mutex); + js_std_free_handlers(wh->rt); + handlers_initialized = 0; + JS_FreeContext(wh->ctx); + JS_FreeRuntime(wh->rt); + pthread_mutex_lock(&wh->handle_mutex); + wh->ctx = NULL; + wh->rt = NULL; + wh->state = WALLET_STOPPED; + pthread_cond_broadcast(&wh->state_changed); + pthread_mutex_unlock(&wh->handle_mutex); + return NULL; + +fail: + if (handlers_initialized) { + js_std_free_handlers(wh->rt); + } + if (wh->ctx) { + JS_FreeContext(wh->ctx); + } + if (wh->rt) { + JS_FreeRuntime(wh->rt); + } + pthread_mutex_lock(&wh->handle_mutex); + wh->ctx = NULL; + wh->rt = NULL; + wh->state = WALLET_FAILED; + pthread_cond_broadcast(&wh->state_changed); + pthread_mutex_unlock(&wh->handle_mutex); return NULL; } @@ -240,42 +332,103 @@ TALER_WALLET_run (struct TALER_WALLET_Instance *wh) pthread_t wallet_thread; pthread_attr_t tattr; + if (!wh) { + return -1; + } pthread_mutex_lock(&wh->handle_mutex); + if (wh->state != WALLET_CREATED) { + pthread_mutex_unlock(&wh->handle_mutex); + return -1; + } + wh->state = WALLET_STARTING; if (0 != pthread_attr_init(&tattr)) { + wh->state = WALLET_CREATED; pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not initialize pthread attr\n"); return -1; } if (0 != pthread_attr_setstacksize(&tattr, WALLET_THREAD_STACK_SIZE)) { + pthread_attr_destroy(&tattr); + wh->state = WALLET_CREATED; pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not set stack size\n"); return -1; } if (0 != pthread_create(&wallet_thread, &tattr, run, wh)) { + pthread_attr_destroy(&tattr); + wh->state = WALLET_CREATED; pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not create wallet thread\n"); return -1; } wh->wallet_thread = wallet_thread; + wh->wallet_thread_valid = 1; + pthread_attr_destroy(&tattr); + pthread_mutex_unlock(&wh->handle_mutex); return 0; } void TALER_WALLET_join(struct TALER_WALLET_Instance *wh) { - pthread_join(wh->wallet_thread, NULL); + pthread_t thread; + + if (!wh) { + return; + } + pthread_mutex_lock(&wh->handle_mutex); + if (!wh->wallet_thread_valid) { + pthread_mutex_unlock(&wh->handle_mutex); + return; + } + thread = wh->wallet_thread; + pthread_mutex_unlock(&wh->handle_mutex); + if (!pthread_equal(pthread_self(), thread)) { + pthread_join(thread, NULL); + pthread_mutex_lock(&wh->handle_mutex); + wh->wallet_thread_valid = 0; + pthread_mutex_unlock(&wh->handle_mutex); + } } void TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi) { - pthread_kill(twi->wallet_thread, SIGKILL); + pthread_t thread; + int have_thread; + + if (!twi) { + return; + } + pthread_mutex_lock(&twi->handle_mutex); + while (twi->state == WALLET_STARTING) { + pthread_cond_wait(&twi->state_changed, &twi->handle_mutex); + } + have_thread = twi->wallet_thread_valid; + thread = twi->wallet_thread; + if (twi->state == WALLET_RUNNING && twi->ctx) { + js_std_request_stop(twi->ctx); + } + pthread_mutex_unlock(&twi->handle_mutex); + + if (have_thread && pthread_equal(pthread_self(), thread)) { + return; + } + if (have_thread) { + pthread_join(thread, NULL); + } + if (twi->owns_http_impl) { + js_curl_http_client_destroy(twi->http_impl); + } + pthread_cond_destroy(&twi->state_changed); + pthread_mutex_destroy(&twi->handle_mutex); + free(twi); } @@ -289,9 +442,11 @@ static struct LogRedirectContext redir_ctx; static int pfd[2]; static pthread_t log_thr; -static void *thread_func(void *) { +static void *thread_func(void *cls) { ssize_t rdsz; char buf[1024]; + + (void) cls; while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { if (buf[rdsz - 1] == '\n') --rdsz; buf[rdsz] = 0; /* add null-terminator */ @@ -304,6 +459,12 @@ static void *thread_func(void *) { int TALER_start_redirect_std(TALER_LogFn logfn, void *cls) { + int saved_stdout; + int saved_stderr; + + if (!logfn) { + return -1; + } if (redir_ctx.active) { return -2; } @@ -312,18 +473,48 @@ TALER_start_redirect_std(TALER_LogFn logfn, void *cls) setvbuf(stderr, 0, _IONBF, 0); /* create the pipe and redirect stdout and stderr */ - pipe(pfd); - dup2(pfd[1], 1); - dup2(pfd[1], 2); - + if (pipe(pfd) != 0) { + return -1; + } + saved_stdout = dup(STDOUT_FILENO); + saved_stderr = dup(STDERR_FILENO); + if (saved_stdout < 0 || saved_stderr < 0 || + dup2(pfd[1], STDOUT_FILENO) < 0 || + dup2(pfd[1], STDERR_FILENO) < 0) { + if (saved_stdout >= 0) { + (void) dup2(saved_stdout, STDOUT_FILENO); + } + if (saved_stderr >= 0) { + (void) dup2(saved_stderr, STDERR_FILENO); + } + if (saved_stdout >= 0) { + close(saved_stdout); + } + if (saved_stderr >= 0) { + close(saved_stderr); + } + close(pfd[0]); + close(pfd[1]); + return -1; + } redir_ctx.cls = cls; redir_ctx.logfn = logfn; redir_ctx.active = 1; /* spawn the logging thread */ - if (pthread_create(&log_thr, 0, thread_func, 0) == -1) { + if (pthread_create(&log_thr, 0, thread_func, 0) != 0) { + (void) dup2(saved_stdout, STDOUT_FILENO); + (void) dup2(saved_stderr, STDERR_FILENO); + close(saved_stdout); + close(saved_stderr); + close(pfd[0]); + close(pfd[1]); + redir_ctx.active = 0; return -1; } + close(saved_stdout); + close(saved_stderr); + close(pfd[1]); pthread_detach(log_thr); return 0; } @@ -332,7 +523,17 @@ void TALER_set_http_client_implementation(struct TALER_WALLET_Instance *twi, struct JSHttpClientImplementation *impl) { + pthread_mutex_lock(&twi->handle_mutex); + if (twi->state != WALLET_CREATED) { + pthread_mutex_unlock(&twi->handle_mutex); + return; + } + if (twi->owns_http_impl) { + js_curl_http_client_destroy(twi->http_impl); + } twi->http_impl = impl; + twi->owns_http_impl = 0; + pthread_mutex_unlock(&twi->handle_mutex); } #ifndef NO_CURL @@ -341,6 +542,13 @@ TALER_set_curl_http_client(struct TALER_WALLET_Instance *twi) { struct JSHttpClientImplementation *client = js_curl_http_client_create(); TALER_set_http_client_implementation(twi, client); + pthread_mutex_lock(&twi->handle_mutex); + if (twi->http_impl == client) { + twi->owns_http_impl = client != NULL; + } else { + js_curl_http_client_destroy(client); + } + pthread_mutex_unlock(&twi->handle_mutex); } #endif diff --git a/taler_wallet_core_lib.h b/taler_wallet_core_lib.h @@ -131,9 +131,10 @@ TALER_WALLET_join(struct TALER_WALLET_Instance *twi); * an appropriate shutdown message should be sent first, * and destroy() should only be called after the wallet has * sent a response to the shutdown message. + * Must not be called from a wallet message or log callback. */ -//void -//TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi); +void +TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi); /** * Handler for messages that should be logged. @@ -155,6 +156,7 @@ TALER_start_redirect_std(TALER_LogFn logfn, void *handler_p); /** * Set the HTTP client implementation to be used by the wallet. + * Must be called before #TALER_WALLET_run. * * @param twi wallet-core instance * @param impl HTTP client implementation