forked from cory/tildefriends
Cory McWilliams
3487f335e5
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3646 ed5197a5-7fde-0310-b194-c3ffbd925b24
361 lines
13 KiB
C
361 lines
13 KiB
C
#include "taskstub.h"
|
|
|
|
#include "packetstream.h"
|
|
#include "serialize.h"
|
|
#include "task.h"
|
|
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "quickjs-libc.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <windows.h>
|
|
#include <ws2tcpip.h>
|
|
static const int STDIN_FILENO = 0;
|
|
static const int STDOUT_FILENO = 1;
|
|
static const int STDERR_FILENO = 2;
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
static JSClassID _classId;
|
|
static char _executable[1024];
|
|
|
|
typedef struct _tf_taskstub_t {
|
|
taskid_t _id;
|
|
JSValue _object;
|
|
JSValue _taskObject;
|
|
|
|
JSValue _on_exit;
|
|
JSValue _on_error;
|
|
|
|
tf_task_t* _owner;
|
|
tf_packetstream_t* _stream;
|
|
uv_process_t _process;
|
|
bool _finalized;
|
|
} tf_taskstub_t;
|
|
|
|
void tf_taskstub_startup() {
|
|
static bool initialized;
|
|
if (!initialized) {
|
|
JS_NewClassID(&_classId);
|
|
size_t size = sizeof(_executable);
|
|
uv_exepath(_executable, &size);
|
|
initialized = true;
|
|
}
|
|
}
|
|
|
|
static JSValue _taskstub_activate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_execute(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_setImports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_getExports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_setRequires(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_kill(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_get_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_set_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_get_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_set_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static JSValue _taskstub_loadFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
|
static void _taskstub_on_process_exit(uv_process_t* process, int64_t status, int terminationSignal);
|
|
static void _taskstub_finalizer(JSRuntime *runtime, JSValue value);
|
|
|
|
static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_task_t* parent = tf_task_get(context);
|
|
tf_taskstub_t* stub = malloc(sizeof(tf_taskstub_t));
|
|
memset(stub, 0, sizeof(*stub));
|
|
stub->_stream = tf_packetstream_create();
|
|
|
|
JSValue taskObject = JS_NewObjectClass(context, _classId);
|
|
JS_SetOpaque(taskObject, stub);
|
|
stub->_owner = parent;
|
|
stub->_on_exit = JS_UNDEFINED;
|
|
stub->_on_error = JS_UNDEFINED;
|
|
stub->_object = JS_DupValue(context, taskObject);
|
|
|
|
JSAtom atom = JS_NewAtom(context, "onExit");
|
|
JS_DefinePropertyGetSet(
|
|
context,
|
|
taskObject,
|
|
atom,
|
|
JS_NewCFunction(context, _taskstub_get_on_exit, "getOnExit", 0),
|
|
JS_NewCFunction(context, _taskstub_set_on_exit, "setOnExit", 0),
|
|
0);
|
|
JS_FreeAtom(context, atom);
|
|
|
|
atom = JS_NewAtom(context, "onError");
|
|
JS_DefinePropertyGetSet(
|
|
context,
|
|
taskObject,
|
|
atom,
|
|
JS_NewCFunction(context, _taskstub_get_on_error, "getOnError", 0),
|
|
JS_NewCFunction(context, _taskstub_set_on_error, "setOnError", 0),
|
|
0);
|
|
JS_FreeAtom(context, atom);
|
|
|
|
JS_SetPropertyStr(context, taskObject, "activate", JS_NewCFunction(context, _taskstub_activate, "activate", 0));
|
|
JS_SetPropertyStr(context, taskObject, "execute", JS_NewCFunction(context, _taskstub_execute, "execute", 1));
|
|
JSAtom imports = JS_NewAtom(context, "imports");
|
|
JS_SetPropertyStr(context, taskObject, "setImports", JS_NewCFunction(context, _taskstub_setImports, "setImports", 1));
|
|
JS_FreeAtom(context, imports);
|
|
JS_SetPropertyStr(context, taskObject, "getExports", JS_NewCFunction(context, _taskstub_getExports, "getExports", 0));
|
|
JS_SetPropertyStr(context, taskObject, "setRequires", JS_NewCFunction(context, _taskstub_setRequires, "setRequires", 1));
|
|
JS_SetPropertyStr(context, taskObject, "kill", JS_NewCFunction(context, _taskstub_kill, "kill", 0));
|
|
JS_SetPropertyStr(context, taskObject, "loadFile", JS_NewCFunction(context, _taskstub_loadFile, "loadFile", 1));
|
|
|
|
taskid_t id = k_task_parent_id;
|
|
if (parent) {
|
|
id = tf_task_allocate_task_id(parent, (tf_taskstub_t*)stub);
|
|
}
|
|
stub->_id = id;
|
|
|
|
char arg1[] = "sandbox";
|
|
char* command_argv[] = { _executable, arg1, 0 };
|
|
|
|
uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
|
|
memset(pipe, 0, sizeof(*pipe));
|
|
if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0) {
|
|
fprintf(stderr, "uv_pipe_init failed\n");
|
|
}
|
|
|
|
uv_stdio_container_t io[3];
|
|
io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE;
|
|
io[0].data.stream = (uv_stream_t*)pipe;
|
|
io[1].flags = UV_INHERIT_FD;
|
|
io[1].data.fd = STDOUT_FILENO;
|
|
io[2].flags = UV_INHERIT_FD;
|
|
io[2].data.fd = STDERR_FILENO;
|
|
|
|
uv_process_options_t options = {0};
|
|
options.args = command_argv;
|
|
options.exit_cb = _taskstub_on_process_exit;
|
|
options.stdio = io;
|
|
options.stdio_count = sizeof(io) / sizeof(*io);
|
|
options.file = command_argv[0];
|
|
|
|
JSValue result = JS_NULL;
|
|
stub->_process.data = stub;
|
|
int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options);
|
|
if (spawn_result == 0) {
|
|
tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub);
|
|
tf_packetstream_start(stub->_stream);
|
|
|
|
result = taskObject;
|
|
} else {
|
|
fprintf(stderr, "uv_spawn failed: %s\n", uv_strerror(spawn_result));
|
|
JS_FreeValue(context, taskObject);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void _taskstub_gc_mark(JSRuntime* rt, JSValueConst value, JS_MarkFunc mark_func) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(value, _classId);
|
|
if (stub) {
|
|
JS_MarkValue(rt, stub->_on_exit, mark_func);
|
|
JS_MarkValue(rt, stub->_on_error, mark_func);
|
|
}
|
|
}
|
|
|
|
JSValue tf_taskstub_init(JSContext* context) {
|
|
JSClassDef def = {
|
|
.class_name = "TaskStub",
|
|
.finalizer = &_taskstub_finalizer,
|
|
.gc_mark = _taskstub_gc_mark,
|
|
};
|
|
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0) {
|
|
fprintf(stderr, "Failed to register TaskStub class.\n");
|
|
}
|
|
return JS_NewCFunction2(context, _taskstub_create, "TaskStub", 0, JS_CFUNC_constructor, 0);
|
|
}
|
|
|
|
taskid_t tf_taskstub_get_id(const tf_taskstub_t* stub) {
|
|
return stub->_id;
|
|
}
|
|
|
|
JSValue tf_taskstub_get_task_object(const tf_taskstub_t* stub) {
|
|
return stub->_taskObject;
|
|
}
|
|
|
|
tf_packetstream_t* tf_taskstub_get_stream(const tf_taskstub_t* stub) {
|
|
return stub->_stream;
|
|
}
|
|
|
|
tf_task_t* tf_taskstub_get_owner(const tf_taskstub_t* stub) {
|
|
return stub->_owner;
|
|
}
|
|
|
|
tf_taskstub_t* tf_taskstub_create_parent(tf_task_t* task, uv_file file) {
|
|
JSValue parentObject = JS_NewObject(tf_task_get_context(task));
|
|
tf_taskstub_t* parentStub = malloc(sizeof(tf_taskstub_t));
|
|
memset(parentStub, 0, sizeof(tf_taskstub_t));
|
|
parentStub->_stream = tf_packetstream_create();
|
|
parentStub->_on_exit = JS_UNDEFINED;
|
|
parentStub->_on_error = JS_UNDEFINED;
|
|
|
|
parentStub->_taskObject = parentObject;
|
|
JS_SetOpaque(parentObject, parentStub);
|
|
parentStub->_owner = task;
|
|
parentStub->_id = k_task_parent_id;
|
|
parentStub->_object = JS_DupValue(tf_task_get_context(task), parentObject);
|
|
|
|
if (uv_pipe_init(tf_task_get_loop(task), tf_packetstream_get_pipe(parentStub->_stream), 1) != 0) {
|
|
fprintf(stderr, "uv_pipe_init failed\n");
|
|
}
|
|
tf_packetstream_set_on_receive(parentStub->_stream, tf_task_on_receive_packet, parentStub);
|
|
if (uv_pipe_open(tf_packetstream_get_pipe(parentStub->_stream), file) != 0) {
|
|
fprintf(stderr, "uv_pipe_open failed\n");
|
|
}
|
|
tf_packetstream_start(parentStub->_stream);
|
|
|
|
return parentStub;
|
|
}
|
|
|
|
static void _taskstub_cleanup(tf_taskstub_t* stub)
|
|
{
|
|
if (!stub->_process.data &&
|
|
JS_IsUndefined(stub->_object) &&
|
|
stub->_finalized) {
|
|
free(stub);
|
|
}
|
|
}
|
|
|
|
static void _taskstub_finalizer(JSRuntime* runtime, JSValue value) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(value, _classId);
|
|
stub->_on_exit = JS_UNDEFINED;
|
|
stub->_on_error = JS_UNDEFINED;
|
|
tf_packetstream_destroy(stub->_stream);
|
|
stub->_stream = NULL;
|
|
stub->_finalized = true;
|
|
tf_task_remove_child(stub->_owner, stub);
|
|
_taskstub_cleanup(stub);
|
|
}
|
|
|
|
static void _taskstub_on_handle_close(uv_handle_t* handle)
|
|
{
|
|
tf_taskstub_t* stub = handle->data;
|
|
handle->data = NULL;
|
|
tf_task_remove_child(stub->_owner, stub);
|
|
_taskstub_cleanup(stub);
|
|
}
|
|
|
|
static void _taskstub_on_process_exit(uv_process_t* process, int64_t status, int terminationSignal) {
|
|
tf_taskstub_t* stub = process->data;
|
|
JSContext* context = tf_task_get_context(stub->_owner);
|
|
if (!JS_IsUndefined(stub->_on_exit)) {
|
|
JSValue argv[] = { JS_NewInt32(context, status), JS_NewInt32(context, terminationSignal) };
|
|
JSValue result = JS_Call(context, stub->_on_exit, JS_NULL, 2, argv);
|
|
tf_task_report_error(stub->_owner, result);
|
|
JS_FreeValue(context, result);
|
|
tf_task_run_jobs(stub->_owner);
|
|
JS_FreeValue(context, argv[0]);
|
|
JS_FreeValue(context, argv[1]);
|
|
}
|
|
tf_packetstream_close(stub->_stream);
|
|
uv_close((uv_handle_t*)process, _taskstub_on_handle_close);
|
|
tf_taskstub_destroy(stub);
|
|
}
|
|
|
|
static JSValue _taskstub_getExports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
promiseid_t promise = tf_task_allocate_promise(stub->_owner);
|
|
tf_task_send_promise_message(stub->_owner, (tf_taskstub_t*)stub, kGetExports, promise, JS_UNDEFINED);
|
|
return tf_task_get_promise(stub->_owner, promise);
|
|
}
|
|
|
|
static JSValue _taskstub_setImports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
void* buffer;
|
|
size_t size;
|
|
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
|
tf_packetstream_send(stub->_stream, kSetImports, (char*)buffer, size);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue _taskstub_setRequires(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
void* buffer;
|
|
size_t size;
|
|
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
|
tf_packetstream_send(stub->_stream, kSetRequires, (char*)buffer, size);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue _taskstub_loadFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
void* buffer;
|
|
size_t size;
|
|
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
|
tf_packetstream_send(stub->_stream, kLoadFile, (char*)buffer, size);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue _taskstub_get_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
return JS_DupValue(context, stub->_on_exit);
|
|
}
|
|
|
|
static JSValue _taskstub_set_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
if (!JS_IsUndefined(stub->_on_exit)) {
|
|
JS_FreeValue(context, stub->_on_exit);
|
|
}
|
|
stub->_on_exit = JS_DupValue(context, argv[0]);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue _taskstub_get_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
return JS_DupValue(context, stub->_on_error);
|
|
}
|
|
|
|
static JSValue _taskstub_set_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
if (!JS_IsUndefined(stub->_on_error)) {
|
|
JS_FreeValue(context, stub->_on_error);
|
|
}
|
|
stub->_on_error = JS_DupValue(context, argv[0]);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue _taskstub_activate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
if (stub) {
|
|
tf_packetstream_send(stub->_stream, kActivate, 0, 0);
|
|
}
|
|
return JS_NULL;
|
|
}
|
|
|
|
static JSValue _taskstub_execute(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
promiseid_t promise = tf_task_allocate_promise(stub->_owner);
|
|
tf_task_send_promise_message(stub->_owner, (tf_taskstub_t*)stub, kExecute, promise, argv[0]);
|
|
return tf_task_get_promise(stub->_owner, promise);
|
|
}
|
|
|
|
static JSValue _taskstub_kill(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
|
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
|
uv_process_kill(&stub->_process, SIGTERM);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
void tf_taskstub_destroy(tf_taskstub_t* stub) {
|
|
if (!JS_IsUndefined(stub->_object)) {
|
|
JSValue object = stub->_object;
|
|
stub->_object = JS_UNDEFINED;
|
|
JS_FreeValue(tf_task_get_context(stub->_owner), object);
|
|
}
|
|
}
|
|
|
|
void tf_taskstub_on_error(tf_taskstub_t* stub, JSValue error)
|
|
{
|
|
JSContext* context = tf_task_get_context(stub->_owner);
|
|
if (!JS_IsUndefined(stub->_on_error)) {
|
|
JSValue result = JS_Call(context, stub->_on_error, JS_NULL, 1, &error);
|
|
tf_task_report_error(stub->_owner, result);
|
|
JS_FreeValue(context, result);
|
|
tf_task_run_jobs(stub->_owner);
|
|
}
|
|
}
|