Skip to main content

folk_ext/
worker.rs

1//! ZTS worker threads — each thread runs an independent PHP context.
2//!
3//! A worker thread:
4//! 1. Registers with TSRM via `ZtsThreadGuard`
5//! 2. Starts a PHP request (`request_startup`)
6//! 3. Executes the PHP worker script (which registers handlers)
7//! 4. Enters the recv/send loop via `bridge::do_recv()`/`bridge::do_send()`
8//! 5. Cleans up on exit
9
10use std::sync::mpsc;
11use std::thread;
12
13use tracing::{error, info};
14
15use crate::bridge;
16use crate::zts;
17
18/// Spawn a ZTS worker thread that executes the given PHP script.
19///
20/// The worker thread receives tasks via `task_rx` and signals
21/// readiness via `ready_tx`. The script is expected to call
22/// `folk_worker_ready()` and then loop on `folk_worker_recv()`/`folk_worker_send()`.
23pub fn spawn_zts_worker(
24    worker_id: u32,
25    script: String,
26    task_rx: mpsc::Receiver<bridge::TaskRequest>,
27    ready_tx: mpsc::SyncSender<()>,
28) -> thread::JoinHandle<()> {
29    thread::Builder::new()
30        .name(format!("folk-worker-{worker_id}"))
31        .spawn(move || {
32            if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
33                run_zts_worker(worker_id, &script, task_rx, ready_tx);
34            })) {
35                error!(worker_id, "ZTS worker thread panicked: {e:?}");
36            }
37        })
38        .expect("failed to spawn worker thread")
39}
40
41fn run_zts_worker(
42    worker_id: u32,
43    script: &str,
44    task_rx: mpsc::Receiver<bridge::TaskRequest>,
45    ready_tx: mpsc::SyncSender<()>,
46) {
47    // 1. Register with TSRM.
48    let _guard = zts::ZtsThreadGuard::new();
49
50    // 2. Start PHP request.
51    if let Err(e) = zts::request_startup() {
52        error!(worker_id, error = ?e, "php_request_startup failed");
53        return;
54    }
55
56    // 3. Install worker bridge channels (thread-local).
57    bridge::init_worker_state(worker_id, task_rx, ready_tx);
58
59    // 4. Execute the PHP worker script.
60    //    The script calls folk_worker_ready() and enters the recv/send loop.
61    //    This call blocks until the script exits (channel closed).
62    if let Err(e) = zts::execute_script(script) {
63        error!(worker_id, error = ?e, "worker script failed");
64    }
65
66    // 5. Clean up.
67    bridge::cleanup_worker_state();
68    zts::request_shutdown();
69    // ZtsThreadGuard drop handles ts_free_thread().
70
71    info!(worker_id, "ZTS worker thread exiting");
72}