From d643a730390a064daedc7a376fa5b0c8a9eeb5e9 Mon Sep 17 00:00:00 2001 From: "Yang, Bo" Date: Tue, 15 Mar 2022 19:34:49 +0000 Subject: [PATCH 1/2] Add Condition->trySucceed and Condition->tryFail --- src/async/Condition.php | 44 +++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/async/Condition.php b/src/async/Condition.php index 64acefab..eb277dc3 100644 --- a/src/async/Condition.php +++ b/src/async/Condition.php @@ -21,34 +21,58 @@ class Condition { * Notify the condition variable of success and set the result. */ final public function succeed(T $result): void { + invariant($this->trySucceed($result), 'Unable to notify Condition twice'); + } + + /** + * Notify the condition variable of failure and set the exception. + */ + final public function fail(\Exception $exception): void { + invariant($this->tryFail($exception), 'Unable to notify Condition twice'); + } + + /** + * Notify the condition variable of success and set the $result. + * + * @return + * true if the condition is set to $result successfully, false if the + * condition was previously set to another result or exception. + */ + final public function trySucceed(T $result): bool { if ($this->condition === null) { $this->condition = async { return $result; }; + return true; } else { - invariant( - $this->condition is ConditionWaitHandle<_>, - 'Unable to notify AsyncCondition twice', - ); + if (!($this->condition is ConditionWaitHandle<_>)) { + return false; + } /* HH_FIXME[4110]: Type error revealed by type-safe instanceof feature. See https://fburl.com/instanceof */ $this->condition->succeed($result); + return true; } } /** - * Notify the condition variable of failure and set the exception. + * Notify the condition variable of failure and set the $exception. + * + * @return + * true if the condition is set to $exception successfully, false if the + * condition was previously set to another result or exception. */ - final public function fail(\Exception $exception): void { + final public function tryFail(\Exception $exception): bool { if ($this->condition === null) { $this->condition = async { throw $exception; }; + return true; } else { - invariant( - $this->condition is ConditionWaitHandle<_>, - 'Unable to notify AsyncCondition twice', - ); + if (!($this->condition is ConditionWaitHandle<_>)) { + return false; + } $this->condition->fail($exception); + return true; } } From cb02cb0c009d0883c79bd422f6e5250afde9d540 Mon Sep 17 00:00:00 2001 From: "Yang, Bo" Date: Tue, 15 Mar 2022 19:39:09 +0000 Subject: [PATCH 2/2] Improve Condition: 1. Introduce the ADT type ConditionState to replace ?Awaitable, suppressing HH_FIXME[4110] 2. Added a Finish state to remove the reference from the child awaitable to the Condition, in case of memory leak 3. Added wait_for_notification_async and ConditionNotifyee to hide the setState function --- src/_Private/ConditionState.php | 139 ++++++++++++++++++++++++++++++++ src/async/Condition.php | 123 ++++++++++++++++------------ 2 files changed, 209 insertions(+), 53 deletions(-) create mode 100644 src/_Private/ConditionState.php diff --git a/src/_Private/ConditionState.php b/src/_Private/ConditionState.php new file mode 100644 index 00000000..72cb2b0b --- /dev/null +++ b/src/_Private/ConditionState.php @@ -0,0 +1,139 @@ +> +interface ConditionState { + public function waitForNotificationAsync( + Condition $condition, + Awaitable $notifiers, + ): Awaitable; + public function trySucceed(Condition $condition, T $result): bool; + public function tryFail(Condition $condition, \Exception $exception): bool; +} + +final class NotStarted implements ConditionState { + private function __construct() {} + <<__Memoize>> + public static function getInstance(): this { + return new self(); + } + + public async function waitForNotificationAsync( + Condition $condition, + Awaitable $notifiers, + ): Awaitable { + $handle = ConditionWaitHandle::create($notifiers); + $condition->setState(new AsyncResult($handle)); + try { + return await $handle; + } finally { + $condition->setState(Finished::getInstance()); + } + } + + public function trySucceed(Condition $condition, T $result): bool { + $condition->setState( + new SyncResult( + async { + return $result; + }, + ), + ); + return true; + } + public function tryFail( + Condition $condition, + \Exception $exception, + ): bool { + $condition->setState( + new SyncResult( + async { + throw $exception; + }, + ), + ); + return true; + } +} + +final class AsyncResult implements ConditionState { + public function __construct(private ConditionWaitHandle $resultHandle) {} + public function waitForNotificationAsync( + Condition $_state_ref, + Awaitable $_notifiers, + ): Awaitable { + invariant_violation('Unable to wait for notification twice'); + } + public function trySucceed(Condition $condition, T $result): bool { + $this->resultHandle->succeed($result); + return true; + } + public function tryFail( + Condition $condition, + \Exception $exception, + ): bool { + $this->resultHandle->fail($exception); + return true; + } + +} + +final class SyncResult implements ConditionState { + public function __construct(private Awaitable $resultAwaitable) {} + public function waitForNotificationAsync( + Condition $condition, + Awaitable $_notifiers, + ): Awaitable { + $condition->setState(Finished::getInstance()); + return $this->resultAwaitable; + } + + public function trySucceed(Condition $condition, T $result): bool { + return false; + } + public function tryFail( + Condition $condition, + \Exception $exception, + ): bool { + return false; + } +} + +final class Finished implements ConditionState { + private function __construct() {} + <<__Memoize>> + public static function getInstance(): this { + return new self(); + } + public function waitForNotificationAsync( + Condition $_state_ref, + Awaitable $_notifiers, + ): Awaitable { + invariant_violation('Unable to wait for notification twice'); + } + public function trySucceed(Condition $condition, T $result): bool { + return false; + } + public function tryFail( + Condition $condition, + \Exception $exception, + ): bool { + return false; + } +} diff --git a/src/async/Condition.php b/src/async/Condition.php index eb277dc3..196c4c82 100644 --- a/src/async/Condition.php +++ b/src/async/Condition.php @@ -10,70 +10,42 @@ namespace HH\Lib\Async; +use namespace HH\Lib\_Private; + /** * A wrapper around ConditionWaitHandle that allows notification events * to occur before the condition is awaited. */ -class Condition { - private ?Awaitable $condition = null; +class Condition implements ConditionNotifyee { + private _Private\ConditionState $state; + public function __construct() { + $this->state = _Private\NotStarted::getInstance(); + } + + public function setState(_Private\ConditionState $state): void { + $this->state = $state; + } - /** - * Notify the condition variable of success and set the result. - */ final public function succeed(T $result): void { - invariant($this->trySucceed($result), 'Unable to notify Condition twice'); + invariant( + $this->state->trySucceed($this, $result), + 'Unable to notify Condition twice', + ); } - /** - * Notify the condition variable of failure and set the exception. - */ final public function fail(\Exception $exception): void { - invariant($this->tryFail($exception), 'Unable to notify Condition twice'); + invariant( + $this->state->tryFail($this, $exception), + 'Unable to notify Condition twice', + ); } - /** - * Notify the condition variable of success and set the $result. - * - * @return - * true if the condition is set to $result successfully, false if the - * condition was previously set to another result or exception. - */ final public function trySucceed(T $result): bool { - if ($this->condition === null) { - $this->condition = async { - return $result; - }; - return true; - } else { - if (!($this->condition is ConditionWaitHandle<_>)) { - return false; - } - /* HH_FIXME[4110]: Type error revealed by type-safe instanceof feature. See https://fburl.com/instanceof */ - $this->condition->succeed($result); - return true; - } + return $this->state->trySucceed($this, $result); } - /** - * Notify the condition variable of failure and set the $exception. - * - * @return - * true if the condition is set to $exception successfully, false if the - * condition was previously set to another result or exception. - */ final public function tryFail(\Exception $exception): bool { - if ($this->condition === null) { - $this->condition = async { - throw $exception; - }; - return true; - } else { - if (!($this->condition is ConditionWaitHandle<_>)) { - return false; - } - $this->condition->fail($exception); - return true; - } + return $this->state->tryFail($this, $exception); } /** @@ -90,9 +62,54 @@ final public function tryFail(\Exception $exception): bool { final public async function waitForNotificationAsync( Awaitable $notifiers, ): Awaitable { - if ($this->condition === null) { - $this->condition = ConditionWaitHandle::create($notifiers); - } - return await $this->condition; + return await $this->state->waitForNotificationAsync($this, $notifiers); } } + + +/** + * Asynchronously wait for the condition variable to be notified and + * return the result or throw the exception received via notification. + * + * The caller must provide an Awaitable $notifiers (which must be a + * WaitHandle) that must not finish before the notification is received. + * This means $notifiers must represent work that is guaranteed to + * eventually trigger the notification. As long as the notification is + * issued only once, asynchronous execution unrelated to $notifiers is + * allowed to trigger the notification. + */ +function wait_for_notification_async( + (function(ConditionNotifyee): Awaitable) $notifiers, +): Awaitable { + $condition = new Condition(); + return $condition->waitForNotificationAsync($notifiers($condition)); +} + +interface ConditionNotifyee<-T> { + + /** + * Notify the condition variable of success and set the $result. + */ + public function succeed(T $result): void; + /** + * Notify the condition variable of success and set the $result. + * + * @return + * true if the condition is set to $result successfully, false if the + * condition was previously set to another result or exception. + */ + public function trySucceed(T $result): bool; + + /** + * Notify the condition variable of failure and set the exception. + */ + public function fail(\Exception $exception): void; + /** + * Notify the condition variable of failure and set the $exception. + * + * @return + * true if the condition is set to $exception successfully, false if the + * condition was previously set to another result or exception. + */ + public function tryFail(\Exception $exception): bool; +}