Repository navigation
Expand file tree
/
Copy pathhermes_state_pidns.py
More file actions
128 lines (96 loc) · 5.02 KB
/
Copy pathhermes_state_pidns.py
File metadata and controls
128 lines (96 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""PID-namespace identity for state.db lock/lease holders.
A ``pid=<n>`` holder is relative to its writer's PID namespace; a reader in a
sibling namespace (two containers on one volume, systemd ``PrivatePIDs=``) sees
a live holder as absent and would reclaim its unexpired row. Holders are
stamped with ``pidns=<inode of /proc/self/ns/pid>`` and a foreign stamp is
never probed. Unstamped (pre-upgrade) records differ by policy:
* :func:`holder_pid_checkable` — STRICT, for TTL rows (compression locks,
turn leases): unstamped defers to its expiry (<= TTL; a false defer
self-heals, a false reclaim ends a live turn).
* :func:`persistent_record_pidns_checkable` — LEGACY, for no-expiry flock
records: unstamped keeps probing so orphaned-lock cleanup still works.
Platforms without PID namespaces (macOS, Windows) keep the plain pid probe; a
failed Linux lookup is not cached and makes every holder unverifiable.
"""
from __future__ import annotations
import os
import re
import sys
from typing import NamedTuple, Optional
# ``pidns=`` token inside a structured holder string (``pid=123:pidns=4026533184:turn=…``).
_PIDNS_TOKEN_RE = re.compile(r"(?:^|:)pidns=(\d+)(?::|$)")
class LocalPidNamespace(NamedTuple):
"""What this process knows about its own PID namespace.
Three states, and the ``checkable`` predicates treat each differently:
``supported`` False — the platform has no namespace identity at all;
``supported`` True with an ``id`` — resolved; ``supported`` True with an
``id`` of None — resolution failed, see the module docstring.
"""
id: Optional[str]
supported: bool
_UNSUPPORTED = LocalPidNamespace(None, False)
# Cached once there is a definite answer: a process cannot move itself into
# another PID namespace (``setns`` applies to its future children), so a
# successful read never changes for us. A failed read is NOT cached — it may
# be transient, and while it lasts every structured holder is unverifiable.
_LOCAL_PID_NS: Optional[LocalPidNamespace] = None
def _resolve_local_pid_namespace() -> LocalPidNamespace:
if sys.platform != "linux":
return _UNSUPPORTED
try:
link = os.readlink("/proc/self/ns/pid")
except OSError:
return LocalPidNamespace(None, True)
match = re.search(r"\[(\d+)\]", link) # "pid:[4026532534]"
return LocalPidNamespace(match.group(1) if match else None, True)
def _local_pid_namespace() -> LocalPidNamespace:
"""This process' PID-namespace identity (see :class:`LocalPidNamespace`)."""
global _LOCAL_PID_NS
if _LOCAL_PID_NS is None:
local = _resolve_local_pid_namespace()
if local.id is not None or not local.supported:
_LOCAL_PID_NS = local
return local
return _LOCAL_PID_NS
def pid_namespace_id() -> Optional[str]:
"""What a holder records: the ``/proc/self/ns/pid`` inode on Linux, ``None``
where there is no namespace identity or the lookup failed. A holder never
asserts a namespace it cannot prove."""
return _local_pid_namespace().id
def holder_namespace_token() -> str:
"""The ``:pidns=<id>`` fragment to append to a new holder string, or ``""``.
Callers build ``f"pid={os.getpid()}{holder_namespace_token()}:turn=…"``.
"""
ns = pid_namespace_id()
return f":pidns={ns}" if ns else ""
def _recorded_namespace(holder: str) -> Optional[str]:
"""The ``pidns=`` stamp from a holder string, or None when it carries none."""
match = _PIDNS_TOKEN_RE.search(holder or "")
return match.group(1) if match else None
def _qualify(recorded: Optional[str], *, unstamped_checkable: bool) -> bool:
"""The shared matrix; *unstamped_checkable* is the policy for a record that
carries no namespace (see the module docstring for why there are two)."""
local = _local_pid_namespace()
if not local.supported:
return True # single-namespace platform: a pid is evidence on its own
if recorded is None:
return unstamped_checkable
if local.id is None:
return False # our own lookup failed: unknown authority is not authority
return recorded == local.id
def holder_pid_checkable(holder: str) -> bool:
"""STRICT: may a structured holder string's ``pid=`` be probed here?
The policy for TTL-bounded rows (compression locks, session turn leases):
a foreign-namespace or unstamped holder is never probed — it defers to the
row's own expiry instead (at most the remaining TTL; a false defer
self-heals, a false reclaim ends a live turn).
"""
return _qualify(_recorded_namespace(holder), unstamped_checkable=False)
def persistent_record_pidns_checkable(recorded: Optional[str]) -> bool:
"""LEGACY ROLLOUT: may a persistent record (no expiry) be probed here?
The policy for the flock holder records: a foreign-namespace record is
never probed, while an unstamped one keeps main's behavior — those records
never expire, so refusing to probe them would permanently disable
orphaned-lock cleanup (see the module docstring).
"""
return _qualify(recorded, unstamped_checkable=True)