Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
26fe874
Add Static Module Support
eifrah-aws Mar 22, 2026
df6c34f
Fix Remove Cached Eval Scripts On Engine Unregister
eifrah-aws Mar 25, 2026
aacd575
Fix Lua Monotonic Time Handling For Static Builds
eifrah-aws Mar 25, 2026
114c783
Update server startup with LUA & Refactor Lua Build Modes
eifrah-aws Mar 25, 2026
2be8ea9
Fix Lua Module Build Flag Propagation
eifrah-aws Mar 25, 2026
2010ad6
Fix Shutdown Hook Log Assertion
eifrah-aws Mar 29, 2026
4b7ff25
Removed extra log line during shutdown
eifrah-aws Mar 29, 2026
412e8c8
Use weak attributes on duplicate symbols
eifrah-aws Mar 30, 2026
08553b6
Mark `sha1hex` as a weak symbol
eifrah-aws Mar 30, 2026
e2cfc88
Merge branch 'unstable' into static-lua
eifrah-aws Apr 5, 2026
ec69dd1
Fixed loading on FreeBSD
eifrah-aws Apr 7, 2026
686fde1
Refactor Static Module Loading Documentation
eifrah-aws Apr 7, 2026
4c548f0
Merge branch 'unstable' into static-lua
eifrah-aws Apr 7, 2026
6e25514
Lua Makefile: Use a Variable to Declare the Target
eifrah-aws Apr 7, 2026
eadeb47
Merge branch 'unstable' into static-lua
eifrah-aws Apr 12, 2026
c1ad9c5
Removed un-needed attributes
eifrah-aws Apr 12, 2026
09d0891
Merge branch 'unstable' into static-lua
eifrah-aws Apr 13, 2026
cf5a994
Refine module lifecycle and cleanup logic
eifrah-aws Apr 13, 2026
83c258f
Remove always_inline attribute from `getClientType`
eifrah-aws Apr 13, 2026
f0886db
Refactor Module Loading to Eliminate Code Duplication
eifrah-aws Apr 14, 2026
251f2fe
Addressed comments
eifrah-aws Apr 14, 2026
17c0128
When possible, use engine's time helpers
eifrah-aws Apr 14, 2026
68a09c7
Merge branch 'unstable' into static-lua
eifrah-aws Apr 16, 2026
8125f2c
Merge branch 'unstable' into static-lua
eifrah-aws Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if (APPLE)
endif ()

# Options
option(BUILD_LUA "Build Valkey Lua scripting engine" ON)
set(BUILD_LUA "static" CACHE STRING "Build Valkey Lua scripting engine: static (default), module, no")
option(BUILD_UNIT_GTESTS "Build valkey-unit-gtests" OFF)
option(BUILD_TEST_MODULES "Build all test modules" OFF)
option(BUILD_EXAMPLE_MODULES "Build example modules" OFF)
Expand Down
39 changes: 24 additions & 15 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,34 @@ add_dependencies(valkey-server generate_commands_def)
add_dependencies(valkey-server generate_fmtargs_h)
add_dependencies(valkey-server release_header)

if (BUILD_LUA)
if (NOT BUILD_LUA STREQUAL "no")
message(STATUS "Build Lua scripting engine module")
if (BUILD_LUA STREQUAL "static")
add_compile_definitions(STATIC_LUA=1)
message(STATUS "Building LUA as a STATIC module")
else ()
add_compile_definitions(STATIC_LUA=0)
message(STATUS "Building LUA as a DYNAMIC module")
endif ()
add_subdirectory(modules/lua)
add_dependencies(valkey-server valkeylua)
if (BUILD_LUA STREQUAL "static")
target_link_libraries(valkey-server $<LINK_LIBRARY:WHOLE_ARCHIVE,valkeylua>)
else ()
add_dependencies(valkey-server valkeylua)
endif ()
target_compile_definitions(valkey-server PRIVATE LUA_ENABLED)
if (UNIX AND NOT APPLE)
target_compile_definitions(valkey-server PRIVATE LUA_LIB=libvalkeylua.so)
target_link_options(valkey-server PRIVATE -Wl,--disable-new-dtags)
else ()
target_compile_definitions(valkey-server PRIVATE LUA_LIB=libvalkeylua.dylib)
endif ()
set(VALKEY_INSTALL_RPATH "")
set_target_properties(valkey-server PROPERTIES
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR};${CMAKE_LIBRARY_OUTPUT_DIRECTORY}"
INSTALL_RPATH_USE_LINK_PATH TRUE
BUILD_WITH_INSTALL_RPATH TRUE
)
endif()
set_target_properties(
valkey-server
PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR};${CMAKE_LIBRARY_OUTPUT_DIRECTORY}"
INSTALL_RPATH_USE_LINK_PATH TRUE
BUILD_WITH_INSTALL_RPATH TRUE)
endif ()
unset(BUILD_LUA CACHE)

if (VALKEY_RELEASE_BUILD)
Expand All @@ -49,9 +59,8 @@ if (DEBUG_FORCE_DEFRAG)
endif ()

if (BUILD_SANITIZER)
# 'BUILD_SANITIZER' is defined in ValkeySetup module (based on user input)
# If defined, the variables 'VALKEY_SANITAIZER_CFLAGS' and 'VALKEY_SANITAIZER_LDFLAGS'
# are set with the link & compile flags required
# 'BUILD_SANITIZER' is defined in ValkeySetup module (based on user input) If defined, the variables
# 'VALKEY_SANITAIZER_CFLAGS' and 'VALKEY_SANITAIZER_LDFLAGS' are set with the link & compile flags required
message(STATUS "Adding sanitizer flags for target valkey-server")
target_compile_options(valkey-server PRIVATE ${VALKEY_SANITAIZER_CFLAGS})
target_link_options(valkey-server PRIVATE ${VALKEY_SANITAIZER_LDFLAGS})
Expand Down Expand Up @@ -118,10 +127,10 @@ endif ()

# Friendly hint like the Makefile one
file(RELATIVE_PATH _CMAKE_DIR_RELATIVE_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
add_custom_target(hint ALL
add_custom_target(
hint ALL
DEPENDS valkey-server valkey-cli valkey-benchmark
COMMAND ${CMAKE_COMMAND} -E echo ""
COMMAND ${CMAKE_COMMAND} -E echo "Hint: It is a good idea to run tests with your CMake-built binaries \\;\\)"
COMMAND ${CMAKE_COMMAND} -E echo " ./${_CMAKE_DIR_RELATIVE_PATH}/runtest"
COMMAND ${CMAKE_COMMAND} -E echo ""
)
COMMAND ${CMAKE_COMMAND} -E echo "")
35 changes: 25 additions & 10 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -254,22 +254,37 @@ endif
FINAL_CFLAGS+= -I../deps/libvalkey/include -I../deps/linenoise -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float

# Lua scripting engine module
LUA_MODULE_NAME:=modules/lua/libvalkeylua.so
ifeq ($(BUILD_LUA),no)
LUA_MODULE_NAME=
LUA_MODULE=
LUA_MODULE_INSTALL=
else
FINAL_CFLAGS+=-DSTATIC_LUA=0
else ifeq ($(BUILD_LUA),module)
LUA_MODULE_NAME=modules/lua/libvalkeylua.so
LUA_MODULE=$(LUA_MODULE_NAME)
LUA_MODULE_INSTALL=install-lua-module

current_dir = $(shell pwd)
FINAL_CFLAGS+=-DLUA_ENABLED -DLUA_LIB=libvalkeylua.so
ifeq ($(uname_S),Darwin)
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib
FINAL_LDFLAGS+= -Wl,-rpath,$(current_dir)/modules/lua
FINAL_CFLAGS+=-DLUA_ENABLED -DLUA_LIB=libvalkeylua.so -DSTATIC_LUA=0
ifeq ($(uname_S),Darwin)
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib
FINAL_LDFLAGS+= -Wl,-rpath,$(current_dir)/modules/lua
else
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib:$(current_dir)/modules/lua -Wl,--disable-new-dtags
endif
else
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib:$(current_dir)/modules/lua -Wl,--disable-new-dtags
endif
# The default: building Lua as a static module.
LUA_MODULE_NAME=modules/lua/libvalkeylua.a
LUA_MODULE=$(LUA_MODULE_NAME)
current_dir = $(shell pwd)
FINAL_CFLAGS+=-DLUA_ENABLED -DSTATIC_LUA=1
ifeq ($(uname_S),Darwin)
LUA_LDFLAGS=-Wl,-export_dynamic -Wl,-force_load,$(current_dir)/modules/lua/libvalkeylua.a ../deps/lua/src/liblua.a
else ifeq ($(uname_S),FreeBSD)
LUA_LDFLAGS=-Wl,--export-dynamic -Wl,--whole-archive $(current_dir)/modules/lua/libvalkeylua.a -Wl,--no-whole-archive ../deps/lua/src/liblua.a
else
LUA_LDFLAGS=-Wl,--whole-archive $(current_dir)/modules/lua/libvalkeylua.a -Wl,--no-whole-archive ../deps/lua/src/liblua.a
endif
endif

# Determine systemd support and/or build preference (defaulting to auto-detection)
Expand Down Expand Up @@ -687,7 +702,7 @@ endif

# valkey-server
$(SERVER_NAME): $(ENGINE_SERVER_OBJ) $(LUA_MODULE)
$(SERVER_LD) -o $@ $(ENGINE_SERVER_OBJ) ../deps/libvalkey/lib/libvalkey.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)
$(SERVER_LD) -o $@ $(ENGINE_SERVER_OBJ) ../deps/libvalkey/lib/libvalkey.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS) $(LUA_LDFLAGS)

# Valkey static library, used to compile against for unit testing
$(ENGINE_LIB_NAME): $(ENGINE_SERVER_OBJ)
Expand Down Expand Up @@ -715,7 +730,7 @@ $(RDMA_MODULE_NAME): $(SERVER_NAME)

# engine_lua.so
$(LUA_MODULE_NAME): .make-prerequisites
cd modules/lua && $(MAKE) OPTIMIZATION="$(OPTIMIZATION)"
$(MAKE) -C modules/lua OPTIMIZATION="$(OPTIMIZATION)" BUILD_LUA="$(BUILD_LUA)"

# valkey-cli
$(ENGINE_CLI_NAME): $(ENGINE_CLI_OBJ)
Expand Down
1 change: 1 addition & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,7 @@ void rewriteConfigLoadmoduleOption(struct rewriteConfigState *state) {
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct ValkeyModule *module = dictGetVal(de);
if (module->is_static_module) continue;
line = moduleLoadQueueEntryToLoadmoduleOptionStr(module, "loadmodule");
rewriteConfigRewriteLine(state, "loadmodule", line, 1);
}
Expand Down
27 changes: 27 additions & 0 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ void freeEvalScripts(dict *scripts, list *scripts_lru_list, list *engine_callbac
}
}

void freeCachedScriptsForEngine(scriptingEngine *engine) {
if (engine == NULL) {
return;
}
}

Comment thread
eifrah-aws marked this conversation as resolved.
Outdated
static void resetEngineEvalEnvCallback(scriptingEngine *engine, void *context) {
int async = context != NULL;
callableLazyEnvReset *callback = scriptingEngineCallResetEnvFunc(engine, VMSE_EVAL, async);
Expand All @@ -182,6 +188,27 @@ void evalRelease(int async) {
}


/* Remove all cached eval scripts associated with the given scripting engine.
* Called when a scripting engine is unregistered to avoid dangling engine
* pointers in the eval script cache. */
void evalRemoveScriptsOfEngine(scriptingEngine *engine, const char *engine_name) {
UNUSED(engine_name);
dictIterator *iter = dictGetSafeIterator(evalCtx.scripts);
dictEntry *entry;
while ((entry = dictNext(iter))) {
evalScript *es = dictGetVal(entry);
if (es->engine == engine) {
sds sha = dictGetKey(entry);
evalCtx.scripts_mem -= sdsAllocSize(sha) + getStringObjectSdsUsedMemory(es->body);
if (es->node) {
listDelNode(evalCtx.scripts_lru_list, es->node);
}
dictDelete(evalCtx.scripts, sha);
}
}
dictReleaseIterator(iter);
}

void evalReset(int async) {
evalRelease(async);
evalInit();
Expand Down
3 changes: 3 additions & 0 deletions src/eval.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#ifndef _EVAL_H_
#define _EVAL_H_

typedef struct scriptingEngine scriptingEngine;

void evalInit(void);
void evalReset(int async);
void evalRemoveScriptsOfEngine(scriptingEngine *engine, const char *engine_name);
void *evalActiveDefragScript(void *ptr);

#endif /* _EVAL_H_ */
157 changes: 147 additions & 10 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,17 @@
* replacements are done, such as the replacement of RM with ValkeyModule in
* function names. For details, see the script src/modules/gendoc.rb.
* -------------------------------------------------------------------------- */

#include "server.h"
#include "cluster.h"
#include "commandlog.h"
#include "rdb.h"
#include "monotonic.h"
#include "script.h"
#include "call_reply.h"
#include "hdr_histogram.h"
#include "crc16_slottable.h"
#include "valkeymodule.h"
#include "module.h"
#include "call_reply.h"
#include "io_threads.h"
#include "scripting_engine.h"
#include "cluster_migrateslots.h"
Expand Down Expand Up @@ -12876,6 +12875,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa
dictAdd(modules, ctx.module->name, ctx.module);
ctx.module->blocked_clients = 0;
ctx.module->handle = handle;
ctx.module->is_static_module = 0;
ctx.module->loadmod = zmalloc(sizeof(struct moduleLoadQueueEntry));
ctx.module->loadmod->path = sdsnew(path);
ctx.module->loadmod->argv = module_argc ? zmalloc(sizeof(robj *) * module_argc) : NULL;
Expand Down Expand Up @@ -12919,6 +12919,137 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa
return C_OK;
}

/* Resolve a symbol from a statically linked module. The symbol is looked up
* by constructing the name "<symbol_name>_<module_name>" and searching for it
* in the current process via dlopen(NULL)/dlsym(). On success, '*out' is set
* to the symbol address, '*handle' is set to the dlopen handle, and C_OK is
* returned. On failure C_ERR is returned and an appropriate warning is logged. */
static int moduleLoadStaticSymbol(void **out, void **handle, const char *symbol_name, const char *module_name) {
char symbol_full_name[128];
int n = snprintf(symbol_full_name, sizeof(symbol_full_name), "%s_%s", symbol_name, module_name);
if (n >= (int)sizeof(symbol_full_name)) {
serverLog(LL_WARNING, "Module name is too long");
return C_ERR;
}

// Open a handle to self
Comment thread
eifrah-aws marked this conversation as resolved.
Outdated
*handle = dlopen(NULL, RTLD_NOW);
if (*handle == NULL) {
char *error = dlerror();
if (error == NULL) error = "Unknown error";

serverLog(LL_WARNING, "Failed to load static module: %s. %s", module_name, error);
return C_ERR;
}

*out = dlsym(*handle, symbol_full_name);
if (*out == NULL) {
char *error = dlerror();
if (error == NULL) error = "Unknown error";

serverLog(LL_WARNING,
"Failed to load static module: %s. Could not load method: %s. %s", module_name,
symbol_full_name, error);
dlclose(*handle);
return C_ERR;
Comment thread
eifrah-aws marked this conversation as resolved.
}
return C_OK;
}

/* Load a statically linked module and initialize it. This is the static
* counterpart of moduleLoad(): instead of dlopen()ing a shared object from
* a file path, it resolves the module's entry point from the running
* executable itself.
*
* The entry point is located by constructing the symbol name
* "ValkeyModule_OnLoad_<module_name>" and resolving it with
* moduleLoadStaticSymbol(). For example, a module named "mymodule" must
* provide a function called ValkeyModule_OnLoad_mymodule.
*
* Once the entry point is found, the function creates a temporary module
* context, invokes the OnLoad callback, and on success registers the module
* in the global modules dictionary with is_static_module set to 1 and handle
* set to NULL (since there is no shared-object handle to keep open).
*
* If 'is_loadex' is true, the function also validates that all queued module
* configurations were consumed; otherwise the module is unloaded.
*
* On success C_OK is returned, otherwise C_ERR is returned. */
int moduleLoadStatic(const char *module_name, void **module_argv, int module_argc, int is_loadex) {
// Locate the load
Comment thread
eifrah-aws marked this conversation as resolved.
Outdated
ModuleLoadFunc onload;
void *handle = NULL;
if (moduleLoadStaticSymbol((void **)&onload, &handle, "ValkeyModule_OnLoad", module_name) != C_OK) {
return C_ERR;
}

ValkeyModuleCtx ctx;
moduleCreateContext(&ctx, NULL, VALKEYMODULE_CTX_TEMP_CLIENT); /* We pass NULL since we don't have a module yet. */
if (onload((void *)&ctx, module_argv, module_argc) == VALKEYMODULE_ERR) {
if (ctx.module) {
serverLog(LL_WARNING, "Static Module %s initialization failed. Module not loaded.", module_name);
moduleUnregisterCleanup(ctx.module);
moduleRemoveCateogires(ctx.module);
moduleFreeModuleStructure(ctx.module);
} else {
/* If there is no ctx.module, this means that our ValkeyModule_Init call failed,
* and currently init will only fail on busy name. */
serverLog(LL_WARNING, "Static Module %s initialization failed. Module name is busy.", module_name);
}
moduleFreeContext(&ctx);
dlclose(handle);
return C_ERR;
}

dlclose(handle);

/* Module loaded! Register it. */
dictAdd(modules, ctx.module->name, ctx.module);
ctx.module->blocked_clients = 0;
ctx.module->handle = NULL; // We will re-open when needed.
Comment thread
eifrah-aws marked this conversation as resolved.
Outdated
ctx.module->is_static_module = 1;
ctx.module->loadmod = zmalloc(sizeof(struct moduleLoadQueueEntry));
ctx.module->loadmod->path = sdsnew(module_name);
ctx.module->loadmod->argv = module_argc ? zmalloc(sizeof(robj *) * module_argc) : NULL;
ctx.module->loadmod->argc = module_argc;
for (int i = 0; i < module_argc; i++) {
ctx.module->loadmod->argv[i] = module_argv[i];
incrRefCount(ctx.module->loadmod->argv[i]);
}

/* If module commands have ACL categories, recompute command bits
* for all existing users once the modules has been registered. */
if (ctx.module->num_commands_with_acl_categories) {
ACLRecomputeCommandBitsFromCommandRulesAllUsers();
}
serverLog(LL_NOTICE, "Static Module '%s' successfully loaded", ctx.module->name);
ctx.module->onload = 0;

int post_load_err = 0;
if (listLength(ctx.module->module_configs) && !ctx.module->configs_initialized) {
serverLogRaw(LL_WARNING,
"Module Configurations were not set, likely a missing LoadConfigs call. Unloading the module.");
post_load_err = 1;
}

if (is_loadex && dictSize(server.module_configs_queue)) {
serverLogRaw(LL_WARNING,
"Loadex configurations were not applied, likely due to invalid arguments. Unloading the module.");
post_load_err = 1;
}

if (post_load_err) {
moduleUnload(ctx.module->name, NULL);
moduleFreeContext(&ctx);
return C_ERR;
}

/* Fire the loaded modules event. */
moduleFireServerEvent(VALKEYMODULE_EVENT_MODULE_CHANGE, VALKEYMODULE_SUBEVENT_MODULE_LOADED, ctx.module);
moduleFreeContext(&ctx);
return C_OK;
}

static int moduleUnloadInternal(struct ValkeyModule *module, const char **errmsg) {
if (listLength(module->types)) {
*errmsg = "the module exports one or more module-side data "
Expand Down Expand Up @@ -12951,15 +13082,21 @@ static int moduleUnloadInternal(struct ValkeyModule *module, const char **errmsg
}

/* Give module a chance to clean up. */
const char *onUnloadNames[] = {"ValkeyModule_OnUnload", "RedisModule_OnUnload"};
int (*onunload)(void *) = NULL;
for (size_t i = 0; i < sizeof(onUnloadNames) / sizeof(onUnloadNames[0]); i++) {
onunload = (int (*)(void *))(unsigned long)dlsym(module->handle, onUnloadNames[i]);
if (onunload) {
if (i != 0) {
serverLog(LL_NOTICE, "Legacy Redis Module %s found", module->name);
ModuleUnLoadFunc onunload = NULL;
if (module->is_static_module == 1) {
if (moduleLoadStaticSymbol((void **)&onunload, &module->handle, "ValkeyModule_OnUnload", module->name) != C_OK) {
return C_ERR;
}
Comment thread
eifrah-aws marked this conversation as resolved.
} else {
const char *onUnloadNames[] = {"ValkeyModule_OnUnload", "RedisModule_OnUnload"};
for (size_t i = 0; i < sizeof(onUnloadNames) / sizeof(onUnloadNames[0]); i++) {
onunload = (int (*)(void *))(unsigned long)dlsym(module->handle, onUnloadNames[i]);
if (onunload) {
if (i != 0) {
serverLog(LL_NOTICE, "Legacy Redis Module %s found", module->name);
}
break;
}
break;
}
}

Expand Down
Loading
Loading