fix: nph linting

This commit is contained in:
Gabriel Cruz 2026-06-09 11:23:29 -03:00
parent e7c2be66f0
commit 3b3ce57330
No known key found for this signature in database
GPG Key ID: 2E467754A6BA9BA5
10 changed files with 40 additions and 43 deletions

View File

@ -35,7 +35,6 @@ export cbor_serialization, options, results
const CborNullByte*: byte = 0xf6'u8
## CBOR encoding of `null` — used as the wire sentinel for empty OK payloads.
proc cborEncode*[T](x: T): seq[byte] =
## CBOR-encode any cbor_serialization-supported type (plus `pointer` / `ptr T`
## via our custom writers) into a fresh `seq[byte]`.

View File

@ -63,7 +63,6 @@ proc reqStructName(p: FFIProcMeta): string =
else:
camel & "Req"
proc generateCargoToml*(libName: string): string =
# `flume` is the unified callback channel (PR #23 Rust review, item 8): one
# primitive that supports both `recv_timeout` (blocking trampoline) and

View File

@ -47,8 +47,10 @@ proc emitLivenessEvent[T, P](ctx: ptr FFIContext[T], name: string, payload: P) =
chronicles.error "liveness event encode failed", name = name, err = exc.msg
return
let dataPtr: pointer =
if event.len > 0: cast[pointer](unsafeAddr event[0])
else: cast[pointer](emptyListenerPayload)
if event.len > 0:
cast[pointer](unsafeAddr event[0])
else:
cast[pointer](emptyListenerPayload)
ctx.dispatchToListeners(name, dataPtr, event.len)
proc onNotResponding*(ctx: ptr FFIContext) =
@ -105,8 +107,7 @@ proc check[T](hb: var HeartbeatMonitor, ctx: ptr FFIContext[T]) =
hb.lastValue = cur
hb.lastChange = Moment.now()
hb.notifiedStale = false
elif not hb.notifiedStale and
Moment.now() - hb.lastChange > FFIHeartbeatStaleThreshold:
elif not hb.notifiedStale and Moment.now() - hb.lastChange > FFIHeartbeatStaleThreshold:
onNotResponding(ctx)
hb.notifiedStale = true

View File

@ -32,13 +32,16 @@ type FFIContext*[T] = object
reqReceivedSignal: ThreadSignalPtr
# to signal main thread, interfacing with the FFI thread, that FFI thread received the request
stopSignal: ThreadSignalPtr
threadExitSignal: ThreadSignalPtr # bounds destroyFFIContext's wait so a blocked loop cannot hang the caller
eventQueueSignal: ThreadSignalPtr # wakes the event thread on enqueue (used once dispatch is rewired in PR #69)
threadExitSignal: ThreadSignalPtr
# bounds destroyFFIContext's wait so a blocked loop cannot hang the caller
eventQueueSignal: ThreadSignalPtr
# wakes the event thread on enqueue (used once dispatch is rewired in PR #69)
eventThreadExitSignal: ThreadSignalPtr # mirrors threadExitSignal for the event thread
userData*: pointer
eventRegistry*: FFIEventRegistry
eventQueue*: EventQueue
ffiHeartbeat*: Atomic[int64] # advanced each FFI-thread loop; event thread reads for liveness
ffiHeartbeat*: Atomic[int64]
# advanced each FFI-thread loop; event thread reads for liveness
running: Atomic[bool] # To control when the threads are running
registeredRequests: ptr Table[cstring, FFIRequestProc]
# Pointer to with the registered requests at compile time

View File

@ -10,7 +10,6 @@ import std/[locks, sequtils, options, tables]
import chronicles
import ./ffi_types, ./cbor_serial
type EventEnvelope*[T] = object
## Standard wire shape for CBOR-encoded FFI events:
## { eventType: tstr, payload: <T> }
@ -18,7 +17,6 @@ type EventEnvelope*[T] = object
eventType*: string
payload*: T
type
FFIEventListener* = object
id*: uint64
@ -33,7 +31,6 @@ type
nextId*: uint64 ## Monotonic id source. 0 is reserved as "invalid"; ids start at 1.
byEvent*: Table[string, seq[FFIEventListener]]
proc initEventRegistry*(reg: var FFIEventRegistry) =
## Must be called exactly once on the owning thread before the registry
## is shared. The embedded `Lock` wraps a platform primitive that cannot
@ -129,7 +126,6 @@ proc snapshotListeners*(
listeners.add(l)
listeners
const EventQueueCapacity* = 1024
## ~24 KiB per context. Sustained backlog at this depth means a
## listener is wedged — what the stuck flag exists to surface.
@ -202,7 +198,6 @@ proc eventQueueLen*(q: var EventQueue): int {.raises: [], gcsafe.} =
withLock q.lock:
return q.count
const emptyListenerPayload*: cstring = ""
## Non-nil zero-length buffer handed to listeners when a payload is
## empty, so a consumer doing `std::string(data, len)` / `memcpy` never
@ -216,8 +211,10 @@ proc notifyListeners*(
## consumer doing `std::string(data, len)` / `memcpy` never receives nil.
let n = max(dataLen, 0)
let dataPtr =
if n > 0 and not data.isNil(): cast[ptr cchar](data)
else: cast[ptr cchar](emptyListenerPayload)
if n > 0 and not data.isNil():
cast[ptr cchar](data)
else:
cast[ptr cchar](emptyListenerPayload)
for listener in listeners:
listener.callback(retCode, dataPtr, cast[csize_t](n), listener.userData)
@ -225,8 +222,10 @@ proc notifyListenersErr*(listeners: seq[FFIEventListener], msg: string) =
## Error fan-out: adapts the message string to `notifyListeners`, which
## supplies the non-nil pointer for the empty-message case.
let p =
if msg.len > 0: cast[pointer](unsafeAddr msg[0])
else: cast[pointer](emptyListenerPayload)
if msg.len > 0:
cast[pointer](unsafeAddr msg[0])
else:
cast[pointer](emptyListenerPayload)
notifyListeners(listeners, RET_ERR, p, msg.len)
var ffiCurrentEventRegistry* {.threadvar.}: ptr FFIEventRegistry
@ -269,8 +268,10 @@ template dispatchFFIEvent*(eventName: string, body: untyped) =
withFFIEventDispatch(eventName, listeners):
let event = body
let dataPtr: pointer =
if event.len > 0: cast[pointer](unsafeAddr event[0])
else: cast[pointer](emptyListenerPayload)
if event.len > 0:
cast[pointer](unsafeAddr event[0])
else:
cast[pointer](emptyListenerPayload)
notifyListeners(listeners, RET_OK, dataPtr, event.len)
template dispatchFFIEventCbor*(eventName: string, eventPayload: typed) =

View File

@ -7,7 +7,6 @@ when defined(ffiGenBindings):
import ../codegen/cpp
import ../codegen/cddl
proc isPtr(typ: NimNode): bool =
## True iff `typ` is a `ptr T` type expression — i.e. an `nnkPtrTy` AST node.
## Used by the binding-generator metadata path to flag pointer-typed params
@ -597,7 +596,6 @@ macro ffiRaw*(prc: untyped): untyped =
echo stmts.repr
return stmts
macro ffi*(prc: untyped): untyped =
## Simplified FFI macro — applies to procs or types.
##
@ -837,7 +835,6 @@ macro ffi*(prc: untyped): untyped =
echo stmts.repr
return stmts
proc buildCtorRequestType(
reqTypeName: NimNode, paramNames: seq[string], paramTypes: seq[NimNode]
): NimNode =
@ -1248,7 +1245,6 @@ macro ffiCtor*(prc: untyped): untyped =
echo stmts.repr
return stmts
macro ffiDtor*(prc: untyped): untyped =
## Defines a C-exported destructor that tears down the FFIContext after the
## body runs.
@ -1361,7 +1357,6 @@ macro ffiDtor*(prc: untyped): untyped =
echo stmts.repr
return stmts
macro ffiEvent*(wireName: static[string], prc: untyped): untyped =
## Declares a library-initiated event. The annotated proc has an empty
## body — the macro fills it with a `dispatchFFIEventCbor` call so the
@ -1449,7 +1444,6 @@ macro ffiEvent*(wireName: static[string], prc: untyped): untyped =
echo generated.repr
return generated
macro genBindings*(
outputDir: static[string] = ffiOutputDir, nimSrcRelPath: static[string] = ffiSrcPath
): untyped =

View File

@ -81,9 +81,18 @@ elseif(NIM_FFI_SANITIZER STREQUAL "tsan")
"TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1:history_size=7")
endif()
# Discover at test time, not build time: a POST_BUILD discovery run launches the
# freshly-linked exe while Windows Defender is still scanning it (and its staged
# DLLs), which routinely overran the default 5s timeout on CI. PRE_TEST defers
# enumeration to `ctest`, and the bumped timeout absorbs first-launch scan delays.
include(GoogleTest)
if(_san_test_env)
gtest_discover_tests(timer_e2e_tests PROPERTIES ENVIRONMENT "${_san_test_env}")
gtest_discover_tests(timer_e2e_tests
DISCOVERY_MODE PRE_TEST
DISCOVERY_TIMEOUT 120
PROPERTIES ENVIRONMENT "${_san_test_env}")
else()
gtest_discover_tests(timer_e2e_tests)
gtest_discover_tests(timer_e2e_tests
DISCOVERY_MODE PRE_TEST
DISCOVERY_TIMEOUT 120)
endif()

View File

@ -235,7 +235,6 @@ when not defined(gcRefc):
# actually landed so a silently-broken dispatch loop is caught.
check evt.called
## A foreign-thread mutation must not be able to invalidate the
## listener's `userData` while an in-flight dispatch is mid-invocation.
## The dispatch templates hold `reg.lock` for the entire snapshot +
@ -317,16 +316,14 @@ suite "liveness events":
defer:
deinitCallbackData(evt)
discard addEventListener(
ctx[].eventRegistry, NotRespondingEventName, captureCb, addr evt
)
discard
addEventListener(ctx[].eventRegistry, NotRespondingEventName, captureCb, addr evt)
onNotResponding(ctx)
waitCallback(evt)
check evt.retCode == RET_OK
let decoded =
cborDecode(callbackBytes(evt), EventEnvelope[NotRespondingEvent])
let decoded = cborDecode(callbackBytes(evt), EventEnvelope[NotRespondingEvent])
check decoded.isOk()
check decoded.value.eventType == NotRespondingEventName
@ -343,9 +340,8 @@ suite "liveness events":
defer:
deinitCallbackData(evt)
discard addEventListener(
ctx[].eventRegistry, RespondingEventName, captureCb, addr evt
)
discard
addEventListener(ctx[].eventRegistry, RespondingEventName, captureCb, addr evt)
onResponding(ctx)
@ -387,9 +383,7 @@ suite "event thread drains queued events":
deinitCallbackData(evt)
const QueuedEvtName = "queued_evt"
discard addEventListener(
ctx[].eventRegistry, QueuedEvtName, captureCb, addr evt
)
discard addEventListener(ctx[].eventRegistry, QueuedEvtName, captureCb, addr evt)
# `tryEnqueueEvent` takes ownership of both buffers on success; the
# event thread c_frees them after dispatch returns.

View File

@ -48,7 +48,6 @@ proc tagCb(
copyMem(addr payload[0], msg, int(len))
record(t[].rec[], t[].name, retCode, payload)
suite "FFIEventRegistry mutation":
test "addEventListener assigns monotonically increasing non-zero ids":
var reg: FFIEventRegistry

View File

@ -324,7 +324,6 @@ suite "sendRequestToFFIThread":
check d.retCode == RET_OK
check cborDecode(callbackBytes(d), string).value == "pong:" & msg
type SimpleLib = object
value: int
@ -372,7 +371,6 @@ suite "ffiCtor macro":
check SimpleLibFFIPool.destroyFFIContext(ctx).isOk()
type SendConfig {.ffi.} = object
message: string