Resolving Shared Client State and Cancellation
To prevent timeout leakage and enable graceful termination of long-running calls in Phalcon, you should move away from a shared service for any client requiring dynamic timeouts or external cancellation. The most robust approach is to transition the DI registration from a shared service to a factory-scoped service and implement a per-request cancellation token via a transport-level callback.
Implementation Strategy
Because Phalcon's HTTP client wrappers typically maintain state (like timeouts) on the instance, a shared service creates a race condition or state leak. To isolate settings and allow cancellation, follow these steps:
- Change DI Scope: Register the client without the
shared flag. This ensures every request to the DI container returns a fresh instance with its own socket and configuration.
- Implement a Cancellation Token: Create a simple token object (or use a boolean flag) that is passed into the request method.
- Hook into the Transport: If the client uses cURL, use
CURLOPT_PROGRESSFUNCTION (or CURLOPT_XFERINFOFUNCTION in newer versions). The client should check the cancellation token inside this callback; returning a non-zero value will immediately abort the transfer and close the handle.
- Map the Exception: Catch the abort result and throw a specific
CancelledException. This distinguishes an intentional termination from a network timeout, preventing unnecessary retry logic.
Comparison of DI Scopes
| Scope |
Timeout Behavior |
Cancellation Safety |
Resource Impact |
| Shared |
Global/Leaky |
Dangerous (Affects all calls) |
Low (Single instance) |
| Factory |
Isolated per call |
Safe (Handle is unique) |
Moderate (New instance/handle) |
Verification Steps
To verify this implementation, use the following scoped tests:
- Isolation Check: Execute two sequential requests with different timeouts. Confirm the second request does not inherit the timeout of the first.
- Cancellation Check: Initiate a request to a slow endpoint. Trigger the cancellation token mid-flight and verify the process terminates immediately with a
CancelledException.
- Leak Check: Use
lsof -i or netstat to ensure sockets are closed immediately upon cancellation and not left in CLOSE_WAIT.
Assumptions and Constraints
This solution assumes the underlying transport is cURL. If the client uses PHP stream wrappers, cancellation is significantly harder as streams are blocking and do not provide a native "progress" hook for external signals without using non-blocking I/O and stream_select(). Additionally, if you are using a persistent runtime (e.g., Swoole or RoadRunner), shared mutable clients are strictly discouraged due to concurrency risks.
Diagnostic Detail Needed: Please specify if your client implementation uses curl or stream wrappers, as the cancellation mechanism differs fundamentally between the two.