Rework of sockets to mimic php-src sockets API #839
Conversation
7f485cd
to
417fdf2
Compare
@dktapps @tpunt please review this PR. Looks like Appveyor has problems with phpsdk_deps, don't know why. (Edit: https://twitter.com/nikita_ppv/status/965905195019526145) After this PR has been merged, more additions and tests has to come. PR #828, extension of get/setOptions(), importStream()/exportSocket() and getErrorMessage() are in preparation. Later on the IPV6 stuff. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've given it a quick review.
@@ -183,7 +183,7 @@ PHP_METHOD(Socket, setOption) { | |||
zend_long value = 0; | |||
|
|||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll", &level, &name, &value) != SUCCESS) { | |||
return; | |||
RETURN_FALSE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why you've changed this to return false
, but conventionally, when the ZPP API fails to parse the input, it will return null
(this null
return is usually ignored in the docs, since only the correct usage is documented). The socket_set_option
function does the same internally. This leads to the following output:
tpunt:~ tpunt$ php -nr 'var_dump(socket_set_option());'
Warning: socket_set_option() expects exactly 4 parameters, 0 given in Command line code on line 1
NULL
For parity with other internal functions, it would likely be best to keep these as returning null
, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
socket_set_option
has no explicit return type defined by ZEND_BEGIN_ARG_INFO_EX
, but Socket::setOption uses ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX
. Without return false
, this leads to the following output:
$socket = new Socket(\Socket::AF_INET, \Socket::SOCK_STREAM, \Socket::SOL_TCP); $socket->getOption();
Warning: Socket::getOption() expects exactly 2 parameters, 0 given in ...
Fatal error: Return value of Socket::getOption() must be of the type integer, null returned in Unknown on line 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I do see the problem. This is also why the return type information macros are not particularly pervasive in php-src. I'm not so concerned what solution is chosen here (current solution looks fine).
classes/socket.h
Outdated
zend_long usec = 0; | ||
|
||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/!a/!a/!|llz/", &read, &write, &except, &sec, &usec, &errorno) != SUCCESS) { | ||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/!a/!a/!z/!|lz/", &read, &write, &except, &sec, &usec, &errorno) != SUCCESS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separation of the sec
parameter zval is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sec
needs to be null
able. Is that possible with zend_long
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the parameter isn't being typed, then it is perfectly valid to pass in null
to it. Separation is only needed for by-reference passing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to remove the separation. What would you recommend, keeping sec
as zval or moving back to zend_long
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I messed up something. zend_long
is just an int32_t, so it won't be possible to handle null with it, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct. The exclamation mark will enable for it to be nullable, but you won't be able to differentiate between passing null
or 0
to the parameter. So it must be kept as a zval.
src/socket.c
Outdated
PTHREADS_HANDLE_SOCKET_ERROR(eno); \ | ||
#define PTHREADS_SOCKET_ERROR(socket, msg, eno) do { \ | ||
(socket)->error = (eno); \ | ||
PTHREADS_HANDLE_SOCKET_ERROR((eno), (msg)); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapping these arguments in parentheses isn't actually necessary (since they are being expanded inside of another macro, which should be wrapping them anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'm not familar with arguments in macros. Will change that.
src/socket.c
Outdated
@@ -125,12 +123,12 @@ void pthreads_socket_set_option(zval *object, zend_long level, zend_long name, z | |||
void pthreads_socket_get_option(zval *object, zend_long level, zend_long name, zval *return_value) { | |||
pthreads_object_t *threaded = | |||
PTHREADS_FETCH_FROM(Z_OBJ_P(object)); | |||
socklen_t unused; | |||
socklen_t unused = sizeof(int); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably not needed, but if we are going to initialise this value, then it should be sizeof(zend_long)
, since that's the size used by an integer zval.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If unused
is not initialized, Socket::getOption()
returns always 0
for the first call. int
was copied from php-src, will change it to zend_long
.
src/socket.c
Outdated
@@ -235,6 +244,7 @@ void pthreads_socket_bind(zval *object, zend_string *address, zend_long port, zv | |||
PTHREADS_FETCH_FROM(Z_OBJ_P(object)); | |||
php_sockaddr_storage sa_storage = {0}; | |||
struct sockaddr *sock_type = (struct sockaddr*) &sa_storage; | |||
zend_long retval = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation (not that it was particularly consistent before this PR)
case AF_INET: { | ||
struct sockaddr_in *sa = (struct sockaddr_in *) sock_type; | ||
|
||
sa->sin_family = AF_INET; | ||
sa->sin_port = htons((unsigned short) port); | ||
|
||
if (!pthreads_socket_set_inet_addr(threaded->store.sock, sa, address)) { | ||
/* throw */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason why we do not throw here? Same as below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The corresponding exception will be thrown in pthreads_socket_set_inet_addr()
. We just have to return
at this point. Same for pthreads_socket_set_inet6_addr()
.
src/socket.c
Outdated
default: | ||
return; | ||
/* throw */ | ||
if (retval != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: comparing to SUCCESS
would be semantically clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be clearer
src/socket.c
Outdated
/* overflow check */ | ||
if ((length + 1) < 2) { | ||
RETURN_FALSE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zend_long
is a signed type, and overflow on signed types is undefined behaviour according to the C standard. PHP has a ZEND_LONG_MAX
macro that could be used instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the term overflow check
is not right here. I've copied this snippet from php-src and think it is used to ensure length
is not negativ and at least 1
. Because length <= 0
would not make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds correct - perhaps the comment should be removed and the condition updated to length < 1
.
if (sec || usec) { | ||
|
||
/* If seconds is not set to null, build the timeval, else we wait indefinitely */ | ||
if (sec != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell, sec
can never be NULL
, since it is not an optional parameter (the same check in php-src looks superfluous).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sec
needs to be null
able. The following applies to Socket::select()
and socket_select()
sec
< 0 - Invalid argumentsec
= 0 - returns immediatelysec
> 0 - waits definitelysec
= null - waits indefinitely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I see. I didn't realise sec
would be NULL
(C null) if a PHP null
was passed in to it (I assumed it would have been Z_TYPE_P(sec) == IS_NULL
). My mistake.
src/socket.c
Outdated
tv_p = &tv; | ||
|
||
if (sec == &tmp) { | ||
zval_dtor(&tmp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Longs are not reference counted, and so there's no need to destroy them (the same operation in php-src looks superfluous - probably outdated code from PHP 5, when every type was reference counted).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know. I've removed the zval_dtor
call.
@tpunt thanks for your review so far, any further notes? |
src/socket.c
Outdated
} break; | ||
#endif | ||
} | ||
|
||
if (retval != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: retval != SUCCESS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've missed this line, sorry!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sirsnyder This looks fine to me now. Feel free to squash and merge it.
e9b197f
to
26eace5
Compare
|
||
public function accept($class = self::class){} | ||
|
||
public function connect(string $host, int $port = 0) : bool{} | ||
|
||
public static function select(array &$read, array &$write, array &$except, int $sec = 0, int $usec = 0, int &$error = null){} | ||
public static function select(array &$read, array &$write, array &$except, int $sec, int $usec = 0, int &$error = null){} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be ?int $sec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, do you see any other issues?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing off the hop, this is the only thing I noticed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm experiencing various test failures on Windows as noted in the comments.
int(0) | ||
int(0) | ||
bool(false) | ||
int(22) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test does not work as expected on Windows (I'm getting all zeros, no errors) - not sure if this is a Windows-specific bug or what, haven't investigated in-depth yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On win32, a negative timeout seems to be handled like a zero timeout, without any error.
} catch(Throwable $throwable) { | ||
var_dump($throwable->getMessage()); | ||
} | ||
var_dump($socket->getOption(SOL_SOCKET, SO_REUSEADDR)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of global ext/socket constants causes this test to fail without ext/sockets
string(%d) "Argument 1 passed to Socket::__construct() must be of the type integer, array given" | ||
string(%d) "Unable to create socket (%d): Address family not supported by protocol" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message is different on Windows:
005+ string(96) "Unable to create socket (10047): An address incompatible with the requested protocol was used.
005- string(%d) "Unable to create socket (%d): Address family not supported by protocol"
006+ "
Note the trailing newline as well (as seen before with Windows sockets).
var_dump($throwable->getMessage()); | ||
} | ||
var_dump($socket->setOption(SOL_SOCKET, SO_REUSEADDR, 1)); | ||
var_dump($socket->getOption(SOL_SOCKET, SO_REUSEADDR)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same again here - use of global ext/sockets constants causes this to fail without ext/sockets
if (file_exists($address)) | ||
die('Temporary file socket already exists.'); | ||
|
||
$unixSocket = new \Socket(\Socket::AF_UNIX,\Socket::SOCK_STREAM, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails on Windows (no UNIX sockets).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to split the test, fixed.
I will start a second branch for fixes |
PTHREADS_SOCKET_ERROR
andPTHREADS_HANDLE_SOCKET_ERROR
extended bymsg
andeno
for detailed exceptionssec
of Socket::select() is mandatory. Valid values are0
,> 0
andnull
.port
of Socket::bind() is optional, default value is0
.backlog
of Socket::listen() ist optional, default value is0
.