Giter Site home page Giter Site logo

amphp / amp Goto Github PK

View Code? Open in Web Editor NEW
4.2K 132.0 253.0 2.61 MB

A non-blocking concurrency framework for PHP applications. ๐Ÿ˜

Home Page: https://amphp.org/amp

License: MIT License

PHP 100.00%
php async coroutines promises amphp asynchronous concurrency futures revolt

amp's Introduction

amphp/amp

AMPHP is a collection of event-driven libraries for PHP designed with fibers and concurrency in mind. amphp/amp specifically provides futures and cancellations as fundamental primitives for asynchronous programming. We're now using Revolt instead of shipping an event loop implementation with amphp/amp.

Amp makes heavy use of fibers shipped with PHP 8.1 to write asynchronous code just like synchronous, blocking code. In contrast to earlier versions, there's no need for generator based coroutines or callbacks. Similar to threads, each fiber has its own call stack, but fibers are scheduled cooperatively by the event loop. Use Amp\async() to run things concurrently.

Motivation

Traditionally, PHP follows a sequential execution model. The PHP engine executes one line after the other in sequential order. Often, however, programs consist of multiple independent sub-programs which can be executed concurrently.

If you query a database, you send the query and wait for the response from the database server in a blocking manner. Once you have the response, you can start doing the next thing. Instead of sitting there and doing nothing while waiting, we could already send the next database query, or do an HTTP call to an API. Let's make use of the time we usually spend on waiting for I/O!

Revolt allows such concurrent I/O operations. We keep the cognitive load low by avoiding callbacks. Our APIs can be used like any other library, except that things also work concurrently, because we use non-blocking I/O under the hood. Run things concurrently using Amp\async() and await the result using Future::await() where and when you need it!

There have been various techniques for implementing concurrency in PHP over the years, e.g. callbacks and generators shipped in PHP 5. These approaches suffered from the "What color is your function" problem, which we solved by shipping Fibers with PHP 8.1. They allow for concurrency with multiple independent call stacks.

Fibers are cooperatively scheduled by the event-loop, which is why they're also called coroutines. It's important to understand that only one coroutine is running at any given time, all other coroutines are suspended in the meantime.

You can compare coroutines to a computer running multiple programs using a single CPU core. Each program gets a timeslot to execute. Coroutines, however, are not preemptive. They don't get their fixed timeslot. They have to voluntarily give up control to the event loop.

Any blocking I/O function blocks the entire process while waiting for I/O. You'll want to avoid them. If you haven't read the installation guide, have a look at the Hello World example that demonstrates the effect of blocking functions. The libraries provided by AMPHP avoid blocking for I/O.

Installation

This package can be installed as a Composer dependency.

composer require amphp/amp

If you use this library, it's very likely you want to schedule events using Revolt, which you should require separately, even if it's automatically installed as a dependency.

composer require revolt/event-loop

These packages provide the basic building blocks for asynchronous / concurrent applications in PHP. We offer a lot of packages building on top of these, e.g.

Requirements

This package requires PHP 8.1 or later. No extensions required!

Extensions are only needed if your app necessitates a high numbers of concurrent socket connections, usually this limit is configured up to 1024 file descriptors.

Usage

Coroutines

Coroutines are interruptible functions. In PHP, they can be implemented using fibers.

Note Previous versions of Amp used generators for a similar purpose, but fibers can be interrupted anywhere in the call stack making previous boilerplate like Amp\call() unnecessary.

At any given time, only one fiber is running. When a coroutine suspends, execution of the coroutine is temporarily interrupted, allowing other tasks to be run. Execution is resumed once a timer expires, stream operations are possible, or any awaited Future completes.

Low-level suspension and resumption of coroutines is handled by Revolt's Suspension API.

<?php

require __DIR__ . '/vendor/autoload.php';

use Revolt\EventLoop;

$suspension = EventLoop::getSuspension();

EventLoop::delay(5, function () use ($suspension): void {
    print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;

    $suspension->resume(null);
});

print '++ Suspending to event loop...' . PHP_EOL;

$suspension->suspend();

print '++ Script end' . PHP_EOL;

Callbacks registered on the Revolt event-loop are automatically run as coroutines and it's safe to suspend them. Apart from the event-loop API, Amp\async() can be used to start an independent call stack.

<?php

use function Amp\delay;

require __DIR__ . '/vendor/autoload.php';

Amp\async(function () {
    print '++ Executing callback passed to async()' . PHP_EOL;

    delay(3);

    print '++ Finished callback passed to async()' . PHP_EOL;
});

print '++ Suspending to event loop...' . PHP_EOL;
delay(5);

print '++ Script end' . PHP_EOL;

Future

A Future is an object representing the eventual result of an asynchronous operation. There are three states:

  • Completed successfully: The future has been completed successfully.
  • Errored: The future failed with an exception.
  • Pending: The future is still pending.

A successfully completed future is analog to a return value, while an errored future is analog to throwing an exception.

One way to approach asynchronous APIs is using callbacks that are passed when the operation is started and called once it completes:

doSomething(function ($error, $value) {
    if ($error) {
        /* ... */
    } else {
        /* ... */
    }
});

The callback approach has several drawbacks.

  • Passing callbacks and doing further actions in them that depend on the result of the first action gets messy really quickly.
  • An explicit callback is required as input parameter to the function, and the return value is simply unused. There's no way to use this API without involving a callback.

That's where futures come into play. They're placeholders for the result that are returned like any other return value. The caller has the choice of awaiting the result using Future::await() or registering one or several callbacks.

try {
    $value = doSomething()->await();
} catch (...) {
    /* ... */
}

Combinators

In concurrent applications, there will be multiple futures, where you might want to await them all or just the first one.

await

Amp\Future\await($iterable, $cancellation) awaits all Future objects of an iterable. If one of the Future instances errors, the operation will be aborted with that exception. Otherwise, the result is an array matching keys from the input iterable to their completion values.

The await() combinator is extremely powerful because it allows you to concurrently execute many asynchronous operations at the same time. Let's look at an example using amphp/http-client to retrieve multiple HTTP resources concurrently:

<?php

use Amp\Future;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;

$httpClient = HttpClientBuilder::buildDefault();
$uris = [
    "google" => "https://www.google.com",
    "news"   => "https://news.google.com",
    "bing"   => "https://www.bing.com",
    "yahoo"  => "https://www.yahoo.com",
];

try {
    $responses = Future\await(array_map(function ($uri) use ($httpClient) {
        return Amp\async(fn () => $httpClient->request(new Request($uri, 'HEAD')));
    }, $uris));

    foreach ($responses as $key => $response) {
        printf(
            "%s | HTTP/%s %d %s\n",
            $key,
            $response->getProtocolVersion(),
            $response->getStatus(),
            $response->getReason()
        );
    }
} catch (Exception $e) {
    // If any one of the requests fails the combo will fail
    echo $e->getMessage(), "\n";
}
awaitAnyN

Amp\Future\awaitAnyN($count, $iterable, $cancellation) is the same as await() except that it tolerates individual errors. A result is returned once exactly $count instances in the iterable complete successfully. The return value is an array of values. The individual keys in the component array are preserved from the iterable passed to the function for evaluation.

awaitAll

Amp\Promise\awaitAll($iterable, $cancellation) awaits all futures and returns their results as [$errors, $values] array.

awaitFirst

Amp\Promise\awaitFirst($iterable, $cancellation) unwraps the first completed Future, whether successfully completed or errored.

awaitAny

Amp\Promise\awaitAny($iterable, $cancellation) unwraps the first successfully completed Future.

Future Creation

Futures can be created in several ways. Most code will use Amp\async() which takes a function and runs it as coroutine in another Fiber.

Sometimes an interface mandates a Future to be returned, but results are immediately available, e.g. because they're cached. In these cases Future::complete(mixed) and Future::error(Throwable) can be used to construct an immediately completed Future.

DeferredFuture

Note The DeferredFuture API described below is an advanced API that many applications probably don't need. Use Amp\async() or combinators instead where possible.

Amp\DeferredFuture is responsible for completing a pending Future. You create a Amp\DeferredFuture and uses its getFuture method to return an Amp\Future to the caller. Once result is ready, you complete the Future held by the caller using complete or error on the linked DeferredFuture.

final class DeferredFuture
{
    public function getFuture(): Future;
    public function complete(mixed $value = null);
    public function error(Throwable $throwable);
}

Warning If you're passing DeferredFuture objects around, you're probably doing something wrong. They're supposed to be internal state of your operation.

Warning You can't complete a future with another future; Use Future::await() before calling DeferredFuture::complete() in such cases.

Here's a simple example of an asynchronous value producer asyncMultiply() creating a DeferredFuture and returning the associated Future to its caller.

<?php // Example async producer using DeferredFuture

use Revolt\EventLoop;

function asyncMultiply(int $x, int $y): Future
{
    $deferred = new Amp\DeferredFuture;

    // Complete the async result one second from now
    EventLoop::delay(1, function () use ($deferred, $x, $y) {
        $deferred->complete($x * $y);
    });

    return $deferred->getFuture();
}

$future = asyncMultiply(6, 7);
$result = $future->await();

var_dump($result); // int(42)

Cancellation

Every operation that supports cancellation accepts an instance of Cancellation as argument. Cancellations are objects that allow registering handlers to subscribe to cancellation requests. These objects are passed down to sub-operations or have to be handled by the operation itself.

$cancellation->throwIfRequested() can be used to fail the current operation with a CancelledException once cancellation has been requested. While throwIfRequested() works well, some operations might want to subscribe with a callback instead. They can do so using Cancellation::subscribe() to subscribe any cancellation requests that might happen.

The caller creates a Cancellation by using one of the implementations below.

Note Cancellations are advisory only. A DNS resolver might ignore cancellation requests after the query has been sent as the response has to be processed anyway and can still be cached. An HTTP client might continue a nearly finished HTTP request to reuse the connection, but might abort a chunked encoding response as it cannot know whether continuing is actually cheaper than aborting.

TimeoutCancellation

A TimeoutCancellations automatically cancels itself after the specified number of seconds.

request("...", new Amp\TimeoutCancellation(30));

SignalCancellation

A SignalCancellation automatically cancels itself after a specified signal has been received by the current process.

request("...", new Amp\SignalCancellation(SIGINT));

DeferredCancellation

A DeferredCancellation allows manual cancellation with the call of a method. This is the preferred way if you need to register some custom callback somewhere instead of shipping your own implementation. Only the caller has access to the DeferredCancellation and can cancel the operation using DeferredCancellation::cancel().

$deferredCancellation = new Amp\DeferredCancellation();

// Register some custom callback somewhere
onSomeEvent(fn () => $deferredCancellation->cancel());

request("...", $deferredCancellation->getCancellation());

NullCancellation

A NullCancellation will never be cancelled. Cancellation is often optional, which is usually implemented by making the parameter nullable. To avoid guards like if ($cancellation), a NullCancellation can be used instead.

$cancellation ??= new NullCancellationToken();

CompositeCancellation

A CompositeCancellation combines multiple independent cancellation objects. If any of these cancellations is cancelled, the CompositeCancellation itself will be cancelled.

Versioning

amphp/amp follows the semver semantic versioning specification like all other amphp packages.

Compatible Packages

Compatible packages should use the amphp topic on GitHub.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

License

The MIT License (MIT). Please see LICENSE for more information.

amp's People

Contributors

andrewcarteruk avatar bilge avatar bwoebi avatar daverandom avatar dependabot[bot] avatar descawed avatar douggr avatar enumag avatar foremtehan avatar gitter-badger avatar joshdifabio avatar jsor avatar kelunik avatar krlv avatar lt avatar lwlwilliam avatar lyrixx avatar mickaelandrieu avatar mmasiukevich avatar nevay avatar peehaa avatar prolic avatar rdlowrey avatar samnela avatar sedatsevgili avatar simpod avatar staabm avatar trowski avatar wyrihaximus avatar xpader avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

amp's Issues

Native reactor doesn't exit

As always, there's the possibility of extreme user error, but the code below doesn't seem to be finishing for me, both on OSX, and in a Centos Vagrant box.

<?php

require_once(realpath(__DIR__).'/../vendor/autoload.php');

$reactor = new \Alert\NativeReactor();
$client = new \Artax\AsyncClient($reactor);

$client->setOption('connecttimeout', 3);
$client->setOption('keepalivetimeout', 3);
$client->setOption('transfertimeout', 3);
$client->setOption('usekeepalive', false);

$onResponse = function (\Artax\Response $response) {
    echo "Response status = ".$response->getStatus()."\n";
};
$onError = function ($param) {
    echo "Somethings went wrong ".var_dump($param)."\n";
};


$client->request("http://www.google.com", $onResponse, $onError);

$watcherId = null;
$secondsCount = 0;

$msInterval = 2000;

$watcherId = $reactor->repeat(function() use ($reactor, &$secondsCount, &$watcherId, $msInterval) {
        $secondsCount += 1;
        echo "- Time: ".(($secondsCount * $msInterval ) / 1000 )."\n";

    }, $msInterval);


$reactor->run();

echo "fin.";

The count is currently up over 500 seconds.

Bug in onReadable

It looks like this function doesn't exist in 2.0?

However, this affects Aerys with php-uv reactor.

In native:

Amp\onReadable( $this->socket, function ( $watcherId, $socket ) use ( &$data ) {
  $data[2] .= @fread( $socket, 1024 );
  . . . 

That will work, even if the fread doesn't read all the data in one go, it will still call the function on the next tick. With php-uv, it will not. It appears that it has to wait until new data hits the socket. This means that a programmer would have to know the maximum buffers of the socket in the target environment, or risk missing the tail end of a transaction.

Package needs fixed on Packagist

There must be a naming issue because this project fails when you try to download through composer. Maybe "Alert" instead of "alert"?

Why do we have Amp\Future

What's the purpose of Amp\Future? It's documented to be used only in internal code, no public APIs?

Fix failing tests

Test results when running locally with PHP 7.1.3RC1 and uv and event extensions.

PHPUnit 6.0.8 by Sebastian Bergmann and contributors.

Error:         No code coverage driver is available

...............................................................  63 / 624 ( 10%)
..........................................................SSSSS 126 / 624 ( 20%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 189 / 624 ( 30%)
SSSSSSSSSSSSSSSSSSSSSSS..........S............................. 252 / 624 ( 40%)
.....S.....................SSS...............................S. 315 / 624 ( 50%)
.................................S.....................SSS..... 378 / 624 ( 60%)
..........................S.................................FS. 441 / 624 ( 70%)
....................SSS........................................ 504 / 624 ( 80%)
................................F.............................. 567 / 624 ( 90%)
.........................................................       624 / 624 (100%)

Time: 11.83 seconds, Memory: 37.01MB

There were 2 failures:

1) Amp\Test\Loop\UvDriverTest::testExecutionOrderGuarantees
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'01 02 03 04 05 05 05 05 05 05 05 05 10 11 12 13 13 13 13 20 21 21 21 21 30 40 41 '
+'01 02 03 04 05 05 05 05 05 10 11 12 13 05 20 21 21 21 21 30 05 05 40 13 13 13 41 '

2) Amp\Test\ProducerTest::testEmitReactBackPressure
Failed asserting that 299.91698265075684 is greater than 300.

/home/kelunik/GitHub/amphp/amp/test/ProducerTest.php:140

FAILURES!
Tests: 8814, Assertions: 1275, Failures: 2, Skipped: 106.

Amp\Parallel\ChannelException: The socket unexpectedly closed

This excpetion is throw ยฑ 1 out of 10 times when running this test. The test itself will take a path of an image, scales it down and saves it about 10 times. Each downscaling is a separate Fork.

This is the testcode.

    public function test_async() {
        $url = 'img/image.jpeg';
        $factory = new ResponsiveFactory(new DefaultConfigurator([
            'publicPath'   => $this->publicPath,
            'engine'       => 'gd',
            'stepModifier' => 0.5,
            'scaler'       => 'filesize',
            'enableCache'  => false,
            'async'        => true,
        ]));

        $responsiveImage = $factory->create($url);

        $this->assertTrue(count($responsiveImage->getSrcset()) > 1);
        $this->assertEquals("/{$url}", $responsiveImage->src());

        $testCase = $this;
        $responsiveImage->onSaved(function () use ($testCase, $responsiveImage) {
            $fs = new Filesystem();

            foreach ($responsiveImage->getSrcset() as $src) {
                $src = trim($src, '/');

                $testCase->assertTrue($fs->exists("{$testCase->publicPath}/{$src}"));
            }
        });

        \Amp\wait($responsiveImage->getMainPromise());
    }

And this is the exception trace.

PHPUnit 5.7.14 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 673 ms, Memory: 30.00MB

There was 1 error:

1) Brendt\Image\Tests\Phpunit\ResponsiveFactoryTest::test_async
Amp\Parallel\ChannelException: The socket unexpectedly closed

/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/parallel/lib/Sync/ChannelledSocket.php:91
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/loop/lib/NativeLoop.php:113
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/loop/lib/NativeLoop.php:42
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/loop/lib/Loop.php:96
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/loop/lib/Loop.php:47
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/async-interop/event-loop/src/Loop.php:78
/Users/brent1/Documents/sites/brendt/responsive-image/vendor/amphp/amp/lib/functions.php:116
/Users/brent1/Documents/sites/brendt/responsive-image/tests/phpunit/ResponsiveFactoryTest.php:164

The code for downscaling images:

$factory = $this;

$promise = Fork::spawn(function () use ($factory, $imageObject, $width, $height, $scaledFilePath) {
    $scaledImage = $imageObject->resize((int) $width, (int) $height)->encode($imageObject->extension);
    $factory->saveImageFile($scaledFilePath, $scaledImage);
})->join();

return $promise;

All promises are combined into one using \Amp\all().

How to install dev-master?

{
    "minimum-stability": "dev",
    "require": {
        "php": ">=7.0",
        "amphp/amp": "dev-master"
    }
}
composer update
  Problem 1
    - Installation request for amphp/amp dev-master -> satisfiable by amphp/amp[dev-master].
    - amphp/amp dev-master requires async-interop/event-loop-implementation ^0.5 -> no matching package found.

CallableMaker

Explain where it's useful (private methods for loop or promise callbacks without a new closure very time).

Promise Combinators

all, any, some, first

Parts can be extracted from the already existing managing-concurrency.md

Cannot tick() recursively; event reactor already active

I feel like this is going to be a common pitfall so I'm creating an issue for tracking. I'm finding that not being able to synchronously wait within an asynchronous context is overly limiting. Contrived example:

\Amp\run(function() {
    var_dump(\Amp\wait(\Amp\Dns\resolve('google.ca')));
});

This code throws

'LogicException' with message 'Cannot tick() recursively; event reactor already active'

More commonly when adapting old synchronous code is to have asynchronous promises higher in the call stack and various yields lower in the call stack. Its quite annoying to pass promises and chain the entire call stack.

I've created a gist with a more complete example of what I'm trying to accomplish: https://gist.github.com/chrisleavoy/9c2386e3b408399c4ba2e5a4db055888

Can someone recommend a better workaround?

The leading contender right now is to use Icicle's loop reactor, but then adapt promises so that i can use Amp\Dns instead of Icicle's DNS implementation. Kudos on Amp\Dns, which is the best library I've found when evaluating all of the php async packages for DNS.

Working example that uses Icicle but embeds Amp\Dns:
https://gist.github.com/chrisleavoy/18b4032402d7b2d7f688655fe28eab60

I would appreciate some insight into reasons why Amp throws this logic exception and whether there is a work around that doesn't involve converting the entire call stack into async. (php5.5 support required)

Throwing coroutine results in error

(function () {
    if (false) {
        yield;
    }

    throw new \Exception("rethrow test");
})();

Running that as a coroutine results in Cannot get return value of a generator that hasn't returned

Version 2 Review

@trowski @bwoebi @rdlowrey I reviewed Amp v2.0 and here are my comments.

Internal\WhenQueue

How important is the layer of indirection? If we're worried about the call stack, should we instead resolve promises asynchronously?

It could also be renamed to a more generic name like CallbackQueue or CallQueue.

Internal\Placeholder

Annotation for $onResolved is callable|\Amp\Internal\WhenQueue|null, should be ?WhenQueue or ?WhenQueue|null instead.

Coroutine

Why does it not accept a callable as constructor parameter in addition of generators?

Emitter

if ($this->resolved) {
    return;
}

^ Does that ignore double resolution silently?

Pause

Do we really need $value there? What about keep_alive = false?

Internal\LazyPromise

Why is this one internal? Why is Amp\lazy suggested to be used only? Why do we even have Amp\lazy? Instead of using arguments to Amp\lazy, those parameters can just be use'd in a closure by the user instead of providing them as arguments.

Amp\wrap vs. Amp\coroutine

It is weird to have both and usage will be confusion for users.

Amp\timeout

I think Amp\timeout shouldn't keep the loop running. The watchers should probably be unreferenced.

Amp\lift

Do we need lift? I always ask that when reading the docs regarding it.

Amp\filter

Amp\filter filters observables now instead of promises.

Cron emulation

It would be nice to add a Reactor::schedule method to the interface that takes two arguments:

  • a callback
  • a cron format string (i.e. * * * * * *)

Because the event reactors already make it possible to write long-running CLI applications, cron emulation will trivialize daemon-style tasks in PHP without resorting to task schedulers, cron, etc. Libraries currently exist in PHP to parse cron strings but I'd prefer not to introduce dependencies into such a low-level library. If anyone cares to help with adding this functionality please let me know. It's a nice feature but I'm very busy right now and probably won't have the time to do it myself for a while.

Consider supporting thenables

In order to use Aerys in front of my existing async application I have to convert all of my Guzzle and React promises to Amp promises, which means I have to invest a load of time writing boilerplate code before I can properly test out Aerys, which means I probably won't give it a proper try because that time will have been wasted if we subsequently decide not to use Aerys. Are there any plans to support thenables in the Amp coroutine implementation in order to make it easier to work with non-Amp libraries?

Comparison of extensions

Does anyone know how the backend extensions compare in terms of CPU usage, memory usage and wall time?

Changing "when" to "onResolve" or "onComplete"

It doesn't matter for normal promises, but I think it matters for more advanced use cases where the promise API is just one way of consumption. Looking at a list of functions, onResolve / onComplete are way more expressive than when.

Invalid yields

Currently, InvalidYieldErrors are thrown back into the generator. I'm not sure whether that's a good idea. Maybe they should directly be forwarded to the error handler and the coroutine aborted?

Async programming with blocking operations

Hi,

I am trying to figure out how to get some blocking tasks performed in parallel, the example is here:

https://gist.github.com/yoanisgil/a28d57abca781fd84af1

Roughly the script will launch 5 asynchronous tasks (asyncMultiply) each of which will perform a random sleep. When I run the script with:

time docker run -v $(PWD):/usr/src/myapp  -ti --rm yoanisgil/php56-cli-composer php test.php

it takes about ~ 16 secs, which indicates that tasks are performed synchronously (each task performs a random sleep between 2 - 4 seconds).

Given the deferred/asynchronous nature of promises and that I'm using Amp\immediately I would have expected about ~ 4 seconds of execution.

For sure I most be doing something wrong. Any ideas what that might be?

Bests,

Yoanis.

Fix risky tests

We have quite a few risky tests since the upgrade to PHPUnit 6.

Stopping nested loops

Nested loops can result in cases where multiple calls to stop are timed in such a way that the lower-level loop does not stop.

This code continues the base loop indefinitely, even though it seems it should be stopped.

Loop::run(function () {
    Loop::repeat(1000, function () {
        print microtime(true) . PHP_EOL;
    });

    Loop::defer(function () {
        Loop::stop();
    });

    Loop::run(function () {
        Loop::stop();
    });
});

While this is an edge-case, I can see this potentially happening if multiple calls to wait() are made and the promises are resolved in the same tick.

This happens because we ignore multiple calls to stop. Perhaps multiple calls should not be ignored, but rather stop the prior loop if running as a nested loop.

Struct

Useful for all structs with public properties. Could probably be combined with #96 into a helper document.

Removal of 1.0.x-dev breaks dependent packages

Several packages (rdlowrey/nbsock, amphp/dns, amphp/artax 2.x-dev, amphp/mysql to name a few) rely on 1.0.x-dev as a version dependency.

You can currently get around this by using a require inline alias, but maybe keeping 1.0.x-dev as a branch-alias in composer.json could keep those packages working until they've been updated?

Call to undefined function uv_loop_new()

Hi,

using new Amp\UvReactor() directly (instead of Amp\getReactor) doesn't
check if php-uv is loaded and then it dies with a 'Call to undefined
function' message.

What about throw a RuntimeException if php-uv is not loaded?

Logo

There are already multiple drafts in our design repository, please add your ideas here and share feedback.

Implicit all combinator for coroutines

I think it makes sense to support yielding arrays as implicit all combinator, especially with the changes in PHP 7.1 allowing [] for restructuring.

<?php

function coroutine() {
    [$a, $b, $c] = yield [
        asyncOperation1(),
        asyncOperation2(),
        asyncOperation3(),
    ];
};

Amp Pattern Question

I'm rewriting a db driver to use Amp 1.x in Aerys. It's pretty straight forward, except that I hit a wall with how promises are implemented. Here's some example code that shows my issue:

function connect($options) {
  /* connect */
  Amp\onReadable($socket, function($watcherId) {
    /* read data */
    $inFlight[$token]->succeed($data); // $inFlight[$token] is the matching request for this query
}

function query($query) {
  /* Send query */
  $inFlight[$token] = new Amp\deferred();
  $promise = $inFlight[$token]->promise();
  return $promise->when(function($error, $data) use (/*lots of stuff from query send*/) {
    /* transform $data into native objects */
  }
}

function myCoRoutine() {
  $data = yield query($query);
}

However, $data in myCoRoutine holds the results of the original response from the server, before being transformed. Do you have any tips on how to get the result from the when in the query() function, instead of from the watcher?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.