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