This is an old revision of the document!
PHP RFC: Fiber
- Version: 0.1
- Date: 2017-09-13
- Author: Haitao Lvi@lvht.net, Dmitry Stogovdmitry@zend.com, Martin Schröderm.schroeder2007@googlemail.com
- Status: Under Discussion
- First Published at: http://wiki.php.net/rfc/fiber
Introduction
Fibers are primitives for implementing light weight cooperative concurrency in PHP. Basically they are a means of creating Closure that can be paused and resumed. The scheduling of fiber must be done by the programmer and not the VM.
As opposed to Generator (a stackless light weight concurrency implementation), each fiber comes with a stack. This enables the fiber to be paused from deeply nested function calls within the fiber block.
When a fiber is created it will not run automatically. Rather it must be explicitly asked to run using the Fiber::resume method. The code running inside the fiber can give up control by calling Fiber::yield in which case it yields control back to caller (the caller of the Fiber::resume).
Proposal
Why not make it as a Extension?
Fiber is a major language feature, that allows significant benefits for asynchronous frameworks. Providing it as an optional extension, just doesn't make sense.
Implementation
Proposed API
final class Fiber { public const STATUS_SUSPENDED = 1; public const STATUS_RUNNING = 2; public const STATUS_FINISHED = 3; public const STATUS_DEAD = 4; /** * @param callable $callable any php callable to be paused * @param int $stack_size fiber stack init size */ public function __construct(callable $callable = null, int stack_size = null) {} /** * pause the current fiber and ~return~ the $arg1 * as the Fiber::resume's return value. */ public static function yield($arg1) {} /** * Start or resume a fiber. * * If the fiber is not started, call resume will init * the $callable with all args. * * If the fiber is paused, call resume will send the first arg * as the last Fiber::yield's return value. */ public function resume($arg1...) {} /** * Throw an exception into the fiber. * * You code can use try/catch to process error in the * top level function call. Some framework make heavy * usage of this feature. */ public function throw(Throwable $e) {} }
Usage Demo
function sub1() { return Fiber::yield(1); } $fiber = new Fiber(function ($a, $b) { $c = Fiber::yield($a + $b); $d = sub1(); return $d.$c; }); echo $fiber->resume(1, 2); // echo 3 echo $fiber->resume("world"); // echo 1 echo $fiber->resume("hello "); // echo "hello world"
Implementation Detail
In our simple implementation, we only backup/restore the zend stack. If you pause a Fiber in some internal function call like `array_map`, the c stack frame maybe over write which will trigger coredump.
Martin Schröder has another implementation at https://github.com/fiberphp/fiber-ext/pull/30. Martin's work backup/restore both the zend stack and the c stack.
Property | Martin's Fiber | Our Fiber |
---|---|---|
Minimum Memory Usage | VM stack only (4 KB) | VM & C stack (4 KB + 4 KB) |
Supported Architecturs | any platform supported by compiler | only x86 systems at this time |
Yield in Internal Function | unsupported | supported |
Yield in Iterator | unsupported | supported |
Backward Incompatible Changes
“Fiber” are now globally defined classes, which might collide with user defined classes of the same name in the global namespace. However, the risk of the introduction of them is considered to be very low, since the global namespace should not be used by PHP users.
Proposed PHP Version(s)
7.3
RFC Impact
To SAPIs
None
To Existing Extensions
None
To Opcache
None
New Constants
None
php.ini Defaults
- fiber.stack_size default stack size for A fiber
Open Issues
What happens if there are internal calls on the call stack?Say something like array_map(function() { await; }, [1, 2, 3]); inside a fiber.
Calling Fiber::yield in a internal call will trigger a fatal error.
How do you determine when a fiber has returned? Separate methods similar to Generator would be better.
Offering methods like Fiber::alive(),Fiber::running() makes any meaningful difference to check the return value of Fiber::status(). It is just a coding style issue.
And as a language feature, Fiber should only offer the essential API and let other works to the userland code.
Using Fiber::resume() to initialize the fiber and resume feels awkward. Separate methods again would be better.
Both Ruby's Fiber and Lua's coroutine using the same resume() API to init and resume their coroutine.
I think a keyword here would be beneficial, even if it has a minor BC impact.
Introducing new keywords like await/emit does not offer any essential benefit but only cause BC impact.
Both Ruby's Fiber and Lua's coroutine use method to pause and resume their coroutine.
Unaffected PHP Functionality
None
Future Scope
This sections details areas where the feature might be improved in future, but that are not currently proposed in this RFC.
Proposed Voting Choices
2/3+1 voting majority
Patches and Tests
Implementation
After the project is implemented, this section should contain
- the version(s) it was merged to
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
- a link to the language specification section (if any)