Skip to content

Commit f20978e

Browse files
committed
Improve Condition:
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
1 parent 6315fec commit f20978e

File tree

2 files changed

+206
-33
lines changed

2 files changed

+206
-33
lines changed

src/_Private/ConditionState.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?hh
2+
/*
3+
* Copyright (c) 2004-present, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the hphp/hsl/ subdirectory of this source tree.
8+
*
9+
*/
10+
11+
namespace HH\Lib\_Private;
12+
13+
use type HH\Lib\Async\Condition;
14+
15+
interface ConditionState<T> {
16+
public function waitForNotificationAsync(
17+
Condition<T> $condition,
18+
Awaitable<void> $notifiers,
19+
): Awaitable<T>;
20+
public function trySucceed(Condition<T> $condition, T $result): bool;
21+
public function tryFail(Condition<T> $condition, \Exception $exception): bool;
22+
}
23+
24+
final class NotStarted<T> implements ConditionState<T> {
25+
private function __construct() {}
26+
<<__Memoize>>
27+
public static function getInstance(): this {
28+
return new self();
29+
}
30+
31+
public async function waitForNotificationAsync(
32+
Condition<T> $condition,
33+
Awaitable<void> $notifiers,
34+
): Awaitable<T> {
35+
$handle = ConditionWaitHandle::create($notifiers);
36+
$condition->setState(new AsyncResult($handle));
37+
try {
38+
return await $handle;
39+
} finally {
40+
$condition->setState(Finished::getInstance());
41+
}
42+
}
43+
44+
public function trySucceed(Condition<T> $condition, T $result): bool {
45+
$condition->setState(
46+
new SyncResult(
47+
async {
48+
return $result;
49+
},
50+
),
51+
);
52+
return true;
53+
}
54+
public function tryFail(
55+
Condition<T> $condition,
56+
\Exception $exception,
57+
): bool {
58+
$condition->setState(
59+
new SyncResult(
60+
async {
61+
throw $exception;
62+
},
63+
),
64+
);
65+
return true;
66+
}
67+
}
68+
69+
final class AsyncResult<T> implements ConditionState<T> {
70+
public function __construct(private ConditionWaitHandle<T> $resultHandle) {}
71+
public function waitForNotificationAsync(
72+
Condition<T> $_state_ref,
73+
Awaitable<void> $_notifiers,
74+
): Awaitable<T> {
75+
invariant_violation('Unable to wait for notification twice');
76+
}
77+
public function trySucceed(Condition<T> $condition, T $result): bool {
78+
$this->resultHandle->succeed($result);
79+
return true;
80+
}
81+
public function tryFail(
82+
Condition<T> $condition,
83+
\Exception $exception,
84+
): bool {
85+
$this->resultHandle->fail($exception);
86+
return true;
87+
}
88+
89+
}
90+
91+
final class SyncResult<T> implements ConditionState<T> {
92+
public function __construct(private Awaitable<T> $resultAwaitable) {}
93+
public function waitForNotificationAsync(
94+
Condition<T> $condition,
95+
Awaitable<void> $_notifiers,
96+
): Awaitable<T> {
97+
$condition->setState(Finished::getInstance());
98+
return $this->resultAwaitable;
99+
}
100+
101+
public function trySucceed(Condition<T> $condition, T $result): bool {
102+
return false;
103+
}
104+
public function tryFail(
105+
Condition<T> $condition,
106+
\Exception $exception,
107+
): bool {
108+
return false;
109+
}
110+
}
111+
112+
final class Finished<T> implements ConditionState<T> {
113+
private function __construct() {}
114+
<<__Memoize>>
115+
public static function getInstance(): this {
116+
return new self();
117+
}
118+
public function waitForNotificationAsync(
119+
Condition<T> $_state_ref,
120+
Awaitable<void> $_notifiers,
121+
): Awaitable<T> {
122+
invariant_violation('Unable to wait for notification twice');
123+
}
124+
public function trySucceed(Condition<T> $condition, T $result): bool {
125+
return false;
126+
}
127+
public function tryFail(
128+
Condition<T> $condition,
129+
\Exception $exception,
130+
): bool {
131+
return false;
132+
}
133+
}

src/async/Condition.php

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,43 @@
1010

1111
namespace HH\Lib\Async;
1212

13+
use namespace HH\Lib\_Private;
14+
1315
/**
1416
* A wrapper around ConditionWaitHandle that allows notification events
1517
* to occur before the condition is awaited.
1618
*/
17-
class Condition<T> {
18-
private ?Awaitable<T> $condition = null;
19+
final class Condition<T> implements ConditionNotifyee<T> {
20+
private _Private\ConditionState<T> $state;
21+
public function __construct() {
22+
$this->state = _Private\NotStarted::getInstance();
23+
}
24+
25+
public function setState(_Private\ConditionState<T> $state): void {
26+
$this->state = $state;
27+
}
1928

20-
/**
21-
* Notify the condition variable of success and set the result.
22-
*/
2329
final public function succeed(T $result): void {
24-
if ($this->condition === null) {
25-
$this->condition = async {
26-
return $result;
27-
};
28-
} else {
29-
invariant(
30-
$this->condition is ConditionWaitHandle<_>,
31-
'Unable to notify AsyncCondition twice',
32-
);
33-
/* HH_FIXME[4110]: Type error revealed by type-safe instanceof feature. See https://fburl.com/instanceof */
34-
$this->condition->succeed($result);
30+
if (!$this->state->trySucceed($this, $result)) {
31+
invariant_violation('Unable to notify Condition twice');
3532
}
3633
}
3734

38-
/**
39-
* Notify the condition variable of failure and set the exception.
40-
*/
4135
final public function fail(\Exception $exception): void {
42-
if ($this->condition === null) {
43-
$this->condition = async {
44-
throw $exception;
45-
};
46-
} else {
47-
invariant(
48-
$this->condition is ConditionWaitHandle<_>,
49-
'Unable to notify AsyncCondition twice',
50-
);
51-
$this->condition->fail($exception);
36+
if (!$this->state->tryFail($this, $exception)) {
37+
invariant_violation('Unable to notify Condition twice');
38+
5239
}
5340
}
5441

42+
final public function trySucceed(T $result): bool {
43+
return $this->state->trySucceed($this, $result);
44+
}
45+
46+
final public function tryFail(\Exception $exception): bool {
47+
return $this->state->tryFail($this, $exception);
48+
}
49+
5550
/**
5651
* Asynchronously wait for the condition variable to be notified and
5752
* return the result or throw the exception received via notification.
@@ -66,9 +61,54 @@ final public function fail(\Exception $exception): void {
6661
final public async function waitForNotificationAsync(
6762
Awaitable<void> $notifiers,
6863
): Awaitable<T> {
69-
if ($this->condition === null) {
70-
$this->condition = ConditionWaitHandle::create($notifiers);
71-
}
72-
return await $this->condition;
64+
return await $this->state->waitForNotificationAsync($this, $notifiers);
7365
}
7466
}
67+
68+
69+
/**
70+
* Asynchronously wait for the condition variable to be notified and
71+
* return the result or throw the exception received via notification.
72+
*
73+
* The caller must provide an Awaitable $notifiers (which must be a
74+
* WaitHandle) that must not finish before the notification is received.
75+
* This means $notifiers must represent work that is guaranteed to
76+
* eventually trigger the notification. As long as the notification is
77+
* issued only once, asynchronous execution unrelated to $notifiers is
78+
* allowed to trigger the notification.
79+
*/
80+
function wait_for_notification_async<T>(
81+
(function(ConditionNotifyee<T>): Awaitable<void>) $notifiers,
82+
): Awaitable<T> {
83+
$condition = new Condition();
84+
return $condition->waitForNotificationAsync($notifiers($condition));
85+
}
86+
87+
interface ConditionNotifyee<-T> {
88+
89+
/**
90+
* Notify the condition variable of success and set the $result.
91+
*/
92+
public function succeed(T $result): void;
93+
/**
94+
* Notify the condition variable of success and set the $result.
95+
*
96+
* @return
97+
* true if the condition is set to $result successfully, false if the
98+
* condition was previously set to another result or exception.
99+
*/
100+
public function trySucceed(T $result): bool;
101+
102+
/**
103+
* Notify the condition variable of failure and set the exception.
104+
*/
105+
public function fail(\Exception $exception): void;
106+
/**
107+
* Notify the condition variable of failure and set the $exception.
108+
*
109+
* @return
110+
* true if the condition is set to $exception successfully, false if the
111+
* condition was previously set to another result or exception.
112+
*/
113+
public function tryFail(\Exception $exception): bool;
114+
}

0 commit comments

Comments
 (0)