asynManager: fail cleanly instead of hanging on an oversize traceIO truncate size#235
Merged
MarkRivers merged 1 commit intoJul 17, 2026
Conversation
…runcate size setTraceIOTruncateSize() takes a size_t, but the two callers pass a signed value: asynRecord's TSIZ field (epicsInt32) and the iocsh asynSetTraceIOTruncateSize(..., int size). A negative value such as -1 converts to a size_t near SIZE_MAX. With that size the function called callocMustSucceed(), which loops forever (epicsThreadSuspendSelf) when the allocation cannot be satisfied -- and it does so while holding the global pasynBase->lockTrace mutex, so every other thread that traces then blocks too. A single `caput <record>.TSIZ -1` wedges tracing for the whole IOC. The old code also freed the existing traceBuffer before allocating the replacement, so even a recoverable allocator would leave traceBuffer dangling on failure. Allocate the replacement with calloc() and, on NULL, unlock and return asynError so an unsatisfiable request fails cleanly. Free the existing buffer only once the new one is in hand, so a failure leaves the current trace buffer intact. Both callers reach this single owner, so the fix closes the record and iocsh entry points together. Add an asynPortDriverTest case: setTraceIOTruncateSize((size_t)-1) returns asynError and a normal size still succeeds. Without the fix the test hangs on the oversize request instead of completing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
setTraceIOTruncateSize()takes asize_t, but its two callers pass asigned value:
asynRecord'sTSIZfield isepicsInt32(asynRecord.c)asynSetTraceIOTruncateSize(..., int size)(
asynShellCommands.c)A negative value such as
-1converts to asize_tnearSIZE_MAX.With that size the function called
callocMustSucceed()loops forever (epicsThreadSuspendSelf) when theallocation can't be satisfied — and it does so while holding the global
pasynBase->lockTracemutex, so every other thread that traces then blockson that mutex too. A single
wedges tracing for the whole IOC.
The pre-existing
free()before the allocation is a second problem: even arecoverable allocator would leave
traceBufferdangling on failure.Fix
Allocate the replacement with
calloc()and, onNULL, unlock and returnasynError— an unsatisfiable request fails cleanly instead of hanging.Free the existing buffer only once the new one is in hand, so a failure
leaves the current trace buffer intact. Both entry points funnel through
this single owner, so the record and iocsh paths are fixed together.
Test
Adds an
asynPortDriverTestcase:setTraceIOTruncateSize((size_t)-1)returns
asynError, and a normal size still returnsasynSuccess. Withoutthe fix the test hangs on the oversize request instead of completing
(241/241 with the fix).