amphp / file

An abstraction layer and non-blocking file access solution that keeps your application responsive.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Worker not found in openFile

razshare opened this issue · comments

Hi, I think I'm having an issue with openFile.

First of all here's a reproduction: https://github.com/tncrazvan/amphp-file-parallel-driver-issue-reproduction

The program is a web server and it's serving a web application to the client.

However when sending many requests in a row and canceling them (you can reproduce this by refreshing the browser very fast) I get this error in the console:

PHP Fatal error:  Uncaught UnexpectedValueException: Object not found in /home/raz/GitHub/tncrazvan/amphp-file-reproduction-SplObjectStorage-issue/vendor/amphp/file/src/Driver/ParallelFilesystemDriver.php:49
Stack trace:
#0 /home/raz/GitHub/tncrazvan/amphp-file-reproduction-SplObjectStorage-issue/vendor/amphp/file/src/Internal/FileWorker.php(26): Amp\File\Driver\ParallelFilesystemDriver::Amp\File\Driver\{closure}()
#1 [internal function]: Amp\File\Internal\FileWorker->__destruct()
#2 {main}

Next Error: Cannot use object of type SplObjectStorage as array in /home/raz/GitHub/tncrazvan/amphp-file-reproduction-SplObjectStorage-issue/vendor/amphp/file/src/Driver/ParallelFilesystemDriver.php:49
Stack trace:
#0 /home/raz/GitHub/tncrazvan/amphp-file-reproduction-SplObjectStorage-issue/vendor/amphp/file/src/Internal/FileWorker.php(26): Amp\File\Driver\ParallelFilesystemDriver::Amp\File\Driver\{closure}()
#1 [internal function]: Amp\File\Internal\FileWorker->__destruct()
#2 {main}
  thrown in /home/raz/GitHub/tncrazvan/amphp-file-reproduction-SplObjectStorage-issue/vendor/amphp/file/src/Driver/ParallelFilesystemDriver.php on line 49

Note

The error is only visible after closing the program (ctrl+c).

The problem seems to be this assert.

I think I'm doing something wrong but I'm not sure exactly what.

It's probably too much to ask for a solution because there's a lot of code behind my use case.

I was wondering if you have any ideas as to what is causing that worker to disappear from the storage.

It's a bit difficult to debug because the problem doesn't seem to happen at all while running with xdebug, it just works fine when running with xdebug for some reason.

My first instinct tells me it's somewhat related to timing and xdebug slows the application enough to the point where the worker doesn't get detached at the wrong time?

Other notes

  • My whole application is wrapped in one single

    async(...)->await();

    You can see that by following Bootstrap::start.

  • Other than openFile and the amp http server, there are no other async operations in the application going on.
    However, I am trying serve the client byte range responses when possible.
    Which is this piece of code here.
    It's nothing fancy, it just reads the range header and throws the exception if it's invalid and falls back to streaming the file as usual.

While writing this issue I found a fix, but it's weird... and I don't understand why it seems to work.

As I mentioned above, the byte range part throws an exception, well, this is the line that throws that exception.

/**
   * 
   * @param  ByteRangeWriterInterface       $interface
   * @throws InvalidByteRangeQueryException
   * @return Response
   */
  public function response(
      ByteRangeWriterInterface $interface
  ): Response {
      $response      = new Response();
      $headers       = [];
      $rangeQuery    = $interface->getRangeQuery();
      $contentLength = $interface->getContentLength();
      $contentType   = $interface->getContentType();

      $ranges = $this->parse($rangeQuery); // <========= HERE
      $count  = $ranges->count();

      /** @var ReadableIterableStream $reader */
      /** @var WritableIterableStream $writer */

      [$reader,$writer] = duplex();

And here's the weird fix

/**
   * 
   * @param  ByteRangeWriterInterface       $interface
   * @throws InvalidByteRangeQueryException
   * @return Response
   */
  public function response(
      ByteRangeWriterInterface $interface
  ): Response {
      $response      = new Response();
      $headers       = [];
      $rangeQuery    = $interface->getRangeQuery();
      $contentLength = $interface->getContentLength();
      $contentType   = $interface->getContentType();
      
      try {
          $ranges = $this->parse($rangeQuery);  // <========= HERE
      } catch(InvalidByteRangeQueryException $e) {
          throw $e;
      }

      $count = $ranges->count();

      /** @var ReadableIterableStream $reader */
      /** @var WritableIterableStream $writer */

      [$reader,$writer] = duplex();

... wrapping the line in a try/catch and throwing again the exception seems to fix things...

I reverted the reproduction repository to a previous version of the library, 1.0.68, which showcases the issue as I described it in the original post above.

You can increase the version to 1.0.69 to see the fix.

My question remains still though: am I doing something wrong?

Hi again @tncrazvan!

It looks as though you might be running into a weird destructor ordering issue, causing the SplObjectStorage instance to be accessed after the worker has already been destroyed.

Cannot use object of type SplObjectStorage as array

🤨 That error is very strange.

I pushed a commit which replaces the assertion with a check to see if the SplObjectStorage instance contains the worker and returns early if it is not found. Please try dev-3.x as 3.0.2 as the requirement for amphp/file in your composer.json to see if that update fixes the issue.

Hi @trowski , thank you for the blazing fast response! You really didn't have to!
I tried pulling down the new version but it seems the CI failed for php 8.3.

However I checked the commit you made and manually modified the file in my vendor directory with those changes and I can confirm it now works fine!

No need to rush the release, I'll roll with the weird try/catch fix until the actual update goes downstream.

I don't know if I should close this issue now though.
On this side the fix you made works fine, but the CI failed.
I believe you know best when to close it.


Happy new year if we don't get in touch again anytime soon!

The CI failed for an unrelated reason with the eio extension not installing on PHP 8.3. I worked around this for now until I can find out why CI was failing to install eio, as it works fine for me locally on 8.3.

I just tagged the fix under 3.0.2.

Thanks for the report and Happy New Year to you too!