aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages/src/symfony/console/signal_registry/signal_registry.rs
blob: f1f67d0921abacc0e10d86e2bb4b9e3998deea43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! ref: composer/vendor/symfony/console/SignalRegistry/SignalRegistry.php

use indexmap::IndexMap;

/// A signal handler receives the signal number and whether a further handler follows.
pub type SignalHandler = Box<dyn Fn(i64, bool)>;

pub struct SignalRegistry {
    // signal number => list of handlers
    signal_handlers: IndexMap<i64, Vec<SignalHandler>>,
}

impl std::fmt::Debug for SignalRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SignalRegistry")
            .field("signal_handlers", &self.signal_handlers.keys())
            .finish_non_exhaustive()
    }
}

impl Default for SignalRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl SignalRegistry {
    pub fn new() -> Self {
        if shirabe_php_shim::function_exists("pcntl_async_signals") {
            shirabe_php_shim::pcntl_async_signals(true);
        }

        Self {
            signal_handlers: IndexMap::new(),
        }
    }

    pub fn register(&mut self, signal: i64, signal_handler: SignalHandler) {
        if !self.signal_handlers.contains_key(&signal) {
            let previous_callback = shirabe_php_shim::pcntl_signal_get_handler(signal);

            if shirabe_php_shim::is_callable(&previous_callback) {
                // $this->signalHandlers[$signal][] = $previousCallback;
                // The previous handler is an opaque PHP callable obtained from pcntl;
                // it is invoked through the runtime callable mechanism.
                self.signal_handlers
                    .entry(signal)
                    .or_default()
                    .push(Box::new(move |signal, has_next| {
                        shirabe_php_shim::call_php_callable(
                            &previous_callback,
                            &[
                                shirabe_php_shim::PhpMixed::Int(signal),
                                shirabe_php_shim::PhpMixed::Bool(has_next),
                            ],
                        );
                    }));
            }
        }

        self.signal_handlers
            .entry(signal)
            .or_default()
            .push(signal_handler);

        // pcntl_signal($signal, [$this, 'handle'])
        // TODO(plugin): the PHP callback `[$this, 'handle']` captures the registry
        // instance. Wiring this object method as a C-level signal handler requires the
        // runtime callable mechanism; see review notes.
        shirabe_php_shim::pcntl_signal(signal, shirabe_php_shim::PhpMixed::Null);
    }

    pub fn is_supported() -> bool {
        if !shirabe_php_shim::function_exists("pcntl_signal") {
            return false;
        }

        if shirabe_php_shim::explode(
            ",",
            &shirabe_php_shim::ini_get("disable_functions").unwrap_or_default(),
        )
        .contains(&"pcntl_signal".to_string())
        {
            return false;
        }

        true
    }

    pub fn handle(&self, signal: i64) {
        let handlers = match self.signal_handlers.get(&signal) {
            Some(handlers) => handlers,
            None => return,
        };
        let count = handlers.len();

        for (i, signal_handler) in handlers.iter().enumerate() {
            let has_next = i != count - 1;
            signal_handler(signal, has_next);
        }
    }
}