Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions src/tidesdb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,19 @@ end

tidesdb.Transaction = Transaction

-- A committed transaction synchronously invokes the column family's commit hook
-- (a LuaJIT FFI callback) from inside tidesdb_txn_commit. LuaJIT cannot run an
-- FFI callback that is reached from JIT-compiled code -- once the commit trace is
-- hot it aborts with "PANIC: bad callback". Force the commit path to stay
-- interpreted so the hook is always invoked from the interpreter. Guarded so the
-- module still loads under a plain (non-JIT) Lua interpreter.
do
local ok, jit = pcall(require, "jit")
if ok and jit and jit.off then
jit.off(Transaction.commit)
end
end

-- TidesDB class
local TidesDB = {}
TidesDB.__index = TidesDB
Expand Down
70 changes: 50 additions & 20 deletions tests/test_tidesdb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1721,26 +1721,56 @@ function tests.test_db_stats_wa_fields()
end

function tests.test_init_finalize()
-- The library auto-initializes lazily, so finalize() then init() should
-- succeed and leave the library usable for subsequent operations.
tidesdb.finalize()
local rc = tidesdb.init(nil, nil, nil, nil)
assert_eq(rc, 0, "init after finalize should return 0 (freshly initialized)")
local rc2 = tidesdb.init(nil, nil, nil, nil)
assert_eq(rc2, -1, "second init should return -1 (already initialized)")

-- confirm the library still works after re-init
local path = "./test_db_init"
cleanup_db(path)
local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
db:create_column_family("c")
local cf = db:get_column_family("c")
local txn = db:begin_txn()
txn:put(cf, "k", "v")
txn:commit()
txn:free()
db:close()
cleanup_db(path)
-- tidesdb.init installs custom allocator functions (malloc/calloc/realloc/free)
-- and must run before any other TidesDB call; passing nil keeps the system
-- allocators. As in the C API it is honored only as the first initialization
-- and returns -1 once the library is already initialized, because the
-- allocators cannot be swapped under a live library. init/finalize mutate
-- process-global state, so we must NOT call them in this shared-process suite
-- -- doing so would tear down the library and crash the next sibling test.

-- The bindings must exist.
assert_true(type(tidesdb.init) == "function", "tidesdb.init should be a function")
assert_true(type(tidesdb.finalize) == "function", "tidesdb.finalize should be a function")

-- Exercise the real finalize -> init(custom allocators) -> use cycle in an
-- isolated child process so it cannot disturb other tests, and so the init
-- return-value contract can be asserted deterministically (ordering is fixed
-- here: finalize first, so the next init is the first one and succeeds).
local script = [[
local tidesdb = require("tidesdb")
tidesdb.finalize()
-- nil keeps the system allocators; this is where a real caller would pass custom
-- malloc/calloc/realloc/free. The first init after finalize installs them...
assert(tidesdb.init(nil, nil, nil, nil) == 0, "first init after finalize should return 0")
-- ...and a second init is rejected because allocators are already locked in.
assert(tidesdb.init(nil, nil, nil, nil) == -1, "init on an already-initialized library should return -1")
local path = "./test_db_init_child"
os.execute("rm -rf " .. path)
local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
db:create_column_family("c")
local cf = db:get_column_family("c")
local txn = db:begin_txn()
txn:put(cf, "k", "v")
txn:commit()
txn:free()
db:close()
os.execute("rm -rf " .. path)
print("CHILD_OK")
]]
local tmp = "./_init_finalize_child.lua"
local fh = assert(io.open(tmp, "w"))
fh:write(script)
fh:close()

-- Reuse the interpreter and environment the suite is already running under.
local interp = (arg and arg[-1]) or "luajit"
local p = assert(io.popen(interp .. " " .. tmp .. " 2>&1"))
local out = p:read("*a")
p:close()
os.execute("rm -f " .. tmp)
assert_true(out:find("CHILD_OK", 1, true) ~= nil,
"isolated init/finalize cycle should succeed, got:\n" .. tostring(out))
print("PASS: test_init_finalize")
end

Expand Down
Loading