Move database.get and database.set off the main thread.

This commit is contained in:
2024-06-10 15:30:14 -04:00
parent e5fee5c306
commit c259defab5
4 changed files with 194 additions and 1153 deletions

View File

@ -4,6 +4,7 @@
#include "mem.h"
#include "ssb.h"
#include "task.h"
#include "util.js.h"
#include "sqlite3.h"
@ -91,57 +92,159 @@ static void _database_finalizer(JSRuntime* runtime, JSValue value)
--_database_count;
}
typedef struct _database_get_t
{
const char* id;
const char* key;
size_t key_length;
char* out_value;
size_t out_length;
JSValue promise[2];
} database_get_t;
static void _database_get_work(tf_ssb_t* ssb, void* user_data)
{
database_get_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_value = data;
work->out_length = length;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
if (work->out_value)
{
result = JS_NewStringLen(context, work->out_value, work->out_length);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work->out_value);
tf_free(work);
}
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue entry = JS_UNDEFINED;
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
size_t length;
const char* key = JS_ToCStringLen(context, &length, argv[0]);
database_get_t* work = tf_malloc(sizeof(database_get_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_t)
{
size_t length;
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0));
}
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return entry;
return result;
}
typedef struct _database_set_t
{
const char* id;
const char* key;
size_t key_length;
const char* value;
size_t value_length;
bool result;
JSValue promise[2];
} database_set_t;
static void _database_set_work(tf_ssb_t* ssb, void* user_data)
{
database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
{
work->result = true;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _database_set_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_set_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
size_t key_length = 0;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
size_t value_length = 0;
const char* value = JS_ToCStringLen(context, &value_length, argv[1]);
database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1);
*work = (database_set_t)
{
size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
size_t valueLength;
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
{
}
JS_FreeCString(context, keyString);
JS_FreeCString(context, valueString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
.key_length = key_length,
.value_length = value_length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, key_length + 1);
memcpy((char*)work->value, value, value_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_set_work, _database_set_after_work, work);
JS_FreeCString(context, key);
JS_FreeCString(context, value);
}
return JS_UNDEFINED;
return result;
}
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)

View File

@ -266,32 +266,35 @@ static void _test_promise_remote_reject(const tf_test_options_t* options)
static void _test_database(const tf_test_options_t* options)
{
_write_file("out/test.js",
"var db = new Database('testdb');\n"
"if (db.get('a')) {\n"
" exit(1);\n"
"}\n"
"db.set('a', 1);\n"
"if (db.get('a') != 1) {\n"
" exit(2);\n"
"}\n"
"db.set('b', 2);\n"
"db.set('c', 3);\n"
"async function main() {\n"
" var db = new Database('testdb');\n"
" if (await db.get('a')) {\n"
" exit(1);\n"
" }\n"
" await db.set('a', 1);\n"
" if (await db.get('a') != 1) {\n"
" exit(2);\n"
" }\n"
" await db.set('b', 2);\n"
" await db.set('c', 3);\n"
"\n"
"var expected = ['a', 'b', 'c'];\n"
"var have = db.getAll();\n"
"for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n"
" exit(3);\n"
" } else {\n"
" expected.splice(expected.indexOf(item), 1);\n"
" var expected = ['a', 'b', 'c'];\n"
" var have = db.getAll();\n"
" for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n"
" exit(3);\n"
" } else {\n"
" expected.splice(expected.indexOf(item), 1);\n"
" }\n"
" }\n"
" if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
" }\n"
"}\n"
"if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
"}\n");
"main();");
char command[256];
unlink("out/test_db0.sqlite");