mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-05-06 18:29:27 +00:00
* protect against mem leak in case of failures sending requests to ffi thread * better cleanup if failures in createFFIContext * avoid dangling cstring in handleRes under ARC/ORC * better resource cleanup in destroyFFIContext * invoke onNotResponding if failure in destroyFFIContext * correct seq copy in alloc * make sure the lock is init before cleanUpResources * better possible exception handling in processReq * guard allocSharedSeq if given seq is empty * enhance error handling in ffi_context * add new tests and some corrections
138 lines
3.8 KiB
Nim
138 lines
3.8 KiB
Nim
import std/locks
|
|
import unittest2
|
|
import results
|
|
import ../ffi
|
|
|
|
type TestLib = object
|
|
|
|
## Per-request callback state. The test thread blocks on `cond` until the
|
|
## FFI thread signals it — no polling, no CPU waste.
|
|
type CallbackData = object
|
|
lock: Lock
|
|
cond: Cond
|
|
called: bool
|
|
retCode: cint
|
|
msg: array[512, char]
|
|
msgLen: int
|
|
|
|
proc initCallbackData(d: var CallbackData) =
|
|
d.lock.initLock()
|
|
d.cond.initCond()
|
|
|
|
proc deinitCallbackData(d: var CallbackData) =
|
|
d.cond.deinitCond()
|
|
d.lock.deinitLock()
|
|
|
|
proc testCallback(
|
|
retCode: cint, msg: ptr cchar, len: csize_t, userData: pointer
|
|
) {.cdecl, gcsafe, raises: [].} =
|
|
let d = cast[ptr CallbackData](userData)
|
|
acquire(d[].lock)
|
|
d[].retCode = retCode
|
|
let n = min(int(len), d[].msg.high)
|
|
if n > 0 and not msg.isNil:
|
|
copyMem(addr d[].msg[0], msg, n)
|
|
d[].msg[n] = '\0'
|
|
d[].msgLen = n
|
|
d[].called = true
|
|
signal(d[].cond)
|
|
release(d[].lock)
|
|
|
|
proc waitCallback(d: var CallbackData) =
|
|
acquire(d.lock)
|
|
while not d.called:
|
|
wait(d.cond, d.lock)
|
|
release(d.lock)
|
|
|
|
proc callbackMsg(d: var CallbackData): string =
|
|
result = newString(d.msgLen)
|
|
if d.msgLen > 0:
|
|
copyMem(addr result[0], addr d.msg[0], d.msgLen)
|
|
|
|
registerReqFFI(PingRequest, lib: ptr TestLib):
|
|
proc(message: cstring): Future[Result[string, string]] {.async.} =
|
|
return ok("pong:" & $message)
|
|
|
|
registerReqFFI(FailRequest, lib: ptr TestLib):
|
|
proc(): Future[Result[string, string]] {.async.} =
|
|
return err("intentional failure")
|
|
|
|
registerReqFFI(EmptyOkRequest, lib: ptr TestLib):
|
|
proc(): Future[Result[string, string]] {.async.} =
|
|
return ok("")
|
|
|
|
suite "createFFIContext / destroyFFIContext":
|
|
test "create and destroy succeeds":
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
checkpoint "createFFIContext failed: " & $error
|
|
check false
|
|
return
|
|
check destroyFFIContext(ctx).isOk()
|
|
|
|
test "double destroy is safe via running flag":
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
check false
|
|
return
|
|
check destroyFFIContext(ctx).isOk()
|
|
|
|
suite "sendRequestToFFIThread":
|
|
test "successful request triggers RET_OK callback":
|
|
var d: CallbackData
|
|
initCallbackData(d)
|
|
defer: deinitCallbackData(d)
|
|
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
check false
|
|
return
|
|
defer: discard destroyFFIContext(ctx)
|
|
|
|
check sendRequestToFFIThread(ctx, PingRequest.ffiNewReq(testCallback, addr d, "hello".cstring)).isOk()
|
|
waitCallback(d)
|
|
check d.retCode == RET_OK
|
|
check callbackMsg(d) == "pong:hello"
|
|
|
|
test "failing request triggers RET_ERR callback":
|
|
var d: CallbackData
|
|
initCallbackData(d)
|
|
defer: deinitCallbackData(d)
|
|
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
check false
|
|
return
|
|
defer: discard destroyFFIContext(ctx)
|
|
|
|
check sendRequestToFFIThread(ctx, FailRequest.ffiNewReq(testCallback, addr d)).isOk()
|
|
waitCallback(d)
|
|
check d.retCode == RET_ERR
|
|
|
|
test "empty ok response delivers empty message":
|
|
var d: CallbackData
|
|
initCallbackData(d)
|
|
defer: deinitCallbackData(d)
|
|
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
check false
|
|
return
|
|
defer: discard destroyFFIContext(ctx)
|
|
|
|
check sendRequestToFFIThread(ctx, EmptyOkRequest.ffiNewReq(testCallback, addr d)).isOk()
|
|
waitCallback(d)
|
|
check d.retCode == RET_OK
|
|
check d.msgLen == 0
|
|
|
|
test "sequential requests are all processed":
|
|
let ctx = createFFIContext[TestLib]().valueOr:
|
|
check false
|
|
return
|
|
defer: discard destroyFFIContext(ctx)
|
|
|
|
for i in 1 .. 5:
|
|
var d: CallbackData
|
|
initCallbackData(d)
|
|
let msg = "msg" & $i
|
|
check sendRequestToFFIThread(ctx, PingRequest.ffiNewReq(testCallback, addr d, msg.cstring)).isOk()
|
|
waitCallback(d)
|
|
deinitCallbackData(d)
|
|
check d.retCode == RET_OK
|
|
check callbackMsg(d) == "pong:" & msg
|