Move database.get and database.set off the main thread.
This commit is contained in:
		
							
								
								
									
										47
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								core/core.js
									
									
									
									
									
								
							| @@ -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') |  | ||||||
| 					).then(function () { |  | ||||||
| 					let db = new Database('auth'); | 					let db = new Database('auth'); | ||||||
|  |  | ||||||
| 					db.remove('user:' + user); | 					db.remove('user:' + user); | ||||||
|  |  | ||||||
| 					let users = new Set(); | 					let users = new Set(); | ||||||
| 						let users_original = db.get('users'); | 					let users_original = await db.get('users'); | ||||||
| 					try { | 					try { | ||||||
| 						users = new Set(JSON.parse(users_original)); | 						users = new Set(JSON.parse(users_original)); | ||||||
| 					} catch {} | 					} catch {} | ||||||
| 					users.delete(user); | 					users.delete(user); | ||||||
| 					users = JSON.stringify([...users].sort()); | 					users = JSON.stringify([...users].sort()); | ||||||
| 					if (users !== users_original) { | 					if (users !== users_original) { | ||||||
| 							db.set('users', users); | 						await 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); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -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; | ||||||
| } | } | ||||||
|  |  | ||||||
| static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | typedef struct _database_get_t | ||||||
| { | { | ||||||
| 	JSValue entry = JS_UNDEFINED; | 	const char* id; | ||||||
| 	database_t* database = JS_GetOpaque(this_val, _database_class_id); | 	const char* key; | ||||||
| 	if (database) | 	size_t key_length; | ||||||
| 	{ | 	char* out_value; | ||||||
| 		tf_ssb_t* ssb = tf_task_get_ssb(database->task); | 	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_stmt* statement; | ||||||
| 	sqlite3* db = tf_ssb_acquire_db_reader(ssb); | 	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_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) | ||||||
| 	{ | 	{ | ||||||
| 			size_t length; | 		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 && | ||||||
| 			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) | 			sqlite3_step(statement) == SQLITE_ROW) | ||||||
| 		{ | 		{ | ||||||
| 				entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)); | 			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; | ||||||
| 		} | 		} | ||||||
| 			JS_FreeCString(context, keyString); |  | ||||||
| 		sqlite3_finalize(statement); | 		sqlite3_finalize(statement); | ||||||
| 	} | 	} | ||||||
| 	tf_ssb_release_db_reader(ssb, db); | 	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); | ||||||
| 	} | 	} | ||||||
| 	return entry; | 	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 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); | ||||||
|  |  | ||||||
|  | 		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) | ||||||
|  | 		{ | ||||||
|  | 			.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 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 keyLength; | 		size_t value_length = 0; | ||||||
| 			const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]); | 		const char* value = JS_ToCStringLen(context, &value_length, argv[1]); | ||||||
| 			size_t valueLength; |  | ||||||
| 			const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]); | 		database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1); | ||||||
| 			if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK && | 		*work = (database_set_t) | ||||||
| 				sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK) |  | ||||||
| 		{ | 		{ | ||||||
|  | 			.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); | ||||||
| 	} | 	} | ||||||
| 			JS_FreeCString(context, keyString); | 	return result; | ||||||
| 			JS_FreeCString(context, valueString); |  | ||||||
| 			sqlite3_finalize(statement); |  | ||||||
| 		} |  | ||||||
| 		tf_ssb_release_db_writer(ssb, db); |  | ||||||
| 	} |  | ||||||
| 	return JS_UNDEFINED; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| 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) | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								src/tests.c
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								src/tests.c
									
									
									
									
									
								
							| @@ -266,20 +266,21 @@ 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" | ||||||
|  | 		"	if (await db.get('a')) {\n" | ||||||
| 		"		exit(1);\n" | 		"		exit(1);\n" | ||||||
| 		"}\n" | 		"	}\n" | ||||||
| 		"db.set('a', 1);\n" | 		"	await db.set('a', 1);\n" | ||||||
| 		"if (db.get('a') != 1) {\n" | 		"	if (await db.get('a') != 1) {\n" | ||||||
| 		"		exit(2);\n" | 		"		exit(2);\n" | ||||||
| 		"}\n" | 		"	}\n" | ||||||
| 		"db.set('b', 2);\n" | 		"	await db.set('b', 2);\n" | ||||||
| 		"db.set('c', 3);\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" | ||||||
| @@ -287,11 +288,13 @@ static void _test_database(const tf_test_options_t* options) | |||||||
| 		"		} else {\n" | 		"		} else {\n" | ||||||
| 		"			expected.splice(expected.indexOf(item), 1);\n" | 		"			expected.splice(expected.indexOf(item), 1);\n" | ||||||
| 		"		}\n" | 		"		}\n" | ||||||
| 		"}\n" | 		"	}\n" | ||||||
| 		"if (expected.length) {\n" | 		"	if (expected.length) {\n" | ||||||
| 		"		print('Expected but did not find: ' + JSON.stringify(expected));\n" | 		"		print('Expected but did not find: ' + JSON.stringify(expected));\n" | ||||||
| 		"		exit(4);\n" | 		"		exit(4);\n" | ||||||
| 		"}\n"); | 		"	}\n" | ||||||
|  | 		"}\n" | ||||||
|  | 		"main();"); | ||||||
|  |  | ||||||
| 	char command[256]; | 	char command[256]; | ||||||
| 	unlink("out/test_db0.sqlite"); | 	unlink("out/test_db0.sqlite"); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user