src/DeadmanSwitch.php source

1 <?php
2
3 namespace phasync;
4
5 /**
6 * A safety mechanism that triggers a callback when garbage collected.
7 *
8 * This is useful for detecting when a coroutine terminates unexpectedly,
9 * allowing cleanup actions like marking a buffer as failed or closing a connection.
10 *
11 * Usage:
12 * ```php
13 * phasync::go(function() use ($sb) {
14 * $deadman = $sb->getDeadmanSwitch();
15 *
16 * while ($data = fread($socket, 8192)) {
17 * $sb->write($data);
18 * }
19 * $sb->end(); // Always end properly
20 * // If coroutine exits without calling end(), $deadman triggers on GC
21 * });
22 * ```
23 */
24 final class DeadmanSwitch
25 {
26 private ?\Closure $callback;
27 private bool $triggered = false;
28
29 /**
30 * Create a new DeadmanSwitch.
31 *
32 * @param callable $callback The callback to execute when the switch is triggered
33 */
34 public function __construct(callable $callback)
35 {
36 $this->callback = $callback(...);
37 }
38
39 /**
40 * Destructor triggers the callback if not already triggered or disarmed.
41 */
42 public function __destruct()
43 {
44 if (!$this->triggered && null !== $this->callback) {
45 $this->trigger();
46 }
47 }
48
49 /**
50 * Manually trigger the switch, executing the callback.
51 * Can only be triggered once.
52 */
53 public function trigger(): void
54 {
55 if ($this->triggered) {
56 return;
57 }
58 $this->triggered = true;
59 if (null !== $this->callback) {
60 ($this->callback)();
61 $this->callback = null;
62 }
63 }
64
65 /**
66 * Disarm the switch, preventing the callback from being executed.
67 * Use this when the resource was closed normally.
68 */
69 public function disarm(): void
70 {
71 $this->triggered = true;
72 $this->callback = null;
73 }
74
75 /**
76 * Check if the switch has been triggered.
77 */
78 public function isTriggered(): bool
79 {
80 return $this->triggered;
81 }
82 }
83