@@ -53,9 +53,29 @@ Each call **replaces** the entire snapshot atomically.
5353- Throws ` RuntimeException ` if not called from a sidekick context
5454- Throws ` ValueError ` if keys or values are not strings
5555
56- ### ` frankenphp_sidekick_should_stop (): bool `
56+ ### ` frankenphp_sidekick_get_signaling_stream (): resource `
5757
58- Returns ` true ` when FrankenPHP is shutting down.
58+ Returns a readable stream that becomes ready when FrankenPHP is shutting down or restarting workers.
59+ Use ` stream_select() ` instead of ` sleep() ` or ` usleep() ` to wait between iterations:
60+
61+ ``` php
62+ function sidekick_should_stop(float $timeout = 0): bool
63+ {
64+ static $signalingStream;
65+ $signalingStream ??= frankenphp_sidekick_get_signaling_stream();
66+ $s = (int) $timeout;
67+
68+ return 0 !== stream_select(...[[$signalingStream], [], [], $s, (int) (($timeout - $s) * 1e6)]);
69+ };
70+
71+ do {
72+ // ... do work, call set_vars() ...
73+ } while (!sidekick_should_stop(5));
74+ ```
75+
76+ ** Why not ` sleep() ` ?** ` sleep() ` and ` usleep() ` block at the C level and cannot be interrupted.
77+ A sidekick using ` sleep(60) ` would delay shutdown or worker restart by up to 60 seconds.
78+ ` stream_select() ` with the signaling stream wakes up immediately when FrankenPHP needs the thread to stop.
5979
6080- Throws ` RuntimeException ` if not called from a sidekick context
6181
@@ -72,25 +92,29 @@ require __DIR__.'/../vendor/autoload.php';
7292$command = $_SERVER['FRANKENPHP_SIDEKICK_NAME'] ?? '';
7393
7494match ($command) {
75- 'redis-watcher' => runRedisWatcher (),
95+ 'redis-watcher' => run_redis_watcher (),
7696 default => throw new \RuntimeException("Unknown sidekick: $command"),
7797};
7898
79- function runRedisWatcher(): void
99+ function sidekick_should_stop(float $timeout = 0): bool
100+ {
101+ static $signalingStream;
102+ $signalingStream ??= frankenphp_sidekick_get_signaling_stream();
103+ $s = (int) $timeout;
104+
105+ return 0 !== stream_select(...[[$signalingStream], [], [], $s, (int) (($timeout - $s) * 1e6)]);
106+ };
107+
108+ function run_redis_watcher(): void
80109{
81- frankenphp_sidekick_set_vars([
82- 'MASTER_HOST' => '10.0.0.1',
83- 'MASTER_PORT' => '6379',
84- ]);
85110
86- while (!frankenphp_sidekick_should_stop()) {
87- $master = discoverRedisMaster ();
111+ do {
112+ $master = discover_redis_master ();
88113 frankenphp_sidekick_set_vars([
89114 'MASTER_HOST' => $master['host'],
90115 'MASTER_PORT' => (string) $master['port'],
91116 ]);
92- usleep(100_000);
93- }
117+ } while (!sidekick_should_stop(0.1));
94118}
95119```
96120
@@ -132,5 +156,5 @@ if (function_exists('frankenphp_sidekick_get_vars')) {
132156- ` SCRIPT_FILENAME ` is set to the entrypoint's full path
133157- ` $_SERVER['FRANKENPHP_SIDEKICK_NAME'] ` and ` $_SERVER['argv'][1] ` contain the sidekick name
134158- Crash recovery: automatic restart with exponential backoff
135- - Graceful shutdown via ` frankenphp_sidekick_should_stop ()`
159+ - Graceful shutdown via ` frankenphp_sidekick_get_signaling_stream() ` and ` stream_select ()`
136160- Use ` error_log() ` or ` frankenphp_log() ` for logging — avoid ` echo `
0 commit comments