Hi internals,
I propose to introduce the Fiber feature AGAIN.
The main purpose of the RFC is to introducing a lightweight stackful coroutine support for PHP and make it possible to write non-blocking code in the blocking style.
In this RFC, no new keyword is needed. So it will not break the PHP 7.3 release.
Please see the RFC https://wiki.php.net/rfc/fiber
Dmitry and I are working on the implementation at https://github.com/fiberphp/fiber-ext
And a series of usage demo can be found at https://github.com/fiberphp/fiber-demo
Please offer you comments.
Thank you.
Haitao Lv
Hi internals,
I propose to introduce the Fiber feature AGAIN.
The main purpose of the RFC is to introducing a lightweight stackful coroutine support for PHP and make it possible to write non-blocking code in the blocking style.
In this RFC, no new keyword is needed. So it will not break the PHP 7.3 release.
Please see the RFC https://wiki.php.net/rfc/fiber
Dmitry and I are working on the implementation at https://github.com/fiberphp/fiber-ext
And a series of usage demo can be found at https://github.com/fiberphp/fiber-demoPlease offer you comments.
Thank you.
Haitao Lv
Hi Haitao,
I'm very excited to see this sort of feature coming to PHP.
A couple of questions and thoughts:
-
How do you determine when a fiber has returned? Looking at the source, it appears Fiber::status() must be used, comparing against constants. Separate methods similar to Generator would be better IMO. e.g.: Fiber::alive(), Fiber::suspended(), Fiber::running()
-
What about throwing exceptions into a fiber?
-
Using Fiber::resume() to initialize the fiber and resume feels awkward. Separate methods again would be better here, perhaps Fiber::init(...$args) and Fiber::resume($send).
-
What happens if the sub1() function in the RFC is invoked outside of a fiber?
-
I think a keyword here would be beneficial, even if it has a minor BC impact. Fibers could then be written like generators.
await
oremit
as a keyword perhaps? This would be a less verbose API, feel less magical (a static method call that actually pauses execution feels out of place), and would allow Fibers to be returned from methods, named functions, etc with less boilerplate.
Thanks to you and Dmitry for working on this!
Aaron Piotrowski
Hi internals,
I propose to introduce the Fiber feature AGAIN.
The main purpose of the RFC is to introducing a lightweight stackful coroutine support for PHP and make it possible to write non-blocking code in the blocking style.
In this RFC, no new keyword is needed. So it will not break the PHP 7.3 release.
Please see the RFC https://wiki.php.net/rfc/fiber
Dmitry and I are working on the implementation at https://github.com/fiberphp/fiber-ext
And a series of usage demo can be found at https://github.com/fiberphp/fiber-demoPlease offer you comments.
Thank you.
Haitao Lv
Hi Haitao,
I'm very excited to see this sort of feature coming to PHP.
A couple of questions and thoughts:
- How do you determine when a fiber has returned? Looking at the source, it appears Fiber::status() must be used, comparing against constants. Separate methods similar to Generator would be better IMO. e.g.: Fiber::alive(), Fiber::suspended(), Fiber::running()
Offering methods like Fiber::alive, Fiber::running makes no difference to check the Fiber::status() return value. This is just a style issue. And as a language feature,
Fiber only offer the essential API and let other works to the user land.
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land code could check
the value of Fiber::yield and throw exception themselves. The Ruby's Fiber and Lua's
coroutine also does not support such api as well.
- Using Fiber::resume() to initialize the fiber and resume feels awkward. Separate methods again would be better here, perhaps Fiber::init(...$args) and Fiber::resume($send).
All Fiber created with a suspended status. So make resume to do both the init and resume
do make sense.
Please see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of a fiber?
You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC impact. Fibers could then be written like generators.
await
oremit
as a keyword perhaps? This would be a less verbose API, feel less magical (a static method call that actually pauses execution feels out of place), and would allow Fibers to be returned from methods, named functions, etc with less boilerplate.
Wishing this to be accepted by the community in the PHP 7.3, so no keyword is accepted.
And if the community cannot accept, the Fiber can be still distributed as a standalone
extension. So we cannot depend on a new keyword.
Thanks to you and Dmitry for working on this!
Aaron Piotrowski
- How do you determine when a fiber has returned? Looking at the source,
it appears Fiber::status() must be used, comparing against constants.
Separate methods similar to Generator would be better IMO. e.g.:
Fiber::alive(), Fiber::suspended(), Fiber::running()Offering methods like Fiber::alive, Fiber::running makes no difference to
check the Fiber::status() return value. This is just a style issue. And as
a language feature,
Fiber only offer the essential API and let other works to the user land.
The language should offer a sane API, not the absolute minimum required to
work for these things.
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land code
could check
the value of Fiber::yield and throw exception themselves. The Ruby's Fiber
and Lua's
coroutine also does not support such api as well.
And throw the exception where? That means async code with fibers can't
really handle errors?
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.
It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.
Please see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of a
fiber?You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC
impact. Fibers could then be written like generators.await
oremit
as
a keyword perhaps? This would be a less verbose API, feel less magical (a
static method call that actually pauses execution feels out of place), and
would allow Fibers to be returned from methods, named functions, etc with
less boilerplate.Wishing this to be accepted by the community in the PHP 7.3, so no keyword
is accepted.
And if the community cannot accept, the Fiber can be still distributed as
a standalone
extension. So we cannot depend on a new keyword.
Right, if it's a standalone extension it can't use a new keyword, but as a
language feature it totally can.
Looking at the current README, there are two issues that must be completely
solved IMO before accepting this:
Each Fiber has a separate 4k stack. You can use the fiber.stack_size ini
option to change the default stack size. You can also use the second
argument of Fiber::__construct to set the stack size on fly.
Resizing of the stack should happen automatically, just like generators
resize automatically.
Fiber::yield cannot be used in internal callback
This also seems problematic and will make fibers quite less useful,
especially as these yields can happen anywhere down the stack.
Regards, Niklas
- How do you determine when a fiber has returned? Looking at the source,
it appears Fiber::status() must be used, comparing against constants.
Separate methods similar to Generator would be better IMO. e.g.:
Fiber::alive(), Fiber::suspended(), Fiber::running()Offering methods like Fiber::alive, Fiber::running makes no difference to
check the Fiber::status() return value. This is just a style issue. And as
a language feature,
Fiber only offer the essential API and let other works to the user land.The language should offer a sane API, not the absolute minimum required to
work for these things.
The Ruby's Fiber do offer a live? method but does not have a getStatus method.
The Lua's coroutine only offer a status method.
So do we really need to offer three additional helper method? Or what is your
advice about these API?
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land code
could check
the value of Fiber::yield and throw exception themselves. The Ruby's Fiber
and Lua's
coroutine also does not support such api as well.And throw the exception where? That means async code with fibers can't
really handle errors?
Actually you can transfer any thing to Fiber by the resume method. And you can
check the return value of Fiber::yield to handle error.
Fiber is designed as a primitive, low level, and lightweight feature. User land
code seldom not need to use them directly in your normal code.
So the following is not a big problem,
$a = Fiber::yield(...);
if ($a === false) {
throw new Exception(...);
}
And both the Ruby and Lua does not offer such API as well.
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.
This Ruby's Fiber and Lua's coroutine using one resume API to init and resume
the coroutine. I do not think a dedicate is really required.
The generator cannot be init by it's send method. And if you want to implement
coroutine feature(without stack) by it, you have to write code
function run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = null;
return $retval;
}
}
It is verbose.
See https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
Please see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of a
fiber?You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC
impact. Fibers could then be written like generators.await
oremit
as
a keyword perhaps? This would be a less verbose API, feel less magical (a
static method call that actually pauses execution feels out of place), and
would allow Fibers to be returned from methods, named functions, etc with
less boilerplate.Wishing this to be accepted by the community in the PHP 7.3, so no keyword
is accepted.
And if the community cannot accept, the Fiber can be still distributed as
a standalone
extension. So we cannot depend on a new keyword.Right, if it's a standalone extension it can't use a new keyword, but as a
language feature it totally can.
In my opinion, using a keyword or call a method is just a coding style problem.
Introducing a new keyword does not offer any benefit by makes a minor BC.
Both Ruby's Fiber and Lua's coroutine does not required a dedicate keyword.
Looking at the current README, there are two issues that must be completely
solved IMO before accepting this:Each Fiber has a separate 4k stack. You can use the fiber.stack_size ini
option to change the default stack size. You can also use the second
argument of Fiber::__construct to set the stack size on fly.Resizing of the stack should happen automatically, just like generators
resize automatically.
This size is the init stack size. It means when a Fiber created, it will get a
dedicate stack of 4k size. When the fiber use all the stack space, zend vm will
allocate additional space for feature call frame, automatically.
The default size is 4k means that every fiber requires at least 4k memory to use
as there own stack. But user can change this by php.ini and the construct argument
to reduce the memory footprint.
Fiber::yield cannot be used in internal callback
This also seems problematic and will make fibers quite less useful,
especially as these yields can happen anywhere down the stack.
This do be a problem. But not so much big problem.
You cannot use Fiber::yield like
$f = new Fiber(function () {
array_map(function ($i) {
Fiber::yield($i);
}, [1, 2, 3]);
});
$f->resume();
$f->resume();
Because when zend execute the array_map, it will push a new frame onto the c stack.
When zend execute Fiber::yield, it only backup it's php stack, and it's c stack
will be overwrites.
However, you can use Fiber::yield like
$f = new Fiber(function () {
Fiber::yield(1); // will cause by resume, by an internal callback
});
ExtEventLoop::onRead($fd, function () { // this is an internal call
$f->resume();
});
Regards, Niklas
This has already been discussed in github
Fiber::resume()
https://github.com/fiberphp/fiber-ext/issues/7
Fiber::throw()
https://github.com/fiberphp/fiber-ext/issues/6
Keywords - async/await
https://github.com/fiberphp/fiber-ext/issues/10
I believe that current API is normal for the Pecl ext, but to become
semantics of PHP language, the API needs to be improved
The language should offer a sane API, not the absolute minimum required
to
work for these things.The Ruby's Fiber do offer a live? method but does not have a getStatus
method.
The Lua's coroutine only offer a status method.So do we really need to offer three additional helper method? Or what is
your
advice about these API?
What's the downside?
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land
code
could check
the value of Fiber::yield and throw exception themselves. The Ruby's
Fiber
and Lua's
coroutine also does not support such api as well.And throw the exception where? That means async code with fibers can't
really handle errors?Actually you can transfer any thing to Fiber by the resume method. And you
can
check the return value of Fiber::yield to handle error.Fiber is designed as a primitive, low level, and lightweight feature. User
land
code seldom not need to use them directly in your normal code.
So the following is not a big problem,$a = Fiber::yield(...);
if ($a === false) {
throw new Exception(...);
}And both the Ruby and Lua does not offer such API as well.
We have started building a PoC library on top of Fibers, see
https://github.com/amphp/green-thread/blob/7bd3470e7986169372d5e9c39500f3652091b512/src/functions.php
.
We'd like to avoid the additional await()
function and rather directly
couple Fibers with promises in the PHP core.
Using await
(the keyword) instead of Fiber::yield()
feels way better
and avoids the need for any wrapping libraries for what should be a core
feature.
async
can be added to create a new Fiber and Fiber can implement Promise.
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.This Ruby's Fiber and Lua's coroutine using one resume API to init and
resume
the coroutine. I do not think a dedicate is really required.The generator cannot be init by it's send method. And if you want to
implement
coroutine feature(without stack) by it, you have to write codefunction run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = null;
return $retval;
}
}It is verbose.
That's why we directly prime coroutines on creation, see
https://github.com/amphp/amp/blob/4a742beb59615f36ed998e2dc210e36576e44c44/lib/Coroutine.php#L36-L52
See https://nikic.github.io/2012/12/22/Cooperative-
multitasking-using-coroutines-in-PHP.htmlPlease see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of
a
fiber?You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC
impact. Fibers could then be written like generators.await
oremit
as
a keyword perhaps? This would be a less verbose API, feel less magical
(a
static method call that actually pauses execution feels out of place),
and
would allow Fibers to be returned from methods, named functions, etc
with
less boilerplate.Wishing this to be accepted by the community in the PHP 7.3, so no
keyword
is accepted.
And if the community cannot accept, the Fiber can be still distributed
as
a standalone
extension. So we cannot depend on a new keyword.Right, if it's a standalone extension it can't use a new keyword, but as
a
language feature it totally can.In my opinion, using a keyword or call a method is just a coding style
problem.
Introducing a new keyword does not offer any benefit by makes a minor BC.
It makes code way easier to write IMO.
Both Ruby's Fiber and Lua's coroutine does not required a dedicate keyword.
Looking at the current README, there are two issues that must be
completely
solved IMO before accepting this:Each Fiber has a separate 4k stack. You can use the fiber.stack_size ini
option to change the default stack size. You can also use the second
argument of Fiber::__construct to set the stack size on fly.Resizing of the stack should happen automatically, just like generators
resize automatically.This size is the init stack size. It means when a Fiber created, it will
get a
dedicate stack of 4k size. When the fiber use all the stack space, zend vm
will
allocate additional space for feature call frame, automatically.The default size is 4k means that every fiber requires at least 4k memory
to use
as there own stack. But user can change this by php.ini and the construct
argument
to reduce the memory footprint.
That's good to hear! Any reason why these need to be configureable? They're
not for generators either. IMO a sane default would probably be enough.
Fiber::yield cannot be used in internal callback
This also seems problematic and will make fibers quite less useful,
especially as these yields can happen anywhere down the stack.This do be a problem. But not so much big problem.
You cannot use Fiber::yield like
$f = new Fiber(function () {
array_map(function ($i) {
Fiber::yield($i);
}, [1, 2, 3]);
});
$f->resume();
$f->resume();Because when zend execute the array_map, it will push a new frame onto the
c stack.
When zend execute Fiber::yield, it only backup it's php stack, and it's c
stack
will be overwrites.However, you can use Fiber::yield like
$f = new Fiber(function () {
Fiber::yield(1); // will cause by resume, by an internal callback
});ExtEventLoop::onRead($fd, function () { // this is an internal call
$f->resume();
});
The issue here is not using it directly within array_map, but rather
somewhere down the stack, which a user can never know.
This might result in subtle BC breaks. It means that a Fiber::yield() can
never be added to code without breaking BC. I guess we need to rewrite all
functions that accept user-defined callbacks for it to work.
IMO PHP's main() function should be a Fiber, too, which makes it impossible
to use Fiber::yield() outside a Fiber and enables top-level await().
Regards, Niklas
Den 2018-02-09 kl. 12:48, skrev Niklas Keller:
We have started building a PoC library on top of Fibers, see
https://github.com/amphp/green-thread/blob/7bd3470e7986169372d5e9c39500f3652091b512/src/functions.php
.We'd like to avoid the additional
await()
function and rather directly
couple Fibers with promises in the PHP core.Using
await
(the keyword) instead ofFiber::yield()
feels way better
and avoids the need for any wrapping libraries for what should be a core
feature.
async
can be added to create a new Fiber and Fiber can implement Promise.
Regarding these potential new keywords await & async.
Any need to look into how Hacklang uses these keywords?
Could there be portability aspects on the functionality in
itself?
Regards //Björn Larsson
2018-02-09 21:53 GMT+01:00 Björn Larsson bjorn.x.larsson@telia.com:
Regarding these potential new keywords await & async.
Any need to look into how Hacklang uses these keywords?
Could there be portability aspects on the functionality in
itself?
No, there's no need to look at Hacklang and be compatible. They do
their own thing.
Regards, Niklas
Hi, all,
I have updated the RFC https://wiki.php.net/rfc/fiber
changes list:
- introduce the
throw(Exception $exceptin)
API - record issues discussed
- How do you determine when a fiber has returned? Looking at the source,
it appears Fiber::status() must be used, comparing against constants.
Separate methods similar to Generator would be better IMO. e.g.:
Fiber::alive(), Fiber::suspended(), Fiber::running()Offering methods like Fiber::alive, Fiber::running makes no difference to
check the Fiber::status() return value. This is just a style issue. And as
a language feature,
Fiber only offer the essential API and let other works to the user land.The language should offer a sane API, not the absolute minimum required to
work for these things.The Ruby's Fiber do offer a live? method but does not have a getStatus method.
The Lua's coroutine only offer a status method.So do we really need to offer three additional helper method? Or what is your
advice about these API?
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land code
could check
the value of Fiber::yield and throw exception themselves. The Ruby's Fiber
and Lua's
coroutine also does not support such api as well.And throw the exception where? That means async code with fibers can't
really handle errors?Actually you can transfer any thing to Fiber by the resume method. And you can
check the return value of Fiber::yield to handle error.Fiber is designed as a primitive, low level, and lightweight feature. User land
code seldom not need to use them directly in your normal code.
So the following is not a big problem,$a = Fiber::yield(...);
if ($a === false) {
throw new Exception(...);
}And both the Ruby and Lua does not offer such API as well.
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.This Ruby's Fiber and Lua's coroutine using one resume API to init and resume
the coroutine. I do not think a dedicate is really required.The generator cannot be init by it's send method. And if you want to implement
coroutine feature(without stack) by it, you have to write codefunction run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = null;
return $retval;
}
}It is verbose.
See https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
Please see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of a
fiber?You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC
impact. Fibers could then be written like generators.await
oremit
as
a keyword perhaps? This would be a less verbose API, feel less magical (a
static method call that actually pauses execution feels out of place), and
would allow Fibers to be returned from methods, named functions, etc with
less boilerplate.Wishing this to be accepted by the community in the PHP 7.3, so no keyword
is accepted.
And if the community cannot accept, the Fiber can be still distributed as
a standalone
extension. So we cannot depend on a new keyword.Right, if it's a standalone extension it can't use a new keyword, but as a
language feature it totally can.In my opinion, using a keyword or call a method is just a coding style problem.
Introducing a new keyword does not offer any benefit by makes a minor BC.Both Ruby's Fiber and Lua's coroutine does not required a dedicate keyword.
Looking at the current README, there are two issues that must be completely
solved IMO before accepting this:Each Fiber has a separate 4k stack. You can use the fiber.stack_size ini
option to change the default stack size. You can also use the second
argument of Fiber::__construct to set the stack size on fly.Resizing of the stack should happen automatically, just like generators
resize automatically.This size is the init stack size. It means when a Fiber created, it will get a
dedicate stack of 4k size. When the fiber use all the stack space, zend vm will
allocate additional space for feature call frame, automatically.The default size is 4k means that every fiber requires at least 4k memory to use
as there own stack. But user can change this by php.ini and the construct argument
to reduce the memory footprint.Fiber::yield cannot be used in internal callback
This also seems problematic and will make fibers quite less useful,
especially as these yields can happen anywhere down the stack.This do be a problem. But not so much big problem.
You cannot use Fiber::yield like
$f = new Fiber(function () {
array_map(function ($i) {
Fiber::yield($i);
}, [1, 2, 3]);
});
$f->resume();
$f->resume();Because when zend execute the array_map, it will push a new frame onto the c stack.
When zend execute Fiber::yield, it only backup it's php stack, and it's c stack
will be overwrites.However, you can use Fiber::yield like
$f = new Fiber(function () {
Fiber::yield(1); // will cause by resume, by an internal callback
});ExtEventLoop::onRead($fd, function () { // this is an internal call
$f->resume();
});Regards, Niklas
Hi, all,
I have updated the RFC https://wiki.php.net/rfc/fiber
changes list:
- introduce the
throw(Exception $exceptin)
API- record issues discussed
What about my suggestion of making PHP's main() automatically a Fiber,
which avoids Fiber::yield() being used outside of a Fiber, because
everything is a Fiber?
This would require another continuation mechanism, as nothing has
access to the automatically created main()-Fiber otherwise.
There would also need to be something that stops the script execution
as soon as there's no non-suspended Fiber anymore.
Regarding the internal calls: A core dump / segfault in case of
Fiber::yield() inside an internal function is unacceptable. It doesn't
give the user any clue what's wrong. Instead, an exception could be
thrown from Fiber::yield(), which just bubbles up then. Full support
for internal functions could be added at a later point then.
Regards, Niklas
Hi, all,
I have updated the RFC https://wiki.php.net/rfc/fiber
changes list:
- introduce the
throw(Exception $exceptin)
API- record issues discussed
What about my suggestion of making PHP's main() automatically a Fiber,
which avoids Fiber::yield() being used outside of a Fiber, because
everything is a Fiber?This would require another continuation mechanism, as nothing has
access to the automatically created main()-Fiber otherwise.There would also need to be something that stops the script execution
as soon as there's no non-suspended Fiber anymore.
As you pointed out, the main process cannot be a Fiber because it need to
schedule other fibers.
Regarding the internal calls: A core dump / segfault in case of
Fiber::yield() inside an internal function is unacceptable. It doesn't
give the user any clue what's wrong. Instead, an exception could be
thrown from Fiber::yield(), which just bubbles up then. Full support
for internal functions could be added at a later point then.
It is impossible to solve this issue before we get a pure stackless Zend VM.
If this feature can be merged into PHP 7.3, we could introduce a new counter
to record the zend vm nested level. Every time you enter an internal call,
let zend increment the counter, and decrement it when out.
When we create a fiber, we remember current vm nested level. When zend execute
Fiber::yield,it will throw an error if the current vm nested level is not
equal to the original one.
This feature cannot be implemented without changing the zend vm code base.
Maybe we could get a stackless zend vm in the feature. like the stackless python
https://github.com/stackless-dev/stackless
Regards, Niklas
There would also need to be something that stops the script execution
as soon as there's no non-suspended Fiber anymore.As you pointed out, the main process cannot be a Fiber because it need to
schedule other fibers.
The conclusion is wrong. There just needs to be a way to access this
fiber, e.g. Fiber::getMain().
Regarding the internal calls: A core dump / segfault in case of
Fiber::yield() inside an internal function is unacceptable. It doesn't
give the user any clue what's wrong. Instead, an exception could be
thrown from Fiber::yield(), which just bubbles up then. Full support
for internal functions could be added at a later point then.It is impossible to solve this issue before we get a pure stackless Zend VM.
If this feature can be merged into PHP 7.3, we could introduce a new counter
to record the zend vm nested level. Every time you enter an internal call,
let zend increment the counter, and decrement it when out.When we create a fiber, we remember current vm nested level. When zend execute
Fiber::yield,it will throw an error if the current vm nested level is not
equal to the original one.This feature cannot be implemented without changing the zend vm code base.
You're proposing this as language feature, not as an external
extension, so that is entirely fine and actually the reason why this
should be in core instead of an external extension.
Regards, Niklas
Hello,
I've only briefly tested current implementation and from my point of view,
people new to the concept of coroutines, the async/await syntax might be
much more readable and understandable what is happening behind and why this
might be useful to have in the core. But yes, I'm sure there is a lot to
integrate and refactor in PHP for that matter. Complex things are never
simple...
I think the implementation should be rethought a bit more. Especially for
the future of asynchronous PHP in general.
If nothing else, maybe the RFC should also explain in more details why
introducing the static calling of the Fiber::yield
instead of doing a bit
more complex integration (from C point of view) and much more readable and
understandable async/await
syntax.
The latest JavaScript and Hacklang have this quite
understandable and readable solved, compared to Ruby's Fibers
implementation.
The RFC might also need to dive deeper into the benefits, and refactor the
current usage example or add some more links what is this chapter all about.
Thanks for all your work so far though. Awesome stuff nonetheless.
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.This Ruby's Fiber and Lua's coroutine using one resume API to init and
resume
the coroutine. I do not think a dedicate is really required.
I agree, two methods are probably not required.
But I also agree with Aaron that the verb "resume" is confusing/misleading
here - it implies something has already been running, which isn't the case
when you initially create the Fiber.
I'd suggest changing the name to start() - you can "start" something
whether it's starting for the first time or starting again after a pause,
but to "resume" something, it must have been already running, so it's a bit
confusing.
- How do you determine when a fiber has returned? Looking at the
source,
it appears Fiber::status() must be used, comparing against constants.
Separate methods similar to Generator would be better IMO. e.g.:
Fiber::alive(), Fiber::suspended(), Fiber::running()Offering methods like Fiber::alive, Fiber::running makes no difference
to
check the Fiber::status() return value. This is just a style issue. And
as
a language feature,
Fiber only offer the essential API and let other works to the user land.The language should offer a sane API, not the absolute minimum required
to
work for these things.The Ruby's Fiber do offer a live? method but does not have a getStatus
method.
The Lua's coroutine only offer a status method.So do we really need to offer three additional helper method? Or what is
your
advice about these API?
- What about throwing exceptions into a fiber?
Currently does not support throw exception into the fiber. User land
code
could check
the value of Fiber::yield and throw exception themselves. The Ruby's
Fiber
and Lua's
coroutine also does not support such api as well.And throw the exception where? That means async code with fibers can't
really handle errors?Actually you can transfer any thing to Fiber by the resume method. And you
can
check the return value of Fiber::yield to handle error.Fiber is designed as a primitive, low level, and lightweight feature. User
land
code seldom not need to use them directly in your normal code.
So the following is not a big problem,$a = Fiber::yield(...);
if ($a === false) {
throw new Exception(...);
}And both the Ruby and Lua does not offer such API as well.
- Using Fiber::resume() to initialize the fiber and resume feels
awkward. Separate methods again would be better here, perhaps
Fiber::init(...$args) and Fiber::resume($send).All Fiber created with a suspended status. So make resume to do both the
init and resume
do make sense.It does't make sense to me. Reading the example in the README and
understanding why the first resume() takes two arguments instead of one
took me quite some minutes.This Ruby's Fiber and Lua's coroutine using one resume API to init and
resume
the coroutine. I do not think a dedicate is really required.The generator cannot be init by it's send method. And if you want to
implement
coroutine feature(without stack) by it, you have to write codefunction run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = null;
return $retval;
}
}It is verbose.
See https://nikic.github.io/2012/12/22/Cooperative-
multitasking-using-coroutines-in-PHP.htmlPlease see Ruby Fiber API https://ruby-doc.org/core-2.2.0/Fiber.html.
- What happens if the sub1() function in the RFC is invoked outside of
a
fiber?You will get a Fatal Error like
Fatal error: Uncaught Error: Cannot call Fiber::yield out of Fiber
- I think a keyword here would be beneficial, even if it has a minor BC
impact. Fibers could then be written like generators.await
oremit
as
a keyword perhaps? This would be a less verbose API, feel less magical
(a
static method call that actually pauses execution feels out of place),
and
would allow Fibers to be returned from methods, named functions, etc
with
less boilerplate.Wishing this to be accepted by the community in the PHP 7.3, so no
keyword
is accepted.
And if the community cannot accept, the Fiber can be still distributed
as
a standalone
extension. So we cannot depend on a new keyword.Right, if it's a standalone extension it can't use a new keyword, but as
a
language feature it totally can.In my opinion, using a keyword or call a method is just a coding style
problem.
Introducing a new keyword does not offer any benefit by makes a minor BC.Both Ruby's Fiber and Lua's coroutine does not required a dedicate keyword.
Looking at the current README, there are two issues that must be
completely
solved IMO before accepting this:Each Fiber has a separate 4k stack. You can use the fiber.stack_size ini
option to change the default stack size. You can also use the second
argument of Fiber::__construct to set the stack size on fly.Resizing of the stack should happen automatically, just like generators
resize automatically.This size is the init stack size. It means when a Fiber created, it will
get a
dedicate stack of 4k size. When the fiber use all the stack space, zend vm
will
allocate additional space for feature call frame, automatically.The default size is 4k means that every fiber requires at least 4k memory
to use
as there own stack. But user can change this by php.ini and the construct
argument
to reduce the memory footprint.Fiber::yield cannot be used in internal callback
This also seems problematic and will make fibers quite less useful,
especially as these yields can happen anywhere down the stack.This do be a problem. But not so much big problem.
You cannot use Fiber::yield like
$f = new Fiber(function () {
array_map(function ($i) {
Fiber::yield($i);
}, [1, 2, 3]);
});
$f->resume();
$f->resume();Because when zend execute the array_map, it will push a new frame onto the
c stack.
When zend execute Fiber::yield, it only backup it's php stack, and it's c
stack
will be overwrites.However, you can use Fiber::yield like
$f = new Fiber(function () {
Fiber::yield(1); // will cause by resume, by an internal callback
});ExtEventLoop::onRead($fd, function () { // this is an internal call
$f->resume();
});Regards, Niklas
I'm just sneaking into the subject to point out that theres already some
work on this with the PHP swoole extension, in case it hasn't been
looked/taked into consideration yet:
https://github.com/swoole/swoole-src#coroutine
https://github.com/swoole/swoole-src#short-api-name
It has great performance and the event based http server demonstrates
how well it performs. Great to see stuff like this could be shipped as
part of the core which transcend PHP uses.
I've used Swoole, and it is in deed a very high quality extension - both in
terms of performance and stability.
I haven't used the low-level co-routines, but the HTTP server was rock
steady, no memory leaks, easy to implement and CRAZY fast :-)
Kind of turns PHP into a better version of Node tbh ;-)
I'm just sneaking into the subject to point out that theres already some
work on this with the PHP swoole extension, in case it hasn't been
looked/taked into consideration yet:https://github.com/swoole/swoole-src#coroutine
https://github.com/swoole/swoole-src#short-api-nameIt has great performance and the event based http server demonstrates how
well it performs. Great to see stuff like this could be shipped as part of
the core which transcend PHP uses.
Fiber and all async functions, without event-loop - useless.
Need new PHP Application Server.
Best variant libuv
in core PHP for event-loop and async I/O, and
implementing as async PHP SAPI as Nginx Unit
module
http://unit.nginx.org/
Thanks.
Fiber and all async functions, without event-loop - useless.
Need new PHP Application Server.
Best variantlibuv
in core PHP for event-loop and async I/O, and
implementing as async PHP SAPI asNginx Unit
module
These are all good ideas, but I don't think any of them are logical "must haves".
- Async functions might be useful in all sorts of contexts outside of a global event loop. For instance, handling non-blocking I/O.
- An event loop doesn't necessarily imply a built-in Application Server, e.g. a worker for a message queue or task broker.
- A full Application Server implies important decisions about the language itself, like how to represent requests and responses, how global and static scopes work, etc. These need to be designed in the abstract, and an interface like Python's WSGI defined, before worrying about implementation details.
- Once we've standardised the userland, the internal Async SAPI interface can be implemented, and an Nginx Node might well be one of the implementations, but that's not something we need to think about yet.
Regards,
--
Rowan Collins
[IMSoP]