Reputation: 229331
I have this decorator:
def timed_out(timeout):
def decorate(f):
if not hasattr(signal, "SIGALRM"):
return f
def handler(signum, frame):
raise TimedOutExc()
@functools.wraps(f)
def new_f(*args, **kwargs):
old = signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout)
try:
result = f(*args, **kwargs)
finally:
signal.signal(signal.SIGALRM, old)
signal.alarm(0)
return result
new_f.func_name = f.func_name
return new_f
return decorate
The code only does anything on linux, though, as on windows, there is no SIGALRM
. What would be the simplest way to have this code work in Windows as well?
Upvotes: 24
Views: 42537
Reputation: 1771
Addressing original question: python: windows equivalent of SIGALRM
There is a way to create a SIGALRM type event on Windows. It makes use of the multi-media timer to trigger the event. This is a general purpose timer which can trigger a single event (periodic=False
), a number of repetitions (n_reps >= 1
), or continuous with an external stopping event ( .stop()
function call)
This timer can be specified to produce very accurately spaced events, which are not dependent on the Python runtime. Here, time intervals are specified in seconds (interval
and resolution
)
import ctypes as ct
from ctypes.wintypes import UINT, DWORD
from typing import Callable, Optional
class mmPeriodicTimer:
"""Periodic timer based upon the Windows Multimedia library
This produces a timed callback with high precision ( ~ < 1ms difference)
executed outside of a threaded structure (based on Windows runtime ).
The advantage of this is that the Python GIL limitation is avoided,
and an aligned "lag" time between all Python processes is not generated;
The disadvantage is that since this is thread independent, the callback
procedure must complete its tasks before the next callback is executed
otherwise collisions may occur
This is based on the example:
https://stackoverflow.com/questions/10717589/how-to-implement-high-speed-consistent-sampling
"""
timeproc = ct.WINFUNCTYPE(None, ct.c_uint, ct.c_uint, DWORD, DWORD, DWORD)
timeSetEvent = ct.windll.winmm.timeSetEvent
timeKillEvent = ct.windll.winmm.timeKillEvent
def Tick(self):
self.i_reps += 1
self.tickFunc()
if not self.periodic:
self.stop()
if self.n_reps is not None and self.i_reps >= self.n_reps:
self.stop()
def CallBack(self, uID, uMsg, dwUser, dw1, dw2):
if self.running:
self.Tick()
def __init__(
self,
interval: int,
tickfunc: Callable,
resolution: Optional[int] = 0,
stopFunc: Optional[Callable] = None,
periodic: Optional[bool] = True,
n_reps: Optional[int] = None,
):
self.interval = UINT(int(interval * 1000))
self.resolution = UINT(int(resolution * 1000))
self.tickFunc = tickfunc
self.stopFunc = stopFunc
self.periodic = periodic
self.n_reps = n_reps
if not self.periodic and self.n_reps is not None:
raise ValueError("n_reps must be None if periodic is False")
self.i_reps = 0
self.id = None
self.running = False
self.calbckfn = self.timeproc(self.CallBack)
def start(self, instant: bool = False):
if not self.running:
self.running = True
if instant:
self.Tick()
self.id = self.timeSetEvent(
self.interval,
self.resolution,
self.calbckfn,
ct.c_ulong(0),
ct.c_uint(self.periodic),
)
def stop(self):
if self.running:
self.timeKillEvent(self.id)
self.running = False
if self.stopFunc:
self.stopFunc()
Example Single Event:
import time
from datetime import datetime
def my_func():
print("current time=", datetime.now())
t = mmPeriodicTimer(interval=1, tickfunc=my_func, periodic=False)
t.start()
Output:
current time= 2024-03-24 22:42:16.475416
Example multiple events (then stop):
t = mmPeriodicTimer(interval=1, tickfunc=my_func, n_reps=5)
t.start()
Output:
current time= 2024-03-24 22:42:11.474459
current time= 2024-03-24 22:42:12.475201
current time= 2024-03-24 22:42:13.474462
current time= 2024-03-24 22:42:14.475113
current time= 2024-03-24 22:42:15.474802
Example terminate manually after given time:
t = mmPeriodicTimer(interval=1, tickfunc=my_func, periodic=True)
t.start()
time.sleep(5)
t.stop()
Output:
current time= 2024-03-24 22:46:32.864423
current time= 2024-03-24 22:46:33.865319
current time= 2024-03-24 22:46:34.864554
current time= 2024-03-24 22:46:35.864762
current time= 2024-03-24 22:46:36.864778
Original credit goes to: How to implement high speed, consistent sampling?
Upvotes: 0
Reputation: 3756
... and it may no longer work with recent versions of Cygwin...
I find this timeout-decorator code very handy, too. (I originally found it in this question answer: How to limit execution time of a function call?)
To make it work on Windows, I use the Python that is installed with Cygwin.
I run setup-x86_64.exe, then select the python3
package from the Python folder. (Or, if you prefer Python 2, the python
package.)
To rename python3 to python2, I define the alias
alias python=python3
from the Cygwin command prompt. Since I don't use this functionality very often, I probably won't put it into a .bashrc or anything.
Related question: Python signal don't work even on Cygwin?
Upvotes: 0
Reputation: 6911
It's not very pretty, but I had to do something similar in a cross-platform way, and I came up with using a separate thread. Signal based systems did not work on all platforms reliably.
Use of this class could be wrapped up in a decorator, or made into a with
context handler.
YMMV.
#!/usr/bin/env python2.7
import time, threading
class Ticker(threading.Thread):
"""A very simple thread that merely blocks for :attr:`interval` and sets a
:class:`threading.Event` when the :attr:`interval` has elapsed. It then waits
for the caller to unset this event before looping again.
Example use::
t = Ticker(1.0) # make a ticker
t.start() # start the ticker in a new thread
try:
while t.evt.wait(): # hang out til the time has elapsed
t.evt.clear() # tell the ticker to loop again
print time.time(), "FIRING!"
except:
t.stop() # tell the thread to stop
t.join() # wait til the thread actually dies
"""
# SIGALRM based timing proved to be unreliable on various python installs,
# so we use a simple thread that blocks on sleep and sets a threading.Event
# when the timer expires, it does this forever.
def __init__(self, interval):
super(Ticker, self).__init__()
self.interval = interval
self.evt = threading.Event()
self.evt.clear()
self.should_run = threading.Event()
self.should_run.set()
def stop(self):
"""Stop the this thread. You probably want to call :meth:`join` immediately
afterwards
"""
self.should_run.clear()
def consume(self):
was_set = self.evt.is_set()
if was_set:
self.evt.clear()
return was_set
def run(self):
"""The internal main method of this thread. Block for :attr:`interval`
seconds before setting :attr:`Ticker.evt`
.. warning::
Do not call this directly! Instead call :meth:`start`.
"""
while self.should_run.is_set():
time.sleep(self.interval)
self.evt.set()
Upvotes: 13