Enforce a timeout on user SQL queries.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4430 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2023-08-25 20:23:40 +00:00
parent b252b921f8
commit dcea08f73b

View File

@ -26,6 +26,8 @@
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) #define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
#endif #endif
static const int k_sql_async_timeout_ms = 60 * 1000;
static JSClassID _tf_ssb_classId; 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); 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 typedef struct _sql_work_t
{ {
uv_work_t request; uv_work_t request;
uv_async_t async;
uv_timer_t timeout;
tf_ssb_t* ssb; tf_ssb_t* ssb;
sqlite3* db;
uv_mutex_t lock;
const char* query; const char* query;
uint8_t* binds; uint8_t* binds;
size_t binds_count; 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_t* trace = tf_ssb_get_trace(sql_work->ssb);
tf_trace_begin(trace, "sql_async_work"); tf_trace_begin(trace, "sql_async_work");
sqlite3* db = tf_ssb_acquire_db_reader_restricted(sql_work->ssb); 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; sqlite3_stmt* statement = NULL;
sql_work->result = sqlite3_prepare(db, sql_work->query, -1, &statement, NULL); sql_work->result = sqlite3_prepare(db, sql_work->query, -1, &statement, NULL);
if (sql_work->result == SQLITE_OK) if (sql_work->result == SQLITE_OK)
@ -513,7 +523,14 @@ static void _tf_ssb_sqlAsync_work(uv_work_t* work)
sql_work->result = r; sql_work->result = r;
if (r != SQLITE_OK && r != SQLITE_DONE) 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); _tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1);
sqlite3_finalize(statement); 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)); 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_release_db_reader(sql_work->ssb, db);
tf_ssb_record_thread_busy(sql_work->ssb, false); tf_ssb_record_thread_busy(sql_work->ssb, false);
tf_trace_end(trace); 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) static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status)
{ {
sql_work_t* sql_work = work->data; 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); 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, response);
JS_FreeValue(context, row); JS_FreeValue(context, row);
if (is_error)
{
break;
}
} }
else else
{ {
break; break;
} }
} }
tf_free(sql_work->binds);
tf_free(sql_work->rows);
JSValue result = JS_UNDEFINED; JSValue result = JS_UNDEFINED;
if (sql_work->result == SQLITE_OK || sql_work->result == SQLITE_DONE) 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[0]);
JS_FreeValue(context, sql_work->promise[1]); JS_FreeValue(context, sql_work->promise[1]);
JS_FreeValue(context, sql_work->callback); JS_FreeValue(context, sql_work->callback);
JS_FreeCString(context, sql_work->query); _tf_ssb_sqlAsync_destroy(sql_work);
tf_free(sql_work->error);
tf_free(sql_work);
tf_trace_end(trace); 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) 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); 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, .data = work,
}, },
.async =
{
.data = work,
},
.timeout =
{
.data = work,
},
.ssb = ssb, .ssb = ssb,
.callback = JS_DupValue(context, argv[2]), .callback = JS_DupValue(context, argv[2]),
.query = query, .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 result = JS_NewPromiseCapability(context, work->promise);
JSValue error_value = JS_UNDEFINED; JSValue error_value = JS_UNDEFINED;
if (ssb) 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); JSValue call_result = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &error_value);
tf_util_report_error(context, call_result); tf_util_report_error(context, call_result);
JS_FreeValue(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[0]);
JS_FreeValue(context, work->promise[1]); JS_FreeValue(context, work->promise[1]);
JS_FreeValue(context, work->callback); JS_FreeValue(context, work->callback);
JS_FreeValue(context, error_value); _tf_ssb_sqlAsync_destroy(work);
JS_FreeCString(context, query);
tf_free(work->binds);
tf_free(work->error);
tf_free(work);
} }
return result; return result;
} }