Categories
Tech

Timer context in Python

Some time ago I created a timer class for Neutron1. This class was a context manager2 with a defined timeout. This timeout was implemented using the Linux alarm clock3. Once the clock finishes the countdown, sends an alarm signal to the calling process. The problem of this implementation is that is not possible to use two nested context managers: the inner one will overwrite the alarm signal of the outer context. Let’s see if we can fix it.

Signal events.

Python is capable of sending signals4 to any process, using the PID. But is also capable of receiving those signals. signal.signal()5 function allows to set a handler to be executed when a signal is received. That means this handler will act as an interruption when the signal is received.

Watch out: in a multithread program, only the main thread is allowed to set a new signal handler6. Remember this to save a lot of time while debugging.

The alarm signal7 is very simple: after a defined amount of seconds, the system will generate a SIGALRM for the calling process. In our Python program, this signal will trigger the handler method, defined in signal.signal(). There is always a “but”: alarm requests are not stacked, “only one SIGALRM generation can be scheduled in this manner” (from the Linux man page). If, as in our case, we want to have a clock with multiple alarms (same as me in the morning), we need to make out own implementation.

Signals and exceptions.

“With great power comes great responsibility”. Signal handlers and exceptions can be very powerful tools in Python. Regardless of what you are doing, when an alarm signal is received, the handler will be executed. If we raise an exception inside the handler, we can break the current execution in an inelegant but effective way. That means when the alarm rings, we can stop any processing. If we catch the exception we will be able to redirect the code execution.

I know, coding by exception8 is an anti-pattern. But this is a good example, in my opinion, of exception handling. If we use the alarm signal like a watchdog, this is because we don’t want the alarm being raised but just when something wrong happens.

Before starting to describe my implementation, I would like to mention Toshio Kuratomi‘s article “Python, signal handlers, and exceptions“. He found a problem when using the Ansible timeout decorator. The exception raised by the alarm handler can be catch by the running method. This is because the exception is raised in the same context of the current execution. If there is a broad except clause (the try/except context is not filtering any exception), the running method will catch it, with unknown consequences.

The solution proposed by Toshio is very elegant: to run the decorated method in another process controlled by the parent one. This second process can be stopped by the first one at any time and can’t catch the exception raised.

The drawback of this implementation is that the process being executed inside this timer context should be designed to be executed isolated, in a separate process. Any global variable, opened DB session, opened socket and many other instances, can’t be shared.

The result.

This is an example of how I implemented a Timer context manager with timeout: https://github.com/ralonsoh/timer_context/blob/master/timer_context.py. A reusable, reentrant and stackable context manager with a defined timeout.

The Timer class is quite simple: it is a context manager that will set an alarm in the __enter__() function and will stop the same alarm in the __exit__() function; that means the alarm with be set and stopped when the context begins and ends. If, during the context execution, the alarm signal is received, the timeout handler will be called and the exception TimerTimeout raised. Please remember that the code executed inside the context manager should not catch this exception. This is the only precaution the programmer should take.

The Alarm class is just a container for an alarm definition, storing the set time, the timeout, the called ID and the handler.

The _AlarmClock class is the key point here. This is a singleton9 class that can be called from several Timer objects. This class should be used only in Timer and that’s why I made it private. To make things easier, this class has only two public methods and one read only property:

  • remainin_time: a real number representing the remaining time in the running system alarm. If the alarm is not running, this property will be 0.0.
  • set_alarm: this method needs a timeout, a called ID and a signal handler reference. The caller ID should be a unique identifier (I used the instance hash). The signal handler is a reference to a function to be called in the alarm rings. The timeout is a relative time to the current time. Unlike in a physical alarm clock, where you set an absolute time (I want to get up at 7:00), in this alarm clock you set a relative timeout from the moment you launch it.
  • stop_alarm: using the called ID, the class will find this alarm and will remove it from the scheduler. If this specific alarm is running at this moment (remember that only one alarm can run at a time), this call will stop it.

Now back again to the Timer class, when the context begins or ends, the class will call the _AlarmClock instance to manage only its own alarm. Each Timer instance can control only its own alarm, defined by the called ID. There is no interference between alarms; that means there is no interference between contexts. The _AlarmClock instance will start the scheduled alarms in order. If the running alarm is manually stopped or rings, the clock will then set the next one before calling the timeout handler if needed.

Leave a Reply

Your email address will not be published. Required fields are marked *