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

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

View File

@ -206,7 +206,7 @@ function getUser(caller, process) {
* @param {*} process * @param {*} process
* @returns * @returns
*/ */
function getApps(user, process) { async function getApps(user, process) {
if ( if (
process.credentials && process.credentials &&
process.credentials.session && process.credentials.session &&
@ -221,10 +221,12 @@ function getApps(user, process) {
if (user) { if (user) {
let db = new Database(user); let db = new Database(user);
try { try {
let names = JSON.parse(db.get('apps')); let names = JSON.parse(await db.get('apps'));
return Object.fromEntries( let result = {};
names.map((name) => [name, db.get('path:' + name)]) for (let name of names) {
); result[name] = await db.get('path:' + name);
}
return result;
} catch {} } catch {}
} }
return {}; return {};
@ -320,9 +322,9 @@ async function getProcessBlob(blobId, key, options) {
} }
}, },
user: getUser(process, process), user: getUser(process, process),
users: function () { users: async function () {
try { try {
return JSON.parse(new Database('auth').get('users')); return JSON.parse(await new Database('auth').get('users'));
} catch { } catch {
return []; return [];
} }
@ -509,25 +511,20 @@ async function getProcessBlob(blobId, key, options) {
setGlobalSettings(gGlobalSettings); setGlobalSettings(gGlobalSettings);
print('Done.'); print('Done.');
}; };
imports.core.deleteUser = function (user) { imports.core.deleteUser = async function (user) {
return Promise.resolve( await imports.core.permissionTest('delete_user')
imports.core.permissionTest('delete_user') let db = new Database('auth');
).then(function () { db.remove('user:' + user);
let db = new Database('auth'); let users = new Set();
let users_original = await db.get('users');
db.remove('user:' + user); try {
users = new Set(JSON.parse(users_original));
let users = new Set(); } catch {}
let users_original = db.get('users'); users.delete(user);
try { users = JSON.stringify([...users].sort());
users = new Set(JSON.parse(users_original)); if (users !== users_original) {
} catch {} await db.set('users', users);
users.delete(user); }
users = JSON.stringify([...users].sort());
if (users !== users_original) {
db.set('users', users);
}
});
}; };
} }
if (options.api) { if (options.api) {
@ -806,10 +803,10 @@ async function getProcessBlob(blobId, key, options) {
* @param {*} settings * @param {*} settings
* @returns * @returns
*/ */
function setGlobalSettings(settings) { async function setGlobalSettings(settings) {
gGlobalSettings = settings; gGlobalSettings = settings;
try { try {
return new Database('core').set('settings', JSON.stringify(settings)); return await new Database('core').set('settings', JSON.stringify(settings));
} catch (error) { } catch (error) {
print('Error storing settings:', error); print('Error storing settings:', error);
} }
@ -1052,7 +1049,7 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user); let database = new Database(user);
let app_object = JSON.parse(utf8Decode(request.body)); let app_object = JSON.parse(utf8Decode(request.body));
let previous_id = database.get('path:' + appName); let previous_id = await database.get('path:' + appName);
if (previous_id) { if (previous_id) {
try { try {
let previous_object = JSON.parse( let previous_object = JSON.parse(
@ -1073,7 +1070,7 @@ async function blobHandler(request, response, blobId, uri) {
let newBlobId = await ssb.blobStore(JSON.stringify(app_object)); let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
let apps = new Set(); let apps = new Set();
let apps_original = database.get('apps'); let apps_original = await database.get('apps');
try { try {
apps = new Set(JSON.parse(apps_original)); apps = new Set(JSON.parse(apps_original));
} catch {} } catch {}
@ -1082,9 +1079,9 @@ async function blobHandler(request, response, blobId, uri) {
} }
apps = JSON.stringify([...apps].sort()); apps = JSON.stringify([...apps].sort());
if (apps != apps_original) { if (apps != apps_original) {
database.set('apps', apps); await database.set('apps', apps);
} }
database.set('path:' + appName, newBlobId); await database.set('path:' + appName, newBlobId);
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('/' + newBlobId); response.end('/' + newBlobId);
} else { } else {
@ -1115,10 +1112,10 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user); let database = new Database(user);
let apps = new Set(); let apps = new Set();
try { try {
apps = new Set(JSON.parse(database.get('apps'))); apps = new Set(JSON.parse(await database.get('apps')));
} catch {} } catch {}
if (apps.delete(appName)) { if (apps.delete(appName)) {
database.set('apps', JSON.stringify([...apps].sort())); await database.set('apps', JSON.stringify([...apps].sort()));
} }
database.remove('path:' + appName); database.remove('path:' + appName);
} else { } else {
@ -1230,7 +1227,7 @@ ssb.addEventListener('connections', function () {
async function loadSettings() { async function loadSettings() {
let data = {}; let data = {};
try { try {
let settings = new Database('core').get('settings'); let settings = await new Database('core').get('settings');
if (settings) { if (settings) {
data = JSON.parse(settings); data = JSON.parse(settings);
} }

1062
run.log

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
#include "mem.h" #include "mem.h"
#include "ssb.h" #include "ssb.h"
#include "task.h" #include "task.h"
#include "util.js.h"
#include "sqlite3.h" #include "sqlite3.h"
@ -91,57 +92,159 @@ static void _database_finalizer(JSRuntime* runtime, JSValue value)
--_database_count; --_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) 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); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); size_t length;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) 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; .id = (const char*)(work + 1),
const char* keyString = JS_ToCStringLen(context, &length, argv[0]); .key = (const char*)(work + 1) + strlen(database->id) + 1,
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK && .key_length = length,
sqlite3_step(statement) == SQLITE_ROW) };
{ memcpy((char*)work->id, database->id, strlen(database->id) + 1);
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)); memcpy((char*)work->key, key, length + 1);
} JS_FreeCString(context, key);
JS_FreeCString(context, keyString);
sqlite3_finalize(statement); tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
} result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_release_db_reader(ssb, db);
} }
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) 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); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task); 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; .id = (const char*)(work + 1),
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]); .key = (const char*)(work + 1) + strlen(database->id) + 1,
size_t valueLength; .value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]); .key_length = key_length,
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK && .value_length = value_length,
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK) };
{
} memcpy((char*)work->id, database->id, strlen(database->id) + 1);
JS_FreeCString(context, keyString); memcpy((char*)work->key, key, key_length + 1);
JS_FreeCString(context, valueString); memcpy((char*)work->value, value, value_length + 1);
sqlite3_finalize(statement);
} result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_release_db_writer(ssb, db); 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) 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) static void _test_database(const tf_test_options_t* options)
{ {
_write_file("out/test.js", _write_file("out/test.js",
"var db = new Database('testdb');\n" "async function main() {\n"
"if (db.get('a')) {\n" " var db = new Database('testdb');\n"
" exit(1);\n" " if (await db.get('a')) {\n"
"}\n" " exit(1);\n"
"db.set('a', 1);\n" " }\n"
"if (db.get('a') != 1) {\n" " await db.set('a', 1);\n"
" exit(2);\n" " if (await db.get('a') != 1) {\n"
"}\n" " exit(2);\n"
"db.set('b', 2);\n" " }\n"
"db.set('c', 3);\n" " await db.set('b', 2);\n"
" await db.set('c', 3);\n"
"\n" "\n"
"var expected = ['a', 'b', 'c'];\n" " var expected = ['a', 'b', 'c'];\n"
"var have = db.getAll();\n" " var have = db.getAll();\n"
"for (var i = 0; i < have.length; i++) {\n" " for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n" " var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n" " if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n" " print('Did not find ' + item + ' in db.');\n"
" exit(3);\n" " exit(3);\n"
" } else {\n" " } else {\n"
" expected.splice(expected.indexOf(item), 1);\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"
"}\n" "}\n"
"if (expected.length) {\n" "main();");
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
"}\n");
char command[256]; char command[256];
unlink("out/test_db0.sqlite"); unlink("out/test_db0.sqlite");