Skip to content

Commit a48d28f

Browse files
committed
Update condition.py,test_condition.py
1 parent 59badc6 commit a48d28f

2 files changed

Lines changed: 84 additions & 41 deletions

File tree

distributed/condition.py

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(self, scheduler):
2727
"condition_notify": self.notify,
2828
"condition_acquire": self.acquire,
2929
"condition_release": self.release,
30+
"condition_notify_all": self.notify_all,
3031
}
3132
)
3233

@@ -37,13 +38,15 @@ def _get_lock(self, name):
3738

3839
@log_errors
3940
async def acquire(self, name=None, id=None):
41+
"""Acquire the underlying lock"""
4042
lock = self._get_lock(name)
4143
await lock.acquire()
4244
self._lock_holders[name] = id
4345
return True
4446

4547
@log_errors
4648
async def release(self, name=None, id=None):
49+
"""Release the underlying lock"""
4750
if self._lock_holders.get(name) != id:
4851
return False
4952

@@ -59,6 +62,7 @@ async def release(self, name=None, id=None):
5962

6063
@log_errors
6164
async def wait(self, name=None, id=None, timeout=None):
65+
"""Wait on condition"""
6266
# Verify lock is held by this client
6367
if self._lock_holders.get(name) != id:
6468
raise RuntimeError("wait() called without holding the lock")
@@ -99,6 +103,7 @@ async def wait(self, name=None, id=None, timeout=None):
99103

100104
@log_errors
101105
def notify(self, name=None, n=1):
106+
"""Notify n waiters"""
102107
if self._lock_holders.get(name) is None:
103108
raise RuntimeError("notify() called without holding the lock")
104109

@@ -111,6 +116,7 @@ def notify(self, name=None, n=1):
111116

112117
@log_errors
113118
def notify_all(self, name=None):
119+
"""Notify all waiters"""
114120
if self._lock_holders.get(name) is None:
115121
raise RuntimeError("notify_all() called without holding the lock")
116122

@@ -123,18 +129,27 @@ def notify_all(self, name=None):
123129
class Condition(SyncMethodMixin):
124130
"""Distributed Condition Variable
125131
132+
Mimics asyncio.Condition API. Allows coordination between
133+
distributed workers using wait/notify pattern.
134+
126135
Parameters
127136
----------
128-
name: str, optional
137+
name : str, optional
129138
Name of the condition. Same name = shared state.
130-
client: Client, optional
139+
client : Client, optional
131140
Client for scheduler communication.
132141
133142
Examples
134143
--------
144+
>>> from distributed import Condition
145+
>>> condition = Condition('my-condition')
146+
>>> async with condition:
147+
... await condition.wait() # Wait for notification
148+
149+
>>> # In another worker/client
135150
>>> condition = Condition('my-condition')
136151
>>> async with condition:
137-
... await condition.wait()
152+
... condition.notify() # Wake one waiter
138153
"""
139154

140155
def __init__(self, name=None, client=None):
@@ -152,11 +167,20 @@ def client(self):
152167
pass
153168
return self._client
154169

170+
@property
171+
def loop(self):
172+
return self.client.loop if self.client else None
173+
155174
def _verify_running(self):
156175
if not self.client:
157-
raise RuntimeError(f"{type(self)} object not properly initialized.")
176+
raise RuntimeError(
177+
f"{type(self)} object not properly initialized. This can happen"
178+
" if the object is being deserialized outside of the context of"
179+
" a Client or Worker."
180+
)
158181

159182
async def acquire(self):
183+
"""Acquire underlying lock"""
160184
self._verify_running()
161185
result = await self.client.scheduler.condition_acquire(
162186
name=self.name, id=self.id
@@ -165,13 +189,29 @@ async def acquire(self):
165189
return result
166190

167191
async def release(self):
192+
"""Release underlying lock"""
168193
if not self._locked:
169194
raise RuntimeError("Cannot release un-acquired lock")
170195
self._verify_running()
171196
await self.client.scheduler.condition_release(name=self.name, id=self.id)
172197
self._locked = False
173198

174199
async def wait(self, timeout=None):
200+
"""Wait until notified
201+
202+
Must be called while lock is held. Releases lock and waits
203+
for notify(), then reacquires lock before returning.
204+
205+
Parameters
206+
----------
207+
timeout : number or string or timedelta, optional
208+
Seconds to wait on the condition in the scheduler.
209+
210+
Returns
211+
-------
212+
bool
213+
True if notified, False if timeout occurred
214+
"""
175215
if not self._locked:
176216
raise RuntimeError("wait() called without holding the lock")
177217

@@ -182,19 +222,43 @@ async def wait(self, timeout=None):
182222
)
183223
return result
184224

185-
async def notify(self, n=1):
225+
def notify(self, n=1):
226+
"""Wake up one or more waiters
227+
228+
Parameters
229+
----------
230+
n : int, optional
231+
Number of waiters to wake. Default is 1.
232+
233+
Returns
234+
-------
235+
int
236+
Number of waiters notified
237+
"""
186238
if not self._locked:
187239
raise RuntimeError("Cannot notify without holding the lock")
188240
self._verify_running()
189-
return await self.client.scheduler.condition_notify(name=self.name, n=n)
241+
return self.client.sync(
242+
self.client.scheduler.condition_notify, name=self.name, n=n
243+
)
244+
245+
def notify_all(self):
246+
"""Wake up all waiters
190247
191-
async def notify_all(self):
248+
Returns
249+
-------
250+
int
251+
Number of waiters notified
252+
"""
192253
if not self._locked:
193254
raise RuntimeError("Cannot notify without holding the lock")
194255
self._verify_running()
195-
return await self.client.scheduler.condition_notify_all(name=self.name)
256+
return self.client.sync(
257+
self.client.scheduler.condition_notify_all, name=self.name
258+
)
196259

197260
def locked(self):
261+
"""Return True if lock is held"""
198262
return self._locked
199263

200264
async def __aenter__(self):

distributed/tests/test_condition.py

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
async def test_condition_acquire_release(c, s, a, b):
1212
"""Test basic lock acquire/release"""
1313
condition = Condition("test-lock")
14-
1514
assert not condition.locked()
1615
await condition.acquire()
1716
assert condition.locked()
@@ -23,7 +22,6 @@ async def test_condition_acquire_release(c, s, a, b):
2322
async def test_condition_context_manager(c, s, a, b):
2423
"""Test context manager interface"""
2524
condition = Condition("test-context")
26-
2725
assert not condition.locked()
2826
async with condition:
2927
assert condition.locked()
@@ -119,7 +117,6 @@ async def waiter():
119117
async with condition:
120118
result = await condition.wait(timeout=0.2)
121119
results.append(f"timeout: {result}")
122-
123120
async with condition:
124121
result = await condition.wait()
125122
results.append(f"notified: {result}")
@@ -142,10 +139,10 @@ async def test_condition_error_without_lock(c, s, a, b):
142139
await condition.wait()
143140

144141
with pytest.raises(RuntimeError, match="Cannot notify"):
145-
await condition.notify()
142+
condition.notify()
146143

147144
with pytest.raises(RuntimeError, match="Cannot notify"):
148-
await condition.notify_all()
145+
condition.notify_all()
149146

150147

151148
@gen_cluster(client=True)
@@ -184,7 +181,6 @@ async def consumer():
184181

185182
await prod_task
186183
results = await cons_task
187-
188184
assert results == [0, 1, 2, 3, 4]
189185

190186

@@ -211,7 +207,6 @@ async def consumer():
211207
return results
212208

213209
results = await asyncio.gather(producer(0), producer(10), consumer(), consumer())
214-
215210
# Last two results are from consumers
216211
consumed = results[2] + results[3]
217212
assert sorted(consumed) == [0, 1, 2, 10, 11, 12]
@@ -222,41 +217,27 @@ async def test_condition_from_worker(c, s, a, b):
222217
"""Test condition accessed from worker tasks"""
223218

224219
def wait_on_condition(name):
225-
226220
from distributed import Condition
227221

228-
async def _wait():
229-
condition = Condition(name)
230-
async with condition:
231-
await condition.wait()
232-
return "worker_notified"
233-
234-
from distributed.worker import get_worker
235-
236-
worker = get_worker()
237-
return worker.loop.run_until_complete(_wait())
222+
condition = Condition(name)
223+
with condition:
224+
condition.wait()
225+
return "worker_notified"
238226

239227
def notify_condition(name):
240-
import asyncio
228+
import time
241229

242230
from distributed import Condition
243231

244-
async def _notify():
245-
await asyncio.sleep(0.2)
246-
condition = Condition(name)
247-
async with condition:
248-
condition.notify()
249-
return "notified"
250-
251-
from distributed.worker import get_worker
252-
253-
worker = get_worker()
254-
return worker.loop.run_until_complete(_notify())
232+
time.sleep(0.2)
233+
condition = Condition(name)
234+
with condition:
235+
condition.notify()
236+
return "notified"
255237

256238
name = "worker-condition"
257239
f1 = c.submit(wait_on_condition, name, workers=[a.address])
258240
f2 = c.submit(notify_condition, name, workers=[b.address])
259-
260241
results = await c.gather([f1, f2])
261242
assert results == ["worker_notified", "notified"]
262243

@@ -267,7 +248,6 @@ async def test_condition_same_name_different_instances(c, s, a, b):
267248
name = "shared-condition"
268249
cond1 = Condition(name)
269250
cond2 = Condition(name)
270-
271251
results = []
272252

273253
async def waiter():
@@ -336,7 +316,6 @@ async def worker(i):
336316
return f"worker-{i}-done"
337317

338318
results = await asyncio.gather(worker(0), worker(1), worker(2))
339-
340319
assert sorted(results) == ["worker-0-done", "worker-1-done", "worker-2-done"]
341320
assert len(arrived) == 3
342321

0 commit comments

Comments
 (0)