Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous server with finer-grained I/O control #22

Merged
merged 14 commits into from
Oct 29, 2022

Conversation

thekid
Copy link
Member

@thekid thekid commented Aug 21, 2022

In a nutshell

This pull request enhances the asynchronous server with finer-grained control for protocol handlers and scheduled tasks:

  • Checks for readability as well as writability. Useful for transferring up- and downloads at the clients' speed instead of potentially blocking.
  • The ability to delay execution by a given number of milliseconds. Think sleep(), but again without blocking.

Example

The request handlers in the xp-forge/web library can make use of the yield statement to hand back control to the server, which can then decide to handle other clients.

use web\Application;
use io\File;
use util\MimeType;

class Downloads extends Application {

  public function routes() {
    return function($req, $res) {
      $f= new File(...);
      $f->open(File::READ);

      $res->answer(200);
      $res->header('Content-Type', MimeType::getByFilename($f->filename));
      
      $out= $res->stream($f->size());
      try {
        while (!$f->eof()) {
          yield; // <-- adding this here enables other clients' requests to be served
          $out->write($f->read());
        }
      } finally {
        $f->close();
      }
    };
  }
}

However, the implementation simply resumes the handler at the first possible time, and the following write() could still potentially block for longer than necessary. To make the handler wait for the socket to become writeable, it needs to be changed as follows:

 while (!$f->eof()) {
-  yield;
+  yield 'write' => $res;
   $out->write($f->read());
 }

Further uses

  • Use yield 'read' => $req; to wait until the socket becomes readable.
  • Use yield 'delay' => 300; to wait for 300 milliseconds.

The plain yield form is equivalent to yield 'delay' => 0;.

Intention

This functionality is intended to be used by library functions such as web.Response::transmit() or the file upload handling inside xp-forge/web:

diff --git a/src/main/php/web/Response.class.php b/src/main/php/web/Response.class.php
index d94113f..282738f 100755
--- a/src/main/php/web/Response.class.php
+++ b/src/main/php/web/Response.class.php
@@ -216,8 +216,8 @@ class Response {
     $out= $this->stream($size);
     try {
       while ($in->available()) {
+        yield 'write' => $out;
         $out->write($in->read());
-        yield;
       }
     } finally {
       $out->close();
diff --git a/src/main/php/web/handler/FilesFrom.class.php b/src/main/php/web/handler/FilesFrom.class.php
index 6b19d54..f25f1c9 100755
--- a/src/main/php/web/handler/FilesFrom.class.php
+++ b/src/main/php/web/handler/FilesFrom.class.php
@@ -67,19 +67,19 @@ class FilesFrom implements Handler {
   /**
    * Copies a given amount of bytes from the specified file to the output
    *
-   * @param  web.io.Output $output
+   * @param  web.io.Output $out
    * @param  io.File $file
    * @param  web.io.Range $range
    * @return iterable
    */
-  private function copy($output, $file, $range) {
+  private function copy($out, $file, $range) {
     $file->seek($range->start());
 
     $length= $range->length();
     while ($length && $chunk= $file->read(min(self::CHUNKSIZE, $length))) {
-      $output->write($chunk);
+      yield 'write' => $out;
+      $out->write($chunk);
       $length-= strlen($chunk);
-      yield;
     }
   }
 
@@ -129,8 +129,8 @@ class FilesFrom implements Handler {
         $file->open(File::READ);
         try {
           do {
+            yield 'write' => $out;
             $out->write($file->read(self::CHUNKSIZE));
-            yield;
           } while (!$file->eof());
         } finally {
           $file->close();

For a discussion about a possible future scope, see xp-forge/rest-client#25 (comment)

See also

@thekid
Copy link
Member Author

thekid commented Aug 24, 2022

Note: The statements yield 'read' => $req; and yield 'write' => $res; includes $req and $res for aesthetic reasons only, they are completely ignored by the implementation!

@thekid thekid merged commit 2ab103c into master Oct 29, 2022
@thekid thekid deleted the refactor/async-server branch October 29, 2022 08:10
@thekid
Copy link
Member Author

thekid commented Oct 29, 2022

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

Successfully merging this pull request may close these issues.

1 participant