diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-23 22:54:18 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-23 22:54:18 +0900 |
| commit | 48a6566f469cc16300232b6faa783d09aa69cdfd (patch) | |
| tree | ede30385b56636c7550d47f4520b631cf6c242d8 /crates/shirabe/src/util | |
| parent | 2d89dc761237b055ddb8c6dc817c46c0cd6dd44d (diff) | |
| download | php-shirabe-48a6566f469cc16300232b6faa783d09aa69cdfd.tar.gz php-shirabe-48a6566f469cc16300232b6faa783d09aa69cdfd.tar.zst php-shirabe-48a6566f469cc16300232b6faa783d09aa69cdfd.zip | |
feat(process-executor): stub plugin-facing execute_async_php path
A plugin can reach ProcessExecutor::executeAsync() through the
rust-proxy stub, which resolves with a real Symfony Process instance
that can't be reconstructed on the Rust side (its state is tied to
whichever process calls proc_open(), and it refuses serialization).
Add execute_async_php() as a todo!() stub, documented as a dual-
instantiation split in plugin-class-classification.md: Rust-internal
callers keep using execute_async(), while the plugin path must forward
spawning to the PHP child once the RPC channel exists.
Diffstat (limited to 'crates/shirabe/src/util')
| -rw-r--r-- | crates/shirabe/src/util/process_executor.rs | 48 |
1 files changed, 42 insertions, 6 deletions
diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs index 1f00eaeb..1f167840 100644 --- a/crates/shirabe/src/util/process_executor.rs +++ b/crates/shirabe/src/util/process_executor.rs @@ -37,8 +37,8 @@ pub struct ProcessExecutor { /// @var int max_jobs: i64, /// PHP throttles async jobs through the $jobs queue and $maxJobs; here concurrent - /// `execute_async` calls hold a permit for the duration of the child process instead - /// (same design as HttpDownloader). + /// `execute_async`/`execute_async_php` calls hold a permit for the duration of the + /// child process instead (same design as HttpDownloader). semaphore: std::rc::Rc<tokio::sync::Semaphore>, /// @var bool allow_async: bool, @@ -593,13 +593,18 @@ impl ProcessExecutor { } } - /// starts a process on the commandline in async mode + /// starts a process on the commandline in async mode, for callers within Rust-ported Composer + /// code (i.e. everything except a plugin holding a `ProcessExecutor` RPC handle — see + /// `execute_async_php` and docs/dev/plugin-class-classification.md, "Process: dual + /// instantiation split by caller"). This is the direct 1:1 port of PHP's `executeAsync()`: + /// almost all calls go through here, spawning in Rust with no PHP child involved. /// /// The returned future does NOT borrow the executor: everything it needs is captured up /// front, so callers can drop their `Ref`/`RefMut` on the shared `Rc<RefCell<ProcessExecutor>>` - /// before awaiting (`let fut = pe.borrow_mut().execute_async(...); fut.await`). Holding a - /// borrow across the await would panic as soon as a sibling future or a sync `execute()` call - /// touches the same executor. The max_jobs throttle is enforced by the semaphore. + /// before awaiting (`let fut = pe.borrow_mut().execute_async(...); fut.await`). Holding + /// a borrow across the await would panic as soon as a sibling future or a sync `execute()` + /// call touches the same executor. The max_jobs throttle is enforced by the semaphore, shared + /// with `execute_async_php`. /// /// Takes `&mut self` (unlike the `&self` used while this only read `self.mock`) so the mock /// branch can update `error_output`/`capture_output` before returning, mirroring @@ -699,6 +704,37 @@ impl ProcessExecutor { }) } + /// Plugin-facing counterpart of `execute_async`. Reached only when the RPC dispatcher + /// relays a plugin's `executeAsync()` call made on the `rust-proxy` `ProcessExecutor` stub + /// (a plugin obtained the handle via `Loop::getProcessExecutor()`) — see + /// docs/dev/plugin-class-classification.md, "Process: dual instantiation split by caller". + /// + /// A `Symfony\Component\Process\Process` cannot be reconstructed on the Rust side: its state + /// (the `proc_open()` resource, the OS pipes) belongs to whichever process calls `start()`, + /// and it refuses serialization outright. So unlike `execute_async`, this must not + /// spawn in Rust: the real `Process::start()` has to run in the PHP child, and the plugin's + /// `.then()` callback must receive that genuine PHP-side object. + // TODO(plugin): once the plugin RPC channel exists, acquire a permit from `self.semaphore` + // (shared with `execute_async`, so the combined job budget — including any shared cap + // with HttpDownloader — stays correct regardless of which path runs a given job), then send + // the spawn request to the PHP child over that channel instead of calling `Process::start()` + // here. Release the permit on the child's completion notification, not by polling a + // Rust-owned process handle. The return type below is provisional: the real deliverable is a + // handle to the live PHP-side Process object, not a `shirabe_external_packages` `Process` + // value, so this signature will need to change once the RPC plumbing exists. + pub fn execute_async_php<C>( + &mut self, + _command: C, + _cwd: Option<&str>, + ) -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>>>> + where + C: IntoExecCommand, + { + todo!( + "forward the spawn to the PHP child over the plugin RPC channel and await its completion notification" + ) + } + fn output_handler( capture_output: bool, io: &mut Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>, |
