260 lines
9.1 KiB
C
260 lines
9.1 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 calogOkE;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|