// 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 #include #include #include #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; }