Skip to content

.NET process tracking keys on raw PID, so a re-used PID is permanently skipped #303

Description

@hoyosjs

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions