CurlMultiHandler
in package
Returns an asynchronous response using curl_multi_* functions.
When using the CurlMultiHandler, custom curl options can be specified as an associative array of curl option constants mapping to values in the curl key of the provided request options.
Tags
Table of Contents
Constants
- CONNECTION_CAP_OPTIONS : mixed = ['max_host_connections' => 'CURLMOPT_MAX_HOST_C...
- KNOWN_CONSTRUCTOR_OPTIONS : mixed = ['handle_factory' => true, 'max_host_connection...
- PROXY_TUNNEL_ISOLATION_OPTIONS : mixed = ['CURLOPT_FRESH_CONNECT', 'CURLOPT_FORBID_REUSE']
- cURL options that isolate a transfer from foreign proxy tunnel connections. Failing to apply either one would fall open into credential-bearing connection reuse.
Properties
- $_mh : resource|CurlMultiHandle
- $active : int
- $activeProxyTunnelHandles : array<int, string>
- $activeProxyTunnelSignatures : array<string, int>
- $connectionCapsApplied : bool
- $customHandleFactory : bool
- $deferredAdds : array<int, object|null>
- $deferredCancels : array<int, EasyHandle, attached: bool}>
- $delays : array<int, float>
- $factory : CurlFactoryInterface
- $finishingDeferredWork : bool
- $handles : array<string|int, mixed>
- $messageProcessingDepth : int
- $multiExecDepth : int
- $multiplexDisabled : bool
- $options : array<string|int, mixed>
- $proxyTunnelOwner : string|null
- $requiredOptions : array<int, true>
- $selectTimeout : int
- $shareHandleState : CurlShareHandleState|null
Methods
- __construct() : mixed
- This handler accepts the following options:
- __destruct() : mixed
- __get() : resource|CurlMultiHandle
- __invoke() : PromiseInterface
- execute() : void
- Runs until all outstanding connections have completed.
- tick() : void
- Ticks the curl event loop.
- addConflictingCurlMultiOption() : void
- addConnectionCapOptions() : void
- addCurlHandle() : void
- addRequest() : void
- applyMultiplexNone() : void
- A Multiplexing::NONE request option is a sole-use guarantee: the transfer must not share its connection with any concurrent transfer.
- applyProxyTunnelOwnership() : void
- Isolates the connection cache when the request's proxy tunnel section differs from the one the multi handle's cache may already hold.
- cancel() : bool
- Cancels a handle from sending and removes references to it.
- cleanupCancelledHandle() : void
- cleanupDeferredCancels() : void
- conflictingCurlMultiOptions() : array<int, string>
- conflictingCurlMultiOptionSinceOverrides() : array<int, string>
- discardPendingRequest() : Throwable
- Rolls back a request that can no longer be attached, releasing the easy handle exactly once and preserving the original failure.
- effectiveSelectTimeout() : float|int
- Bounds a blocking select by the earliest pending request delay so a delayed transfer becoming due does not wait out an unrelated transfer's full select timeout.
- executeMulti() : int
- executeUntil() : bool
- Runs the event loop until the given transfer has finished, so waiting on a promise does not wait for every other transfer on the handler like execute() does.
- failNestedWait() : bool
- Fails a synchronous wait attempted from inside a cURL callback, where native execution cannot progress until the callback returns.
- finishDeferredWork() : void
- Flushes cancels and attachments deferred while the multi handle was busy executing transfers or removing a handle.
- flushDeferredAdds() : void
- Attaches requests whose native attachment was deferred because they were created from inside a cURL callback.
- formatCurlMultiOption() : string
- hasConnectionCapOption() : bool
- hasRequest() : bool
- Checks that the request with the given handle ID is still pending and, when a wait token is given, has not been replaced by a request that reused the ID.
- isolateFromForeignActiveProxyTunnel() : void
- isolateProxyTunnelTransfer() : void
- markProxyTunnelActive() : void
- processMessages() : void
- proxyTunnelIsolationFailureMessage() : string
- rejectConnectionCapOptionConflicts() : void
- rejectMultiplexPipeliningConflict() : void
- The "multiplex" request option sets CURLOPT_PIPEWAIT, which libcurl ignores entirely when the multi handle's CURLMOPT_PIPELINING option disables multiplexing, so an explicit request for multiplexing on a handler configured against it is a configuration error. The required family conflicts marker-independently: a required guarantee on a handler that disables multiplexing is contradictory even when the transfer would not wait. A raw CURLOPT_PIPEWAIT cURL option conflicts with every explicit mode on this handler, where waiting is operationally meaningful: whatever its value, it is a second wait/eager authority applied after the mode's own decision.
- removeCompletedHandleFromMulti() : void
- removeHandleFromMulti() : void
- Removes a transfer from the multi handle under the native execution guard: removing a still-running transfer performs a final progress update that can run a user progress callback.
- secondsToNext() : float
- tickFor() : void
- Ticks the curl event loop, returning before the blocking select if the targeted transfer has settled, been canceled, or been replaced by a request that reused its native handle ID.
- tickInQueue() : void
- Runs \curl_multi_exec() inside the event loop, to prevent busy looping
- timeToNext() : int
- triggerConflictingCurlMultiOptionDeprecations() : void
- unmarkProxyTunnelActive() : void
- unmarkProxyTunnelActiveById() : void
Constants
CONNECTION_CAP_OPTIONS
private
mixed
CONNECTION_CAP_OPTIONS
= ['max_host_connections' => 'CURLMOPT_MAX_HOST_CONNECTIONS', 'max_total_connections' => 'CURLMOPT_MAX_TOTAL_CONNECTIONS']
KNOWN_CONSTRUCTOR_OPTIONS
private
mixed
KNOWN_CONSTRUCTOR_OPTIONS
= ['handle_factory' => true, 'max_host_connections' => true, 'max_total_connections' => true, 'multiplex' => true, 'options' => true, 'select_timeout' => true, 'transport_sharing' => true]
PROXY_TUNNEL_ISOLATION_OPTIONS
cURL options that isolate a transfer from foreign proxy tunnel connections. Failing to apply either one would fall open into credential-bearing connection reuse.
private
mixed
PROXY_TUNNEL_ISOLATION_OPTIONS
= ['CURLOPT_FRESH_CONNECT', 'CURLOPT_FORBID_REUSE']
Properties
$_mh
private
resource|CurlMultiHandle
$_mh
$active
private
int
$active
= 0
Will be higher than 0 when curl_multi_exec is still running.
$activeProxyTunnelHandles
private
array<int, string>
$activeProxyTunnelHandles
= []
Maps an attached handle id to its proxy tunnel signature.
$activeProxyTunnelSignatures
private
array<string, int>
$activeProxyTunnelSignatures
= []
Count of attached transfers per proxy tunnel signature.
$connectionCapsApplied
private
bool
$connectionCapsApplied
= false
Whether any connection cap constructor option was applied
$customHandleFactory
private
bool
$customHandleFactory
= false
Whether a custom "handle_factory" constructor option supplies the easy handles
$deferredAdds
private
array<int, object|null>
$deferredAdds
= []
Wait tokens of requests created from inside a cURL callback, keyed by handle id; native attachment is deferred until the outermost native execution unwinds.
$deferredCancels
private
array<int, EasyHandle, attached: bool}>
$deferredCancels
= []
$delays
private
array<int, float>
$delays
= []
An array of delay times, indexed by handle id in addRequest.
Tags
$factory
private
CurlFactoryInterface
$factory
$finishingDeferredWork
private
bool
$finishingDeferredWork
= false
Guards finishDeferredWork() against re-entry from the guarded native removals it performs while flushing.
$handles
private
array<string|int, mixed>
$handles
= []
Request entry handles, indexed by handle id in addRequest.
Tags
$messageProcessingDepth
private
int
$messageProcessingDepth
= 0
Depth of nested processMessages() calls. Guards against multi-handle recreation re-entrancy from processMessages (a retried transfer re-invokes the handler); a depth is tracked because a completion callback can re-enter tick().
$multiExecDepth
private
int
$multiExecDepth
= 0
Depth of nested guarded native operations (execution and handle removal, both of which can run user callbacks). A callback can re-enter tick(), and the nested frame must not clear the outer frame's guard; deferred work stays parked until the outermost frame unwinds.
$multiplexDisabled
private
bool
$multiplexDisabled
= false
Whether the "multiplex" constructor option disabled multiplexing on this handler's multi handle
$options
private
array<string|int, mixed>
$options
= []
An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
$proxyTunnelOwner
private
string|null
$proxyTunnelOwner
Owner signature of the proxy tunnels the multi handle's connection cache may hold
$requiredOptions
private
array<int, true>
$requiredOptions
= []
Native options derived from first-class constructor options; failing to apply one is an error rather than a compatibility warning.
$selectTimeout
private
int
$selectTimeout
$shareHandleState
private
CurlShareHandleState|null
$shareHandleState
Methods
__construct()
This handler accepts the following options:
public
__construct([array<string|int, mixed> $options = [] ]) : mixed
- handle_factory: An optional factory used to create curl handles
- transport_sharing: Optional transport sharing mode.
- select_timeout: Optional timeout (in seconds) to block before timing out while selecting curl handles. Defaults to 1 second.
- max_host_connections: Optional maximum concurrent connections per host.
- max_total_connections: Optional maximum concurrent connections overall.
- multiplex: Optional Multiplexing::NONE to disallow multiplexing on this handler's multi handle. The eager, wait, and required modes are request options, not handler options; Multiplexing::NONE is also conditionally accepted as a request option value.
- options: An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
Parameters
- $options : array<string|int, mixed> = []
__destruct()
public
__destruct() : mixed
__get()
public
__get(string $name) : resource|CurlMultiHandle
Parameters
- $name : string
Tags
Return values
resource|CurlMultiHandle__invoke()
public
__invoke(RequestInterface $request, array<string|int, mixed> $options) : PromiseInterface
Parameters
- $request : RequestInterface
- $options : array<string|int, mixed>
Return values
PromiseInterfaceexecute()
Runs until all outstanding connections have completed.
public
execute() : void
tick()
Ticks the curl event loop.
public
tick() : void
addConflictingCurlMultiOption()
private
static addConflictingCurlMultiOption(array<int, string> &$options, string $constant, string $replacement) : void
Parameters
- $options : array<int, string>
- $constant : string
- $replacement : string
addConnectionCapOptions()
private
addConnectionCapOptions(array<string|int, mixed> $options) : void
Parameters
- $options : array<string|int, mixed>
addCurlHandle()
private
addCurlHandle(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
addRequest()
private
addRequest(array<string|int, mixed> $entry) : void
Parameters
- $entry : array<string|int, mixed>
applyMultiplexNone()
A Multiplexing::NONE request option is a sole-use guarantee: the transfer must not share its connection with any concurrent transfer.
private
applyMultiplexNone(EasyHandle $easy, array<string|int, mixed> $options) : void
It holds structurally on a handler whose "multiplex" option is Multiplexing::NONE, and for HTTP/1.x transfers, which never join a multiplexed connection and open connections nothing can join. An HTTP/2 request on a handler that multiplexes is rejected, as is any configuration under which the guarantee cannot be verified (custom handle factories control the native handle) or cannot be hardened (challenge-response authentication retries and Expect 417 retries re-enter connection selection as internal follows, which disarm CURLOPT_FRESH_CONNECT). A raw CURLMOPT_PIPELINING multi option, and deprecated-but-applied raw cURL options that can defeat the declared protocol version, retry through internal follows, or replace the managed header list, are rejected by key presence. On runtimes whose matcher can hand an HTTP/1.x transfer an idle multiplexed connection (below libcurl 7.77.0, and 8.11.0-8.12.1), accepted transfers force a fresh connection.
Parameters
- $easy : EasyHandle
- $options : array<string|int, mixed>
applyProxyTunnelOwnership()
Isolates the connection cache when the request's proxy tunnel section differs from the one the multi handle's cache may already hold.
private
applyProxyTunnelOwnership(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
cancel()
Cancels a handle from sending and removes references to it.
private
cancel(int $id[, object|null $waitToken = null ]) : bool
Parameters
- $id : int
-
Handle ID to cancel and remove.
- $waitToken : object|null = null
-
Identity token that must still match the entry when given.
Return values
bool —True on success, false on failure.
cleanupCancelledHandle()
private
cleanupCancelledHandle(EasyHandle $easy, bool $attached) : void
Parameters
- $easy : EasyHandle
- $attached : bool
cleanupDeferredCancels()
private
cleanupDeferredCancels(Throwable|null &$failure) : void
Parameters
- $failure : Throwable|null
conflictingCurlMultiOptions()
private
static conflictingCurlMultiOptions() : array<int, string>
Return values
array<int, string>conflictingCurlMultiOptionSinceOverrides()
private
static conflictingCurlMultiOptionSinceOverrides() : array<int, string>
Return values
array<int, string>discardPendingRequest()
Rolls back a request that can no longer be attached, releasing the easy handle exactly once and preserving the original failure.
private
discardPendingRequest(int $id, Promise, wait_token?: object|null, attached?: bool} $entry, Throwable $failure) : Throwable
Parameters
- $id : int
- $entry : Promise, wait_token?: object|null, attached?: bool}
- $failure : Throwable
Return values
ThrowableeffectiveSelectTimeout()
Bounds a blocking select by the earliest pending request delay so a delayed transfer becoming due does not wait out an unrelated transfer's full select timeout.
private
effectiveSelectTimeout() : float|int
Return values
float|intexecuteMulti()
private
executeMulti() : int
Tags
Return values
intexecuteUntil()
Runs the event loop until the given transfer has finished, so waiting on a promise does not wait for every other transfer on the handler like execute() does.
private
executeUntil(int $id, object $waitToken) : bool
The native cURL handle ID can be reused by a request created from a completion callback, so the wait token guards against waiting on an unrelated transfer that inherited the ID.
Parameters
- $id : int
- $waitToken : object
Return values
bool —Whether another request had reused the native cURL handle ID by the time the loop stopped
failNestedWait()
Fails a synchronous wait attempted from inside a cURL callback, where native execution cannot progress until the callback returns.
private
failNestedWait(int $id, object $token) : bool
Parameters
- $id : int
- $token : object
Return values
bool —Whether another request had reused the native cURL handle ID, which only matters when no transfer was left to fail
finishDeferredWork()
Flushes cancels and attachments deferred while the multi handle was busy executing transfers or removing a handle.
private
finishDeferredWork() : void
flushDeferredAdds()
Attaches requests whose native attachment was deferred because they were created from inside a cURL callback.
private
flushDeferredAdds() : void
formatCurlMultiOption()
private
static formatCurlMultiOption(int|string $option) : string
Parameters
- $option : int|string
Return values
stringhasConnectionCapOption()
private
static hasConnectionCapOption(array<string|int, mixed> $options) : bool
Parameters
- $options : array<string|int, mixed>
Return values
boolhasRequest()
Checks that the request with the given handle ID is still pending and, when a wait token is given, has not been replaced by a request that reused the ID.
private
hasRequest(int $id[, object|null $waitToken = null ]) : bool
Parameters
- $id : int
- $waitToken : object|null = null
Return values
boolisolateFromForeignActiveProxyTunnel()
private
isolateFromForeignActiveProxyTunnel(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
isolateProxyTunnelTransfer()
private
isolateProxyTunnelTransfer(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
markProxyTunnelActive()
private
markProxyTunnelActive(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
processMessages()
private
processMessages() : void
proxyTunnelIsolationFailureMessage()
private
static proxyTunnelIsolationFailureMessage(string $name) : string
Parameters
- $name : string
Return values
stringrejectConnectionCapOptionConflicts()
private
static rejectConnectionCapOptionConflicts(array<string|int, mixed> $constructorOptions, array<string|int, mixed> $multiOptions) : void
Parameters
- $constructorOptions : array<string|int, mixed>
- $multiOptions : array<string|int, mixed>
rejectMultiplexPipeliningConflict()
The "multiplex" request option sets CURLOPT_PIPEWAIT, which libcurl ignores entirely when the multi handle's CURLMOPT_PIPELINING option disables multiplexing, so an explicit request for multiplexing on a handler configured against it is a configuration error. The required family conflicts marker-independently: a required guarantee on a handler that disables multiplexing is contradictory even when the transfer would not wait. A raw CURLOPT_PIPEWAIT cURL option conflicts with every explicit mode on this handler, where waiting is operationally meaningful: whatever its value, it is a second wait/eager authority applied after the mode's own decision.
private
rejectMultiplexPipeliningConflict(EasyHandle $easy, array<string|int, mixed> $options) : void
Parameters
- $easy : EasyHandle
- $options : array<string|int, mixed>
removeCompletedHandleFromMulti()
private
removeCompletedHandleFromMulti(int $id, resource|CurlHandle $handle) : void
Parameters
- $id : int
- $handle : resource|CurlHandle
removeHandleFromMulti()
Removes a transfer from the multi handle under the native execution guard: removing a still-running transfer performs a final progress update that can run a user progress callback.
private
removeHandleFromMulti(resource|CurlHandle $handle) : void
Parameters
- $handle : resource|CurlHandle
secondsToNext()
private
secondsToNext() : float
Return values
float —Seconds until the earliest pending delay is due
tickFor()
Ticks the curl event loop, returning before the blocking select if the targeted transfer has settled, been canceled, or been replaced by a request that reused its native handle ID.
private
tickFor(int|null $targetId, object|null $waitToken) : void
Parameters
- $targetId : int|null
- $waitToken : object|null
tickInQueue()
Runs \curl_multi_exec() inside the event loop, to prevent busy looping
private
tickInQueue() : void
timeToNext()
private
timeToNext() : int
Return values
inttriggerConflictingCurlMultiOptionDeprecations()
private
static triggerConflictingCurlMultiOptionDeprecations(array<string|int, mixed> $options) : void
Parameters
- $options : array<string|int, mixed>
unmarkProxyTunnelActive()
private
unmarkProxyTunnelActive(EasyHandle $easy) : void
Parameters
- $easy : EasyHandle
unmarkProxyTunnelActiveById()
private
unmarkProxyTunnelActiveById(int $id) : void
Parameters
- $id : int