aboutsummaryrefslogtreecommitdiffhomepage
path: root/docs/dev/plugin-class-classification.md
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-31 00:22:02 +0900
committernsfisis <nsfisis@gmail.com>2026-08-31 00:22:02 +0900
commit4a2f024846ca1b0bbdb4f6904bae964756c9f701 (patch)
tree3261da76e9f84e9a07946a7c537a394cf01b29de /docs/dev/plugin-class-classification.md
parent6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea (diff)
downloadphp-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.tar.gz
php-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.tar.zst
php-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.zip
feat(plugin): serve HttpDownloader's async surface
add() and addCopy() answer with a promise, and enableAsync(), wait() and countActiveJobs() answer alongside them. The Rust future runs to completion before the promise is handed over, so requests a plugin starts together run one after another rather than overlapping; overlapping them needs a promise representation that crosses the boundary unresolved. Everything else the surface does is preserved. add() still refuses a downloader outside a Loop, and it does so by throwing out of the call the way PHP does, where a failed request instead arrives as a rejection the caller handles — __shirabe_rejected_promise is the failure half of the resolved- promise helper. wait() and countActiveJobs() answer for a downloader with no outstanding job, which, once every request settles before its call returns, it never has. This is where HttpDownloader parts company with ProcessExecutor, whose async surface stays an explicit error: executeAsync() resolves its promise with a Symfony Process, whose state is the proc_open() resource of whichever process called start(), where a request resolves its promise with a Response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'docs/dev/plugin-class-classification.md')
-rw-r--r--docs/dev/plugin-class-classification.md20
1 files changed, 16 insertions, 4 deletions
diff --git a/docs/dev/plugin-class-classification.md b/docs/dev/plugin-class-classification.md
index a383f7c6..5cb8fd6e 100644
--- a/docs/dev/plugin-class-classification.md
+++ b/docs/dev/plugin-class-classification.md
@@ -415,10 +415,22 @@ its options, its TLS defaults and the authentication it collects into the
run's IO are state the two worlds have to share, and a plugin-`new`ed one
allocates a Rust-side entity rather than a second downloader the graph knows
nothing about. `get()` and `copy()` answer with a
-`Composer\Util\Http\Response` the child holds as a value (see below); the
-async surface — `add()`, `addCopy()`, `wait()`, `enableAsync()`,
-`countActiveJobs()` — is still an explicit error, because driving it needs a
-promise representation that crosses the boundary unresolved.
+`Composer\Util\Http\Response` the child holds as a value (see below), and the
+async surface answers too, with the future driven to completion before the
+promise is handed over. Requests a plugin starts together therefore run one
+after another rather than overlapping; overlapping them needs a promise
+representation that crosses the boundary unresolved. Everything else the async
+surface does is preserved: `add()` still refuses a downloader outside a `Loop`,
+a failed request still arrives as a rejection rather than as a throw, and
+`wait()` and `countActiveJobs()` still answer for a downloader that holds no
+outstanding job — which, once every request settles before its call returns, it
+never does.
+
+This is where `ProcessExecutor` and `HttpDownloader` part company. The
+executor's async surface stays an explicit error because `executeAsync()`
+resolves its promise with a `Symfony\Component\Process\Process`, whose state
+is the `proc_open()` resource of whichever process called `start()`; a request
+resolves its promise with a `Response`, which is data.
`Loop` remains guarded, so `Composer::getLoop()` is still an explicit error
and neither the graph's own executor nor its downloader is reachable through