calog/libs/calogTask.c

301 lines
11 KiB
C

// calogTask.c -- calog task library (see calogTask.h). Spawns and manages other calog
// script contexts over the shared handle table; every native is inline, running on the
// calling script's own context thread.
#include "calogTask.h"
#include "calogHandle.h"
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define TASK_TYPE_CONTEXT 1u
// Process-wide task registry shared by every runtime that registers the natives; refCount
// is one per registered runtime, so the singleton is freed only when the LAST runtime shuts
// down (never out from under a still-live runtime's captured userData).
typedef struct TaskLibT {
CalogHandleTableT *handles;
int32_t refCount;
} TaskLibT;
// What a task handle owns: the spawned context plus the id of the context that spawned it.
// ONLY the owner may eval or close a task. Because a context is single-threaded, that rules
// out the two ways taskClose's pthread_join could misbehave: a task can never own its own
// handle (so it can't self-join and free itself mid-run), and two tasks can never each own
// the other's handle (so they can't dead-lock closing each other).
typedef struct TaskT {
CalogContextT *context;
uint64_t ownerId;
} TaskT;
// Maps an engine name to its vtable, for the engines compiled in. References the extern
// vtables only under their CALOG_WITH_* guard, so unselected engines stay unlinked.
typedef struct TaskEngineT {
const char *name;
const CalogEngineT *engine;
} TaskEngineT;
static const TaskEngineT gTaskEngines[] = {
#ifdef CALOG_WITH_LUA
{ "lua", &calogLuaEngine },
#endif
#ifdef CALOG_WITH_JS
{ "js", &calogJsEngine },
#endif
#ifdef CALOG_WITH_SQUIRREL
{ "squirrel", &calogSquirrelEngine },
#endif
#ifdef CALOG_WITH_MYBASIC
{ "mybasic", &calogMyBasicEngine },
#endif
#ifdef CALOG_WITH_BERRY
{ "berry", &calogBerryEngine },
#endif
#ifdef CALOG_WITH_S7
{ "s7", &calogS7Engine },
#endif
#ifdef CALOG_WITH_WREN
{ "wren", &calogWrenEngine },
#endif
{ NULL, NULL }
};
static pthread_mutex_t gTaskLibMutex = PTHREAD_MUTEX_INITIALIZER;
static TaskLibT *gTaskLib = NULL;
static int32_t taskClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void taskCloser(uint32_t type, void *resource);
static int32_t taskCount(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static const CalogEngineT *taskEngineByName(const char *name);
static int32_t taskEval(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t taskLoad(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t taskSelf(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t taskSpawn(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
int32_t calogTaskRegister(CalogT *calog) {
pthread_mutex_lock(&gTaskLibMutex);
if (gTaskLib == NULL) {
TaskLibT *lib;
lib = (TaskLibT *)calloc(1, sizeof(*lib));
if (lib == NULL) {
pthread_mutex_unlock(&gTaskLibMutex);
return calogErrOomE;
}
lib->handles = calogHandleTableCreate();
if (lib->handles == NULL) {
free(lib);
pthread_mutex_unlock(&gTaskLibMutex);
return calogErrOomE;
}
gTaskLib = lib;
}
gTaskLib->refCount++;
pthread_mutex_unlock(&gTaskLibMutex);
calogRegisterInline(calog, "taskSpawn", taskSpawn, gTaskLib);
calogRegisterInline(calog, "taskLoad", taskLoad, gTaskLib);
calogRegisterInline(calog, "taskEval", taskEval, gTaskLib);
calogRegisterInline(calog, "taskClose", taskClose, gTaskLib);
calogRegisterInline(calog, "taskSelf", taskSelf, gTaskLib);
calogRegisterInline(calog, "taskCount", taskCount, gTaskLib);
return calogOkE;
}
void calogTaskShutdown(void) {
pthread_mutex_lock(&gTaskLibMutex);
if (gTaskLib == NULL) {
pthread_mutex_unlock(&gTaskLibMutex);
return;
}
gTaskLib->refCount--;
if (gTaskLib->refCount <= 0) {
// Last runtime is gone. Free the per-task wrappers; the contexts themselves are
// owned by their runtime and were closed by calogDestroy.
calogHandleTableDestroy(gTaskLib->handles, taskCloser);
free(gTaskLib);
gTaskLib = NULL;
}
pthread_mutex_unlock(&gTaskLibMutex);
}
static int32_t taskClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
TaskLibT *lib;
TaskT *task;
lib = (TaskLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "taskClose expects (handle)");
}
// Only the owner may close (see TaskT). This check makes a self-join or a mutual close
// unreachable, and because the owner is a single thread it also serialises the peek
// below with the remove -- no other thread can claim this handle.
task = (TaskT *)calogHandleGet(lib->handles, args[0].as.i, TASK_TYPE_CONTEXT);
if (task == NULL) {
return calogFail(result, calogErrArgE, "taskClose: invalid task handle");
}
if (task->ownerId != calogCurrentId()) {
return calogFail(result, calogErrArgE, "taskClose: only the task's owner may close it");
}
task = (TaskT *)calogHandleRemove(lib->handles, args[0].as.i, TASK_TYPE_CONTEXT);
if (task == NULL) {
return calogFail(result, calogErrArgE, "taskClose: invalid task handle");
}
calogContextClose(task->context);
free(task);
return calogOkE;
}
static void taskCloser(uint32_t type, void *resource) {
(void)type;
// Free only the wrapper; the context is owned by its runtime (closed by calogDestroy).
free(resource);
}
static int32_t taskCount(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
TaskLibT *lib;
(void)args;
(void)argCount;
lib = (TaskLibT *)userData;
calogValueInt(result, (int64_t)calogHandleCount(lib->handles, TASK_TYPE_CONTEXT));
return calogOkE;
}
static const CalogEngineT *taskEngineByName(const char *name) {
int32_t index;
for (index = 0; gTaskEngines[index].name != NULL; index++) {
if (strcmp(gTaskEngines[index].name, name) == 0) {
return gTaskEngines[index].engine;
}
}
return NULL;
}
static int32_t taskEval(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
TaskLibT *lib;
TaskT *task;
lib = (TaskLibT *)userData;
calogValueNil(result);
if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogStringE) {
return calogFail(result, calogErrArgE, "taskEval expects (handle, code)");
}
task = (TaskT *)calogHandleGet(lib->handles, args[0].as.i, TASK_TYPE_CONTEXT);
if (task == NULL) {
return calogFail(result, calogErrArgE, "taskEval: invalid task handle");
}
if (task->ownerId != calogCurrentId()) {
return calogFail(result, calogErrArgE, "taskEval: only the task's owner may control it");
}
if (calogContextEval(task->context, args[1].as.s.bytes) != calogOkE) {
return calogFail(result, calogErrArgE, "taskEval: could not queue the code");
}
return calogOkE;
}
static int32_t taskLoad(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
TaskLibT *lib;
CalogT *calog;
CalogContextT *context;
TaskT *task;
int64_t handle;
lib = (TaskLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "taskLoad expects (baseName)");
}
calog = calogCurrent();
if (calog == NULL) {
return calogFail(result, calogErrArgE, "taskLoad: must be called from a script");
}
context = calogContextLoad(calog, args[0].as.s.bytes);
if (context == NULL) {
return calogFail(result, calogErrArgE, "taskLoad: no matching script file, or the load failed");
}
task = (TaskT *)malloc(sizeof(*task));
if (task == NULL) {
calogContextClose(context);
return calogFail(result, calogErrOomE, "taskLoad: out of memory");
}
task->context = context;
task->ownerId = calogCurrentId();
handle = calogHandleAdd(lib->handles, TASK_TYPE_CONTEXT, task);
if (handle == 0) {
free(task);
calogContextClose(context);
return calogFail(result, calogErrOomE, "taskLoad: out of memory");
}
calogValueInt(result, handle);
return calogOkE;
}
static int32_t taskSelf(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
calogValueInt(result, (int64_t)calogCurrentId());
return calogOkE;
}
static int32_t taskSpawn(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
TaskLibT *lib;
CalogT *calog;
const CalogEngineT *engine;
CalogContextT *context;
TaskT *task;
int64_t handle;
lib = (TaskLibT *)userData;
calogValueNil(result);
if (argCount != 2 || args[0].type != calogStringE || args[1].type != calogStringE) {
return calogFail(result, calogErrArgE, "taskSpawn expects (engine, code)");
}
engine = taskEngineByName(args[0].as.s.bytes);
if (engine == NULL) {
return calogFail(result, calogErrUnsupportedE, "taskSpawn: unknown or uncompiled engine");
}
calog = calogCurrent();
if (calog == NULL) {
return calogFail(result, calogErrArgE, "taskSpawn: must be called from a script");
}
context = calogContextOpen(calog, engine);
if (context == NULL) {
return calogFail(result, calogErrArgE, "taskSpawn: could not open a context");
}
task = (TaskT *)malloc(sizeof(*task));
if (task == NULL) {
calogContextClose(context);
return calogFail(result, calogErrOomE, "taskSpawn: out of memory");
}
task->context = context;
task->ownerId = calogCurrentId();
handle = calogHandleAdd(lib->handles, TASK_TYPE_CONTEXT, task);
if (handle == 0) {
free(task);
calogContextClose(context);
return calogFail(result, calogErrOomE, "taskSpawn: out of memory");
}
if (calogContextEval(context, args[1].as.s.bytes) != calogOkE) {
calogHandleRemove(lib->handles, handle, TASK_TYPE_CONTEXT);
free(task);
calogContextClose(context);
return calogFail(result, calogErrArgE, "taskSpawn: could not queue the code");
}
calogValueInt(result, handle);
return calogOkE;
}