Skip to content
This repository has been archived by the owner on Dec 16, 2019. It is now read-only.

Rework of sockets to mimic php-src sockets API #839

Merged
merged 2 commits into from Feb 22, 2018
Merged

Conversation

sirsnyder
Copy link
Collaborator

@sirsnyder sirsnyder commented Feb 19, 2018

  • Macros PTHREADS_SOCKET_ERROR and PTHREADS_HANDLE_SOCKET_ERROR extended by msg and eno for detailed exceptions
  • Extended error handling
  • Bug fix in Socket::getOption()
  • Param sec of Socket::select() is mandatory. Valid values are 0, > 0 and null.
  • Param port of Socket::bind() is optional, default value is 0.
  • Param backlog of Socket::listen() ist optional, default value is 0.
  • Added serveral tests

@sirsnyder sirsnyder changed the title WIP: Rework of sockets to mimic php-src sockets API Rework of sockets to mimic php-src sockets API Feb 20, 2018
@sirsnyder sirsnyder requested a review from tpunt February 20, 2018 11:20
@sirsnyder
Copy link
Collaborator Author

sirsnyder commented Feb 20, 2018

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

Copy link
Collaborator

@tpunt tpunt left a 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;
Copy link
Collaborator

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.

Copy link
Collaborator Author

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

Copy link
Collaborator

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) {
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sec needs to be nullable. Is that possible with zend_long ?

Copy link
Collaborator

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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?

Copy link
Collaborator

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)); \
Copy link
Collaborator

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

Copy link
Collaborator Author

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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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;
Copy link
Collaborator

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 */
Copy link
Collaborator

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.

Copy link
Collaborator Author

@sirsnyder sirsnyder Feb 20, 2018

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) {
Copy link
Collaborator

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

Copy link
Collaborator Author

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;
}
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 <= 0would not make sense.

Copy link
Collaborator

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) {
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sec needs to be nullable. The following applies to Socket::select() and socket_select()

  • sec < 0 - Invalid argument
  • sec = 0 - returns immediately
  • sec > 0 - waits definitely
  • sec = null - waits indefinitely

Copy link
Collaborator

@tpunt tpunt Feb 20, 2018

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);
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

@sirsnyder
Copy link
Collaborator Author

@tpunt thanks for your review so far, any further notes?

src/socket.c Outdated
} break;
#endif
}

if (retval != 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: retval != SUCCESS

Copy link
Collaborator Author

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!

Copy link
Collaborator

@tpunt tpunt left a 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.

@sirsnyder sirsnyder merged commit 7c4a62d into master Feb 22, 2018
@sirsnyder sirsnyder deleted the rework_sockets branch February 22, 2018 15:56

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){}
Copy link
Contributor

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

Copy link
Collaborator Author

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?

Copy link
Contributor

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.

Copy link
Contributor

@dktapps dktapps left a 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)
Copy link
Contributor

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.

Copy link
Collaborator Author

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));
Copy link
Contributor

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"
Copy link
Contributor

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));
Copy link
Contributor

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);
Copy link
Contributor

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

Copy link
Collaborator Author

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.

@sirsnyder
Copy link
Collaborator Author

I will start a second branch for fixes

@sirsnyder
Copy link
Collaborator Author

Thanks @tpunt and @dktapps for your support!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants