I have a simple queue that uses a threading.Condition to deal with consumers that wait at an empty queue.
I want this queue also to be consumed by async code.
That's why I added a get_async method.
The problem is now in the consumer below. It uses asyncio.wait_for since I wanted to see whether my queue is safe to be cancelled. Now the observation is, that after I induce the cancellation, my consumer always runs into a timeout.
I guess that this is caused by the CancellationError that prevents the self.condition from being released properly.
Can someone give me a hint what's going wrong here?
import threading
import time
import asyncio
SENTINEL = object()
class Queue:
def __init__(self) -> None:
self.items = []
self.condition = threading.Condition()
def put(self, item):
with self.condition:
self.items.append(item)
self.condition.notify()
def get(self):
with self.condition:
while not self.items:
self.condition.wait()
return self.items.pop(0)
async def get_async(self):
try:
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, lambda: self.get())
except asyncio.CancelledError:
print("I've been cancelled and I think the self.condition is now broken")
async def consumer(n: int, queue: Queue):
while True:
try:
# if I leave out the "wait_for" or make it longer than my sleep(2) below,
# it works
item = await asyncio.wait_for(queue.get_async(), 5)
if item is SENTINEL:
print(f"consumer {n=} got SENTINEL")
queue.put(item)
break
else:
print(f"consumer {n=} got {item=}")
except asyncio.TimeoutError:
print("got timeout")
def producer(queue: Queue):
queue.put("foo")
queue.put("bar")
time.sleep(2)
queue.put("baz")
queue.put(SENTINEL)
q = Queue()
t = threading.Thread(target=producer, args=(q,))
t.start()
asyncio.run(consumer(0, q))
t.join()
With a "long enough" timeout (or without wait_for at all) the output is
consumer n=0 got item='foo'
consumer n=0 got item='bar'
consumer n=0 got item='baz'
consumer n=0 got SENTINEL
When I induce the cancellation, it's
consumer n=0 got item='foo'
consumer n=0 got item='bar'
got timeout
got timeout
got timeout
got timeout
got timeout
(and so on)
It should be like:
consumer n=0 got item='foo'
consumer n=0 got item='bar'
got timeout
got timeout
got timeout
got timeout
consumer n=0 got item='baz'
consumer n=0 got SENTINEL