Skip to content

Commit

Permalink
Improve Condition:
Browse files Browse the repository at this point in the history
1. Introduce the ADT type ConditionState to replace ?Awaitable<T>, 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
  • Loading branch information
Atry committed Mar 14, 2022
1 parent 6315fec commit b372876
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 33 deletions.
133 changes: 133 additions & 0 deletions src/_Private/ConditionState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?hh
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the hphp/hsl/ subdirectory of this source tree.
*
*/

namespace HH\Lib\_Private;

use type HH\Lib\Async\Condition;

interface ConditionState<T> {
public function waitForNotificationAsync(
Condition<T> $condition,
Awaitable<void> $notifiers,
): Awaitable<T>;
public function trySucceed(Condition<T> $condition, T $result): bool;
public function tryFail(Condition<T> $condition, \Exception $exception): bool;
}

final class NotStarted<T> implements ConditionState<T> {
private function __construct() {}
<<__Memoize>>
public static function getInstance(): this {
return new self();
}

public async function waitForNotificationAsync(
Condition<T> $condition,
Awaitable<void> $notifiers,
): Awaitable<T> {
$handle = ConditionWaitHandle::create($notifiers);
$condition->setState(new AsyncResult($handle));
try {
return await $handle;
} finally {
$condition->setState(Finished::getInstance());
}
}

public function trySucceed(Condition<T> $condition, T $result): bool {
$condition->setState(
new SyncResult(
async {
return $result;
},
),
);
return true;
}
public function tryFail(
Condition<T> $condition,
\Exception $exception,
): bool {
$condition->setState(
new SyncResult(
async {
throw $exception;
},
),
);
return true;
}
}

final class AsyncResult<T> implements ConditionState<T> {
public function __construct(private ConditionWaitHandle<T> $resultHandle) {}
public function waitForNotificationAsync(
Condition<T> $_state_ref,
Awaitable<void> $_notifiers,
): Awaitable<T> {
invariant_violation('Unable to wait for notification twice');
}
public function trySucceed(Condition<T> $condition, T $result): bool {
$this->resultHandle->succeed($result);
return true;
}
public function tryFail(
Condition<T> $condition,
\Exception $exception,
): bool {
$this->resultHandle->fail($exception);
return true;
}

}

final class SyncResult<T> implements ConditionState<T> {
public function __construct(private Awaitable<T> $resultAwaitable) {}
public function waitForNotificationAsync(
Condition<T> $condition,
Awaitable<void> $_notifiers,
): Awaitable<T> {
$condition->setState(Finished::getInstance());
return $this->resultAwaitable;
}

public function trySucceed(Condition<T> $condition, T $result): bool {
return false;
}
public function tryFail(
Condition<T> $condition,
\Exception $exception,
): bool {
return false;
}
}

final class Finished<T> implements ConditionState<T> {
private function __construct() {}
<<__Memoize>>
public static function getInstance(): this {
return new self();
}
public function waitForNotificationAsync(
Condition<T> $_state_ref,
Awaitable<void> $_notifiers,
): Awaitable<T> {
invariant_violation('Unable to wait for notification twice');
}
public function trySucceed(Condition<T> $condition, T $result): bool {
return false;
}
public function tryFail(
Condition<T> $condition,
\Exception $exception,
): bool {
return false;
}
}
106 changes: 73 additions & 33 deletions src/async/Condition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,43 @@

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<T> {
private ?Awaitable<T> $condition = null;
class Condition<T> implements ConditionNotifyee<T> {
private _Private\ConditionState<T> $state;
public function __construct() {
$this->state = _Private\NotStarted::getInstance();
}

public function setState(_Private\ConditionState<T> $state): void {
$this->state = $state;
}

/**
* Notify the condition variable of success and set the result.
*/
final public function succeed(T $result): void {
if ($this->condition === null) {
$this->condition = async {
return $result;
};
} else {
invariant(
$this->condition is ConditionWaitHandle<_>,
'Unable to notify AsyncCondition twice',
);
/* HH_FIXME[4110]: Type error revealed by type-safe instanceof feature. See https://fburl.com/instanceof */
$this->condition->succeed($result);
if (!$this->state->trySucceed($this, $result)) {
invariant_violation('Unable to notify Condition twice');
}
}

/**
* Notify the condition variable of failure and set the exception.
*/
final public function fail(\Exception $exception): void {
if ($this->condition === null) {
$this->condition = async {
throw $exception;
};
} else {
invariant(
$this->condition is ConditionWaitHandle<_>,
'Unable to notify AsyncCondition twice',
);
$this->condition->fail($exception);
if (!$this->state->tryFail($this, $exception)) {
invariant_violation('Unable to notify Condition twice');

}
}

final public function trySucceed(T $result): bool {
return $this->state->trySucceed($this, $result);
}

final public function tryFail(\Exception $exception): bool {
return $this->state->tryFail($this, $exception);
}

/**
* Asynchronously wait for the condition variable to be notified and
* return the result or throw the exception received via notification.
Expand All @@ -66,9 +61,54 @@ final public function fail(\Exception $exception): void {
final public async function waitForNotificationAsync(
Awaitable<void> $notifiers,
): Awaitable<T> {
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<T>(
(function(ConditionNotifyee<T>): Awaitable<void>) $notifiers,
): Awaitable<T> {
$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;
}

0 comments on commit b372876

Please sign in to comment.