The Linux .NET helper discovers processes from mmap records and tracks them in the EventPipe (UserEventTracker) and perf-map (PerfMapTracker) workers using the raw PID as the identity key. Once a PID has been enabled it is recorded in the worker's pids map/set and is never removed for the lifetime of the trace. Because the OS reuses PIDs after a process exits, a new process that re-enters
with a PID we have already seen is silently skipped and never gets .NET user_events or perf-map symbols enabled.
Symptoms
- A .NET process is discovered and enabled. It later exits. A new .NET process starts and the OS reuses the same PID.
- The new process emits the
dotnet_ipc_created / doublemapper mmap that normally triggers tracking, but the worker's guard short-circuits it.
- Result: the re-entering process is never enabled - no events, no JIT/perf-map symbols - even though it is a brand-new process we have never actually tracked.
- The stale
UnixStream to the long-gone original process also lingers in the map for the rest of the trace.
Cause
The worker tracking state is keyed on u32 PID alone and entries are only dropped when the worker thread ends (map/set is dropped), never per-process during the trace. PID is not a stable process identity: after a process exits the kernel is free to assign the same PID to an unrelated process. There is no identity component (process start time, or a pidfd) to distinguish "same process we
tracked" from "different process that happens to share the PID".
Relevant code in one_collect/src/helpers/dotnet/os/linux.rs:
// UserEventTracker::worker_thread_proc
let mut pids: HashMap<u32, UnixStream> = HashMap::new();
...
/* Skip if already enabled */
if pids.contains_key(&pid) {
continue;
}
// PerfMapTracker::worker_thread_proc
let mut pids = HashSet::new();
...
/* Skip if already enabled */
if pids.contains(&pid) {
continue;
}
Proposed fix
Give tracked processes a stable identity instead of keying on PID alone:
- Capture the process start time at discovery (
/proc/<pid>/stat) and store it with the entry.
- Treat a discovered PID whose recorded start time differs from the current
/proc/<pid>/stat start time as a new process: evict the stale entry (closing its dead socket / disabling its old perf-map) and enable the new one.
- Alternatively, hold a
pidfd (pidfd_open) per tracked process where kernel support allows, which gives an unambiguous handle to the exact process and a reliable exit signal without PID-reuse ambiguity.
The Linux .NET helper discovers processes from
mmaprecords and tracks them in the EventPipe (UserEventTracker) and perf-map (PerfMapTracker) workers using the raw PID as the identity key. Once a PID has been enabled it is recorded in the worker'spidsmap/set and is never removed for the lifetime of the trace. Because the OS reuses PIDs after a process exits, a new process that re-enterswith a PID we have already seen is silently skipped and never gets .NET user_events or perf-map symbols enabled.
Symptoms
dotnet_ipc_created/doublemappermmap that normally triggers tracking, but the worker's guard short-circuits it.UnixStreamto the long-gone original process also lingers in the map for the rest of the trace.Cause
The worker tracking state is keyed on
u32PID alone and entries are only dropped when the worker thread ends (map/set is dropped), never per-process during the trace. PID is not a stable process identity: after a process exits the kernel is free to assign the same PID to an unrelated process. There is no identity component (process start time, or apidfd) to distinguish "same process wetracked" from "different process that happens to share the PID".
Relevant code in
one_collect/src/helpers/dotnet/os/linux.rs:Proposed fix
Give tracked processes a stable identity instead of keying on PID alone:
/proc/<pid>/stat) and store it with the entry./proc/<pid>/statstart time as a new process: evict the stale entry (closing its dead socket / disabling its old perf-map) and enable the new one.pidfd(pidfd_open) per tracked process where kernel support allows, which gives an unambiguous handle to the exact process and a reliable exit signal without PID-reuse ambiguity.