amphp / amp Goto Github PK
View Code? Open in Web Editor NEWA non-blocking concurrency framework for PHP applications. ๐
Home Page: https://amphp.org/amp
License: MIT License
A non-blocking concurrency framework for PHP applications. ๐
Home Page: https://amphp.org/amp
License: MIT License
setErrorHandler
is currently executed as coroutine, I don't know whether that really makes sense, as exceptions either have to be rethrown to stop the loop or not rethrown to continue. Having a coroutine there, makes the loop always continue.
On this line https://github.com/amphp/amp/blob/master/lib/EvReactor.php#L370
evHandle is null in the IMMEDIATE state because he is unset. So when calling clear() is null, causing error. Do you know to right way to fix that?
Check if not null or ensure to call that before unsetting the handler?
Might fit into #100, but not sure on that.
@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.
Hello.
Need support systemd.socket. This will make, activation and reload demon save all open connections.
https://www.freedesktop.org/software/systemd/man/systemd.socket.html#
Is planned implement?
Thank.
Compiled extensions should be cached for reduced test run times.
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.
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.
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
.
TBD in ./docs/event-loop-api.md
.
Promise and Deferred are already in https://github.com/amphp/amp/blob/f7344f15d0d6694c711247f7d1beff48a9fe42e0/docs/managing-concurrency.md. That should be extracted into it's own document and additionally cover Failure
and Success
all, any, some, first
Parts can be extracted from the already existing managing-concurrency.md
There are already multiple drafts in our design repository, please add your ideas here and share feedback.
We support React promises in combinators, I think we should support them in those functions as well for consistency then?
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.
Currently there's a TODO comment in the docs.
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)
https://github.com/amphp/stream && https://github.com/amphp/socket will be rewritten ?
What are your plans ?
Proposal: Add a "TODO" section in README.
Thank you. ๐
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?
https://github.com/amphp/amp/blob/9df6b4508384bd5dfcf0fa5ca5965e813f775af7/docs/managing-concurrency.md currently contains a copy of the old documentation of v1 and needs an update for v2. The also already existing other two files have already been updated.
We should mention the most important changes in CHANGELOG
and probably create a UPGRADING-1.md
guide.
Line 112 in 8f903b6
try
/ catch
around the $callback
invocation, not around the foreach
.{
"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.
Useful for all structs with public properties. Could probably be combined with #96 into a helper document.
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.
All combinators and helpers should be properly documented.
(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
What's the purpose of Amp\Future
? It's documented to be used only in internal code, no public APIs?
wrap, coroutine, call
We have quite a few risky tests since the upgrade to PHPUnit 6.
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?
Currently, InvalidYieldError
s 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?
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(),
];
};
Listener + Iterator + Message
Emitter + Producer + Backpressure.
Does anyone know how the backend extensions compare in terms of CPU usage, memory usage and wall time?
We should use a monotonic time source where available. We might have one available in PHP 7.2 via php/php-src#2368. UV already uses it's uv_hrtime
which is monotonic.
For Ev
we should maybe have a look at our current implementation. reactphp/event-loop#83 (comment) mentions real time vs. wall time.
https://wiki.php.net/rfc/async_signals
I don't really know what this means, but hey, you guys probably know what they're talking about.
There must be a naming issue because this project fails when you try to download through composer. Maybe "Alert" instead of "alert"?
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()
.
It would be nice to add a Reactor::schedule
method to the interface that takes two arguments:
* * * * * *
)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.
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?
Extension version requirements should be specified in composer.json
. As they're not required, it should at least be in the suggest
.
Explain where it's useful (private methods for loop or promise callbacks without a new closure very time).
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.
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?
https://github.com/cujojs/when/blob/master/docs/api.md#edge-cases
Maybe we want to introduce something similar when assertions are enabled logging warnings.
Hi @rdlowrey ,
I feel it is good to have a place for discussion. Or did you left it to make it on github itself ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.