epics_libcom_rs/lib.rs
1//! EPICS `libCom` for Rust: the layer an IOC is built *on*, with no record
2//! system above it.
3//!
4//! This is `epics_base_rs::runtime` and `epics_base_rs::net` lifted out of
5//! `epics-base-rs` (issue #55) so a consumer — a protocol client, a gateway,
6//! `pvxs-rs` — can take the concurrency and socket primitives without taking
7//! the database with them. The name is C's: `libCom` is where upstream EPICS
8//! keeps `epicsThread`, `epicsTime`, `errlog`, `envDefs` *and* `osiSock`, which
9//! is exactly this crate's two modules.
10//!
11//! `epics-base-rs` re-exports both modules at their original paths, so
12//! `epics_base_rs::runtime::…` and `epics_base_rs::net::…` still resolve and
13//! nothing downstream had to change.
14//!
15//! * [`runtime`] — the task seam and its two backends, `epicsThread`-parity
16//! priority bands, `errlog`, the EPICS string/environment types, the
17//! general-time provider.
18//! * [`net`] — the EPICS protocols' shared socket layer: per-NIC async UDP,
19//! interface enumeration, loopback multicast. Host-only; the wire constants
20//! beside it compile for every target, RTEMS included.
21//! * [`walltime`] — [`WallTime`](walltime::WallTime), the wall-clock instant
22//! `runtime::time` returns. It lived in `epics_base_rs::types` and moved down
23//! with its producer; `epics-base-rs` re-exports it at `types::WallTime`.
24//!
25//! # Features
26//!
27//! The task backend is not one: it is chosen by the
28//! `EPICS_RS_BUILD_EXEC_BACKEND` environment variable, read by `build.rs`. See
29//! [`EXEC_BACKEND`].
30//!
31//! * `linux-rt` — back [`runtime::sync::PriorityInheritanceMutex`] with a
32//! `PTHREAD_PRIO_INHERIT` `pthread_mutex_t` on Linux.
33//!
34//! # C reference pins
35//!
36//! Every `file.c:NNN` citation in this crate resolves at the tree and revision
37//! below, not at whatever that tree's working copy holds today. These trees are
38//! checked out on local branches here and run ahead of their pins.
39//!
40//! | tree | pinned revision |
41//! | --- | --- |
42//! | `epics-base` | `R7.0.10` |
43//! | `pvxs` | `1.5.1-42-gb568e93` |
44//! | `ca-gateway` | `R2-1-3-0-54-g0666f21` |
45//! | `asyn` | `R4-45-19-ge2a281e2` |
46//! | `calc` | `R3-7-5-49-gf207871` |
47//!
48//! **Resolve by symbol at the pin; the line is a hint.** Find the named
49//! function, struct, macro or field first, and treat the line number as a hint
50//! that has to land inside that construct. Three cases follow:
51//!
52//! 1. Construct at the pin, line lands in it — the citation is exact. A
53//! reference checkout ahead of the pin will disagree; that disagreement is
54//! the checkout's, not the citation's.
55//! 2. Construct at the pin, line lands outside it — line drift. Keep the
56//! symbol and move the line to the pin's.
57//! 3. Construct absent at the pin — the citation means code added after it,
58//! and is NOT moved onto the pin, where it would point at lines that do not
59//! exist. It names the revision it means inline, beside the line span: the
60//! upstream PR and commit, and that both are later than the pin this table
61//! gives. `epics-libcom-rs` already carries that form.
62//!
63//! Every pin above passes `git merge-base --is-ancestor <pin> origin/<default>`
64//! in its own tree, which is the test a pin has to meet. A `git describe`
65//! string names an exact commit and is worth as much as a tag; what
66//! disqualifies a revision is being reachable only from a fork branch or an
67//! unmerged PR, because then it names nothing a reader outside this workspace
68//! can fetch.
69//!
70//! Resolve each citation on its own. One sentence can cite two lines that are
71//! right at different revisions, and a check run at either revision then
72//! reports a single tidy error while vouching for the very citation the other
73//! condemns.
74//!
75//! A row reading *no settled pin* means no revision has been agreed for that
76//! tree: say which revision you read, and do not take its `HEAD` for the pin.
77//! Citations into non-EPICS sources (libc, RTEMS, `rtems-libbsd`, VxWorks,
78//! vendored third-party) are outside this table and carry no pin.
79
80// The three `epics-base-rs` crate-level allows this code was written under and
81// still needs — `collapsible_if` and `manual_range_contains` in `runtime`,
82// `io_other_error` in `net`. Narrowed to those three rather than inherited
83// wholesale: the extraction is a move, so the code is byte-identical and a
84// lint it does not trip has no business being silenced here.
85#![allow(
86 clippy::collapsible_if,
87 clippy::io_other_error,
88 clippy::manual_range_contains
89)]
90
91// The exec backend's blocking pumps end a parked reader with a local
92// `shutdown(Shutdown::Both)` and bound a stuck writer through loopback
93// send-backpressure (`runtime::blocking_io`). Both are POSIX blocking-socket
94// semantics; Windows provides neither (measured, PR #56 CI 2026-07-24: a
95// parked `recv` outlived shutdown by the full 120 s test bound, and an
96// 8 MiB frame to a never-reading peer was swallowed in 12 ms), so a Windows
97// build selecting this backend would hang on connection teardown instead of
98// failing visibly. Refuse it at compile time rather than ship that.
99#[cfg(all(windows, exec_backend))]
100compile_error!(
101 "the exec backend (EPICS_RS_BUILD_EXEC_BACKEND=thread) relies on POSIX \
102 blocking-socket semantics (shutdown wakes a parked read; loopback sends \
103 see backpressure) that Windows does not provide; build the default tokio \
104 backend on Windows instead"
105);
106
107// Lets `#[epics_macros_rs::epics_test]` expansions — which name the runtime
108// crate by its external path — resolve inside this crate's own unit tests,
109// where proc-macro-crate reports `FoundCrate::Itself`. Same device as
110// `epics-base-rs`'s alias for the same macro.
111extern crate self as epics_libcom_rs;
112
113pub mod net;
114pub mod runtime;
115pub mod walltime;
116
117/// Which [`runtime::task`] backend this build selected — `true` for the
118/// reactor-free std-thread [`runtime::background`] executor, `false` for tokio.
119///
120/// The predicate is computed once, in this crate's `build.rs`, from the target
121/// OS and `EPICS_RS_BUILD_EXEC_BACKEND`. A crate above that derives the same
122/// `cfg` from its own `build.rs` (`epics-base-rs` does, for `server::scan`)
123/// can pin the two together with a `const _: () = assert!(...)`, so a build
124/// script that did not see the variable fails to compile instead of splitting
125/// the workspace across two backends.
126pub const EXEC_BACKEND: bool = cfg!(exec_backend);