PSR-14: Example - PSR-14 in a non-blocking application server
We continue our exploration of PSR-14's potential with a guest post. Cees-Jan Kiewiet was the Sponsor of PSR-14 (meaning the member of the Core Committee who bridged from the Working Group to the Core Committee), and is on the core team for ReactPHP
. One wouldn't think there's any use cases for PSR-14 in an async environment like React, but one would be wrong.
Here's Cees-Jan with an explanation:
Non-blocking applications like those build using ReactPHP
are build based on a different kind of events then event dispatchers as defined in PSR-14 . However, there are a few uses cases for PSR-14 Event Dispatchers.
Often an non-blocking application server consists of multiple components, for example:
- HTTP Server serving metrics for prometheus/a web UI/an REST/GraphQL/SOAP API
- Message broker consumer like
bunny/bunny
- Scheduled tasks like a
cron
- TCP/UDP socket server (can be any number of protocols)
These components only communicate strictly when necessary through strictly defined contracts. There are however 3 things they all share:
- The event loop
- Starting up
- Shutting down
The first two are tightly coupled and always happens when starting the application but the latter is always uncertain when it happens. It could be a system restart, new deployment stopping the current instance, a k8s pod getting replaced. Exactly that moment is when a (PSR-14) Event Dispatcher has it's moment to shine in an application like this.
ReactPHP's event loop supports catching OS signals
like SIGTERM
. We can use this to dispatch a shutdown event across all components within the application server.
First we'll create a handler that can dispatch our ShutdownEvent
when we catch the SIGTERM
signal:
$eventDispatcher = new EventDispatcher();
$handler = function (int $signal) use ($eventDispatcher) {
$eventDispatcher->dispatch(new ShutdownEvent($signal));
};
Second we add our listener to the event loop:
$loop->addSignal(SIGTERM, $handler);
Now the waiting game begins until the signal comes in. When it does it will be emitted within the ShutdownEvent
, and ultimately caught by an Event Listener like the one below. The Listener below will close the socket
server the HTTP
server builds on listening for incoming requests. By doing so it removes the listening socket from the event loop and won't accept any new requests. Any still active requests will continue to run and complete.
final class ShutdownListener
{
/** @var React\Socket\ServerInterface */
private $socket;
/**
* @param ServerInterface $socket
*/
public function __construct(ServerInterface $socket)
{
$this->socket = $socket;
}
public function __invoke(ShutdownEvent $shutdownEvent): void
{
/** @var ServerInterface */
$this->socket->close();
}
}