zlikavac32/alarm-scheduler

SIGALRM scheduler

0.2.1 2019-04-25 19:13 UTC

This package is auto-updated.

Last update: 2024-10-29 05:46:45 UTC


README

Build Status

This library provides support for multiple SIGALRM handlers.

Table of contents

  1. Introduction
  2. Installation
  3. API
    1. AlarmHandler
    2. AlarmScheduler
    3. InterruptException
    4. InterruptAlarmHandler
    5. CatchThrowableAlarmHandler
  4. Usage
  5. Rule of thumb
  6. Examples

Introduction

As of PHP 7.1, async signals are supported through pcntl_async_signals() function. With that nice feature, pcntl_alarm() became more helpful than ever.

Only issue is, we can set one signal handler per process.

This library provides simple scheduler for SIGALRM and allows multiple targets to be scheduled with some arbitrary delay.

Installation

Recommended installation is through Composer.

composer require zlikavac32/alarm-scheduler

API

Two interfaces are provided, one for alarm handler and the other for the alarm scheduler.

Do note that this library does not call pcntl_async_signals(true);. It's the responsibility of the library user to call it where they find it applicable.

AlarmHandler

Interface \Zlikavac32\AlarmScheduler\AlarmHandler is used to implement alarm handler. Method handle() will be called from the signal handler so check Rule of thumb for more info.

Current scheduler is passed into handle() to allow rescheduling of the handler (or scheduling a new one).

AlarmScheduler

Interface \Zlikavac32\AlarmScheduler\AlarmScheduler is used to implement alarm scheduler. Implementation should take control over SIGALRM handling.

Methods must be safe to be called from within the signal handler. Check Rule of thumb for more info.

InterruptException

Exception representing hard interrupt that must be respected by the scheduler implementation.

It can be thrown from the alarm handler in order to cause exception from the signal handler.

Users are not restricted to this exception for the hard interrupt as described in the Rule of thumb section.

InterruptAlarmHandler

Causes hard interrupt from alarm scheduler by throwing InterruptException

CatchThrowableAlarmHandler

Alarm handler that wraps any other alarm handler and catches any throwable caught from it is implemented in \Zlikavac32\AlarmScheduler\CatchThrowableAlarmHandler. What is caught is passed into \Zlikavac32\AlarmScheduler\ThrowableHandler. If the throwable handler throws anything, it's silently ignored.

Simple throwable handler that just ignores everything is provided through \Zlikavac32\AlarmScheduler\IgnoreThrowableHandler.

Usage

First create the scheduler (currently, only single implementation is provided).

$scheduler = new \Zlikavac32\AlarmScheduler\NaiveAlarmScheduler();

When it's applicable to take over of SIGALRM handling, call

$scheduler->start();

Method start() must be called before any additional use of scheduler methods.

Then implement some alarm handler.

$handler = new class implements \Zlikavac32\AlarmScheduler\AlarmHandler {

   public function handle(\Zlikavac32\AlarmScheduler\AlarmScheduler $scheduler): void {
       echo (new DateTime())->format('i\\m:s\\s'), "\n";
   }
};

Next, schedule alarm handler.

$scheduler->schedule(5, $handler); // to run $handler in 5 seconds

Now, this will not block. If script reaches end before handlers are triggered, it will exit without triggering them.

For testing purposes, we can sleep for a few seconds. We can also print current time.

echo (new DateTime())->format('i\\m:s\\s'), "\n";

$sleep = 7;
while ($sleep = sleep($sleep));

Rule of thumb

If SIGALRM is not blocked, it can interrupt scheduler methods at any point. Scheduler implementations must be safe to be run from within the signal handler. In most cases that means blocking signal until scheduler method is to be finished, and then unblocking it. PHP VM takes care of the rest.

No exception, except \Zlikavac32\AlarmScheduler\InterruptException, should be thrown from the alarm handler. If exception is thrown, and scheduler implementation does not catch it (which is not the requirement), it could leave the scheduler in inconsistent state.

If custom async exception is a requirement, alternative is to use SIGUSR1 or SIGUSR2 to throw exception from a different stack frame. PHP VM will deffer signal handling until current handler finishes. That means it will not affect scheduler stack frame.

pcntl_signal(SIGUSR1, function (): void {
    throw new SomeDomainSpecificException();
});

$scheduler->schedule(2, new class implements \Zlikavac32\AlarmScheduler\AlarmHandler {

    public function handle(\Zlikavac32\AlarmScheduler\AlarmScheduler $scheduler): void {
        posix_kill(getmypid(), SIGUSR1);
    }
});

sleep() may not be safe to use with SIGALRM so check how your system handles sleeping before using this library.

Examples

Examples with code comments can be found in examples directory.