calog/libs/calogExport.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;
}