peachpiecompiler / peachpie

PeachPie - the PHP compiler and runtime for .NET and .NET Core
https://www.peachpie.io
Apache License 2.0
2.33k stars 202 forks source link

`stream_socket_accept()` `timeout` argument does not behave #989

Closed Arkantium closed 2 years ago

Arkantium commented 2 years ago

Hi !

It's again me with socket problem :)

$clientSocket = @stream_socket_accept($this->serverSocket, 0))

I think u have a bug on 0 but it's of with 0.1 (float) or 1.

But, GG !!! I have no memory issue (for now) with the last version of ur lib ;)

jakubmisek commented 2 years ago

thanks!

so $timeout: 0 should use the default socket accept timeout?

Arkantium commented 2 years ago

$timeout: at 0 never accept connection.

jakubmisek commented 2 years ago

I've tried to find more information in php's source code; so with $timeout: 0, it always returns FALSE and a warning that the operation timed out? or if the socked was accepted immediately, the stream_socket_accept accepts it?

Arkantium commented 2 years ago

Hi,

Even when I have an incoming connection with timeout at 0 it returns false so I never have new sockets connexion but with a timeout of 1 it detects new connections well.

[CODE]

while (true)
if (stream_socket_accept($server, 1))
echo "NEW SOCKET" . PHP_EOL;

[RESULT]

NEW SOCKET
NEW SOCKET
NEW SOCKET

[CODE]

while (true)
if (stream_socket_accept($server, 0))
echo "NEW SOCKET" . PHP_EOL;

[RESULT](no result)


I thinks u have to modify the stream_socket_accept:

if (result.AsyncWaitHandle.WaitOne(timeout < 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(timeout)))

By that

if (result.AsyncWaitHandle.WaitOne(timeout < 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(timeout), true))

Or that ?

if (result.AsyncWaitHandle.WaitOne(timeout < 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(timeout), false))
jakubmisek commented 2 years ago

thanks, I still don't understand how should be the $timeout: 0 handled tho? In php's source code, there is not much about it.

0 just seems to not wait and pick the socket immediately if it's available. It means, using web sockets, in most cases this causes the function just return false without waiting.

Arkantium commented 2 years ago

Yeah it's that no wait directly pick

jakubmisek commented 2 years ago

hm, but it seems that's how we have it implemented in PeachPie; do you have a test case where it's not working as expected?

jakubmisek commented 2 years ago

@Arkantium so there were a few other issues with this function. Now it should behave properly for timeout 0 and > 0.

Previously, when timeout was > 0 and the operation timeouted, the next socket was discarded (it was consumed by the previous async operation which was not properly closed)

thanks!

avriltank commented 2 years ago

when set to 0,should return the accepted socket or false!there is no need to walk in a for block!

                if (timeoutVal < 0)
                {
                    serverSocket.Blocking = true;
                    acceptedSocket = serverSocket.Accept();
                }
               //fix block mode when timeout is set to 0
                else if(timeoutVal.Equals(0))
                {
                    serverSocket.Blocking = false;
                    try
                    {
                        // pick first connection without waiting
                        // throws if there is no Socket avail.
                        acceptedSocket = serverSocket.Accept();
                    }
                    catch (SocketException socEx) when (socEx.SocketErrorCode == SocketError.WouldBlock)
                    {
                        // ignore and try again
                    }
                }
                else
                {
                    // NOTE: we need to activelly poll for the accept() operation
                    // since there is no "accept timeout" option
                    serverSocket.Blocking = false;

                    var start = System.DateTime.UtcNow;
                    var delay = TimeSpan.FromTicks(1);

                    for (; ; )
                    {
                        try
                        {
                            // pick first connection without waiting
                            // throws if there is no Socket avail.
                            acceptedSocket = serverSocket.Accept();
                            break;
                        }
                        catch (SocketException socEx) when (socEx.SocketErrorCode == SocketError.WouldBlock)
                        {
                            // ignore and try again
                        }

                        if (start.AddSeconds(timeoutVal) >= System.DateTime.UtcNow)
                        {
                            Thread.Sleep(delay);

                            if (delay.TotalMilliseconds < Math.Min(timeoutVal * 100, 100)) // max 1/10 of timeout
                                delay += TimeSpan.FromMilliseconds(5);
                        }
                        else
                        {
                            // report timeout warning
                            PhpException.Throw(PhpError.Warning, Resources.LibResources.socket_accept_timeout);
                            break;
                        }
                    }
                }