Separate shared service ownership from thread-local logging caches

This commit is contained in:
jdb committed 2026-09-22 07:18:22 -05:00
1 parent c1c8a3c072
commit 761bd66793
6 files changed
+386 -157

No files matched your search

+71
View File
@@ -6,6 +6,7 @@ import ../src/namespaced_logging
const iterations = 1_000
var start: Atomic[bool]
var cloneLock: Lock
var ready: Atomic[int]
proc awaitStart() =
while not start.load():
@@ -27,6 +28,50 @@ proc independentWorker() {.thread.} =
let logger = local.getLogger("worker")
doAssert logger.isEnabled(lvlInfo)
type
AppenderLifetimes = object
created, destroyed: Atomic[int]
TrackedAppenderObj = object of LogAppender
lifetimes: ptr AppenderLifetimes
TrackedAppender = ref TrackedAppenderObj
proc `=destroy`(app: TrackedAppenderObj) =
if app.lifetimes != nil:
app.lifetimes.destroyed.atomicInc()
`=destroy`(app.namespace)
proc trackedAppender(lifetimes: ptr AppenderLifetimes): TrackedAppender =
lifetimes.created.atomicInc()
TrackedAppender(lifetimes: lifetimes)
method clone(app: TrackedAppender): LogAppender {.gcsafe.} =
trackedAppender(app.lifetimes)
proc lifetimeWorker(source: ptr LogService) {.thread.} =
# Borrow the original only until this thread owns a local service. The
# readiness barrier lets the parent release it before we continue.
let local = source[].threadLocalRef()
ready.atomicInc()
awaitStart()
for i in 0 ..< iterations:
var handle = local.service
var copied = handle
handle = LogService()
var moved = move(copied)
moved.setThreshold("worker", lvlInfo)
let nested = moved.threadLocalRef()
doAssert nested.getLogger("worker").isEnabled(lvlInfo)
# Local caches and the last shared owner are destroyed on worker threads.
proc reconfigureWorker(service: LogService) {.thread.} =
var handle = service
let local = service.threadLocalRef()
awaitStart()
for i in 0 ..< iterations:
handle.setThreshold("worker", lvlInfo)
local.ensureFreshness()
doAssert local.getLogger("worker").isEnabled(lvlInfo)
type
CustomState = ref object
value: int
@@ -86,6 +131,32 @@ proc main() =
createThread(workers[i], stateWorker, addr source)
start.store(true)
joinThreads(workers)
of "service-lifetime":
var lifetimes: AppenderLifetimes
var service = initLogService()
block:
let original = trackedAppender(addr lifetimes)
service.addAppender(original)
var workers: array[4, Thread[ptr LogService]]
for i in 0 ..< workers.len:
createThread(workers[i], lifetimeWorker, addr service)
while ready.load() != workers.len:
discard
# Thread arguments contain only a borrowed pointer, so there are no hidden
# owning handles in Thread.data keeping the control block alive here.
service = LogService()
start.store(true)
joinThreads(workers)
doAssert lifetimes.created.load() > workers.len
doAssert lifetimes.destroyed.load() == lifetimes.created.load()
of "service-reconfigure":
var service = initLogService()
service.addAppender(initConsoleLogAppender())
var workers: array[4, Thread[LogService]]
for i in 0 ..< workers.len:
createThread(workers[i], reconfigureWorker, service)
start.store(true)
joinThreads(workers)
of "formatter":
var source = initConsoleLogAppender()
source.formatter = makeFormatter("prefix: ")