forked from cory/tildefriends
1348 lines
41 KiB
C
1348 lines
41 KiB
C
|
#include "task.h"
|
||
|
|
||
|
#include "bcrypt.h"
|
||
|
#include "database.h"
|
||
|
#include "file.h"
|
||
|
#include "packetstream.h"
|
||
|
#include "serialize.h"
|
||
|
#include "socket.h"
|
||
|
#include "ssb.h"
|
||
|
#include "ssb.qjs.h"
|
||
|
#include "taskstub.h"
|
||
|
#include "tlscontextwrapper.h"
|
||
|
#include "trace.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <uv.h>
|
||
|
|
||
|
#include <sqlite3.h>
|
||
|
|
||
|
#include "quickjs.h"
|
||
|
#include "quickjs-libc.h"
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
static const int STDIN_FILENO = 0;
|
||
|
#else
|
||
|
#include <unistd.h>
|
||
|
#endif
|
||
|
|
||
|
static const char* k_version = "1.0";
|
||
|
|
||
|
static JSClassID _import_class_id;
|
||
|
static int _count;
|
||
|
|
||
|
static void _task_timeoutCallback(uv_timer_t* handle);
|
||
|
|
||
|
typedef struct _export_record_t export_record_t;
|
||
|
typedef struct _import_record_t import_record_t;
|
||
|
|
||
|
typedef struct _task_child_node_t task_child_node_t;
|
||
|
|
||
|
typedef struct _task_child_node_t {
|
||
|
taskid_t id;
|
||
|
tf_taskstub_t* stub;
|
||
|
task_child_node_t* next;
|
||
|
} task_child_node_t;
|
||
|
|
||
|
typedef struct _script_export_t script_export_t;
|
||
|
|
||
|
typedef struct _script_export_t {
|
||
|
const char* name;
|
||
|
JSValue value;
|
||
|
script_export_t* next;
|
||
|
} script_export_t;
|
||
|
|
||
|
typedef struct _promise_t promise_t;
|
||
|
typedef struct _promise_t {
|
||
|
promiseid_t id;
|
||
|
JSValue values[3];
|
||
|
promise_t* next;
|
||
|
} promise_t;
|
||
|
|
||
|
typedef struct _tf_task_t {
|
||
|
taskid_t _nextTask;
|
||
|
task_child_node_t* _children;
|
||
|
int _child_count;
|
||
|
|
||
|
tf_taskstub_t* _stub;
|
||
|
tf_taskstub_t* _parent;
|
||
|
const char* _path;
|
||
|
|
||
|
script_export_t* _scriptExports;
|
||
|
|
||
|
bool _trusted;
|
||
|
bool _killed;
|
||
|
int32_t _exitCode;
|
||
|
char _scriptName[256];
|
||
|
|
||
|
JSRuntime* _runtime;
|
||
|
JSContext* _context;
|
||
|
|
||
|
sqlite3* _db;
|
||
|
|
||
|
tf_ssb_t* _ssb;
|
||
|
|
||
|
tf_trace_t* _trace;
|
||
|
|
||
|
promise_t* _promises;
|
||
|
int _promise_count;
|
||
|
promiseid_t _nextPromise;
|
||
|
uv_loop_t* _loop;
|
||
|
|
||
|
export_record_t* _exports;
|
||
|
int _export_count;
|
||
|
exportid_t _nextExport;
|
||
|
|
||
|
import_record_t* _imports;
|
||
|
int _import_count;
|
||
|
JSValue _requires;
|
||
|
JSValue _loadedFiles;
|
||
|
|
||
|
int _ssb_port;
|
||
|
int _http_port;
|
||
|
int _https_port;
|
||
|
const char* _db_path;
|
||
|
const char* _secrets_path;
|
||
|
} tf_task_t;
|
||
|
|
||
|
typedef struct _export_record_t {
|
||
|
exportid_t _export_id;
|
||
|
taskid_t _taskid;
|
||
|
JSValue _function;
|
||
|
int _useCount;
|
||
|
export_record_t* _next;
|
||
|
} export_record_t;
|
||
|
|
||
|
static void _export_record_ref(export_record_t* export) {
|
||
|
export->_useCount++;
|
||
|
}
|
||
|
|
||
|
static bool _export_record_release(export_record_t* export) {
|
||
|
return --export->_useCount == 0;
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_utf8Decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_get_parent(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_childRequire(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_trace(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||
|
|
||
|
static void _tf_task_sendPromiseMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, JSValue result);
|
||
|
static void _tf_task_sendPromiseResolve(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result);
|
||
|
static void _tf_task_sendPromiseReject(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result);
|
||
|
|
||
|
static void _tf_task_sendPromiseExportMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promiseId, exportid_t exportId, JSValue result);
|
||
|
static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name);
|
||
|
static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id);
|
||
|
static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId);
|
||
|
|
||
|
typedef struct _import_record_t {
|
||
|
JSValue _function;
|
||
|
exportid_t _export;
|
||
|
taskid_t _task;
|
||
|
tf_task_t* _owner;
|
||
|
int _useCount;
|
||
|
import_record_t* _next;
|
||
|
} import_record_t;
|
||
|
|
||
|
static void _tf_task_trace_imports(tf_task_t* task)
|
||
|
{
|
||
|
tf_trace_counter(task->_trace, "imports", 1, (const char*[]) { "count" }, (int64_t[]) { task->_import_count });
|
||
|
}
|
||
|
|
||
|
static void _import_record_release(import_record_t* import) {
|
||
|
JSContext* context = import->_owner->_context;
|
||
|
if (--import->_useCount > 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
JS_SetOpaque(import->_function, NULL);
|
||
|
JS_FreeValue(context, import->_function);
|
||
|
import->_function = JS_UNDEFINED;
|
||
|
_tf_task_release_export(_tf_task_get_stub(import->_owner, import->_task), import->_export);
|
||
|
|
||
|
for (import_record_t** it = &import->_owner->_imports;
|
||
|
*it;
|
||
|
it = &(*it)->_next) {
|
||
|
if (*it == import) {
|
||
|
*it = import->_next;
|
||
|
import->_owner->_import_count--;
|
||
|
_tf_task_trace_imports(import->_owner);
|
||
|
import->_next = NULL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(import);
|
||
|
}
|
||
|
|
||
|
static void _import_record_release_for_task(tf_task_t* task, taskid_t task_id)
|
||
|
{
|
||
|
bool any = false;
|
||
|
import_record_t** next = NULL;
|
||
|
for (import_record_t** it = &task->_imports;
|
||
|
*it;
|
||
|
it = next) {
|
||
|
next = &(*it)->_next;
|
||
|
if ((*it)->_task == task_id) {
|
||
|
import_record_t* import = *it;
|
||
|
_import_record_release(import);
|
||
|
next = it;
|
||
|
any = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (any) {
|
||
|
_tf_task_trace_imports(task);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_trace_exports(tf_task_t* task)
|
||
|
{
|
||
|
tf_trace_counter(task->_trace, "exports", 1, (const char*[]) { "count" }, (int64_t[]) { task->_export_count });
|
||
|
}
|
||
|
|
||
|
static void _export_record_release_for_task(tf_task_t* task, taskid_t task_id)
|
||
|
{
|
||
|
bool any = false;
|
||
|
export_record_t** next = NULL;
|
||
|
for (export_record_t** it = &task->_exports;
|
||
|
*it;
|
||
|
it = next) {
|
||
|
next = &(*it)->_next;
|
||
|
if ((*it)->_taskid == task_id) {
|
||
|
export_record_t* export = *it;
|
||
|
if (_export_record_release(export)) {
|
||
|
*it = export->_next;
|
||
|
JS_FreeValue(task->_context, export->_function);
|
||
|
free(export);
|
||
|
task->_export_count--;
|
||
|
_tf_task_trace_exports(task);
|
||
|
next = it;
|
||
|
any = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (any) {
|
||
|
_tf_task_trace_exports(task);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_send_error_to_parent(tf_task_t* task, JSValue error) {
|
||
|
if (task->_parent) {
|
||
|
void* buffer = NULL;
|
||
|
size_t size = 0;
|
||
|
tf_serialize_store(task, task->_parent, &buffer, &size, error);
|
||
|
tf_packetstream_send(tf_taskstub_get_stream(task->_parent), kTaskError, buffer, size);
|
||
|
free(buffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void tf_task_report_error(tf_task_t* task, JSValue error) {
|
||
|
JSContext* context = task->_context;
|
||
|
if (JS_IsError(context, error)) {
|
||
|
const char* value = JS_ToCString(context, error);
|
||
|
printf("ERROR: %s\n", value);
|
||
|
JS_FreeCString(context, value);
|
||
|
|
||
|
JSValue stack = JS_GetPropertyStr(context, error, "stack");
|
||
|
if (!JS_IsUndefined(stack)) {
|
||
|
const char* stack_str = JS_ToCString(context, stack);
|
||
|
printf("%s\n", stack_str);
|
||
|
JS_FreeCString(context, stack_str);
|
||
|
}
|
||
|
JS_FreeValue(context, stack);
|
||
|
|
||
|
_tf_task_send_error_to_parent(task, error);
|
||
|
} else if (JS_IsException(error)) {
|
||
|
error = JS_GetException(context);
|
||
|
const char* value = JS_ToCString(context, error);
|
||
|
printf("Exception: %s\n", value);
|
||
|
JS_FreeCString(context, value);
|
||
|
_tf_task_send_error_to_parent(task, error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const char* _task_loadFile(const char* fileName) {
|
||
|
char* result = NULL;
|
||
|
FILE* file = fopen(fileName, "rb");
|
||
|
if (file) {
|
||
|
fseek(file, 0, SEEK_END);
|
||
|
long fileSize = ftell(file);
|
||
|
fseek(file, 0, SEEK_SET);
|
||
|
result = malloc(fileSize + 1);
|
||
|
fread(result, 1, fileSize, file);
|
||
|
result[fileSize] = '\0';
|
||
|
fclose(file);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
JSValue _tf_task_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
printf("Task[%p:%s]>", task, task->_scriptName);
|
||
|
for (int i = 0; i < argc; ++i) {
|
||
|
if (JS_IsNull(argv[i])) {
|
||
|
printf(" null");
|
||
|
} else {
|
||
|
const char* value = JS_ToCString(task->_context, argv[i]);
|
||
|
printf(" %s", value);
|
||
|
JS_FreeCString(task->_context, value);
|
||
|
}
|
||
|
}
|
||
|
printf("\n");
|
||
|
return JS_NULL;
|
||
|
}
|
||
|
|
||
|
typedef struct _timeout_t {
|
||
|
tf_task_t* _task;
|
||
|
JSValue _callback;
|
||
|
} timeout_t;
|
||
|
|
||
|
static JSValue _task_setTimeout(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
|
||
|
timeout_t* timeout = malloc(sizeof(timeout_t));
|
||
|
*timeout = (timeout_t) {
|
||
|
._task = task,
|
||
|
._callback = JS_DupValue(context, argv[0]),
|
||
|
};
|
||
|
|
||
|
uv_timer_t* timer = malloc(sizeof(uv_timer_t));
|
||
|
memset(timer, 0, sizeof(uv_timer_t));
|
||
|
uv_timer_init(task->_loop, timer);
|
||
|
timer->data = timeout;
|
||
|
|
||
|
int64_t duration;
|
||
|
JS_ToInt64(task->_context, &duration, argv[1]);
|
||
|
uv_timer_start(timer, _task_timeoutCallback, duration, 0);
|
||
|
return JS_NULL;
|
||
|
}
|
||
|
|
||
|
static void _handle_closed(uv_handle_t* handle)
|
||
|
{
|
||
|
free(handle);
|
||
|
}
|
||
|
|
||
|
static void _task_timeoutCallback(uv_timer_t* handle) {
|
||
|
timeout_t* timeout = handle->data;
|
||
|
tf_trace_begin(timeout->_task->_trace, "_task_timeoutCallback");
|
||
|
JSValue result = JS_Call(
|
||
|
timeout->_task->_context,
|
||
|
timeout->_callback,
|
||
|
JS_NULL,
|
||
|
0,
|
||
|
NULL);
|
||
|
tf_task_report_error(timeout->_task, result);
|
||
|
tf_task_run_jobs(timeout->_task);
|
||
|
tf_trace_end(timeout->_task->_trace);
|
||
|
free(timeout);
|
||
|
uv_close((uv_handle_t*)handle, _handle_closed);
|
||
|
}
|
||
|
|
||
|
JSValue _tf_task_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
int exitCode = 0;
|
||
|
JS_ToInt32(task->_context, &exitCode, argv[0]);
|
||
|
exit(exitCode);
|
||
|
return JS_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
int tf_task_execute(tf_task_t* task, const char* fileName) {
|
||
|
bool executed = false;
|
||
|
|
||
|
tf_trace_begin(task->_trace, "tf_task_execute");
|
||
|
const char* source = _task_loadFile(fileName);
|
||
|
printf("Running script %s\n", fileName);
|
||
|
if (!*task->_scriptName) {
|
||
|
strncpy(task->_scriptName, fileName, sizeof(task->_scriptName) - 1);
|
||
|
}
|
||
|
if (!task->_path) {
|
||
|
char* path = strdup(fileName);
|
||
|
char* slash = strrchr(path, '/');
|
||
|
if (slash) {
|
||
|
*slash = '\0';
|
||
|
task->_path = strdup(path);
|
||
|
} else {
|
||
|
task->_path = strdup("./");
|
||
|
}
|
||
|
free(path);
|
||
|
}
|
||
|
if (source) {
|
||
|
JSValue result = JS_Eval(task->_context, source, strlen(source), fileName, 0);
|
||
|
tf_task_report_error(task, result);
|
||
|
if (!JS_IsError(task->_context, result) && !JS_IsException(result)) {
|
||
|
executed = true;
|
||
|
}
|
||
|
JS_FreeValue(task->_context, result);
|
||
|
tf_task_run_jobs(task);
|
||
|
free((void*)source);
|
||
|
} else {
|
||
|
printf("Failed to load file: %s.\n", fileName);
|
||
|
}
|
||
|
tf_trace_end(task->_trace);
|
||
|
return executed;
|
||
|
}
|
||
|
|
||
|
static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id) {
|
||
|
if (id == k_task_parent_id) {
|
||
|
return task->_parent;
|
||
|
}
|
||
|
|
||
|
for (task_child_node_t* node = task->_children; node; node = node->next) {
|
||
|
if (node->id == id) {
|
||
|
return node->stub;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static JSValue _import_call(JSContext* context, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst *argv, int flags)
|
||
|
{
|
||
|
import_record_t* import = JS_GetOpaque(func_obj, _import_class_id);
|
||
|
if (!import) {
|
||
|
return JS_ThrowInternalError(context, "Invoking a function that has been released.");
|
||
|
}
|
||
|
import->_useCount++;
|
||
|
|
||
|
JSValue array = JS_NewArray(context);
|
||
|
JS_SetPropertyUint32(context, array, 0, JS_DupValue(context, this_val));
|
||
|
for (int i = 0; i < argc; ++i) {
|
||
|
JS_SetPropertyUint32(context, array, i + 1, JS_DupValue(context, argv[i]));
|
||
|
}
|
||
|
|
||
|
tf_task_t* sender = JS_GetContextOpaque(context);
|
||
|
JSValue result;
|
||
|
tf_taskstub_t* recipient = _tf_task_get_stub(sender, import->_task);
|
||
|
if (recipient) {
|
||
|
promiseid_t promise = tf_task_allocate_promise(sender);
|
||
|
_tf_task_sendPromiseExportMessage(sender, recipient, kInvokeExport, promise, import->_export, array);
|
||
|
result = tf_task_get_promise(sender, promise);
|
||
|
} else {
|
||
|
result = JS_ThrowInternalError(context, "Invoking a function on a nonexistent task.");
|
||
|
}
|
||
|
JS_FreeValue(context, array);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static export_record_t* _task_get_export(tf_task_t* task, exportid_t export_id) {
|
||
|
for (export_record_t* it = task->_exports; it; it = it->_next) {
|
||
|
if (it->_export_id == export_id) {
|
||
|
return it;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
JSValue _task_invokeExport_internal(tf_taskstub_t* from, tf_task_t* to, exportid_t exportId, const char* buffer, size_t size) {
|
||
|
JSValue result = JS_NULL;
|
||
|
export_record_t* export = _task_get_export(to, exportId);
|
||
|
if (export) {
|
||
|
JSValue arguments = tf_serialize_load((tf_task_t*)to, from, buffer, size);
|
||
|
JSValue* argument_array = NULL;
|
||
|
JSValue length_val = JS_GetPropertyStr(to->_context, arguments, "length");
|
||
|
int length;
|
||
|
JSValue this_val = JS_NULL;
|
||
|
if (JS_ToInt32(to->_context, &length, length_val) == 0) {
|
||
|
if (length > 0) {
|
||
|
this_val = JS_GetPropertyUint32(to->_context, arguments, 0);
|
||
|
argument_array = alloca(sizeof(JSValue) * (length - 1));
|
||
|
}
|
||
|
for (int i = 1; i < length; ++i) {
|
||
|
argument_array[i - 1] = JS_GetPropertyUint32(to->_context, arguments, i);
|
||
|
}
|
||
|
}
|
||
|
JSValue function = export->_function;
|
||
|
JSPropertyDescriptor desc;
|
||
|
const char* name = NULL;
|
||
|
JSAtom atom = JS_NewAtom(to->_context, "name");
|
||
|
if (JS_GetOwnProperty(to->_context, &desc, function, atom) == 1) {
|
||
|
name = JS_ToCString(to->_context, desc.value);
|
||
|
JS_FreeValue(to->_context, desc.value);
|
||
|
}
|
||
|
JS_FreeAtom(to->_context, atom);
|
||
|
tf_trace_begin(to->_trace, name ? name : "_task_invokeExport_internal");
|
||
|
if (name) {
|
||
|
JS_FreeCString(to->_context, name);
|
||
|
}
|
||
|
result = JS_Call(to->_context, function, this_val, length - 1, argument_array);
|
||
|
tf_trace_end(to->_trace);
|
||
|
tf_task_report_error(to, result);
|
||
|
tf_task_run_jobs(to);
|
||
|
} else {
|
||
|
printf("%s: That's not an export we have (exportId=%d, exports=%d)\n", to->_scriptName, exportId, to->_export_count);
|
||
|
}
|
||
|
tf_packetstream_send(tf_taskstub_get_stream(from), kReleaseImport, (void*)&exportId, sizeof(exportId));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static JSValue _invokeThen(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) {
|
||
|
taskid_t taskid = 0;
|
||
|
JS_ToInt32(context, &taskid, func_data[0]);
|
||
|
tf_task_t* from = JS_GetContextOpaque(context);
|
||
|
tf_taskstub_t* to = _tf_task_get_stub(from, taskid);
|
||
|
promiseid_t promiseid = 0;
|
||
|
JS_ToInt32(context, &promiseid, func_data[1]);
|
||
|
_tf_task_sendPromiseMessage(from, to, kResolvePromise, promiseid, argv[0]);
|
||
|
return JS_DupValue(context, this_val);
|
||
|
}
|
||
|
|
||
|
static JSValue _invokeCatch(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) {
|
||
|
taskid_t taskid = 0;
|
||
|
JS_ToInt32(context, &taskid, func_data[0]);
|
||
|
tf_task_t* from = JS_GetContextOpaque(context);
|
||
|
tf_taskstub_t* to = _tf_task_get_stub(from, taskid);
|
||
|
promiseid_t promiseid = 0;
|
||
|
JS_ToInt32(context, &promiseid, func_data[1]);
|
||
|
_tf_task_sendPromiseMessage(from, to, kRejectPromise, promiseid, argv[0]);
|
||
|
return JS_DupValue(context, this_val);
|
||
|
}
|
||
|
|
||
|
static void _forward_promise(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) {
|
||
|
// We're not going to serialize/deserialize a promise...
|
||
|
JSValue data[] = {
|
||
|
JS_NewInt32(from->_context, tf_taskstub_get_id(to)),
|
||
|
JS_NewInt32(from->_context, promise),
|
||
|
};
|
||
|
JSValue promise_then = JS_GetPropertyStr(from->_context, result, "then");
|
||
|
JSValue promise_catch = JS_GetPropertyStr(from->_context, result, "catch");
|
||
|
|
||
|
JSValue then_handler = JS_NewCFunctionData(from->_context, _invokeThen, 0, 0, 2, data);
|
||
|
JSValue catch_handler = JS_NewCFunctionData(from->_context, _invokeCatch, 0, 0, 2, data);
|
||
|
|
||
|
JSValue error = JS_Call(from->_context, promise_then, result, 1, &then_handler);
|
||
|
tf_task_report_error(from, error);
|
||
|
error = JS_Call(from->_context, promise_catch, result, 1, &catch_handler);
|
||
|
tf_task_report_error(from, error);
|
||
|
|
||
|
tf_task_run_jobs(from);
|
||
|
}
|
||
|
|
||
|
static void _tf_task_sendPromiseResolve(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) {
|
||
|
JSValue global = JS_GetGlobalObject(from->_context);
|
||
|
JSValue promiseType = JS_GetPropertyStr(from->_context, global, "Promise");
|
||
|
JS_FreeValue(from->_context, global);
|
||
|
if (JS_IsInstanceOf(from->_context, result, promiseType)) {
|
||
|
_forward_promise(from, to, promise, result);
|
||
|
} else {
|
||
|
_tf_task_sendPromiseMessage(from, to, kResolvePromise, promise, result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_sendPromiseReject(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) {
|
||
|
JSValue global = JS_GetGlobalObject(from->_context);
|
||
|
JSValue promiseType = JS_GetPropertyStr(from->_context, global, "Promise");
|
||
|
JS_FreeValue(from->_context, global);
|
||
|
if (JS_IsInstanceOf(from->_context, result, promiseType)) {
|
||
|
_forward_promise(from, to, promise, result);
|
||
|
} else {
|
||
|
_tf_task_sendPromiseMessage(from, to, kRejectPromise, promise, result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_sendPromiseMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, JSValue result) {
|
||
|
if (to) {
|
||
|
void* buffer;
|
||
|
size_t size;
|
||
|
tf_serialize_store(from, to, &buffer, &size, result);
|
||
|
|
||
|
char* copy = (char*)malloc(sizeof(promise) + size);
|
||
|
memcpy(copy, &promise, sizeof(promise));
|
||
|
memcpy(copy + sizeof(promise), buffer, size);
|
||
|
tf_packetstream_send(tf_taskstub_get_stream(to), messageType, copy, size + sizeof(promise));
|
||
|
free(buffer);
|
||
|
free(copy);
|
||
|
} else {
|
||
|
printf("Sending to a NULL task.\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_sendPromiseExportMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, exportid_t exportId, JSValue result) {
|
||
|
void* buffer;
|
||
|
size_t size;
|
||
|
tf_serialize_store(from, to, &buffer, &size, result);
|
||
|
char* copy = (char*)malloc(sizeof(promise) + sizeof(exportId) + size);
|
||
|
memcpy(copy, &promise, sizeof(promise));
|
||
|
memcpy(copy + sizeof(promise), &exportId, sizeof(exportId));
|
||
|
memcpy(copy + sizeof(promise) + sizeof(exportId), buffer, size);
|
||
|
tf_packetstream_send(tf_taskstub_get_stream(to), messageType, copy, sizeof(promise) + sizeof(exportId) + size);
|
||
|
free(buffer);
|
||
|
free(copy);
|
||
|
}
|
||
|
|
||
|
JSValue _tf_task_get_parent(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
return task->_parent ? tf_taskstub_get_task_object(task->_parent) : JS_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
return JS_NewString(task->_context, k_version);
|
||
|
}
|
||
|
|
||
|
exportid_t tf_task_export_function(tf_task_t* task, tf_taskstub_t* to, JSValue function)
|
||
|
{
|
||
|
export_record_t* export = NULL;
|
||
|
|
||
|
for (export_record_t* it = task->_exports; it; it = it->_next) {
|
||
|
if (JS_VALUE_GET_PTR(it->_function) == JS_VALUE_GET_PTR(function) &&
|
||
|
it->_taskid == tf_taskstub_get_id(to)) {
|
||
|
export = it;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!export) {
|
||
|
int id = -1;
|
||
|
do {
|
||
|
id = task->_nextExport++;
|
||
|
} while (_task_get_export(task, id));
|
||
|
|
||
|
export = malloc(sizeof(export_record_t));
|
||
|
*export = (export_record_t) {
|
||
|
._export_id = id,
|
||
|
._taskid = tf_taskstub_get_id(to),
|
||
|
._function = JS_DupValue(task->_context, function),
|
||
|
._next = task->_exports,
|
||
|
};
|
||
|
task->_exports = export;
|
||
|
task->_export_count++;
|
||
|
_tf_task_trace_exports(task);
|
||
|
}
|
||
|
|
||
|
if (export) {
|
||
|
_export_record_ref(export);
|
||
|
}
|
||
|
|
||
|
return export ? export->_export_id : -1;
|
||
|
}
|
||
|
|
||
|
static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId)
|
||
|
{
|
||
|
if (stub) {
|
||
|
tf_packetstream_t* stream = tf_taskstub_get_stream(stub);
|
||
|
if (stream) {
|
||
|
tf_packetstream_send(stream, kReleaseExport, (void*)&exportId, sizeof(exportId));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_trace(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv){
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
if (!task->_trace) {
|
||
|
return JS_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
char* trace = tf_trace_export(task->_trace);
|
||
|
JSValue result = JS_NewString(context, trace);
|
||
|
free(trace);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||
|
{
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
const char* name = JS_ToCString(context, argv[0]);
|
||
|
JSValue result = JS_GetPropertyStr(context, task->_loadedFiles, name);
|
||
|
JS_FreeCString(context, name);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void tf_task_on_receive_packet(int packetType, const char* begin, size_t length, void* userData)
|
||
|
{
|
||
|
tf_taskstub_t* stub = userData;
|
||
|
tf_taskstub_t* from = stub;
|
||
|
tf_task_t* to = tf_taskstub_get_owner(stub);
|
||
|
tf_trace_begin(to->_trace, "tf_task_on_receive_packet");
|
||
|
|
||
|
switch (packetType) {
|
||
|
case kInvokeExport:
|
||
|
{
|
||
|
promiseid_t promise;
|
||
|
exportid_t exportId;
|
||
|
memcpy(&promise, begin, sizeof(promise));
|
||
|
memcpy(&exportId, begin + sizeof(promise), sizeof(exportId));
|
||
|
|
||
|
JSValue result = _task_invokeExport_internal(from, to, exportId, begin + sizeof(promiseid_t) + sizeof(exportid_t), length);
|
||
|
if (JS_IsException(result)) {
|
||
|
_tf_task_sendPromiseReject(to, from, promise, result);
|
||
|
} else {
|
||
|
_tf_task_sendPromiseResolve(to, from, promise, result);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case kResolvePromise:
|
||
|
case kRejectPromise:
|
||
|
{
|
||
|
JSValue arg = JS_UNDEFINED;
|
||
|
promiseid_t promise;
|
||
|
memcpy(&promise, begin, sizeof(promiseid_t));
|
||
|
if (length > sizeof(promiseid_t)) {
|
||
|
arg = tf_serialize_load((tf_task_t*)to, from, begin + sizeof(promiseid_t), length - sizeof(promiseid_t));
|
||
|
}
|
||
|
if (packetType == kResolvePromise) {
|
||
|
tf_task_resolve_promise(to, promise, arg);
|
||
|
} else {
|
||
|
tf_task_reject_promise(to, promise, arg);
|
||
|
}
|
||
|
JS_FreeValue(to->_context, arg);
|
||
|
}
|
||
|
break;
|
||
|
case kReleaseExport:
|
||
|
{
|
||
|
assert(length == sizeof(exportid_t));
|
||
|
exportid_t exportId;
|
||
|
memcpy(&exportId, begin, sizeof(exportId));
|
||
|
for (export_record_t** it = &to->_exports; *it; it = &(*it)->_next) {
|
||
|
export_record_t* export = *it;
|
||
|
if (export->_export_id == exportId &&
|
||
|
export->_taskid == tf_taskstub_get_id(from)) {
|
||
|
if (_export_record_release(export)) {
|
||
|
*it = export->_next;
|
||
|
JS_FreeValue(to->_context, export->_function);
|
||
|
free(export);
|
||
|
to->_export_count--;
|
||
|
_tf_task_trace_exports(to);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case kReleaseImport:
|
||
|
{
|
||
|
assert(length == sizeof(exportid_t));
|
||
|
exportid_t exportId;
|
||
|
memcpy(&exportId, begin, sizeof(exportId));
|
||
|
for (import_record_t* it = to->_imports; it; it = it->_next) {
|
||
|
if (it->_task == tf_taskstub_get_id(from) && it->_export == exportId) {
|
||
|
_import_record_release(it);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case kSetRequires:
|
||
|
to->_requires = tf_serialize_load((tf_task_t*)to, from, begin, length);
|
||
|
break;
|
||
|
case kLoadFile:
|
||
|
{
|
||
|
JSValue args = tf_serialize_load((tf_task_t*)to, from, begin, length);
|
||
|
JSValue key = JS_GetPropertyUint32(to->_context, args, 0);
|
||
|
JSValue content = JS_GetPropertyUint32(to->_context, args, 1);
|
||
|
const char* name = JS_ToCString(to->_context, key);
|
||
|
JS_SetPropertyStr(to->_context, to->_loadedFiles, name, JS_DupValue(to->_context, content));
|
||
|
JS_FreeCString(to->_context, name);
|
||
|
JS_FreeValue(to->_context, key);
|
||
|
JS_FreeValue(to->_context, content);
|
||
|
JS_FreeValue(to->_context, args);
|
||
|
}
|
||
|
break;
|
||
|
case kActivate:
|
||
|
tf_task_activate(to);
|
||
|
break;
|
||
|
case kExecute:
|
||
|
{
|
||
|
assert(length >= sizeof(promiseid_t));
|
||
|
promiseid_t promise;
|
||
|
memcpy(&promise, begin, sizeof(promiseid_t));
|
||
|
JSValue value = tf_serialize_load((tf_task_t*)to, from, begin + sizeof(promiseid_t), length - sizeof(promiseid_t));
|
||
|
JSValue source = JS_GetPropertyStr(to->_context, value, "source");
|
||
|
JSValue name_val = JS_GetPropertyStr(to->_context, value, "name");
|
||
|
const char* name = JS_ToCString(to->_context, name_val);
|
||
|
JS_FreeValue(to->_context, name_val);
|
||
|
JS_FreeValue(to->_context, value);
|
||
|
|
||
|
JSValue utf8 = _tf_task_utf8Decode(to->_context, JS_NULL, 1, &source);
|
||
|
const char* source_str = JS_ToCString(to->_context, utf8);
|
||
|
JS_FreeValue(to->_context, utf8);
|
||
|
_tf_task_executeSource(to, source_str, name);
|
||
|
JS_FreeCString(to->_context, source_str);
|
||
|
JS_FreeCString(to->_context, name);
|
||
|
_tf_task_sendPromiseResolve(to, from, promise, JS_UNDEFINED);
|
||
|
}
|
||
|
break;
|
||
|
case kKill:
|
||
|
exit(1);
|
||
|
break;
|
||
|
case kSetImports:
|
||
|
{
|
||
|
JSValue global = JS_GetGlobalObject(to->_context);
|
||
|
JSValue imports = tf_serialize_load((tf_task_t*)to, from, begin, length);
|
||
|
|
||
|
JSPropertyEnum* ptab;
|
||
|
uint32_t plen;
|
||
|
JS_GetOwnPropertyNames(to->_context, &ptab, &plen, imports, JS_GPN_STRING_MASK);
|
||
|
for (uint32_t i = 0; i < plen; ++i) {
|
||
|
JSPropertyDescriptor desc;
|
||
|
if (JS_GetOwnProperty(to->_context, &desc, imports, ptab[i].atom) == 1) {
|
||
|
JS_SetProperty(to->_context, global, ptab[i].atom, desc.value);
|
||
|
}
|
||
|
JS_FreeAtom(to->_context, ptab[i].atom);
|
||
|
}
|
||
|
js_free(to->_context, ptab);
|
||
|
|
||
|
JS_SetPropertyStr(to->_context, global, "imports", imports);
|
||
|
JS_FreeValue(to->_context, global);
|
||
|
}
|
||
|
break;
|
||
|
case kGetExports:
|
||
|
{
|
||
|
promiseid_t promise;
|
||
|
assert(length >= sizeof(promise));
|
||
|
memcpy(&promise, begin, sizeof(promiseid_t));
|
||
|
JSValue global = JS_GetGlobalObject(to->_context);
|
||
|
JSValue exportObject = JS_GetPropertyStr(to->_context, global, "exports");
|
||
|
_tf_task_sendPromiseResolve(to, from, promise, exportObject);
|
||
|
JS_FreeValue(to->_context, exportObject);
|
||
|
JS_FreeValue(to->_context, global);
|
||
|
}
|
||
|
break;
|
||
|
case kTaskError:
|
||
|
{
|
||
|
JSValue error = tf_serialize_load((tf_task_t*)to, from, begin, length);
|
||
|
tf_taskstub_on_error(from, error);
|
||
|
JS_FreeValue(to->_context, error);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
tf_trace_end(to->_trace);
|
||
|
}
|
||
|
|
||
|
static const char* _tf_task_resolveRequire(tf_task_t* task, const char* require) {
|
||
|
printf("Looking in %s for %s\n", task->_path, require);
|
||
|
if (strstr(require, "..") || strstr(require, "/") || strstr(require, "\\")) {
|
||
|
return NULL;
|
||
|
}
|
||
|
char test[1024];
|
||
|
snprintf(test, sizeof(test), "%s/%s%s", task->_path, require, strstr(require, ".js") ? "" : ".js");
|
||
|
printf("Testing %s\n", test);
|
||
|
uv_fs_t request;
|
||
|
if (uv_fs_access(task->_loop, &request, test, R_OK, 0) == 0) {
|
||
|
return strdup(test);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static script_export_t* _task_find_script_export(tf_task_t* task, const char* path) {
|
||
|
for (script_export_t* it = task->_scriptExports; it; it = it->next) {
|
||
|
if (strcmp(it->name, path) == 0) {
|
||
|
return it;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
JSValue _tf_task_require(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
JSValue result = JS_NULL;
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
tf_trace_begin(task->_trace, "_tf_task_require");
|
||
|
const char* in_path = JS_ToCString(context, argv[0]);
|
||
|
if (in_path) {
|
||
|
const char* path = _tf_task_resolveRequire(task, in_path);
|
||
|
if (!path) {
|
||
|
result = JS_ThrowReferenceError(task->_context, "require(): Unable to resolve module: %s.", in_path);
|
||
|
} else {
|
||
|
script_export_t* it = _task_find_script_export(task, path);
|
||
|
if (it) {
|
||
|
result = JS_DupValue(task->_context, it->value);
|
||
|
free((void*)path);
|
||
|
} else {
|
||
|
JSValue exports = JS_NewObject(task->_context);
|
||
|
script_export_t* export = malloc(sizeof(script_export_t));
|
||
|
*export = (script_export_t) {
|
||
|
.name = path,
|
||
|
.value = JS_DupValue(task->_context, exports),
|
||
|
.next = task->_scriptExports,
|
||
|
};
|
||
|
task->_scriptExports = export;
|
||
|
const char* source = _task_loadFile(path);
|
||
|
printf("Requiring script %s\n", path);
|
||
|
if (source) {
|
||
|
JSValue global = JS_GetGlobalObject(task->_context);
|
||
|
JSValue oldExports = JS_GetPropertyStr(task->_context, global, "exports");
|
||
|
JS_SetPropertyStr(task->_context, global, "exports", JS_DupValue(task->_context, exports));
|
||
|
JSValue eval = JS_Eval(task->_context, source, strlen(source), path, 0);
|
||
|
tf_task_report_error(task, eval);
|
||
|
tf_task_run_jobs(task);
|
||
|
if (JS_IsError(task->_context, eval) ||
|
||
|
JS_IsException(eval)) {
|
||
|
result = JS_DupValue(task->_context, eval);
|
||
|
} else {
|
||
|
result = JS_DupValue(task->_context, exports);
|
||
|
}
|
||
|
JS_FreeValue(task->_context, eval);
|
||
|
JS_SetPropertyStr(task->_context, global, "exports", oldExports);
|
||
|
JS_FreeValue(task->_context, global);
|
||
|
free((void*)source);
|
||
|
} else {
|
||
|
printf("Failed to load %s.\n", path);
|
||
|
}
|
||
|
JS_FreeValue(task->_context, exports);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
result = JS_ThrowReferenceError(task->_context, "require(): No module specified.");
|
||
|
}
|
||
|
JS_FreeCString(context, in_path);
|
||
|
tf_trace_end(task->_trace);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name) {
|
||
|
tf_trace_begin(task->_trace, "_tf_task_executeSource");
|
||
|
JSValue result = JS_Eval(task->_context, source, strlen(source), name, 0);
|
||
|
tf_task_report_error(task, result);
|
||
|
if (!*task->_scriptName) {
|
||
|
snprintf(task->_scriptName, sizeof(task->_scriptName), "%s", name);
|
||
|
}
|
||
|
tf_task_run_jobs(task);
|
||
|
tf_trace_end(task->_trace);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
JSValue _tf_task_childRequire(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
tf_task_t* task = JS_GetContextOpaque(context);
|
||
|
|
||
|
if (JS_IsObject(task->_requires)) {
|
||
|
const char* name = JS_ToCString(context, argv[0]);
|
||
|
script_export_t* it = _task_find_script_export(task, name);
|
||
|
if (it) {
|
||
|
return it->value;
|
||
|
} else {
|
||
|
JSValue exports = JS_NewObject(context);
|
||
|
const char* name = JS_ToCString(context, argv[0]);
|
||
|
JSValue value = JS_GetProperty(context, task->_requires, JS_ValueToAtom(context, argv[0]));
|
||
|
if (JS_IsString(value)) {
|
||
|
size_t length;
|
||
|
const char* source = JS_ToCStringLen(context, &length, value);
|
||
|
JSValue global = JS_GetGlobalObject(context);
|
||
|
JSValue oldExports = JS_GetPropertyStr(context, global, "exports");
|
||
|
JS_SetPropertyStr(context, global, "exports", JS_DupValue(context, exports));
|
||
|
JSValue result = JS_Eval(context, source, length, name, 0);
|
||
|
tf_task_report_error(task, result);
|
||
|
JS_SetPropertyStr(context, global, "exports", oldExports);
|
||
|
JS_FreeValue(context, global);
|
||
|
tf_task_run_jobs(task);
|
||
|
return exports;
|
||
|
} else {
|
||
|
return JS_ThrowInternalError(context, "Failed to load %s.", name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return JS_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
static JSValue _utf8Decode(JSContext* context, uint8_t* data, size_t length) {
|
||
|
return JS_NewStringLen(context, (const char*)data, length);
|
||
|
}
|
||
|
|
||
|
static JSValue _tf_task_utf8Decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||
|
JSValue result = JS_NULL;
|
||
|
size_t length;
|
||
|
if (JS_IsString(argv[0])) {
|
||
|
result = JS_DupValue(context, argv[0]);
|
||
|
} else {
|
||
|
uint8_t* array = tf_try_get_array_buffer(context, &length, argv[0]);
|
||
|
if (array) {
|
||
|
result = _utf8Decode(context, array, length);
|
||
|
} else {
|
||
|
size_t offset;
|
||
|
size_t element_size;
|
||
|
JSValue buffer = tf_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
||
|
size_t size;
|
||
|
if (!JS_IsException(buffer)) {
|
||
|
array = tf_try_get_array_buffer(context, &size, buffer);
|
||
|
if (array) {
|
||
|
result = _utf8Decode(context, array, size);
|
||
|
}
|
||
|
}
|
||
|
JS_FreeValue(context, buffer);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
uv_loop_t* tf_task_get_loop(tf_task_t* task) {
|
||
|
return task->_loop;
|
||
|
}
|
||
|
|
||
|
static promise_t* _tf_task_find_promise(tf_task_t* task, promiseid_t id) {
|
||
|
for (promise_t* it = task->_promises; it; it = it->next) {
|
||
|
if (it->id == id) {
|
||
|
return it;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void _tf_task_trace_promises(tf_task_t* task)
|
||
|
{
|
||
|
tf_trace_counter(task->_trace, "promises", 1, (const char*[]) { "count" }, (int64_t[]) { task->_promise_count });
|
||
|
}
|
||
|
|
||
|
static void _tf_task_free_promise(tf_task_t* task, promiseid_t id) {
|
||
|
for (promise_t** it = &task->_promises; *it; it = &(*it)->next) {
|
||
|
if ((*it)->id == id) {
|
||
|
promise_t* promise = *it;
|
||
|
*it = (*it)->next;
|
||
|
free(promise);
|
||
|
task->_promise_count--;
|
||
|
_tf_task_trace_promises(task);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
promiseid_t tf_task_allocate_promise(tf_task_t* task) {
|
||
|
promiseid_t promiseId;
|
||
|
do {
|
||
|
promiseId = task->_nextPromise++;
|
||
|
} while (_tf_task_find_promise(task, promiseId));
|
||
|
|
||
|
promise_t* promise = malloc(sizeof(promise_t));
|
||
|
*promise = (promise_t) {
|
||
|
.id = promiseId,
|
||
|
.next = task->_promises,
|
||
|
.values = { JS_NULL, JS_NULL, JS_NULL },
|
||
|
};
|
||
|
promise->values[0] = JS_NewPromiseCapability(task->_context, &promise->values[1]);
|
||
|
task->_promises = promise;
|
||
|
task->_promise_count++;
|
||
|
_tf_task_trace_promises(task);
|
||
|
return promiseId;
|
||
|
}
|
||
|
|
||
|
void tf_task_resolve_promise(tf_task_t* task, promiseid_t promise, JSValue value) {
|
||
|
promise_t* it = _tf_task_find_promise(task, promise);
|
||
|
if (it) {
|
||
|
JSValue result = JS_Call(task->_context, it->values[1], JS_UNDEFINED, 1, &value);
|
||
|
tf_task_report_error(task, result);
|
||
|
JS_FreeValue(task->_context, it->values[1]);
|
||
|
JS_FreeValue(task->_context, it->values[2]);
|
||
|
_tf_task_free_promise(task, promise);
|
||
|
tf_task_run_jobs(task);
|
||
|
} else {
|
||
|
printf("Didn't find promise %d to resolve.\n", promise);
|
||
|
abort();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void tf_task_reject_promise(tf_task_t* task, promiseid_t promise, JSValue value) {
|
||
|
promise_t* it = _tf_task_find_promise(task, promise);
|
||
|
if (it) {
|
||
|
JSValue result = JS_Call(task->_context, it->values[2], JS_UNDEFINED, 1, &value);
|
||
|
tf_task_report_error(task, result);
|
||
|
JS_FreeValue(task->_context, it->values[1]);
|
||
|
JS_FreeValue(task->_context, it->values[2]);
|
||
|
_tf_task_free_promise(task, promise);
|
||
|
tf_task_run_jobs(task);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
JSValue tf_task_get_promise(tf_task_t* task, promiseid_t promise) {
|
||
|
promise_t* it = _tf_task_find_promise(task, promise);
|
||
|
return it ? it->values[0] : JS_NULL;
|
||
|
}
|
||
|
|
||
|
static void _tf_task_trace_children(tf_task_t* task)
|
||
|
{
|
||
|
tf_trace_counter(task->_trace, "child_tasks", 1, (const char*[]) { "count" }, (int64_t[]) { task->_child_count });
|
||
|
}
|
||
|
|
||
|
taskid_t tf_task_allocate_task_id(tf_task_t* task, tf_taskstub_t* stub) {
|
||
|
taskid_t id = 0;
|
||
|
do {
|
||
|
id = task->_nextTask++;
|
||
|
} while (id == k_task_parent_id || _tf_task_get_stub(task, id));
|
||
|
|
||
|
task_child_node_t* node = malloc(sizeof(task_child_node_t));
|
||
|
*node = (task_child_node_t) {
|
||
|
.id = id,
|
||
|
.stub = stub,
|
||
|
.next = task->_children,
|
||
|
};
|
||
|
task->_children = node;
|
||
|
task->_child_count++;
|
||
|
_tf_task_trace_children(task);
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
void tf_task_remove_child(tf_task_t* task, tf_taskstub_t* child)
|
||
|
{
|
||
|
_import_record_release_for_task(task, tf_taskstub_get_id(child));
|
||
|
_export_record_release_for_task(task, tf_taskstub_get_id(child));
|
||
|
for (task_child_node_t** it = &task->_children;
|
||
|
*it;
|
||
|
it = &(*it)->next) {
|
||
|
if ((*it)->stub == child) {
|
||
|
task_child_node_t* node = *it;
|
||
|
*it = node->next;
|
||
|
free(node);
|
||
|
task->_child_count--;
|
||
|
_tf_task_trace_children(task);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _import_finalizer(JSRuntime* runtime, JSValue value)
|
||
|
{
|
||
|
import_record_t* import = JS_GetOpaque(value, _import_class_id);
|
||
|
_import_record_release(import);
|
||
|
}
|
||
|
|
||
|
static void _import_mark_func(JSRuntime* runtime, JSValueConst value, JS_MarkFunc mark_func)
|
||
|
{
|
||
|
import_record_t* import = JS_GetOpaque(value, _import_class_id);
|
||
|
if (import && import->_useCount > 0) {
|
||
|
JS_MarkValue(runtime, import->_function, mark_func);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void _tf_task_promise_rejection_tracker(JSContext* context, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* user_data)
|
||
|
{
|
||
|
tf_task_t* task = user_data;
|
||
|
if (!is_handled) {
|
||
|
tf_task_report_error(task, reason);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tf_task_t* tf_task_create() {
|
||
|
tf_task_t* task = malloc(sizeof(tf_task_t));
|
||
|
*task = (tf_task_t) { 0 };
|
||
|
task->_loop = uv_loop_new();
|
||
|
++_count;
|
||
|
task->_runtime = JS_NewRuntime();
|
||
|
task->_context = JS_NewContext(task->_runtime);
|
||
|
JS_SetContextOpaque(task->_context, task);
|
||
|
|
||
|
JS_SetHostPromiseRejectionTracker(task->_runtime, _tf_task_promise_rejection_tracker, task);
|
||
|
|
||
|
JS_NewClassID(&_import_class_id);
|
||
|
JSClassDef def = {
|
||
|
.class_name = "imported_function",
|
||
|
.finalizer = _import_finalizer,
|
||
|
.gc_mark = _import_mark_func,
|
||
|
.call = _import_call,
|
||
|
};
|
||
|
JS_NewClass(task->_runtime, _import_class_id, &def);
|
||
|
task->_loadedFiles = JS_NewObject(task->_context);
|
||
|
return task;
|
||
|
}
|
||
|
|
||
|
void tf_task_configure_from_stdin(tf_task_t* task) {
|
||
|
task->_parent = tf_taskstub_create_parent(task, STDIN_FILENO);
|
||
|
}
|
||
|
|
||
|
void tf_task_activate(tf_task_t* task)
|
||
|
{
|
||
|
JSContext* context = task->_context;
|
||
|
JSValue global = JS_GetGlobalObject(context);
|
||
|
JS_SetPropertyStr(context, global, "exports", JS_NewObject(context));
|
||
|
|
||
|
JSAtom atom = JS_NewAtom(context, "parent");
|
||
|
JS_DefinePropertyGetSet(context, global, atom, JS_NewCFunction(context, _tf_task_get_parent, "parent", 0), JS_UNDEFINED, 0);
|
||
|
JS_FreeAtom(context, atom);
|
||
|
|
||
|
JSValue tildefriends = JS_NewObject(context);
|
||
|
JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, task->_ssb_port));
|
||
|
JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port));
|
||
|
JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port));
|
||
|
JS_SetPropertyStr(context, global, "tildefriends", tildefriends);
|
||
|
|
||
|
if (task->_trusted) {
|
||
|
sqlite3_open(task->_db_path ? task->_db_path : "db.sqlite", &task->_db);
|
||
|
|
||
|
JS_SetPropertyStr(context, global, "require", JS_NewCFunction(context, _tf_task_require, "require", 1));
|
||
|
JS_SetPropertyStr(context, global, "Task", tf_taskstub_init(context));
|
||
|
JS_SetPropertyStr(context, global, "Socket", tf_socket_init(context));
|
||
|
JS_SetPropertyStr(context, global, "TlsContext", tf_tls_context_wrapper_init(context));
|
||
|
tf_database_init(context, task->_db);
|
||
|
tf_file_init(context);
|
||
|
|
||
|
task->_trace = tf_trace_create();
|
||
|
|
||
|
task->_ssb = tf_ssb_create(task->_loop, task->_context, task->_db, task->_secrets_path);
|
||
|
tf_ssb_set_trace(task->_ssb, task->_trace);
|
||
|
tf_ssb_broadcast_listener_start(task->_ssb, false);
|
||
|
tf_ssb_init(context, task->_ssb);
|
||
|
tf_ssb_server_open(task->_ssb, task->_ssb_port);
|
||
|
|
||
|
JS_SetPropertyStr(context, global, "trace", JS_NewCFunction(context, _tf_task_trace, "trace", 1));
|
||
|
} else {
|
||
|
JS_SetPropertyStr(context, global, "require", JS_NewCFunction(context, _tf_task_childRequire, "childRequire", 0));
|
||
|
}
|
||
|
|
||
|
tf_bcrypt_init(context);
|
||
|
JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _tf_task_utf8Decode, "utf8Decode", 1));
|
||
|
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _tf_task_print, "print", 0));
|
||
|
JS_SetPropertyStr(context, global, "exit", JS_NewCFunction(context, _tf_task_exit, "exit", 1));
|
||
|
JS_SetPropertyStr(context, global, "version", JS_NewCFunction(context, _tf_task_version, "version", 0));
|
||
|
JS_SetPropertyStr(context, global, "setTimeout", JS_NewCFunction(context, _task_setTimeout, "setTimeout", 2));
|
||
|
JS_SetPropertyStr(context, global, "getFile", JS_NewCFunction(context, _tf_task_getFile, "getFile", 1));
|
||
|
JS_FreeValue(context, global);
|
||
|
}
|
||
|
|
||
|
void tf_task_run(tf_task_t* task) {
|
||
|
uv_run(task->_loop, UV_RUN_DEFAULT);
|
||
|
}
|
||
|
|
||
|
void tf_task_set_trusted(tf_task_t* task, bool trusted) {
|
||
|
task->_trusted = trusted;
|
||
|
}
|
||
|
|
||
|
JSContext* tf_task_get_context(tf_task_t* task) {
|
||
|
return task->_context;
|
||
|
}
|
||
|
|
||
|
void tf_task_destroy(tf_task_t* task) {
|
||
|
import_record_t* it = task->_imports;
|
||
|
while (it) {
|
||
|
import_record_t* next = it->_next;
|
||
|
JS_FreeValue(task->_context, it->_function);
|
||
|
free(it);
|
||
|
it = next;
|
||
|
}
|
||
|
task->_imports = NULL;
|
||
|
|
||
|
while (task->_exports) {
|
||
|
export_record_t* export = task->_exports;
|
||
|
JS_FreeValue(task->_context, export->_function);
|
||
|
task->_exports = export->_next;
|
||
|
free(export);
|
||
|
}
|
||
|
while (task->_children) {
|
||
|
task_child_node_t* node = task->_children;
|
||
|
tf_taskstub_destroy(node->stub);
|
||
|
task->_children = node->next;
|
||
|
free(node);
|
||
|
}
|
||
|
if (task->_parent) {
|
||
|
JS_FreeValue(task->_context, tf_taskstub_get_task_object(task->_parent));
|
||
|
}
|
||
|
while (task->_promises) {
|
||
|
tf_task_reject_promise(task, task->_promises->id, JS_NULL);
|
||
|
}
|
||
|
JS_FreeValue(task->_context, task->_requires);
|
||
|
JS_FreeValue(task->_context, task->_loadedFiles);
|
||
|
while (task->_scriptExports) {
|
||
|
script_export_t* export = task->_scriptExports;
|
||
|
JS_FreeValue(task->_context, export->value);
|
||
|
task->_scriptExports = export->next;
|
||
|
free((void*)export->name);
|
||
|
free(export);
|
||
|
}
|
||
|
|
||
|
if (task->_trace) {
|
||
|
tf_trace_destroy(task->_trace);
|
||
|
}
|
||
|
|
||
|
if (task->_ssb) {
|
||
|
tf_ssb_destroy(task->_ssb);
|
||
|
}
|
||
|
JS_FreeContext(task->_context);
|
||
|
JS_FreeRuntime(task->_runtime);
|
||
|
|
||
|
if (task->_db) {
|
||
|
sqlite3_close(task->_db);
|
||
|
}
|
||
|
|
||
|
uv_print_all_handles(task->_loop, stdout);
|
||
|
uv_loop_delete(task->_loop);
|
||
|
--_count;
|
||
|
free((void*)task->_path);
|
||
|
free(task);
|
||
|
}
|
||
|
|
||
|
JSValue tf_task_add_import(tf_task_t* task, taskid_t stub_id, exportid_t export_id)
|
||
|
{
|
||
|
JSValue function = JS_NewObjectClass(task->_context, _import_class_id);
|
||
|
import_record_t* import = malloc(sizeof(import_record_t));
|
||
|
JS_SetOpaque(function, import);
|
||
|
*import = (import_record_t) {
|
||
|
._function = JS_DupValue(task->_context, function),
|
||
|
._export = export_id,
|
||
|
._owner = task,
|
||
|
._task = stub_id,
|
||
|
._useCount = 1,
|
||
|
._next = task->_imports,
|
||
|
};
|
||
|
|
||
|
task->_imports = import;
|
||
|
task->_import_count++;
|
||
|
_tf_task_trace_imports(task);
|
||
|
return function;
|
||
|
}
|
||
|
|
||
|
tf_task_t* tf_task_get(JSContext* context) {
|
||
|
return JS_GetContextOpaque(context);
|
||
|
}
|
||
|
|
||
|
void tf_task_run_jobs(tf_task_t* task) {
|
||
|
while (JS_IsJobPending(task->_runtime)) {
|
||
|
JSContext* context = NULL;
|
||
|
int r = JS_ExecutePendingJob(task->_runtime, &context);
|
||
|
JSValue result = JS_GetException(context);
|
||
|
tf_task_report_error(task, result);
|
||
|
if (r < 0) {
|
||
|
js_std_dump_error(context);
|
||
|
} else if (r == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void tf_task_send_promise_message(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t type, promiseid_t promise, JSValue payload)
|
||
|
{
|
||
|
_tf_task_sendPromiseMessage(from, to, type, promise, payload);
|
||
|
}
|
||
|
|
||
|
JSValue tf_try_get_typed_array_buffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, size_t *pbytes_per_element)
|
||
|
{
|
||
|
JSValue result = JS_GetTypedArrayBuffer(ctx, obj, pbyte_offset, pbyte_length, pbytes_per_element);
|
||
|
JS_FreeValue(ctx, JS_GetException(ctx));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
uint8_t *tf_try_get_array_buffer(JSContext *ctx, size_t *psize, JSValueConst obj)
|
||
|
{
|
||
|
uint8_t* result = JS_GetArrayBuffer(ctx, psize, obj);
|
||
|
JS_FreeValue(ctx, JS_GetException(ctx));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void tf_task_set_ssb_port(tf_task_t* task, int port)
|
||
|
{
|
||
|
task->_ssb_port = port;
|
||
|
}
|
||
|
|
||
|
void tf_task_set_http_port(tf_task_t* task, int port)
|
||
|
{
|
||
|
task->_http_port = port;
|
||
|
}
|
||
|
|
||
|
void tf_task_set_https_port(tf_task_t* task, int port)
|
||
|
{
|
||
|
task->_https_port = port;
|
||
|
}
|
||
|
|
||
|
void tf_task_set_db_path(tf_task_t* task, const char* db_path)
|
||
|
{
|
||
|
task->_db_path = db_path;
|
||
|
}
|
||
|
|
||
|
void tf_task_set_secrets_path(tf_task_t* task, const char* secrets_path)
|
||
|
{
|
||
|
task->_secrets_path = secrets_path;
|
||
|
}
|