From 06c6f4a0aeb01fad14e72175276495489bdffc89 Mon Sep 17 00:00:00 2001 From: Sang Woo Kim Date: Fri, 17 Jul 2026 00:34:07 +0900 Subject: [PATCH] throttleRecord: lock the record in delayFuncCallback before valuePut delayFuncCallback() runs on the general callback thread pool (armed via callbackRequestDelayed() from valuePut() and enterValue()) and called valuePut() with no dbScanLock. valuePut() mutates record fields (wait_flag, delay_flag, sts, sent, wait), fires the output link with dbPutLink(&prec->out), and runs recGblFwdLink(prec), recGblResetAlarms() and db_post_events() -- all of which EPICS requires the record lock to be held for. dbPutLink/recGblFwdLink additionally reach into another record's lockset, which is memory-unsafe when performed unlocked on SMP. valuePut() is reentrant against process(): a scan thread runs process() holding the record lock and reaches valuePut() through enterValue(), while the callback thread runs valuePut() with no lock at all. The two paths race on the same record. Observable failure: process()->enterValue() reads delay_flag while the callback's valuePut() is concurrently clearing it (delay_flag = 0); enterValue() sees the stale delay_flag == 1 and returns without writing, yet the callback has already passed its wait_flag test -- so the value is left in prec->val with wait_flag set, no OUT write, no FLNK, and no timer re-armed, and the throttle stalls until an unrelated later process. The torn writes to sts/sent are the same race's other face. Taking the record lock around valuePut() serializes the callback-thread mutation against the scan-thread process()/enterValue() path, closing the race. The omission is a slip, proven by this file against itself: the other callback, checkLinkCallback(), already wraps its body in dbScanLock()/ dbScanUnlock(). delayFuncCallback is armed only during record processing, never at init, so unlike checkLinkCallback it needs no interruptAccept guard -- only the lock, matching checkLinkCallback's else branch. --- stdApp/src/throttleRecord.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdApp/src/throttleRecord.c b/stdApp/src/throttleRecord.c index c06100a4..53efa4cb 100644 --- a/stdApp/src/throttleRecord.c +++ b/stdApp/src/throttleRecord.c @@ -534,7 +534,9 @@ static void delayFuncCallback(CALLBACK *pcallback) // printf("delayFuncCallback()\n"); callbackGetUser(prec, pcallback); + dbScanLock((struct dbCommon *)prec); valuePut( prec); + dbScanUnlock((struct dbCommon *)prec); } static void valuePut( throttleRecord *prec)