Buy commercial curl support. We
help you work out your issues, debug your libcurl applications, use the API,
port to new platforms, add new features and more. With a team lead by the
curl founder Daniel himself.
[CurlOne] Architecture Review
- Contemporary messages sorted: [ by date ] [ by thread ] [ by subject ] [ by author ] [ by messages with attachments ]
From: Michael via curl-and-php <curl-and-php_at_lists.haxx.se>
Date: Sun, 2 Aug 2026 06:14:34 +0500
The core objective of this design is to encapsulate queue management and
loop mechanics within the C layer, avoiding the need to manage loops or
handle-polling in PHP userland.
The engine centers around a single global multi-handle initialized at
extension startup. Individual transfers are managed via instances of the
internal C structure, which encapsulates the underlying libcurl easy
handle, option configurations, and completion callbacks.
--- CORE LIFECYCLE & MUTABILITY LOCKS ---
Upon CurlOne object instantiation, the extension initializes a libcurl easy
handle, sets default parameters, attaches an internal write memory
callback, and binds a direct pointer of the internal structure to the
handle using the private data option.
When execution is initiated from userland, the engine verifies that the
target handle is idle, applies an active state lock to prevent option
mutations or duplicate execution calls mid-flight, and registers the easy
handle with the global multi-handle. The engine retains internal references
to both the request object and its completion closure, ensuring active
transfers are protected from premature garbage collection while floating in
the loop.
--- GLOBAL TICKER & EVENT LOOP MECHANICS ---
Transfers are driven by a single global execution ticker function
(curl_execute) designed to be invoked periodically from userland. The
execution loop operates across three key phases:
1. CPU Yielding: When no handles are pending, the engine invokes libcurl's
multi-wait function. Compared to multi-poll which introduces extra flag
evaluation overhead, multi-wait provides a thinner abstraction.
2. Transfer Processing: Non-blocking socket I/O is driven across all active
handles using the libcurl multi-perform API.
3. Completion Queue Drainage: The engine reads completion messages, unlinks
finished handles, releases state locks, and executes registered callbacks.
To interface with userland async primitives, the execution ticker returns
explicit status codes:
* Return code -1 indicates a fatal engine or multiplexer error.
* Return code 0 signals that transfers remain active.
* Return code 1 indicates that all queued transfers have fully completed.
--- ZERO-LOOKUP EVENT ROUTING ---
A primary advantage of this architecture over the legacy ext/curl
implementation is the total elimination of event-routing lookup churn.
In legacy ext/curl, handling completed transfers requires double-lookup
overhead. Polling surfaces completion messages through multi-info-read API,
which constructs associative PHP arrays and traverses internal C-level
linked lists to expose the raw handle to userland. Userland code must then
perform a second lookup pass—typically across a PHP array or registry—to
map that handle back to its surrounding asynchronous wrapper object.
The new design bypasses both search phases. Because a direct structure
pointer is bound to the easy handle at setup using the private data option,
the execution ticker extracts the original instance pointer in O(1)
constant time upon completion. The engine immediately retrieves the stored
closure, resets state flags, and invokes the callback directly. Event
routing search passes are reduced from two down to zero.
--- FEATURE BORROWING & DATA HANDLING ---
Rather than re-implementing payload parsing or utility functions in custom
C code, the extension delegates heavy lifting directly to libcurl APIs:
- Unified Form Data via CurlOneFile: Legacy PHP ext/curl separates file
payloads into distinct CURLFile and CURLStringFile userland objects. This
extension unifies both paradigms into a single CurlOneFile helper.
Multipart forms set up through setFormData() construct native curl_mime
structure trees. Depending on whether CurlOneFile references a persistent
file path or an in-memory buffer, the C layer delegates streaming and
cleanup directly to curl_mime_filedata() or curl_mime_data(), allowing
libcurl to manage file lifetime, disk I/O, filename tagging, and MIME
headers natively.
- URL Query Assembly: Instead of constructing URL-encoded POST strings or
query parameters manually, setUrlData() offloads string encoding, delimiter
formatting, and key-value concatenation entirely to libcurl's URL API via
curl_url() and curl_url_set(). Passing CURLU_APPENDQUERY and
CURLU_URLENCODE flags offloads parameter escaping and memory allocation
directly to C-level libcurl primitives before binding the output to
CURLOPT_POSTFIELDS.
- Zero-Copy Request Payload Management: Raw string payloads set via
setRawData() increment the internal reference count of the PHP string and
pass byte pointers directly to CURLOPT_POSTFIELDS. Binary blob options
utilize CURL_BLOB_NOCOPY to anchor userland buffers directly without
duplicating heap memory.
- Header Parsing API: Response header extraction via getHeaders() bypasses
legacy manual header parsing callbacks. It utilizes libcurl's header API
(curl_easy_nextheader), allowing targeted extraction across request origins
(such as intermediate 1xx responses, CONNECT proxy headers, or trailers)
directly from C memory structures.
--- MEMORY ARCHITECTURE & RESPONSE HANDLING ---
Response payload handling is deferred to optimize throughput. Incoming data
frames are appended to a linked list of heap-allocated memory chunks
(curlDataChunk_t) during transfer. Allocation of a single contiguous PHP
string occurs only when userland explicitly requests the body via
getData(), at which point the engine flattens the chunk list into the final
string and frees the backing nodes.
Overall, this refactoring yields a significantly leaner codebase and
smaller binary footprint while dramatically reducing language-boundary
overhead during event dispatching.
Date: Sun, 2 Aug 2026 06:14:34 +0500
The core objective of this design is to encapsulate queue management and
loop mechanics within the C layer, avoiding the need to manage loops or
handle-polling in PHP userland.
The engine centers around a single global multi-handle initialized at
extension startup. Individual transfers are managed via instances of the
internal C structure, which encapsulates the underlying libcurl easy
handle, option configurations, and completion callbacks.
--- CORE LIFECYCLE & MUTABILITY LOCKS ---
Upon CurlOne object instantiation, the extension initializes a libcurl easy
handle, sets default parameters, attaches an internal write memory
callback, and binds a direct pointer of the internal structure to the
handle using the private data option.
When execution is initiated from userland, the engine verifies that the
target handle is idle, applies an active state lock to prevent option
mutations or duplicate execution calls mid-flight, and registers the easy
handle with the global multi-handle. The engine retains internal references
to both the request object and its completion closure, ensuring active
transfers are protected from premature garbage collection while floating in
the loop.
--- GLOBAL TICKER & EVENT LOOP MECHANICS ---
Transfers are driven by a single global execution ticker function
(curl_execute) designed to be invoked periodically from userland. The
execution loop operates across three key phases:
1. CPU Yielding: When no handles are pending, the engine invokes libcurl's
multi-wait function. Compared to multi-poll which introduces extra flag
evaluation overhead, multi-wait provides a thinner abstraction.
2. Transfer Processing: Non-blocking socket I/O is driven across all active
handles using the libcurl multi-perform API.
3. Completion Queue Drainage: The engine reads completion messages, unlinks
finished handles, releases state locks, and executes registered callbacks.
To interface with userland async primitives, the execution ticker returns
explicit status codes:
* Return code -1 indicates a fatal engine or multiplexer error.
* Return code 0 signals that transfers remain active.
* Return code 1 indicates that all queued transfers have fully completed.
--- ZERO-LOOKUP EVENT ROUTING ---
A primary advantage of this architecture over the legacy ext/curl
implementation is the total elimination of event-routing lookup churn.
In legacy ext/curl, handling completed transfers requires double-lookup
overhead. Polling surfaces completion messages through multi-info-read API,
which constructs associative PHP arrays and traverses internal C-level
linked lists to expose the raw handle to userland. Userland code must then
perform a second lookup pass—typically across a PHP array or registry—to
map that handle back to its surrounding asynchronous wrapper object.
The new design bypasses both search phases. Because a direct structure
pointer is bound to the easy handle at setup using the private data option,
the execution ticker extracts the original instance pointer in O(1)
constant time upon completion. The engine immediately retrieves the stored
closure, resets state flags, and invokes the callback directly. Event
routing search passes are reduced from two down to zero.
--- FEATURE BORROWING & DATA HANDLING ---
Rather than re-implementing payload parsing or utility functions in custom
C code, the extension delegates heavy lifting directly to libcurl APIs:
- Unified Form Data via CurlOneFile: Legacy PHP ext/curl separates file
payloads into distinct CURLFile and CURLStringFile userland objects. This
extension unifies both paradigms into a single CurlOneFile helper.
Multipart forms set up through setFormData() construct native curl_mime
structure trees. Depending on whether CurlOneFile references a persistent
file path or an in-memory buffer, the C layer delegates streaming and
cleanup directly to curl_mime_filedata() or curl_mime_data(), allowing
libcurl to manage file lifetime, disk I/O, filename tagging, and MIME
headers natively.
- URL Query Assembly: Instead of constructing URL-encoded POST strings or
query parameters manually, setUrlData() offloads string encoding, delimiter
formatting, and key-value concatenation entirely to libcurl's URL API via
curl_url() and curl_url_set(). Passing CURLU_APPENDQUERY and
CURLU_URLENCODE flags offloads parameter escaping and memory allocation
directly to C-level libcurl primitives before binding the output to
CURLOPT_POSTFIELDS.
- Zero-Copy Request Payload Management: Raw string payloads set via
setRawData() increment the internal reference count of the PHP string and
pass byte pointers directly to CURLOPT_POSTFIELDS. Binary blob options
utilize CURL_BLOB_NOCOPY to anchor userland buffers directly without
duplicating heap memory.
- Header Parsing API: Response header extraction via getHeaders() bypasses
legacy manual header parsing callbacks. It utilizes libcurl's header API
(curl_easy_nextheader), allowing targeted extraction across request origins
(such as intermediate 1xx responses, CONNECT proxy headers, or trailers)
directly from C memory structures.
--- MEMORY ARCHITECTURE & RESPONSE HANDLING ---
Response payload handling is deferred to optimize throughput. Incoming data
frames are appended to a linked list of heap-allocated memory chunks
(curlDataChunk_t) during transfer. Allocation of a single contiguous PHP
string occurs only when userland explicitly requests the body via
getData(), at which point the engine flattens the chunk list into the final
string and frees the backing nodes.
Overall, this refactoring yields a significantly leaner codebase and
smaller binary footprint while dramatically reducing language-boundary
overhead during event dispatching.
-- curl-and-php mailing list curl-and-php_at_lists.haxx.se https://lists.haxx.se/mailman/listinfo/curl-and-phpReceived on 2026-08-02