Good day, Alex. > > Can you please share a bit more details on how the Scheduler is implemented, to make sure that I understand why this contradiction exists? Also with some examples, if possible. >
```php $fiber1 = new Fiber(function () { echo "Fiber 1 starts\n"; $fiber2 = new Fiber(function () use (&$fiber1) { echo "Fiber 2 starts\n"; Fiber::suspend(); // Suspend the inner fiber echo "Fiber 2 resumes\n"; }); }); ``` Yes, of course, let's try to look at this in more detail. Here is the classic code demonstrating how Fiber works. Fiber1 creates Fiber2. When Fiber2 yields control, execution returns to Fiber1. Now, let's try to do the same thing with Fiber3. Inside Fiber2, we create Fiber3. Everything will work perfectly—Fiber3 will return control to Fiber2, and Fiber2 will return it to Fiber1—this forms a hierarchy. Now, imagine that we want to turn Fiber1 into a *Scheduler* while following these rules. To achieve this, we need to ensure that all Fiber instances are created from the *Scheduler*, so that control can always be properly returned. ```php class Scheduler { private array $queue = []; public function add(callable $task) { $fiber = new Fiber($task); $this->queue[] = $fiber; } public function run() { while (!empty($this->queue)) { $fiber = array_shift($this->queue); if ($fiber->isSuspended()) { $fiber->resume($this); } } } public function yield() { $fiber = Fiber::getCurrent(); if ($fiber) { $this->queue[] = $fiber; Fiber::suspend(); } } } $scheduler = new Scheduler(); $scheduler->add(function (Scheduler $scheduler) { echo "Task 1 - Step 1\n"; $scheduler->yield(); echo "Task 1 - Step 2\n"; }); $scheduler->add(function (Scheduler $scheduler) { echo "Task 2 - Step 1\n"; $scheduler->yield(); echo "Task 2 - Step 2\n"; }); $scheduler->run(); ``` So, to successfully switch between Fibers: 1. A Fiber must return control to the *Scheduler*. 2. The *Scheduler* selects the next Fiber from the queue and switches to it. 3. That Fiber then returns control back to the *Scheduler* again. This algorithm has one drawback: *it requires two context switches instead of one*. We could switch *FiberX* to *FiberY* directly. Breaking the contract not only disrupts the code in this RFC but also affects Revolt's functionality. However, in the case of Revolt, you can say: *"If you use this library, follow the library's contracts and do not use Fiber directly."* But PHP is not just a library, it's a language that must remain consistent and cohesive. > > Reading the RFC initially, I though that the Scheduler is using fibers for everything that runs. > Exactly. > > You mean that when one of the fibers started by the Scheduler is starting other fibers they would usually await for them to finish, and that is a blocking operating that blocks also the Scheduler? > When a *Fiber* from the *Scheduler* decides to create another *Fiber* and then tries to call blocking functions inside it, control can no longer return to the *Scheduler* from those functions. Of course, it would be possible to track the state and disable the concurrency mode flag when the user manually creates a *Fiber*. But… this wouldn't lead to anything good. Not only would it complicate the code, but it would also result in a mess with different behavior inside and outside of *Fiber*. This is even worse than calling *startScheduler*. The hierarchical switching rule is a *design flaw* that happened because a *low-level component* was introduced into the language as part of the implementation of a *higher-level component*. However, the high-level component is in *User-land*, while the low-level component is in *PHP core*. It's the same as implementing $this in OOP but requiring it to be explicitly passed in every method. This would lead to inconsistent behavior. So, this situation needs to be resolved one way or another. -- Ed