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))))
#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;
}