Skip to content

Commit 9e251bb

Browse files
metaduvfacebook-github-bot
authored andcommitted
Fix a bug in assigning mutex to next owner
Summary: When we release a "lock" in XReqSync and there is already another pending waiter, we need to set the new waiter as the new owner before waking up the thread with success. Reviewed By: paulbiss Differential Revision: D67980743 fbshipit-source-id: b5bdbda85005fef22db62490de5b307788442d82
1 parent 258d4ca commit 9e251bb

File tree

5 files changed

+114
-9
lines changed

5 files changed

+114
-9
lines changed

hphp/runtime/ext/xreqsync/ext_xreqsync.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,8 @@ void XReqAsioBoolEvent::unserialize(TypedValue& result) {
4646
///////////////////////////////////////////////////////////////////////////////
4747
// XReqCallback
4848

49-
XReqCallback::XReqCallback(XReqAsioBoolEvent* event) :
50-
m_event(event),
51-
m_invalidated(false),
52-
m_expireAt(AsioSession::TimePoint::min()) {}
53-
54-
XReqCallback::XReqCallback(XReqAsioBoolEvent* event, AsioSession::TimePoint expireAt) :
49+
XReqCallback::XReqCallback(XReqAsioBoolEvent* event, AsioSession::TimePoint expireAt, req_id waiterId) :
50+
m_waiterId(waiterId),
5551
m_event(event),
5652
m_invalidated(false),
5753
m_expireAt(expireAt) {}
@@ -116,6 +112,7 @@ bool XReqSyncImpl::mutex_try_unlock(req_id unlocker) {
116112
auto next = std::move(m_waiters.back());
117113
m_waiters.pop_back();
118114
if (next->isValid()) {
115+
m_mutex_owner.store(next->getWaiterId());
119116
next->call();
120117
break;
121118
}
@@ -284,7 +281,7 @@ c_Awaitable* XReqSync::genLock(int64_t timeout_ms) {
284281
auto expiration = timeout_ms > 0
285282
? AsioSession::TimePoint::clock::now() + std::chrono::milliseconds(timeout_ms)
286283
: AsioSession::TimePoint::min();
287-
callback = std::make_shared<XReqCallback>(event, expiration);
284+
callback = std::make_shared<XReqCallback>(event, expiration, m_self_id);
288285

289286
// Store the callback in per-request queue for invalidation when we die
290287
this->m_waiters.push_back(callback);

hphp/runtime/ext/xreqsync/ext_xreqsync.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,16 @@ struct XReqAsioBoolEvent : XReqAsioEvent<bool> {
8686
*/
8787
class XReqCallback {
8888
public:
89-
explicit XReqCallback(XReqAsioBoolEvent* event);
90-
XReqCallback(XReqAsioBoolEvent* event, AsioSession::TimePoint expireAt);
89+
XReqCallback(XReqAsioBoolEvent* event, AsioSession::TimePoint expireAt, req_id waiterId);
9190
void call();
9291
bool isValid();
9392
void invalidate() { m_invalidated = true; }
93+
req_id getWaiterId() { return m_waiterId; }
9494
AsioSession::TimePoint getExpireAt() { return m_expireAt; }
9595
static bool earlier(std::shared_ptr<XReqCallback> x, const std::shared_ptr<XReqCallback> y);
9696

9797
private:
98+
req_id m_waiterId;
9899
XReqAsioBoolEvent* m_event;
99100
bool m_invalidated;
100101
AsioSession::TimePoint m_expireAt;

hphp/test/slow/xreqsync/stress.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?hh
2+
3+
<<__EntryPoint>>
4+
function main(): void {
5+
$ctx = HH\execution_context();
6+
if ($ctx === 'xbox') {
7+
return;
8+
}
9+
10+
HH\Asio\join(genMain());
11+
}
12+
13+
function getAPCKey(string $lockname) {
14+
return $lockname.'_count';
15+
}
16+
17+
async function reportProgress(string $lockname): Awaitable<void> {
18+
$apc_key = getAPCKey($lockname);
19+
20+
// For up to 20 seconds at 0.1 intervals
21+
$last_finished = 0;
22+
for ($i = 0; $i < 200; $i++) {
23+
$success = false;
24+
$count = apc_fetch($apc_key, inout $success);
25+
if ($count !== $last_finished) {
26+
$last_finished = $count;
27+
echo "Threads finished: $count\n";
28+
if ($count == 8) {
29+
$seconds = $i * 0.1;
30+
if ($seconds > 15) {
31+
echo "Overall time over 15s\n";
32+
} else {
33+
echo "Overall time under 15s\n";
34+
}
35+
return;
36+
}
37+
}
38+
// 100ms
39+
await SleepWaitHandle::create(1000 * 100);
40+
}
41+
}
42+
43+
async function genMain(): Awaitable<void> {
44+
$lockname = 'lock'.time().rand();
45+
$apc_key = getAPCKey($lockname);
46+
apc_store($apc_key, 0);
47+
48+
// 8 Threads, 1s each. Should take 8s to complete.
49+
concurrent {
50+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
51+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
52+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
53+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
54+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
55+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
56+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
57+
await fb_gen_user_func_array(__FILE__, 'thread', vec[$lockname]);
58+
await reportProgress($lockname);
59+
}
60+
}
61+
62+
<<__DynamicallyCallable>>
63+
function thread(string $lockname) {
64+
echo "Thread started\n";
65+
usleep(1000 * 500);
66+
$lock = HH\XReqSync::get($lockname);
67+
HH\Asio\join($lock->genLock());
68+
echo "Thread got lock\n";
69+
sleep(1);
70+
echo "Thread releasing lock\n";
71+
$lock->unlock();
72+
$success = false;
73+
apc_inc(getAPCKey($lockname), 1, inout $success);
74+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Thread started
2+
Thread started
3+
Thread started
4+
Thread started
5+
Thread started
6+
Thread started
7+
Thread started
8+
Thread started
9+
Thread got lock
10+
Thread releasing lock
11+
Thread got lock
12+
Threads finished: 1
13+
Thread releasing lock
14+
Thread got lock
15+
Threads finished: 2
16+
Thread releasing lock
17+
Thread got lock
18+
Threads finished: 3
19+
Thread releasing lock
20+
Thread got lock
21+
Threads finished: 4
22+
Thread releasing lock
23+
Thread got lock
24+
Threads finished: 5
25+
Thread releasing lock
26+
Thread got lock
27+
Threads finished: 6
28+
Thread releasing lock
29+
Thread got lock
30+
Threads finished: 7
31+
Thread releasing lock
32+
Threads finished: 8
33+
Overall time under 15s

hphp/test/slow/xreqsync/stress.php.norepo

Whitespace-only changes.

0 commit comments

Comments
 (0)