On 18/03/2021 09:20, Josh Di Fabio wrote:
"If you want to enable fibers in your application, you must be
confident about the implementation details of all of the code in your
application, including that of your dependencies, which are written
and maintained by other developers."
I don't have anything to add to my previous point in that I disagree
that this is practical.
While I agree that this is extremely difficult, and slows adoption of
asynchronous technologies, I think the challenge is not identifying
asynchronous code, it's identifying shared state.
In your example, you show code that was written to use shared state
unwittingly calling code that was written to be asynchronous:
private function capturePayment()
{
$paymentRequest = preparePaymentRequest($this->currentOrder);
$this->paymentGateway->capturePayment($paymentRequest);
$this->currentOrder->setTransactionId($paymentRequest->getTransactionId());
}
However, the same problem exists the other way around - code written to
be asynchronous unwittingly calling code written to use shared state:
private async function capturePayment()
{
$paymentRequest = $this->someDependency->preparePaymentRequest();
await $this->paymentGateway->capturePayment($paymentRequest);
$this->someDependency->setTransactionId($paymentRequest->getTransactionId());
}
This all looks fine - but what if someDependency is actually calling
into a library which stores the current order in a static variable? Now
you have exactly the same race condition for the opposite reason. And
the solution is the same: carefully vet all your dependencies.
I can think of a few ways of solving this:
1) Require all the code to be synchronous. This is easy in PHP, even if
Fibers are supported: just don't run in an asynchronous framework.
2) Require most of the code to be synchronous. This is the common
approach of labelling functions as "async" or converting return values
to Generators or Promises. The big disadvantage is that it requires
rewriting a lot of code that doesn't care one way or the other if it's
called synchronously.
3) Require all the code to be free of shared state. This is ultimately
the only way you'll get the full advantage of asynchronous code.
4) Require most of the code to be free of shared state. Having some
primitives in the language to mark out code that definitely *can't* be
asynchronous would probably be useful. Perhaps you could mark a function
as "no-interrupt"; this could then be used as a wrapper when calling
into a library you know or suspect of using state in unsafe ways.
Perhaps we could rather make fibers*opt in* at the*callsite*
(similar to goroutine calls) in order to prevent functions
unexpectedly being executed asynchronously due to faraway changes.
This would be safe and predictable while also avoiding the "What color
is your function" problem.
Although this would avoid keeping both synchronous and asynchronous
versions of the same function, it would require adding that keyword to
every single function call, just in case somewhere inside it wants to
take advantage of your asynchronous environment.
From my limited understanding, goroutines are a completely different
concept. Saying "go someFunction()" in Go immediately starts a new
thread-like thing, and doesn't return anything. The runtime manages the
scheduling of the thread, but if you want to get any data out of the
goroutine, you have to pass it explicitly, e.g. via a channel.
Regards,
--
Rowan Tommins
[IMSoP]
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php