Skip to content

feat: enable persistent CurlShareHandle objects #15603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/curl/curl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -3743,7 +3743,7 @@ function curl_share_close(CurlShareHandle $share_handle): void {}
function curl_share_errno(CurlShareHandle $share_handle): int {}

/** @refcount 1 */
function curl_share_init(): CurlShareHandle {}
function curl_share_init(array $share_options = [], ?string $persistent_id = null): CurlShareHandle {}

function curl_share_setopt(CurlShareHandle $share_handle, int $option, mixed $value): bool {}

Expand Down
4 changes: 3 additions & 1 deletion ext/curl/curl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion ext/curl/curl_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,19 @@
#define SAVE_CURL_ERROR(__handle, __err) \
do { (__handle)->err.no = (int) __err; } while (0)

ZEND_BEGIN_MODULE_GLOBALS(curl)
HashTable persistent_share_handles;
ZEND_END_MODULE_GLOBALS(curl)

ZEND_EXTERN_MODULE_GLOBALS(curl)

#define CURL_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(curl, v)

PHP_MINIT_FUNCTION(curl);
PHP_MSHUTDOWN_FUNCTION(curl);
PHP_MINFO_FUNCTION(curl);
PHP_GINIT_FUNCTION(curl);
PHP_GSHUTDOWN_FUNCTION(curl);

typedef struct {
zend_fcall_info_cache fcc;
Expand Down Expand Up @@ -125,10 +135,13 @@ typedef struct {
} php_curlm;

typedef struct _php_curlsh {
CURLSH *share;
CURLSH *share;
zend_string *persistent_id;

struct {
int no;
} err;

zend_object std;
} php_curlsh;

Expand All @@ -152,7 +165,9 @@ static inline php_curlsh *curl_share_from_obj(zend_object *obj) {
#define Z_CURL_SHARE_P(zv) curl_share_from_obj(Z_OBJ_P(zv))

void curl_multi_register_handlers(void);
void curl_share_free_persistent(zval *data);
void curl_share_register_handlers(void);
void curl_share_free_obj(zend_object *object);
void curlfile_register_class(void);
zend_result curl_cast_object(zend_object *obj, zval *result, int type);

Expand Down
23 changes: 20 additions & 3 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
#include "ext/standard/url.h"
#include "curl_private.h"

ZEND_DECLARE_MODULE_GLOBALS(curl)

#ifdef __GNUC__
/* don't complain about deprecated CURLOPT_* we're exposing to PHP; we
need to keep using those to avoid breaking PHP API compatibility */
Expand Down Expand Up @@ -215,7 +217,11 @@ zend_module_entry curl_module_entry = {
NULL,
PHP_MINFO(curl),
PHP_CURL_VERSION,
STANDARD_MODULE_PROPERTIES
PHP_MODULE_GLOBALS(curl),
PHP_GINIT(curl),
PHP_GSHUTDOWN(curl),
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */

Expand Down Expand Up @@ -416,6 +422,17 @@ PHP_MINIT_FUNCTION(curl)
}
/* }}} */

PHP_GINIT_FUNCTION(curl)
{
zend_hash_init(&curl_globals->persistent_share_handles, 0, NULL, curl_share_free_persistent, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will need some discussion in the RFC whether we want to stop using the EG(persistent_list).

GC_MAKE_PERSISTENT_LOCAL(&curl_globals->persistent_share_handles);
}

PHP_GSHUTDOWN_FUNCTION(curl)
{
zend_hash_destroy(&curl_globals->persistent_share_handles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it would make sense to explicitly close persistent shares with curl_share_close? It's a no-op for regular shares, but it may be useful for persistent ones?

}

/* CurlHandle class */

static zend_object *curl_create_object(zend_class_entry *class_type) {
Expand Down Expand Up @@ -728,8 +745,8 @@ static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_
// gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid
// zend_call_known_fcc() with an uninitialized FCC.
if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
return rval;
}
return rval;
}

#if PHP_CURL_DEBUG
fprintf(stderr, "curl_prereqfunction() called\n");
Expand Down
128 changes: 112 additions & 16 deletions ext/curl/share.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,126 @@
#endif

#include "php.h"
#include "Zend/zend_exceptions.h"

#include "curl_private.h"

#include <curl/curl.h>

#define SAVE_CURLSH_ERROR(__handle, __err) (__handle)->err.no = (int) __err;

/* {{{ Initialize a share curl handle */
/**
* Free a persistent curl share handle.
*/
void curl_share_free_persistent(zval *data)
{
CURLSH *handle = Z_PTR_P(data);

if (!handle) {
return;
}

curl_share_cleanup(handle);
}

/**
* Initialize a share curl handle, optionally with share options and a persistent ID.
*/
PHP_FUNCTION(curl_share_init)
{
zval *share_opts = NULL, *entry = NULL;
zend_string *persistent_id = NULL;

php_curlsh *sh;

ZEND_PARSE_PARAMETERS_NONE();
CURLSHcode error;

ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY(share_opts)
Z_PARAM_STR_OR_NULL(persistent_id)
ZEND_PARSE_PARAMETERS_END();

object_init_ex(return_value, curl_share_ce);
sh = Z_CURL_SHARE_P(return_value);

if (persistent_id) {
zval *persisted = zend_hash_find(&CURL_G(persistent_share_handles), persistent_id);

if (persisted) {
sh->share = Z_PTR_P(persisted);
sh->persistent_id = zend_string_copy(persistent_id);

return;
}
}

// The user did not provide a persistent share ID, or we could not find an existing share handle, so we'll have to
// create one.
sh->share = curl_share_init();

if (share_opts) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) {
ZVAL_DEREF(entry);

error = curl_share_setopt(sh->share, CURLSHOPT_SHARE, zval_get_long_ex(entry, true));

if (error != CURLSHE_OK) {
zend_throw_exception_ex(
NULL,
0,
"Could not construct cURL share handle: %s",
curl_share_strerror(error)
);

goto error;
}
} ZEND_HASH_FOREACH_END();
}

if (persistent_id) {
// It's important not to mark this as persistent until *after* we've successfully set all of the share options,
// as otherwise the curl_share_free_obj in the error handler won't free the CURLSH.
sh->persistent_id = zend_string_copy(persistent_id);

// We use the zend_hash_str form here, since we want to ensure it allocates a new persistent string, instead of
// using the request-bound persistent_id.
zend_hash_str_add_new_ptr(
&CURL_G(persistent_share_handles),
ZSTR_VAL(persistent_id),
ZSTR_LEN(persistent_id),
sh->share
);
}

return;

error:
zval_ptr_dtor(return_value);

RETURN_THROWS();
}
/* }}} */

/* {{{ Close a set of cURL handles */
/**
* Close a persistent curl share handle. NOP for non-persistent share handles.
*/
PHP_FUNCTION(curl_share_close)
{
zval *z_sh;

php_curlsh *sh;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_OBJECT_OF_CLASS(z_sh, curl_share_ce)
ZEND_PARSE_PARAMETERS_END();

sh = Z_CURL_SHARE_P(z_sh);

if (sh->persistent_id) {
// Deleting the share from the hash table will trigger curl_share_free_persistent, so no need to call it here.
zend_hash_del(&CURL_G(persistent_share_handles), sh->persistent_id);
}
}
/* }}} */

static bool _php_curl_share_setopt(php_curlsh *sh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */
{
Expand All @@ -73,13 +162,14 @@ static bool _php_curl_share_setopt(php_curlsh *sh, zend_long option, zval *zvalu

return error == CURLSHE_OK;
}
/* }}} */

/* {{{ Set an option for a cURL transfer */
/**
* Set an option for a cURL transfer.
*/
PHP_FUNCTION(curl_share_setopt)
{
zval *z_sh, *zvalue;
zend_long options;
zend_long options;
php_curlsh *sh;

ZEND_PARSE_PARAMETERS_START(3,3)
Expand All @@ -96,13 +186,14 @@ PHP_FUNCTION(curl_share_setopt)
RETURN_FALSE;
}
}
/* }}} */

/* {{{ Return an integer containing the last share curl error number */
/**
* Return an integer containing the last share curl error number.
*/
PHP_FUNCTION(curl_share_errno)
{
zval *z_sh;
php_curlsh *sh;
zval *z_sh;
php_curlsh *sh;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_OBJECT_OF_CLASS(z_sh, curl_share_ce)
Expand All @@ -112,10 +203,11 @@ PHP_FUNCTION(curl_share_errno)

RETURN_LONG(sh->err.no);
}
/* }}} */


/* {{{ return string describing error code */
/**
* Return a string describing the error code.
*/
PHP_FUNCTION(curl_share_strerror)
{
zend_long code;
Expand All @@ -132,7 +224,6 @@ PHP_FUNCTION(curl_share_strerror)
RETURN_NULL();
}
}
/* }}} */

/* CurlShareHandle class */

Expand All @@ -154,7 +245,12 @@ void curl_share_free_obj(zend_object *object)
{
php_curlsh *sh = curl_share_from_obj(object);

curl_share_cleanup(sh->share);
if (!sh->persistent_id) {
curl_share_cleanup(sh->share);
} else {
zend_string_release(sh->persistent_id);
}

zend_object_std_dtor(&sh->std);
}

Expand Down
44 changes: 44 additions & 0 deletions ext/curl/tests/curl_share_persistent.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
Basic curl_share test
--EXTENSIONS--
curl
--SKIPIF--
<?php
include 'skipif-nocaddy.inc';
?>
--FILE--
<?php

function get_persistent_share_handle(): CurlShareHandle {
return curl_share_init(
[
CURL_LOCK_DATA_CONNECT,
],
"persistent-test",
);
}

$sh1 = get_persistent_share_handle();
$ch1 = curl_init("https://localhost");

curl_setopt($ch1, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch1, CURLOPT_SHARE, $sh1);

$sh2 = get_persistent_share_handle();
$ch2 = curl_init("https://localhost");

curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch2, CURLOPT_SHARE, $sh2);

var_dump(curl_exec($ch1));
var_dump(curl_exec($ch2));

var_dump("second connect_time: " . (curl_getinfo($ch2)["connect_time"]));

?>
--EXPECT--
string(23) "Caddy is up and running"
string(23) "Caddy is up and running"
string(22) "second connect_time: 0"
1 change: 1 addition & 0 deletions ext/curl/tests/skipif-nocaddy.inc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

$ch = curl_init("https://localhost");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$body = curl_exec($ch);

Expand Down