204 lines
6.9 KiB
C
204 lines
6.9 KiB
C
// calogExport.c -- calog export library (see calogExport.h). A process-wide, mutex-guarded
|
|
// map of name -> reference-counted function value. calogExport/calogUnexport/calogCall are
|
|
// natives; __calogExportResolve is the hook the per-engine dynamic-name resolvers look up.
|
|
//
|
|
// The registry state is STATIC (not heap), and never freed: the four natives are reachable
|
|
// (via any context, and via every Lua unknown-global lookup) right up until calogDestroy
|
|
// tears the broker registry down, so freeing the state in calogExportShutdown -- which the
|
|
// contract requires BEFORE calogDestroy -- would leave those natives dereferencing freed
|
|
// memory. calogExportShutdown instead just releases the exported functions and empties the
|
|
// map; the small static bookkeeping persists for the process (no per-cycle leak).
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calogExport.h"
|
|
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define EXPORT_INITIAL_CAP 8
|
|
#define EXPORT_GROWTH 2
|
|
|
|
typedef struct ExportEntryT {
|
|
char *name;
|
|
CalogFnT *fn;
|
|
} ExportEntryT;
|
|
|
|
// The map (heap array), guarded by gMapMutex. refCount (guarded by gInitMutex) counts
|
|
// registered runtimes so the LAST calogExportShutdown empties the map.
|
|
static pthread_mutex_t gMapMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static ExportEntryT *gEntries = NULL;
|
|
static int32_t gCount = 0;
|
|
static int32_t gCap = 0;
|
|
static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static int32_t gRefCount = 0;
|
|
|
|
static int32_t exportCall(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t exportFindLocked(const char *name);
|
|
static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t exportRemove(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t exportResolve(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
|
|
|
|
int32_t calogExportRegister(CalogT *calog) {
|
|
pthread_mutex_lock(&gInitMutex);
|
|
gRefCount++;
|
|
pthread_mutex_unlock(&gInitMutex);
|
|
calogRegisterInline(calog, "calogExport", exportPublish, NULL);
|
|
calogRegisterInline(calog, "calogUnexport", exportRemove, NULL);
|
|
calogRegisterInline(calog, "calogCall", exportCall, NULL);
|
|
calogRegisterInline(calog, "__calogExportResolve", exportResolve, NULL);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
void calogExportShutdown(void) {
|
|
pthread_mutex_lock(&gInitMutex);
|
|
if (gRefCount > 0) {
|
|
gRefCount--;
|
|
}
|
|
if (gRefCount <= 0) {
|
|
int32_t index;
|
|
pthread_mutex_lock(&gMapMutex);
|
|
for (index = 0; index < gCount; index++) {
|
|
free(gEntries[index].name);
|
|
calogFnRelease(gEntries[index].fn);
|
|
}
|
|
free(gEntries);
|
|
gEntries = NULL;
|
|
gCount = 0;
|
|
gCap = 0;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
}
|
|
pthread_mutex_unlock(&gInitMutex);
|
|
}
|
|
|
|
|
|
static int32_t exportCall(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogFnT *fn;
|
|
int32_t index;
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount < 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "calogCall expects (name, ...args)");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = exportFindLocked(args[0].as.s.bytes);
|
|
if (index < 0) {
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogFail(result, calogErrNotFoundE, "calogCall: no such exported function");
|
|
}
|
|
// Hold a reference across the (unlocked) call so a concurrent unexport can't free it.
|
|
fn = gEntries[index].fn;
|
|
calogFnRetain(fn);
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
status = calogFnInvoke(fn, &args[1], argCount - 1, result);
|
|
calogFnRelease(fn);
|
|
return status;
|
|
}
|
|
|
|
|
|
static int32_t exportFindLocked(const char *name) {
|
|
int32_t index;
|
|
|
|
for (index = 0; index < gCount; index++) {
|
|
if (strcmp(gEntries[index].name, name) == 0) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
const char *name;
|
|
char *nameCopy;
|
|
int32_t index;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 2 || args[0].type != calogStringE || args[1].type != calogFnE) {
|
|
return calogFail(result, calogErrArgE, "calogExport expects (name, function)");
|
|
}
|
|
name = args[0].as.s.bytes;
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = exportFindLocked(name);
|
|
if (index >= 0) {
|
|
// Replace: drop the old function, retain the new one.
|
|
calogFnRelease(gEntries[index].fn);
|
|
calogFnRetain(args[1].as.fn);
|
|
gEntries[index].fn = args[1].as.fn;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|
|
if (gCount == gCap) {
|
|
int32_t newCap;
|
|
ExportEntryT *grown;
|
|
newCap = (gCap == 0) ? EXPORT_INITIAL_CAP : gCap * EXPORT_GROWTH;
|
|
grown = (ExportEntryT *)realloc(gEntries, (size_t)newCap * sizeof(ExportEntryT));
|
|
if (grown == NULL) {
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogFail(result, calogErrOomE, "calogExport: out of memory");
|
|
}
|
|
gEntries = grown;
|
|
gCap = newCap;
|
|
}
|
|
nameCopy = strdup(name);
|
|
if (nameCopy == NULL) {
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogFail(result, calogErrOomE, "calogExport: out of memory");
|
|
}
|
|
calogFnRetain(args[1].as.fn);
|
|
gEntries[gCount].name = nameCopy;
|
|
gEntries[gCount].fn = args[1].as.fn;
|
|
gCount++;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t exportRemove(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t index;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "calogUnexport expects (name)");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = exportFindLocked(args[0].as.s.bytes);
|
|
if (index >= 0) {
|
|
free(gEntries[index].name);
|
|
calogFnRelease(gEntries[index].fn);
|
|
gEntries[index] = gEntries[gCount - 1];
|
|
gCount--;
|
|
}
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t exportResolve(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t index;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "__calogExportResolve expects (name)");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = exportFindLocked(args[0].as.s.bytes);
|
|
if (index >= 0) {
|
|
CalogFnT *fn;
|
|
fn = gEntries[index].fn;
|
|
// The result carries a reference; whoever consumes it releases that reference.
|
|
calogFnRetain(fn);
|
|
calogValueFn(result, fn);
|
|
}
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|