Documentation

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
final

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

$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
see
CurlMultiHandler::addRequest

$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
see
CurlMultiHandler::addRequest

$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.

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> = []

__get()

public __get(string $name) : resource|CurlMultiHandle
Parameters
$name : string
Tags
throws
BadMethodCallException

when another field as _mh will be gotten

RuntimeException

when curl can not initialize a multi handle

InvalidArgumentException

when a required cURL multi option cannot be applied

Return values
resource|CurlMultiHandle

execute()

Runs until all outstanding connections have completed.

public execute() : 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
Throwable

effectiveSelectTimeout()

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|int

executeMulti()

private executeMulti() : int
Tags
phpstan-impure
Return values
int

executeUntil()

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
string

hasConnectionCapOption()

private static hasConnectionCapOption(array<string|int, mixed> $options) : bool
Parameters
$options : array<string|int, mixed>
Return values
bool

hasRequest()

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
bool

isolateFromForeignActiveProxyTunnel()

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

proxyTunnelIsolationFailureMessage()

private static proxyTunnelIsolationFailureMessage(string $name) : string
Parameters
$name : string
Return values
string

rejectConnectionCapOptionConflicts()

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

triggerConflictingCurlMultiOptionDeprecations()

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
On this page

Search results