calog/libs/calogExport.c

260 lines
9.2 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>
#include "calogInternal.h"
typedef struct ExportEntryT {
char *name;
int64_t nameLen;
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 exportFindFoldLocked(const char *bytes, int64_t length);
static int32_t exportFindLocked(const char *bytes, int64_t length);
static void exportFreeAll(void);
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);
static int32_t exportResolveByFind(CalogValueT *args, int32_t argCount, CalogValueT *result, int32_t (*find)(const char *, int64_t));
static int32_t exportResolveFold(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
int32_t calogExportRegister(CalogT *calog) {
calogRegistryRetain(&gInitMutex, &gRefCount);
calogRegisterInline(calog, "calogExport", exportPublish, NULL);
calogRegisterInline(calog, "calogUnexport", exportRemove, NULL);
calogRegisterInline(calog, "calogCall", exportCall, NULL);
calogRegisterInline(calog, "__calogExportResolve", exportResolve, NULL);
// Case-insensitive variant: my-basic uppercases every identifier at parse time, so its
// bare-name resolver matches an export regardless of the case it was registered with.
calogRegisterInline(calog, "__calogExportResolveFold", exportResolveFold, NULL);
return calogAtDestroy(calog, calogExportShutdown, calogDestroyAfterContextsE);
}
void calogExportShutdown(void) {
// calogRegistryRelease holds gInitMutex for the whole call, including exportFreeAll below.
calogRegistryRelease(&gInitMutex, &gRefCount, exportFreeAll);
}
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, args[0].as.s.length);
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 exportFindFoldLocked(const char *bytes, int64_t length) {
int32_t index;
int64_t at;
for (index = 0; index < gCount; index++) {
if (gEntries[index].nameLen != length) {
continue;
}
for (at = 0; at < length; at++) {
char lhs;
char rhs;
lhs = gEntries[index].name[at];
rhs = bytes[at];
if (lhs >= 'A' && lhs <= 'Z') {
lhs = (char)(lhs - 'A' + 'a');
}
if (rhs >= 'A' && rhs <= 'Z') {
rhs = (char)(rhs - 'A' + 'a');
}
if (lhs != rhs) {
break;
}
}
if (at == length) {
return index;
}
}
return -1;
}
static int32_t exportFindLocked(const char *bytes, int64_t length) {
int32_t index;
for (index = 0; index < gCount; index++) {
if (gEntries[index].nameLen == length && memcmp(gEntries[index].name, bytes, (size_t)length) == 0) {
return index;
}
}
return -1;
}
// Invoked by calogRegistryRelease while gInitMutex is held, exactly once, when the last
// reference drops.
static void exportFreeAll(void) {
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);
}
static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
void *buffer;
int64_t capacity;
const char *name;
char *nameCopy;
int64_t nameLen;
int32_t index;
int32_t status;
(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;
nameLen = args[0].as.s.length;
pthread_mutex_lock(&gMapMutex);
index = exportFindLocked(name, nameLen);
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;
}
buffer = gEntries;
capacity = gCap;
status = calogGrow(&buffer, &capacity, (int64_t)gCount + 1, sizeof(ExportEntryT));
if (status != calogOkE) {
pthread_mutex_unlock(&gMapMutex);
return calogFail(result, status, "calogExport: out of memory");
}
gEntries = (ExportEntryT *)buffer;
gCap = (int32_t)capacity;
nameCopy = (char *)malloc((size_t)nameLen + 1);
if (nameCopy == NULL) {
pthread_mutex_unlock(&gMapMutex);
return calogFail(result, calogErrOomE, "calogExport: out of memory");
}
if (nameLen > 0) {
memcpy(nameCopy, name, (size_t)nameLen);
}
nameCopy[nameLen] = '\0';
calogFnRetain(args[1].as.fn);
gEntries[gCount].name = nameCopy;
gEntries[gCount].nameLen = nameLen;
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, args[0].as.s.length);
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) {
(void)userData;
return exportResolveByFind(args, argCount, result, exportFindLocked);
}
static int32_t exportResolveByFind(CalogValueT *args, int32_t argCount, CalogValueT *result, int32_t (*find)(const char *, int64_t)) {
int32_t index;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "export resolve expects (name)");
}
pthread_mutex_lock(&gMapMutex);
index = find(args[0].as.s.bytes, args[0].as.s.length);
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;
}
static int32_t exportResolveFold(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
return exportResolveByFind(args, argCount, result, exportFindFoldLocked);
}