mirror of
https://github.com/vincentbernat/i3wm-configuration.git
synced 2025-08-08 04:05:11 +02:00
i3-companion: make debounce decorator composable
This allows us to move the retry behavior into a separate decorator. The semantic is a bit different as, now, if there is another iteration pending, it is lost.
This commit is contained in:
parent
a5e5b23ec8
commit
bf08c327a3
1 changed files with 41 additions and 30 deletions
|
@ -135,14 +135,39 @@ def on(*events):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def debounce(sleep, *, unless=None, retry=0):
|
def retry(max_retries):
|
||||||
"""Debounce a function call (batch successive calls into only one).
|
"""Retry an async function."""
|
||||||
Optional immediate execution. Optional retry on failure. Ensure
|
|
||||||
only one instance is executed. It is assumed the arguments
|
|
||||||
provided to the debounced function have no effect on its
|
|
||||||
execution.
|
|
||||||
|
|
||||||
"""
|
def decorator(fn):
|
||||||
|
@functools.wraps(fn)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
retries = max_retries
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
logger.debug(f"execute {fn} (remaining tries: {retries})")
|
||||||
|
return await fn(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
if retries > 0:
|
||||||
|
retries -= 1
|
||||||
|
logger.warning(
|
||||||
|
f"while executing {fn} (remaining tries: %d): %s",
|
||||||
|
retries,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.exception(f"while executing {fn}: %s", e)
|
||||||
|
return
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def debounce(sleep, *, unless=None):
|
||||||
|
"""Debounce a function call (batch successive calls into only one).
|
||||||
|
Optional immediate execution. Ensure only one instance is
|
||||||
|
executed. It is assumed the arguments provided to the debounced
|
||||||
|
function have no effect on its execution."""
|
||||||
|
|
||||||
def decorator(fn):
|
def decorator(fn):
|
||||||
async def worker():
|
async def worker():
|
||||||
|
@ -155,7 +180,7 @@ def debounce(sleep, *, unless=None, retry=0):
|
||||||
logger.debug(f"urgent work received for {fn}")
|
logger.debug(f"urgent work received for {fn}")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
retry, args, kwargs = workers[fn].queue
|
args, kwargs = workers[fn].queue
|
||||||
workers[fn].queue = None
|
workers[fn].queue = None
|
||||||
workers[fn].urgent.clear()
|
workers[fn].urgent.clear()
|
||||||
|
|
||||||
|
@ -164,26 +189,10 @@ def debounce(sleep, *, unless=None, retry=0):
|
||||||
try:
|
try:
|
||||||
await fn(*args, **kwargs)
|
await fn(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not retry:
|
logger.debug(f"while running {fn}, worker got %s", e)
|
||||||
logger.exception(f"while executing {fn}: %s", e)
|
workers[fn] = None
|
||||||
return
|
raise
|
||||||
retry -= 1
|
|
||||||
logger.warning(
|
|
||||||
f"while executing {fn} (remaining tries: %d): %s",
|
|
||||||
retry,
|
|
||||||
str(e),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Retry, unless we have something already scheduled
|
|
||||||
if workers[fn].queue is not None:
|
|
||||||
logger.debug(f"retry now with queued event for {fn}")
|
|
||||||
workers[fn].urgent.set()
|
|
||||||
continue
|
|
||||||
logger.debug(f"reschedule retry for {fn}")
|
|
||||||
workers[fn].queue = (retry, args, kwargs)
|
|
||||||
if unless is not None and unless(*args, **kwargs):
|
|
||||||
logger.debug(f"wake up now for retry of {fn}")
|
|
||||||
workers[fn].urgent.set()
|
|
||||||
# Do we still have something to do?
|
# Do we still have something to do?
|
||||||
if workers[fn].queue is None:
|
if workers[fn].queue is None:
|
||||||
break
|
break
|
||||||
|
@ -199,12 +208,13 @@ def debounce(sleep, *, unless=None, retry=0):
|
||||||
workers[fn] = types.SimpleNamespace()
|
workers[fn] = types.SimpleNamespace()
|
||||||
workers[fn].task = asyncio.create_task(worker())
|
workers[fn].task = asyncio.create_task(worker())
|
||||||
workers[fn].urgent = asyncio.Event()
|
workers[fn].urgent = asyncio.Event()
|
||||||
workers[fn].queue = (retry, args, kwargs)
|
workers[fn].queue = (args, kwargs)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"enqueue new work for {fn}")
|
logger.debug(f"enqueue new work for {fn}")
|
||||||
if unless is not None and unless(*args, **kwargs):
|
if unless is not None and unless(*args, **kwargs):
|
||||||
logger.debug(f"wake up now for {fn}")
|
logger.debug(f"wake up now for {fn}")
|
||||||
workers[fn].urgent.set()
|
workers[fn].urgent.set()
|
||||||
|
return await workers[fn].task
|
||||||
|
|
||||||
workers[fn] = None
|
workers[fn] = None
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -624,7 +634,8 @@ async def bluetooth_notifications(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@static(last=None)
|
@static(last=None)
|
||||||
@debounce(0.2, retry=2)
|
@retry(2)
|
||||||
|
@debounce(0.2)
|
||||||
async def bluetooth_status(i3, event, *args):
|
async def bluetooth_status(i3, event, *args):
|
||||||
"""Update bluetooth status for polybar."""
|
"""Update bluetooth status for polybar."""
|
||||||
if event is StartEvent:
|
if event is StartEvent:
|
||||||
|
@ -797,12 +808,12 @@ async def network_manager_notifications(i3, event, path, state, reason):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@static(last=None)
|
@static(last=None)
|
||||||
|
@retry(2)
|
||||||
@debounce(
|
@debounce(
|
||||||
1,
|
1,
|
||||||
unless=lambda i3, event, *args: (
|
unless=lambda i3, event, *args: (
|
||||||
isinstance(event, DBusSignal) and event.interface.endswith(".Active")
|
isinstance(event, DBusSignal) and event.interface.endswith(".Active")
|
||||||
),
|
),
|
||||||
retry=2,
|
|
||||||
)
|
)
|
||||||
async def network_manager_status(i3, event, *args):
|
async def network_manager_status(i3, event, *args):
|
||||||
"""Compute network manager status."""
|
"""Compute network manager status."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue