| File: Dcp\DcpProcessMonitor.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using SystemProcess = System.Diagnostics.Process; namespace Aspire.Hosting.Dcp; internal sealed record DcpProcessIdentity(int ProcessId, DateTime Timestamp); internal static class DcpProcessMonitor { internal static DcpProcessIdentity GetMonitorProcessIdentity(SystemProcess parentProcess) { ArgumentNullException.ThrowIfNull(parentProcess); var monitorProcessId = parentProcess.Id; var timestamp = GetProcessIdentityTimestamp(parentProcess); if (timestamp is null) { throw new InvalidOperationException($"Could not determine the identity timestamp for monitor process {monitorProcessId}."); } return new(monitorProcessId, timestamp.Value); } private static DateTime? GetProcessIdentityTimestamp(SystemProcess parentProcess) { if (OperatingSystem.IsLinux()) { return GetLinuxProcessIdentityTimestamp(parentProcess.Id); } try { return parentProcess.StartTime.ToUniversalTime(); } catch (ArgumentException) { return null; } catch (InvalidOperationException) { return null; } } private static DateTime GetLinuxProcessIdentityTimestamp(int processId) { // DCP inspects the *host* process table, so honor HOST_PROC when this check runs inside a // container whose host /proc is mounted elsewhere. Orphan/liveness detection deliberately uses the // current namespace's /proc instead (see ProcessStartTimeHelper.TryGetProcessStartTime), which is // why the /proc root is a parameter of the shared reader rather than baked into it. var procRoot = Environment.GetEnvironmentVariable("HOST_PROC") ?? "/proc"; // Share the /proc start-ticks reader with every other Aspire watchdog so there is one Linux // process-identity implementation. The value is boot-relative (field 22 of /proc/<pid>/stat), // which is why it is immune to wall-clock drift. if (ProcessStartTimeHelper.TryGetLinuxProcessStartTicks(processId, procRoot) is not { } startTicks) { throw new InvalidOperationException($"Could not read a valid start time for monitor process {processId} from '{procRoot}'."); } // Convert the boot-relative start ticks into a DateTime offset from DateTime.MinValue instead of // estimating a wall-clock time. Kept in milliseconds for parity with the timestamp DCP compares // against, which is a distinct identity domain from the whole-second value used by the orphan // detectors, so the two never cross-compare. var startTimeMilliseconds = (startTicks * 1000) / (ulong)ProcessStartTimeHelper.GetLinuxClockTicksPerSecond(); return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc).AddMilliseconds(startTimeMilliseconds); } }