Skip to content

Commit

Permalink
Bring sleep back to WgpuAwaitable. Slowly back off on the amount of t…
Browse files Browse the repository at this point in the history
…ime sleeping. (#655)

* Bring sleep back to WgpuAwaitable.  Slowly back off on the amount of time sleeping.

* Need anyio

* Need anyio

* Need anyio

* Remove no-longer-needed-comment
  • Loading branch information
fyellin authored Dec 28, 2024
1 parent 948ef5b commit dad4343
Showing 1 changed file with 39 additions and 20 deletions.
59 changes: 39 additions & 20 deletions wgpu/backends/wgpu_native/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ctypes
import sys
import time
from collections.abc import Generator
from queue import deque

import sniffio
Expand Down Expand Up @@ -255,14 +256,8 @@ def set_result(self, result):
def set_error(self, error):
self.result = (None, error)

def _is_done(self):
self.poll_function()
return self.result is not None

def _finish(self):
try:
if not self.result:
raise RuntimeError(f"Waiting for {self.title} timed out.")
result, error = self.result
if error:
raise RuntimeError(error)
Expand All @@ -278,27 +273,51 @@ def sync_wait(self):
elif not self.poll_function:
raise RuntimeError("Expected callback to have already happened")
else:
while not self._is_done():
time.sleep(0)
backoff_time_generator = self._get_backoff_time_generator()
while True:
self.poll_function()
if self.result is not None:
break
time.sleep(next(backoff_time_generator))
# We check the result after sleeping just in case another thread
# causes the callback to happen
if self.result is not None:
break

return self._finish()

def __await__(self):
# There is no documentation on what __await__() is supposed to return, but we
# can certainly copy from a function that *does* know what to return
# can certainly copy from a function that *does* know what to return.
# It would also be nice if wait_for_callback and sync_wait() could be merged,
# but Python has no wait of combining them.
async def wait_for_callback():
# In all the async cases that I've tried, the result is either already set, or
# resolves after the first call to the poll function. To make sure that our
# sleep-logic actually works, we always do at least one sleep call.
await async_sleep(0)
if self.result is not None:
return
if not self.poll_function:
pass
elif not self.poll_function:
raise RuntimeError("Expected callback to have already happened")
while not self._is_done():
await async_sleep(0)

yield from wait_for_callback().__await__()
return self._finish()
else:
backoff_time_generator = self._get_backoff_time_generator()
while True:
self.poll_function()
if self.result is not None:
break
await async_sleep(next(backoff_time_generator))
# We check the result after sleeping just in case another
# flow of control causes the callback to happen
if self.result is not None:
break
return self._finish()

return (yield from wait_for_callback().__await__())

def _get_backoff_time_generator(self) -> Generator[float, None, None]:
for _ in range(5):
yield 0
for i in range(1, 20):
yield i / 2000.0 # ramp up from 0ms to 10ms
while True:
yield 0.01


class ErrorHandler:
Expand Down

0 comments on commit dad4343

Please sign in to comment.