pub struct MandatoryThread { /* private fields */ }Expand description
A thread the IOC cannot correctly run without — the scan rates, the callback bands, the delayed-callback timer, the boot script.
§Invariant
An IOC that fails to start a mandatory thread MUST NOT continue serving.
A thread-local panic is not that: on a panic = "unwind" target — and RTEMS
and VxWorks both default to unwind — Builder::spawn(..).expect(..) kills
only the thread that called it. Measured on a VxWorks 7 RTP on a 1 GB guest:
EAGAIN from the periodic-scan spawn panicked the scan-owner thread, the
stop guard it held unwound and stopped the rates that had started, and the
process went on answering CA with zero periodic scanning — a half-IOC whose
records simply never process.
C has no such state. spawnPeriodic (dbScan.c:943-959) calls
epicsThreadCreateOpt and then epicsEventWait(startStopEvent); the event
is posted by periodicTask itself, so when the thread was never created
nobody posts it and iocInit wedges. C never reaches “serving”.
§Why there is no Result on spawn
Because there is nothing a caller could do with one that satisfies the
invariant. Every caller that is not inside a fallible boot step would have
to re-derive “this must be fatal” locally, and that is precisely the .expect
the type exists to remove. The one shape that can satisfy it without
aborting — a caller still inside a boot step that returns its error to the
owner that decides whether to serve — is try_spawn, and
that obligation is stated on it.
Name, band and stack class are constructor parameters for the same reason
they are on spawn_dedicated_thread: a caller cannot omit what it must
pass, so the RTEMS thread census (2 MiB default stacks, OS-anonymous
threads) is closed by signature rather than by a source sweep.
Implementations§
Source§impl MandatoryThread
impl MandatoryThread
Sourcepub fn new(
name: impl Into<String>,
priority: ThreadPriority,
stack: StackSizeClass,
) -> Self
pub fn new( name: impl Into<String>, priority: ThreadPriority, stack: StackSizeClass, ) -> Self
Declare a mandatory thread: its C thread name, the EPICS band it holds, and the stack class the C IOC gives it.
Sourcepub fn spawn<F>(self, f: F) -> JoinHandle<()>
pub fn spawn<F>(self, f: F) -> JoinHandle<()>
Start it, or take the process down.
For every caller with no error path back to whoever decides that this
IOC serves — a constructor returning Self, a OnceLock initialiser, a
future that parks forever. See the type docs for why this returns no
Result.
Sourcepub fn try_spawn<F>(self, f: F) -> Result<JoinHandle<()>>
pub fn try_spawn<F>(self, f: F) -> Result<JoinHandle<()>>
Start it, handing the failure to a caller that is still inside a fallible boot step.
The obligation this carries: the returned error MUST reach the owner
that decides whether the IOC serves, and that owner MUST refuse. It must
not be unwrapped, logged-and-ignored, or turned into a warning — any of
those re-opens exactly the half-IOC the type docs describe. Use
spawn when no such path exists.