Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126547 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 0233E1A00BC for ; Mon, 3 Mar 2025 09:34:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1740994338; bh=w4mGBvUEP8hJ038LlgjCIXiBHpBvnRVBWNpAd8/jG+w=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=kTyoSSFmI0ElYasHwrx3edY0FCclJoC2FZJX7LAJAG4exA7aq6oAVSKwEZEa5oTnD j6dGsXaJh5Mq9U2YXyNqs3GdAr75CkTKIMRdsLHQaL7OkTatL/wp1wCbNrar/mD0aO AK+gwgV0F7b364yR89Dv35GMbMK5DLpv0T6uYam6AyLXbtDDCytv1vNdl7j4949XLs Av2FSb3/gHHbps3hy5fyiTqEUSEbvQrNIfBIUp2Pdnpqve0jg0AcwHFvEtDrveD28X 75AGNiZ5qOP133ABePG4h2nJdbtKPMxyZgBRl2UyjsBQkjMBvXx3Pw0LpRvfME5/pT YcQNMEpHdaxbQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 65035180080 for ; Mon, 3 Mar 2025 09:32:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-3.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-yb1-f180.google.com (mail-yb1-f180.google.com [209.85.219.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 3 Mar 2025 09:32:16 +0000 (UTC) Received: by mail-yb1-f180.google.com with SMTP id 3f1490d57ef6-e60b75f8723so1867674276.0 for ; Mon, 03 Mar 2025 01:34:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1740994492; x=1741599292; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=fPr4bS+xVDtruDn+NsuVobSD8/YQt1++ZL0O8iM1P9g=; b=LnJ2u32Qd6/T431mcr7K7L/d/OYWlSAmgPUCwAYMjHEMc9ju/qvg8t4lgi1ZHrlKT+ jzpm7pja3KdssqG3ySB6sq+cklr2kCuMB50wezZEf1PnGuec/gzdSJPfJtR3pF31ocf3 dnW2EsAZLrOqRN+MNoavUx8Ifluv8tO4Ju3o84qBROHOtd2jgdPgxV2Y1B93QzKEOVGQ Jvzrn4Xfm2ldKvQTa/GKv6xCcX3VIyJQkkPSm1LlmxnFzS1OSrGot+xYUtSCRfXe7QPn kcGRMuJGAk8m8xNV09yKCQjOpWaPmHH8MkSpeysQiD6dBWL9ZltyDAYw///vrHu0eoMZ ih7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740994492; x=1741599292; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=fPr4bS+xVDtruDn+NsuVobSD8/YQt1++ZL0O8iM1P9g=; b=AAShrcYVnTZIPcUYNGS46xhcXZ6SlbFew/FICH75reQskwTiVzXveBQwXIJYRL3JxH d3E4K9nxFCTUoURyd4PZkaGwrGtNnGmWZEg/ek8USpZ9rwJPG+zE+9Ob+XQBh4Nt4dZE l/91JcoFgR75GZhZB2GEsBErhZhFAAweqv7FODbG12iNZJnIVfZRBY/7WtAqP2uwHmrt zeF+/Tl0FIAbIa50u/uymt7A8yHPwgwPtEcBVaWZ3Xt9VJGsAf7O/LNmCH8WjWngWVPW t/R9CJnTThKqA1QCM7aj3u8bLg1MklFtIsFGQ+O9qBAZfO8CbgdZ4sbZGJrLCkVRVQc/ 5dtw== X-Gm-Message-State: AOJu0YyXECEP+upEpMistah1Yh9Dtr/xDDLg6AnPUXWhW6wRP00Cas2m y6y9qzNum1PWka2PohajPH3KWG76istFMwYBHVjicKRLDeALzCr81CrE49qgn2/FS4zsmS2I2ci 5RO3twdCjqk9e/i2GIiYiXoM/4abLu7MH X-Gm-Gg: ASbGncvKcGNAJvwKvdAXLrRwPVgnUIk1TTtKc9terdzmxXWMZI/p7SuH6MBzPCImyJp N061xMM9+kJajqKtew7/Eb+ONdFYdMDjXyidOS8u0NekocrAO84a3KivxKArpseVBYz1PHf4C/Y YuM/QRN8tJsZet6rS0R8MZkSE+JA== X-Google-Smtp-Source: AGHT+IFdgY0qPaTsRwxifmwuehLh4aAL3E3imU9iGMMyy/g9p2rjAVNz4yqjoti+LcymrAnBVILJYBbNHklVrFyEK8U= X-Received: by 2002:a05:6902:1b08:b0:e57:5f47:220e with SMTP id 3f1490d57ef6-e60b2ea0a6bmr16446103276.11.1740994491757; Mon, 03 Mar 2025 01:34:51 -0800 (PST) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Mon, 3 Mar 2025 11:34:40 +0200 X-Gm-Features: AQ5f1JrrdZpLJcMSiYsTod8E2BxSa-90f4ly-CebjeSC6KaAqah3OQ1NzOlNv60 Message-ID: Subject: Re: [PHP-DEV] PHP True Async RFC To: Rob Landers Cc: internals@lists.php.net Content-Type: multipart/alternative; boundary="0000000000003cbde0062f6cde56" From: edmond.ht@gmail.com (Edmond Dantes) --0000000000003cbde0062f6cde56 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable > > There should be a defined ordering (or at least, some guarantees). The execution order, which is *part of the contract*, is as follows: 1. Microtasks are executed first. 2. Then I/O events and OS signals are processed. 3. Then timer events are executed. 4. Only after that are fibers scheduled for execution. In the current implementation, fibers are stored in *a queue without priorities* (this is not a random choice). During one cycle period, only one fiber is taken from the queue. This results in the following code (I've removed unnecessary details): do { execute_microtasks_handler(); has_handles =3D execute_callbacks_handler(circular_buffer_is_not_empty(&ASYNC_G(deferred_re= sumes))); execute_microtasks_handler(); bool was_executed =3D execute_next_fiber_handler(); if (UNEXPECTED( false =3D=3D has_handles && false =3D=3D was_executed && zend_hash_num_elements(&ASYNC_G(fibers_state)) > 0 && circular_buffer_is_empty(&ASYNC_G(deferred_resumes)) && circular_buffer_is_empty(&ASYNC_G(microtasks)) && resolve_deadlocks() )) { break; } } while (zend_hash_num_elements(&ASYNC_G(fibers_state)) > 0 || circular_buffer_is_not_empty(&ASYNC_G(microtasks)) || reactor_loop_alive_fn() ); If we go into details, it is also noticeable that microtasks are executed twice - before and after event processing - because an event handler might enqueue a microtask, and the loop ensures that this code executes as early as possible. The contract for the execution order of microtasks and events is important because it must be considered when developing event handlers. The concurrent iterator relies on this rule. However, making assumptions about when a fiber will be executed is *not* part of the contract, if only because this algorithm can be changed at any moment. *// Execution is paused until the fiber completes* $result =3D Async\await( > $fiber); // immediately enter $fiber without queuing So is it possible to change the execution order and optimize context switches? Yes, there are ways to do this. However, it would require modifying the Fiber code, possibly in a significant way (I haven't explored this aspect in depth). But=E2=80=A6 let's consider whether this would be a good idea. We have a web server. A single thread is handling five requests. They all compete with each other because this is a typical application interacting with MySQL. In each Fiber, you send a query and wait for the result as quickly as possible. In what case should we create a new coroutine within a request handler? The answer: usually, we do this when we want to run something in the background while continuing to process the request and return a response as soon as possible. In this paradigm, it is beneficial to execute coroutines in the order they were enqueued. For other scenarios, it might be a better approach for a child coroutine to execute immediately. In that case, these scenarios should be considered, and it may be worth introducing specific semantics for such cases. Won't php code behave exactly the same as it did before once enabling the > scheduler? Suppose we have a sleep() function. Normally, it calls php_sleep((unsigned int)num). The php_sleep function blocks the execution of the thread. But we need to add an alternative path: if (IN_ASYNC_CONTEXT) { async_wait_timeout((unsigned int) num * 1000, NULL); RETURN_LONG(0); } The IN_ASYNC_CONTEXT condition consists of two points: - The current execution context is inside a *Fiber*. - The *Scheduler* is active. What=E2=80=99s the difference? If the *Scheduler* is not active, calling sleep() will block the entire *Thread* because, without an event loop, it simply cannot correctly handle concurrency. However, if the *Scheduler* is active, the code will set up handlers and return control to the "main loop", which will pick the next Fiber from the queue, and so on. This means that *without a Scheduler and Reactor, concurrent execution is impossible (*without additional effort*)*. From the perspective of a PHP developer, if they are working with *AMPHP/Swoole*, nothing changes, because the code inside the if condition will *never execute* in their case. Does this change the execution order inside a *Fiber*? No. If you had code working with *RabbitMQ sockets*, and you copied this code into a *Fiber*, then enabled concurrency, it would work exactly the same way. If the code used *blocking sockets*, the *Fiber* would yield control to the *Scheduler*. And if two such *Fibers* are running, they will start working with *RabbitMQ sequentially*. Of course, each *Fiber* should use a different socket. The same applies to *CURL*. Do you have an existing module that sends requests to a service using *CURL* in a synchronous style? Just copy the code into a coroutine. This means *almost* 98% transparency. Why *almost*? Because there might be nuances in *helper functions* and *internal states*. There may also be *differences in OS state management* or *file system*, which could affect the final result. > How will a library take advantage of this feature if it cannot be certain= the scheduler is > running or not? Do I need to write a library for async and another versio= n for non-async? > Or do all the async functions with this feature work without the schedule= r running, or do > they throw a catchable error? > > This means that the launchScheduler() function should be called *only once* during the entire lifecycle of the application. If an error occurs and is not handled, the application should *terminate*. This is not a technical limitation but rather a *logical constraint*. If launchScheduler() were replaced with a CLI option, such as php --enable-scheduler, where the *Scheduler* is implicitly activated, then it would be like the *last line of code *it must exist *only once*. Will this change the way os signals are handled then? Will it break compatibility if a > library uses pcntl traps and I'm using true async traps too? Note there a= re several > different ways (timeout) signals are handled in PHP -- so if (per-chance)= the scheduler > could always be running, maybe we can unify the way signals are handled i= n php. > > > Regarding this phrase in the RFC: it refers to the *window close event* in Windows, which provides a few seconds before the process is forcibly terminated. There are signals intended for *application termination*, such as *SIGBREAK= * or *CTRL-C*, which should typically be handled in *only one place* in the application. Developers are often tempted to insert signal handlers in multiple locations, making the code dependent on the environment. But more importantly, this *should not happen at all*. *True Async* explicitly defines a *Flow* for emergency or unexpected application termination. Attempting to disrupt this *Flow* by adding a custom termination signal handler introduces *ambiguity*. There should be *only one* termination handler. And at the end of its execution, it *must* call gracefulShutdown. As for *pcntl*, this will need to be tested. > What if it never resumes at all? If a *Fiber* is never resumed, it means the application has completely crashed with no way to recover :) The RFC has *two sections* dedicated to this issue: *Cancellation Operation* + *Graceful Shutdown*. If the application *terminates due to an unhandled exception*, *all Fibers must be executed*. Any *Fiber* can be canceled *at any time*, and there is *no need* to use *explicit Cancellation*, which I personally find an inconvenient pattern. The RFC doesn=E2=80=99t mention the stack trace. Will it throw away any inf= ormation > about the inner exception? This is literally *"exception transfer"*. The stack trace will be exactly the same as if the exception were thrown at the call site. To be honest, I haven=E2=80=99t had enough time to thoroughly test this. Le= t's try it: 004+ Error Object 005+ ( 006+ [message:protected] =3D> Error 007+ [string:Error:private] =3D> 008+ [code:protected] =3D> 0 009+ [file:protected] =3D> async.php 010+ [line:protected] =3D> 8 011+ [trace:Error:private] =3D> Array 012+ ( 013+ [0] =3D> Array 014+ ( 015+ [function] =3D> {closure:{closure:async.php:3}:6} 016+ [args] =3D> Array 017+ ( 018+ ) 019+ ) 020+ [1] =3D> Array 021+ ( 022+ [file] =3D> async.php 023+ [line] =3D> 14 024+ [function] =3D> Async\launchScheduler 025+ [args] =3D> Array 026+ ( 027+ ) 028+ ) 029+ ) 030+ [previous:Error:private] =3D> 031+ ) Seems perfectly correct. What will calling exit or die do? I completely forgot about them! Well, of course, Swoole override them. This needs to be added to the TODO. Why is this the case? For example, consider a *long-running* application where a *service* is a class that remains in memory continuously. The *web server* receives an HTTP request and starts a *Fiber* for each request. Each request has its own *User Session ID*. You want to call a service function, but you *don=E2=80=99t* want to pass t= he *Session ID* every time, because there are also *5-10 other request-related variables*. However, you *cannot* simply store the *Session ID* in a class property, because *context switching is unpredictable*. At one moment, you're handling *Request #1*, and a second later, you're already processing *Request #2*. When a *Fiber* creates another *Fiber*, it copies a *reference* to the *con= text object*, which has *minimal performance impact* while maintaining execution *environment consistency*. *Closure variables work as expected *they are pure *closures* with no modifications. I didn=E2=80=99t mean that *True Async* breaks anything at the language lev= el. The issue is *logical*: You *cannot* use a *global variable* in two *Fibers*, modify it, read it, and expect its state to remain *consistent*. By this point we have covered FiberHandle, Resume, and Contexts. Now we > have Futures? Can we simplify this to just Futures? Why do we need all > these different ways to handle execution? *Futures* and *Notifiers* are two different patterns. - A *Future* changes its state *only once*. - A *Notifier* generates *one or more* events. - Internally, *Future* uses *Notifier*. In the *RFC*, I mention that these are essentially *two APIs*: - *High-level API* - *Low-level API* One of the open questions is whether both APIs should remain in *PHP-land*. The *low-level API* allows for close interaction with the *event loop*, which might be useful if someone wants to write a *service* in PHP that requires this level of control. Additionally, this API helps *minimize Fiber context switches*, since its callbacks execute *without switching*. This is *both an advantage and a disadvantage*. > It's also not clear what the value of most of these function is. For exam= ple: > > Your comment made me think, especially in the context of anti-patterns. And I agree that it's better to remove unnecessary methods than to let programmers shoot themselves in the foot. As for the single producer method, I am not sure why you would use this. > > Yes, in other languages there are no explicit restrictions. If the single producer approach is indeed rarely used, then it's not such an important feature to include. However, I lack certainty on whether it's truly a rare case. On the other hand, these functions are inexpensive to implement and do not affect performance. Moreover, they have another drawback: they increase the number of behavioral variants in a single class, which seems a more significant disadvantage than the frequency of use. It isn't clear what happens when `trySend` fails. Is this an error or does nothing? > > Yes, this is a documentation oversight. I'll add it to the TODO. Thinking through it, there may be cases where `trySend` is valid, Code using tryReceive could be useful in cases where a channel is used to implement a pool. Suppose you need to retrieve an object from the pool, but if it's not available, you=E2=80=99d prefer to do something else (like thro= w an exception) rather than block the fiber. Overall, though, you=E2=80=99re right =E2=80=94 it=E2=80=99s an antipattern= . It=E2=80=99s better to implement the pool as an explicit class and reserve channels for their classic use. Can you expand on what this means in the RFC? Why expose it if it shouldn't > be used? I answered a similar question above. > I also noticed that you seem to be relying heavily on the current > implementation to define behavior. I love an iterative approach: prototype =3D> RFC =3D> prototype =3D> RFC. Thank you for the excellent remarks and analysis! Ed. --0000000000003cbde0062f6cde56 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
There should be a defi= ned ordering (or at least, some guarantees).

The execution o= rder, which is part of the contract, is as follows:

    Microtasks are executed first.
  1. Then I/O events and OS signals are= processed.
  2. Then timer events are executed.
  3. Only after that= are fibers scheduled for execution.

In the current implementat= ion, fibers are stored in a queue without priorities (this is not a = random choice). During one cycle period, only one fiber is taken from the q= ueue.

This results in the following code (I've removed unnecess= ary details):=C2=A0

do {
=
execute_microtasks_handler();

has_handles =3D execute_callbacks_= handler(circular_buffer_is_not_empty(&ASYNC_G(deferred_resumes)));
<= br>execute_microtasks_handler();

bool was_executed =3D execute_next_= fiber_handler();

if (UNEXPECTED(
=C2=A0 =C2=A0 false =3D=3D has_h= andles
=C2=A0 =C2=A0 && false =3D=3D was_executed
=C2=A0 =C2= =A0 && zend_hash_num_elements(&ASYNC_G(fibers_state)) > 0=C2=A0 =C2=A0 && circular_buffer_is_empty(&ASYNC_G(deferred_re= sumes))
=C2=A0 =C2=A0 && circular_buffer_is_empty(&ASYNC_G(m= icrotasks))
=C2=A0 =C2=A0 && resolve_deadlocks()
=C2=A0 =C2= =A0 )) {
=C2=A0 =C2=A0 break;
}

} while (zend_hash_num_element= s(&ASYNC_G(fibers_state)) > 0
=C2=A0 =C2=A0 || circular_buffer_is= _not_empty(&ASYNC_G(microtasks))
=C2=A0 =C2=A0 || reactor_loop_alive= _fn()
);

= *// Execution is paused unti= l the fiber completes* $result =3D Async\await($fibe= r); // immediately enter $fiber without queuing=

So is it possible to change the execution or= der and optimize context switches? Yes, there are ways to do this. However,= it would require modifying the Fiber code, possibly in a sign= ificant way (I haven't explored this aspect in depth).

But=E2=80= =A6 let's consider whether this would be a good idea.

We have a w= eb server. A single thread is handling five requests. They all compete with= each other because this is a typical application interacting with MySQL.In each Fiber, you send a query and wait for the result as q= uickly as possible.

In what case should we create a new coroutine wit= hin a request handler?
The answer: usually, we do this when we want to r= un something in the background while continuing to process the request and = return a response as soon as possible.

In this paradigm, it is benefi= cial to execute coroutines in the order they were enqueued.

=C2=A0 Fo= r other scenarios, it might be a better approach for a child coroutine to e= xecute immediately. In that case, these scenarios should be considered, and= it may be worth introducing specific semantics for such cases.=C2=A0=C2=A0=


=C2=A0= Won't php code behave exactly the same as it did before once enabling = the scheduler?=C2=A0

Suppose we have a sle= ep() function. Normally, it calls php_sleep((unsigned int)num)= .
The php_sleep function blocks the execution of the= thread.
But we need to add an alternative path:=C2=A0
How will a library take advantag=
e of this feature if it cannot be certain the scheduler is
running or not? Do I need to write a library for async and another version =
for non-async?
Or do all the async functions with this feature work without the scheduler =
running, or do
they throw a catchable error?

This means that the launchScheduler() function should be called only once during the entire lifecycle of the application. If an error occurs and= is not handled, the application should terminate. This is= not a technical limitation but rather a logical constraint.

If launchScheduler() were replaced with a CLI optio= n, such as php --enable-scheduler, where the Scheduler= is implicitly activated, then it would be like the last l= ine of code=C2=A0it must exist only once.=C2=A0

=
=
Will this change the way os signals are handled then? Will it break compati=
bility if a
library uses pcntl traps and I'm using true async traps too? Note there=
 are several
different ways (timeout) signals are handled in PHP -- so if (per-chance) t=
he scheduler
could always be running, maybe we can unify the way signals are handled in =
php.

Regarding this phra= se in the RFC: it refers to the window close event in Wind= ows, which provides a few seconds before the process is forcibly terminated= .=C2=A0

There are signals intended for application terminatio= n, such as SIGBREAK or CTRL-C, w= hich should typically be handled in only one place in the = application. Developers are often tempted to insert signal handlers in mult= iple locations, making the code dependent on the environment. But more impo= rtantly, this should not happen at all.

True= Async explicitly defines a Flow for emergency or= unexpected application termination. Attempting to disrupt this Flo= w by adding a custom termination signal handler introduces ambiguity.

There should be only one termin= ation handler. And at the end of its execution, it must ca= ll gracefulShutdown.

As for pcntl, this= will need to be tested.

=C2=A0What if it never resumes at all?

If = a Fiber is never resumed, it means the application has com= pletely crashed with no way to recover :)

The RFC has two sec= tions dedicated to this issue:
Cancellation Operation + Graceful Shutdown.

If the application terminates due to an unhandled exception, all Fibers m= ust be executed.

Any Fiber can be cancele= d at any time, and there is no need to us= e explicit Cancellation, which I personally find an inconv= enient pattern.=C2=A0

The RFC doesn=E2=80=99t mention the stack trace. Will it throw away any i= nformation about the inner exception?

= This is literally "exception transfer". The stac= k trace will be exactly the same as if the exception were thrown at the cal= l site.

To be honest, I haven=E2=80=99t had enough time to thorough= ly test this. Let's try it:=C2=A0
<?php

Async\async(funct= ion() {
=C2=A0 =C2=A0 echo "async function 1\n";

=C2=A0= =C2=A0 Async\async(function() {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 echo "= 2\n";
=C2=A0 =C2=A0 =C2=A0 =C2=A0 throw new Error("Error"= );
=C2=A0 =C2=A0 });
});

echo "start\n";
try {=C2=A0 =C2=A0 Async\launchScheduler();
} catch (\Throwable $exception) = {
=C2=A0 =C2=A0 print_r($exception);
}
echo "end\n";
=
?>

004+ Error Object
005+ (<= br>006+ =C2=A0 =C2=A0 [message:protected] =3D> Error
007+ =C2=A0 =C2= =A0 [string:Error:private] =3D>
008+ =C2=A0 =C2=A0 [code:protected] = =3D> 0
009+ =C2=A0 =C2=A0 [file:protected] =3D> async.php
010+= =C2=A0 =C2=A0 [line:protected] =3D> 8
011+ =C2=A0 =C2=A0 [trace:Erro= r:private] =3D> Array
012+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
013+ =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [0] =3D> Array
014+ =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
015+ =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [function] =3D> {cl= osure:{closure:async.php:3}:6}
016+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [args] =3D> Array
017+ =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (=
018+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 )
019+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 )
020+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [1] =3D> = Array
021+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
= 022+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = [file] =3D> async.php
023+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 [line] =3D> 14
024+ =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [function] =3D> Async\l= aunchScheduler
025+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 [args] =3D> Array
026+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
027+ =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 )
028+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
= 029+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
030+ =C2=A0 =C2=A0 [previous:Error:pr= ivate] =3D>
031+ )


Seems perfectly correct.

What will calling=C2=A0exit=C2=A0or=C2=A0die=C2=A0do?

I completely forgot about them! Well, of cou= rse, Swoole override them. This needs to be added to the TODO.

Why is this the case?=C2=A0
=C2=A0

For example, consider a long-running application where a service is a class that remains i= n memory continuously. The web server receives an HTTP req= uest and starts a Fiber for each request. Each request has= its own User Session ID.

You want to call a service= function, but you don=E2=80=99t want to pass the = Session ID every time, because there are also 5-10 other r= equest-related variables. However, you cannot sim= ply store the Session ID in a class property, because context switching is unpredictable. At one moment, you're = handling Request #1, and a second later, you're alread= y processing Request #2.

When a Fiber creates another Fiber, it copies a reference to the context object, which has minimal pe= rformance impact while maintaining execution environment c= onsistency.

Closure variables work as expected=C2=A0= they are pure closures with no modifications.
= I didn=E2=80=99t mean that True Async breaks anything at t= he language level. The issue is logical:

You <= strong>cannot use a global variable in two Fibers, modify it, read it, and expect its state to remain consistent.

By this point we have covered FiberHandle, Resume, and Contexts. Now we= have Futures? Can we simplify this to just Futures? Why do we need all the= se different ways to handle execution?
=C2=A0
=
=C2=A0 =C2=A0Futures and Notifiers a= re two different patterns.
  • A Future changes i= ts state only once.
  • A Notifier ge= nerates one or more events.
  • Internally, Fu= ture uses Notifier.

In the RF= C, I mention that these are essentially two APIs:=

  • High-level API
  • Low-level API

One of the open questions is whether both APIs should r= emain in PHP-land.

The low-level API allows for close=C2=A0interaction with the event loop, which might be useful if someone wants to write a service in PHP that requires this level of control.

Additionally, this = API helps minimize Fiber context switches, since its callb= acks execute without switching.
This is both an= advantage and a disadvantage.

It's also not clear what the value of most of th=
ese function is. For example:
Your comment made me think, especially in the context of anti-patterns. And= I agree that it's better to remove unnecessary methods than to let pro= grammers shoot themselves in the foot.=C2=A0

As for the single producer method, =
I am not sure why you would use this.

Yes, in other languages there are no explicit restrictions. If the single p= roducer approach is indeed rarely used, then it's not such an important= feature to include. However, I lack certainty on whether it's truly a = rare case. On the other hand, these functions are inexpensive to implement = and do not affect performance. Moreover, they have another drawback: they i= ncrease the number of behavioral variants in a single class, which seems a = more significant disadvantage than the frequency of use. =C2=A0=C2=A0

It isn't clear what happens when `trySend` fails. I=
s this an error or does nothing? 
Yes, this is a documentation oversight. I'll add it to the TODO.=C2=A0<= /div>

Thinking through it, there= may be cases where `trySend` is valid,
=C2=A0Code = using tryReceive could be useful in cases where a channel is used to implem= ent a pool. Suppose you need to retrieve an object from the pool, but if it= 's not available, you=E2=80=99d prefer to do something else (like throw= an exception) rather than block the fiber.
Overall, though, you= =E2=80=99re right =E2=80=94 it=E2=80=99s an antipattern. It=E2=80=99s bette= r to implement the pool as an explicit class and reserve channels for their= classic use.

Can you ex= pand on what this means in the RFC? Why expose it if it shouldn't be us= ed?=C2=A0=C2=A0

=C2=A0 I answered a = similar question above.
=C2=A0
= I also noticed that you seem to be relying heavily on the current implement= ation to define=C2=A0
behav= ior.=C2=A0=C2=A0

I love an iterative approach: prototype =3D> RFC =3D> prototype =3D&g= t; RFC.=C2=A0

Thank you for the excellent remarks and=20 analysis!

Ed.=C2=A0 =C2=A0
--0000000000003cbde0062f6cde56--