From dcea08f73ba62aaa783e631a0116a7f4a1ae0580 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 25 Aug 2023 20:23:40 +0000 Subject: [PATCH] Enforce a timeout on user SQL queries. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4430 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- src/ssb.js.c | 95 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/src/ssb.js.c b/src/ssb.js.c index 0d1c934d..990c0889 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -26,6 +26,8 @@ #define _countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) #endif +static const int k_sql_async_timeout_ms = 60 * 1000; + static JSClassID _tf_ssb_classId; void _tf_ssb_on_rpc(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data); @@ -403,7 +405,11 @@ static JSValue _tf_ssb_closeConnection(JSContext* context, JSValueConst this_val typedef struct _sql_work_t { uv_work_t request; + uv_async_t async; + uv_timer_t timeout; tf_ssb_t* ssb; + sqlite3* db; + uv_mutex_t lock; const char* query; uint8_t* binds; size_t binds_count; @@ -429,6 +435,10 @@ static void _tf_ssb_sqlAsync_work(uv_work_t* work) tf_trace_t* trace = tf_ssb_get_trace(sql_work->ssb); tf_trace_begin(trace, "sql_async_work"); sqlite3* db = tf_ssb_acquire_db_reader_restricted(sql_work->ssb); + uv_mutex_lock(&sql_work->lock); + sql_work->db = db; + uv_mutex_unlock(&sql_work->lock); + uv_async_send(&sql_work->async); sqlite3_stmt* statement = NULL; sql_work->result = sqlite3_prepare(db, sql_work->query, -1, &statement, NULL); if (sql_work->result == SQLITE_OK) @@ -513,7 +523,14 @@ static void _tf_ssb_sqlAsync_work(uv_work_t* work) sql_work->result = r; if (r != SQLITE_OK && r != SQLITE_DONE) { - sql_work->error = tf_strdup(sqlite3_errmsg(db)); + if (sqlite3_is_interrupted(db)) + { + sql_work->error = tf_strdup("Timed out"); + } + else + { + sql_work->error = tf_strdup(sqlite3_errmsg(db)); + } } _tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1); sqlite3_finalize(statement); @@ -522,11 +539,38 @@ static void _tf_ssb_sqlAsync_work(uv_work_t* work) { sql_work->error = tf_strdup(sqlite3_errmsg(db)); } + uv_mutex_lock(&sql_work->lock); + sql_work->db = NULL; + uv_mutex_unlock(&sql_work->lock); tf_ssb_release_db_reader(sql_work->ssb, db); tf_ssb_record_thread_busy(sql_work->ssb, false); tf_trace_end(trace); } +static void _tf_ssb_sqlAsync_handle_close(uv_handle_t* handle) +{ + sql_work_t* work = handle->data; + handle->data = NULL; + if (!work->async.data && + !work->timeout.data) + { + tf_free(work); + } +} + +static void _tf_ssb_sqlAsync_destroy(sql_work_t* work) +{ + tf_free(work->binds); + tf_free(work->error); + if (work->rows) + { + tf_free(work->rows); + } + uv_mutex_destroy(&work->lock); + uv_close((uv_handle_t*)&work->timeout, _tf_ssb_sqlAsync_handle_close); + uv_close((uv_handle_t*)&work->async, _tf_ssb_sqlAsync_handle_close); +} + static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status) { sql_work_t* sql_work = work->data; @@ -582,17 +626,19 @@ static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status) } JSValue response = JS_Call(context, sql_work->callback, JS_UNDEFINED, 1, &row); - tf_util_report_error(context, response); + bool is_error = tf_util_report_error(context, response); JS_FreeValue(context, response); JS_FreeValue(context, row); + if (is_error) + { + break; + } } else { break; } } - tf_free(sql_work->binds); - tf_free(sql_work->rows); JSValue result = JS_UNDEFINED; if (sql_work->result == SQLITE_OK || sql_work->result == SQLITE_DONE) @@ -613,13 +659,27 @@ static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status) JS_FreeValue(context, sql_work->promise[0]); JS_FreeValue(context, sql_work->promise[1]); JS_FreeValue(context, sql_work->callback); - JS_FreeCString(context, sql_work->query); - - tf_free(sql_work->error); - tf_free(sql_work); + _tf_ssb_sqlAsync_destroy(sql_work); tf_trace_end(trace); } +static void _tf_ssb_sqlAsync_timeout(uv_timer_t* timer) +{ + sql_work_t* work = timer->data; + uv_mutex_lock(&work->lock); + if (work->db) + { + sqlite3_interrupt(work->db); + } + uv_mutex_unlock(&work->lock); +} + +static void _tf_ssb_sqlAsync_start_timer(uv_async_t* async) +{ + sql_work_t* work = async->data; + uv_timer_start(&work->timeout, _tf_ssb_sqlAsync_timeout, k_sql_async_timeout_ms, 0); +} + static JSValue _tf_ssb_sqlAsync(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); @@ -631,10 +691,21 @@ static JSValue _tf_ssb_sqlAsync(JSContext* context, JSValueConst this_val, int a { .data = work, }, + .async = + { + .data = work, + }, + .timeout = + { + .data = work, + }, .ssb = ssb, .callback = JS_DupValue(context, argv[2]), .query = query, }; + uv_mutex_init(&work->lock); + uv_async_init(tf_ssb_get_loop(ssb), &work->async, _tf_ssb_sqlAsync_start_timer); + uv_timer_init(tf_ssb_get_loop(ssb), &work->timeout); JSValue result = JS_NewPromiseCapability(context, work->promise); JSValue error_value = JS_UNDEFINED; if (ssb) @@ -690,14 +761,12 @@ static JSValue _tf_ssb_sqlAsync(JSContext* context, JSValueConst this_val, int a JSValue call_result = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &error_value); tf_util_report_error(context, call_result); JS_FreeValue(context, call_result); + JS_FreeValue(context, error_value); + JS_FreeCString(context, query); JS_FreeValue(context, work->promise[0]); JS_FreeValue(context, work->promise[1]); JS_FreeValue(context, work->callback); - JS_FreeValue(context, error_value); - JS_FreeCString(context, query); - tf_free(work->binds); - tf_free(work->error); - tf_free(work); + _tf_ssb_sqlAsync_destroy(work); } return result; }