wafer-cli 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wafer/target_lock.py DELETED
@@ -1,198 +0,0 @@
1
- """Target locking for concurrent access control.
2
-
3
- Uses file locks (fcntl.flock) to ensure only one process uses a target at a time.
4
- Locks are automatically released when the process exits or crashes.
5
-
6
- Usage:
7
- # Try to acquire a single target
8
- with try_acquire_target("mi300x-1") as acquired:
9
- if acquired:
10
- # Got the lock, run eval
11
- ...
12
- else:
13
- # Target busy
14
- ...
15
-
16
- # Acquire first available from a pool
17
- with acquire_from_pool(["mi300x-1", "mi300x-2", "mi300x-3"]) as target:
18
- if target:
19
- # Got a target, run eval
20
- ...
21
- else:
22
- # All targets busy
23
- ...
24
- """
25
-
26
- from __future__ import annotations
27
-
28
- import fcntl
29
- import os
30
- from contextlib import contextmanager
31
- from pathlib import Path
32
- from typing import Iterator
33
-
34
- # Lock directory
35
- LOCKS_DIR = Path.home() / ".wafer" / "locks"
36
-
37
-
38
- def _ensure_locks_dir() -> None:
39
- """Ensure locks directory exists."""
40
- LOCKS_DIR.mkdir(parents=True, exist_ok=True)
41
-
42
-
43
- def _lock_path(target_name: str) -> Path:
44
- """Get path to lock file for a target."""
45
- return LOCKS_DIR / f"{target_name}.lock"
46
-
47
-
48
- @contextmanager
49
- def try_acquire_target(target_name: str) -> Iterator[bool]:
50
- """Try to acquire exclusive lock on a target.
51
-
52
- Args:
53
- target_name: Name of the target to lock
54
-
55
- Yields:
56
- True if lock was acquired, False if target is busy
57
-
58
- The lock is automatically released when the context exits,
59
- or if the process crashes.
60
- """
61
- _ensure_locks_dir()
62
- lock_file = _lock_path(target_name)
63
-
64
- # Open or create lock file
65
- fd = os.open(str(lock_file), os.O_CREAT | os.O_RDWR)
66
-
67
- try:
68
- # Try non-blocking exclusive lock
69
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
70
- # Write PID to lock file for debugging
71
- os.ftruncate(fd, 0)
72
- os.write(fd, f"{os.getpid()}\n".encode())
73
- try:
74
- yield True
75
- finally:
76
- # Release lock
77
- fcntl.flock(fd, fcntl.LOCK_UN)
78
- except BlockingIOError:
79
- # Lock is held by another process
80
- yield False
81
- finally:
82
- os.close(fd)
83
-
84
-
85
- @contextmanager
86
- def acquire_from_pool(target_names: list[str]) -> Iterator[str | None]:
87
- """Acquire first available target from a list.
88
-
89
- Tries each target in order, returns the first one that's available.
90
-
91
- Args:
92
- target_names: List of target names to try
93
-
94
- Yields:
95
- Name of acquired target, or None if all are busy
96
-
97
- Example:
98
- with acquire_from_pool(["gpu-1", "gpu-2", "gpu-3"]) as target:
99
- if target:
100
- print(f"Got {target}")
101
- run_eval(target)
102
- else:
103
- print("All targets busy")
104
- """
105
- _ensure_locks_dir()
106
-
107
- # Try each target in order
108
- for target_name in target_names:
109
- lock_file = _lock_path(target_name)
110
- fd = os.open(str(lock_file), os.O_CREAT | os.O_RDWR)
111
-
112
- try:
113
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
114
- # Got the lock - write PID and yield
115
- os.ftruncate(fd, 0)
116
- os.write(fd, f"{os.getpid()}\n".encode())
117
- try:
118
- yield target_name
119
- return # Success - exit after context
120
- finally:
121
- fcntl.flock(fd, fcntl.LOCK_UN)
122
- os.close(fd)
123
- except BlockingIOError:
124
- # This target is busy, try next
125
- os.close(fd)
126
- continue
127
-
128
- # All targets busy
129
- yield None
130
-
131
-
132
- def is_target_locked(target_name: str) -> bool:
133
- """Check if a target is currently locked.
134
-
135
- Note: This is a point-in-time check - the lock status can change
136
- immediately after this returns.
137
-
138
- Args:
139
- target_name: Name of the target to check
140
-
141
- Returns:
142
- True if target is locked, False if available
143
- """
144
- _ensure_locks_dir()
145
- lock_file = _lock_path(target_name)
146
-
147
- if not lock_file.exists():
148
- return False
149
-
150
- fd = os.open(str(lock_file), os.O_RDONLY)
151
- try:
152
- # Try non-blocking lock
153
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
154
- # Got it - so it wasn't locked
155
- fcntl.flock(fd, fcntl.LOCK_UN)
156
- return False
157
- except BlockingIOError:
158
- return True
159
- finally:
160
- os.close(fd)
161
-
162
-
163
- def get_lock_holder(target_name: str) -> int | None:
164
- """Get PID of process holding lock on a target.
165
-
166
- Args:
167
- target_name: Name of the target
168
-
169
- Returns:
170
- PID of lock holder, or None if not locked or unknown
171
- """
172
- lock_file = _lock_path(target_name)
173
-
174
- if not lock_file.exists():
175
- return None
176
-
177
- try:
178
- content = lock_file.read_text().strip()
179
- return int(content)
180
- except (ValueError, OSError):
181
- return None
182
-
183
-
184
- def list_locked_targets() -> list[str]:
185
- """List all currently locked targets.
186
-
187
- Returns:
188
- List of target names that are currently locked
189
- """
190
- _ensure_locks_dir()
191
-
192
- locked = []
193
- for lock_file in LOCKS_DIR.glob("*.lock"):
194
- target_name = lock_file.stem
195
- if is_target_locked(target_name):
196
- locked.append(target_name)
197
-
198
- return sorted(locked)