Fix handle close

This commit is contained in:
Arnaud 2026-06-01 18:22:53 +04:00
parent baa8d7a819
commit d65e1098e2
No known key found for this signature in database
GPG Key ID: A6C7C781817146FA

View File

@ -77,8 +77,8 @@ type
resolvedInternalPort: uint16 resolvedInternalPort: uint16
resolvedExternalPort: uint16 resolvedExternalPort: uint16
resolvedExternalHost: array[PLUM_MAX_HOST_LEN, char] resolvedExternalHost: array[PLUM_MAX_HOST_LEN, char]
# Use abandoned pattern for memory freeing # Refcount-like
abandoned: Atomic[bool] signalReleases: Atomic[int]
onStateChange: PlumStateCallback onStateChange: PlumStateCallback
# libplum calls mappingCallback from its own C thread. Under refc, any thread # libplum calls mappingCallback from its own C thread. Under refc, any thread
@ -97,6 +97,10 @@ var activeMappings {.guard: activeMappingsLock.}: Table[cint, MappingHandle]
initLock(activeMappingsLock) initLock(activeMappingsLock)
proc releaseSignal(handle: MappingHandle) {.raises: [].} =
if handle.signalReleases.fetchAdd(1) == 1:
discard handle.signal.close()
# We can be confident that the pattern is GC Safe using # We can be confident that the pattern is GC Safe using
# a lock. # a lock.
template withSafeLock(body: untyped) = template withSafeLock(body: untyped) =
@ -118,12 +122,7 @@ proc mappingCallback(
if plumState == Destroyed: if plumState == Destroyed:
withSafeLock: withSafeLock:
activeMappings.del(id) activeMappings.del(id)
# The handle can be abandoned after a timeout during a handle.releaseSignal()
# mapping creation.
# In that case, destroy is called internally and the
# signal pointer can be closed.
if handle.abandoned.load():
discard handle.signal.close()
# Release the pin set in createMapping: the C library is done with the # Release the pin set in createMapping: the C library is done with the
# raw pointer and will never call this callback again for this mapping. # raw pointer and will never call this callback again for this mapping.
GC_unref(handle) GC_unref(handle)
@ -230,40 +229,23 @@ proc createMapping*(
raise raise
finally: finally:
if not completed: if not completed:
# Timeout or cancellation: we cannot close the signal here because # Timeout or cancellation: a late callback may still fireSync, so the
# the C callback may fire later and call fireSync on it. # mapping is torn down and the close is decided by releaseSignal.
# Mark the handle as abandoned so the DESTROYED callback closes it.
# Access via activeMappings rather than the local `handle` ref,
# in order to make sure we have the valid reference.
withSafeLock:
let h = activeMappings.getOrDefault(id)
if not h.isNil:
h.abandoned.store(true)
discard plum_destroy_mapping(id) discard plum_destroy_mapping(id)
else: handle.releaseSignal()
# Signal fired normally, safe to close.
discard signal.close()
# Reached only when completed = true (CancelledError skips this). # Reached only when completed = true (CancelledError skips this).
if not completed: if not completed:
return err("plum: mapping " & $id & " timed out") return err("plum: mapping " & $id & " timed out")
# Read result via activeMappings rather than the local `handle` ref in let resolvedState = handle.resolvedState
# order to make sure we have the valid reference. let resolvedMapping = PlumMapping(
var resolvedState: PlumState protocol: handle.resolvedProtocol,
var resolvedMapping: PlumMapping mappingProtocol: handle.resolvedMappingProtocol,
internalPort: handle.resolvedInternalPort,
withSafeLock: externalPort: handle.resolvedExternalPort,
let h = activeMappings.getOrDefault(id) externalHost: $cast[cstring](unsafeAddr handle.resolvedExternalHost),
if not h.isNil: )
resolvedState = h.resolvedState
resolvedMapping = PlumMapping(
protocol: h.resolvedProtocol,
mappingProtocol: h.resolvedMappingProtocol,
internalPort: h.resolvedInternalPort,
externalPort: h.resolvedExternalPort,
externalHost: $cast[cstring](unsafeAddr h.resolvedExternalHost),
)
if resolvedState == Success: if resolvedState == Success:
return ok(MappingResult(id: id, mapping: resolvedMapping)) return ok(MappingResult(id: id, mapping: resolvedMapping))