aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/src/env.rs
blob: 1655168fd246fc960718c27773ed6eeed644b448 (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
//! Propagation of environment mutations into the PHP worker.
//!
//! The worker is a long-lived child: it holds the environment it was handed at spawn, so a
//! `putenv()` or a `$_ENV`/`$_SERVER` write the Rust side makes afterwards is invisible to the
//! PHP code running in it — `@putenv` in a `scripts` entry, the bin dir the event dispatcher
//! prepends to `PATH`, `COMPOSER_DEV_MODE`. The shim records every such write; this module
//! replays the ones the worker has not seen yet, before the next call crosses the boundary.
//!
//! TODO(php-runtime): the reverse direction is missing — a write PHP code makes to its own
//! environment is not reflected back into the Rust-side storages.

use crate::PluginValue;
use shirabe_php_shim::EnvStorageKind;
use std::os::unix::ffi::OsStrExt as _;

/// How many recorded mutations the worker has already been told about.
static SYNCED: std::sync::Mutex<usize> = std::sync::Mutex::new(0);

/// The name `__shirabe_sync_env` reads each mutation's target storage under.
fn storage_name(storage: EnvStorageKind) -> &'static str {
    match storage {
        EnvStorageKind::Process => "process",
        EnvStorageKind::Env => "_ENV",
        EnvStorageKind::Server => "_SERVER",
    }
}

/// Replays the environment mutations recorded since the last flush into the worker.
///
/// Called from the outermost `rpc_call` only, so the `__shirabe_sync_env` call it issues re-enters
/// the session instead of recursing into another flush.
pub(crate) fn flush() -> anyhow::Result<()> {
    let mut synced = SYNCED.lock().unwrap();
    let (cursor, mutations) = shirabe_php_shim::env_mutations_since(*synced);
    if mutations.is_empty() {
        return Ok(());
    }

    let mutations = mutations
        .into_iter()
        .map(|mutation| {
            PluginValue::List(vec![
                PluginValue::string(storage_name(mutation.storage)),
                PluginValue::String(mutation.key.as_bytes().to_vec()),
                match mutation.value {
                    Some(value) => PluginValue::String(value.as_bytes().to_vec()),
                    None => PluginValue::Null,
                },
            ])
        })
        .collect();
    let outcome = crate::call_function("__shirabe_sync_env", vec![PluginValue::List(mutations)])?;
    // The worker only writes the three storages; nothing it does there can throw.
    outcome.unwrap_or_else(|throw| panic!("PHP RPC: __shirabe_sync_env threw: {}", throw.message));
    *synced = cursor;
    Ok(())
}