You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GVFS's orphan-lock check (GVFSLock.LockHolder.GetExternalHolder) only
asked the OS "is there a process at this PID?" via IsProcessActive. If
the original lock-holder exited and Windows recycled its PID to an
unrelated process before the next git command arrived, GVFS saw a live
process at that PID and concluded the lock was still held. The git
command then either waited the full 300s timeout or printed a spurious
"Waiting for '<holder>' to release the lock" message.
This is a real product bug, not just a test issue. It surfaced as
flakiness in the OrphanedGVFSLockIsCleanedUp functional test on busy CI
agents (two failure modes: 300s timeout, and assertion failure with
non-empty stderr), but the same race can affect users when a PID is
quickly reassigned by the OS scheduler.
The fix is process-identity tracking. When the lock is granted to an
external requestor, capture that process's creation timestamp
(GetProcessTimes, an opaque 100-ns FILETIME value) alongside its PID.
On every orphan check, look up the current creation timestamp at that
PID and compare. If the process is gone the lookup fails; if a
different process is now at the PID the timestamps differ. Either way
the lock is correctly recognized as orphaned.
Implementation notes:
* New abstract GVFSPlatform.TryGetActiveProcessStartTime returns the
raw 64-bit creation time. It is documented as identity-only; the
value is never decoded as a date.
* The Windows implementation combines GetExitCodeProcess (STILL_ACTIVE
check) and GetProcessTimes. GetProcessTimes alone is not sufficient
because it still returns a creation time for terminated processes
whose kernel object is kept alive by an outstanding handle
elsewhere.
* Acquisition does not fail if start-time capture fails. The existing
IsProcessActiveImplementation has a Process.GetProcessById fallback
for cross-integrity callers that OpenProcess(QueryLimitedInformation)
cannot open, and we preserve the same compatibility surface here. A
HasStartTime flag is stored alongside the PID; when it is false the
orphan check falls back to the legacy IsProcessActive call. The
fallback is recorded in telemetry (StartTimeUnavailable=true on the
TryAcquireLockExternal event) so we can see if it ever becomes
common in the field.
* The ExternalLockHolderExited telemetry event now carries an
ExternalHolderTerminationReason field (ProcessNotActive | PidRecycled
| Unknown) to make future post-mortems straightforward.
* The OrphanedGVFSLockIsCleanedUp functional test is unchanged. It
polls Process.GetProcessById to detect when the lock holder has
exited, then runs git status. With this product fix, git status
succeeds cleanly even if the PID has already been recycled to an
unrelated process by the time mount processes the request, so the
test passes naturally without any test-side workaround.
* Two new unit tests cover the new identity-check path:
TryAcquireLockForExternalRequestor_WhenHolderPidRecycled (verifies
that an orphan is detected when start times differ at the same PID)
and TryAcquireLockForExternalRequestor_WhenHolderStartTimeMatches
(the inverse: a still-alive holder is correctly seen as active and
a new acquisition is denied).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0 commit comments