PHP7 difference - Bad file descriptor

Issue #38 on hold
Sergii Vasylchuk
created an issue

I run the same code on PHP 7.1.4 and PHP 5.6.30-0+deb8u1 this is simple http request

 $request_callback = function ($request, $base) {
            if (!$request instanceof \EventHttpRequest) {
                die('Invalid request');
            }

            $code = $request->getResponseCode();
            $headers = $request->getInputHeaders();
....
}
 $request = new \EventHttpRequest($request_callback, $this->event_base);
 $http = new \EventHttpConnection($this->event_base, $this->event_dns_base, 'localhost', 80);
        $http->makeRequest($request, \EventHttpRequest::CMD_GET, $url);

php 5 works fine, PHP 7 raising error after http request

Warning: EventBase::dispatch(): Epoll ADD(4) on fd 11 failed.  Old events were 0; read change was 0 (none); write change was 1 (add): Bad file descriptor 

after hours of debugging I guess php7 has more modern garbage collector and and destroy $base variable after callback finish

If I create new event base for each http request - it works fine, but I feel this is ugly solution.

so my question:

  1. Is it normal from performance point of view to create new event base for each request?

  2. What does really this error means?

  3. Is any way to use one event base for different events (like http request) it is correct approach?

Comments (5)

  1. Ruslan Osmanov repo owner

    EventBase object is supposed to be created for each event loop. If a bunch of HTTP requests is meant to be run within the context of a single event loop, then it is sufficient to create only one instance of EventBase.

    The cryptic error message is dispatched directly from Libevent, and usually means that some event object has been destroyed prematurely (before the loop is stopped).

    In order to prevent such kind of issues you should keep references to the event objects until the loop finishes its work. E.g.:

    <?php
    class Issue38HttpClient {
        private $base;
        private $conn;
        private $address;
        private $port;
        private $requests = [];
    
        public function __construct(string $address, int $port) {
            $this->address = $address;
            $this->port = $port;
            $this->base = new EventBase();
            $this->conn = new EventHttpConnection($this->base, null, $this->address, $this->port);
            $this->conn->setTimeout(15);
        }
    
        public function __destruct() {
            $this->requests = null;
            $this->conn = null;
        }
    
        public function dispatch() {
            $this->base->loop();
        }
    
        public function makeRequest(string $uri = '/') {
            $req = new EventHttpRequest([$this, 'requestHandler'], $this->base);
            $req->addHeader('Host', $this->address, EventHttpRequest::OUTPUT_HEADER);
            $req->addHeader('Connection', 'close', EventHttpRequest::OUTPUT_HEADER);
            $this->requests[$uri] = $req;
    
            $this->conn->makeRequest($req, EventHttpRequest::CMD_GET, $uri);
        }
    
        public function requestHandler($req, $base) {
            if (is_null($req)) {
                echo "Timed out\n";
            } else {
                $response_code = $req->getResponseCode();
    
                if ($response_code == 0) {
                    echo "Connection refused\n";
                } elseif ($response_code != 200) {
                    echo "Unexpected response: $response_code\n";
                } else {
                    echo "Success: $response_code\n";
                    $buf = $req->getInputBuffer();
                    echo "Body:\n";
                    while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
                        echo $s, PHP_EOL;
                    }
                }
            }
        }
    }
    
    $client = new Issue38HttpClient('php.net', 80);
    $client->makeRequest('/distributions/php-7.1.4.tar.bz2.asc');
    $client->dispatch();
    

    I recommend destroying the objects explicitly (by assigning null) as it is done above in the destructor. Some classes have free methods for freeing the internal libevent data structures. However, most of the time you will need freeing the PHP variables as well: $this->object->free(); $this->object = null. Note that the object destructor handlers actually do cleanup the internal libevent structures, so you don't need to call free(), if the variable is destroyed.

    It is very likely that the error message is caused by an event object being destroyed after going out of scope, e.g.:

    <?php
    
    public function someMethod() {
      $req = new EventHttpRequest([$this, 'requestHandler'], $this->base);
      ...
    
      // $req gets destroyed when it goes out of scope of the function,
      // if you do not save a reference to it!
    }
    
  2. Log in to comment