301 lines
11 KiB
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;
|
|
}
|