-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reviewed By: jano Differential Revision: D67992267 fbshipit-source-id: bc251d2d13facd1677ab7b965e84335e8d2c6f51
- Loading branch information
1 parent
23fe75f
commit ea44027
Showing
20 changed files
with
572 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/* | ||
+----------------------------------------------------------------------+ | ||
| HipHop for PHP | | ||
+----------------------------------------------------------------------+ | ||
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | | ||
| Copyright (c) 1997-2010 The PHP Group | | ||
+----------------------------------------------------------------------+ | ||
| This source file is subject to version 3.01 of the PHP license, | | ||
| that is bundled with this package in the file LICENSE, and is | | ||
| available through the world-wide-web at the following url: | | ||
| http://www.php.net/license/3_01.txt | | ||
| If you did not receive a copy of the PHP license and are unable to | | ||
| obtain it through the world-wide-web, please send a note to | | ||
| license@php.net so we can mail you a copy immediately. | | ||
+----------------------------------------------------------------------+ | ||
*/ | ||
|
||
#include "hphp/runtime/ext/asio/asio-external-thread-event.h" | ||
#include "hphp/runtime/ext/asio/ext_static-wait-handle.h" | ||
#include "hphp/runtime/ext/extension.h" | ||
|
||
#include "hphp/util/hash-set.h" | ||
#include "hphp/util/rds-local.h" | ||
|
||
#include <folly/concurrency/ConcurrentHashMap.h> | ||
#include <atomic> | ||
|
||
namespace HPHP { | ||
|
||
struct SimpleLockEvent; | ||
|
||
namespace { | ||
|
||
using LockAtom = std::atomic<SimpleLockEvent*>; | ||
void unlock(LockAtom*); | ||
SimpleLockEvent* const UNLOCKED = (SimpleLockEvent*)(-1); | ||
folly::ConcurrentHashMapSIMD<std::string, LockAtom> s_locks; | ||
|
||
RDS_LOCAL(hphp_fast_set<LockAtom*>, tl_heldLocks); | ||
|
||
} | ||
|
||
struct SimpleLockEvent : AsioExternalThreadEvent { | ||
~SimpleLockEvent() override { | ||
if (m_held) unlock(m_held); | ||
} | ||
|
||
void acquire(LockAtom* lock) { | ||
m_held = lock; | ||
markAsFinished(); | ||
} | ||
|
||
void unserialize(TypedValue& result) override { | ||
result = make_tv<KindOfNull>(); | ||
tl_heldLocks->emplace(m_held); | ||
m_held = nullptr; | ||
} | ||
|
||
std::atomic<SimpleLockEvent*> m_next{nullptr}; | ||
private: | ||
std::atomic<LockAtom*> m_held{nullptr}; | ||
}; | ||
|
||
namespace { | ||
|
||
LockAtom* get_lock(const std::string& name) { | ||
// ConcurrentHashMapSIMD guarantees reference stability across rehashes, so | ||
// long as we don't erase values from the map it should be safe to continue | ||
// accessing these references without holding a hazard pointer. | ||
{ | ||
auto const it = s_locks.find(name); | ||
if (it != s_locks.end()) return const_cast<LockAtom*>(&it->second); | ||
} | ||
|
||
auto [it, ins] = s_locks.emplace(name, UNLOCKED); | ||
return const_cast<LockAtom*>(&it->second); | ||
} | ||
|
||
SimpleLockEvent* try_lock(LockAtom* lock) { | ||
auto expected = UNLOCKED; | ||
if (lock->compare_exchange_strong(expected, nullptr, | ||
std::memory_order_acq_rel)) { | ||
return nullptr; | ||
} | ||
|
||
auto ev = new SimpleLockEvent(); | ||
do { | ||
ev->m_next = expected; | ||
} while (!lock->compare_exchange_weak(expected, | ||
expected != UNLOCKED ? ev : nullptr, | ||
std::memory_order_acq_rel)); | ||
|
||
if (expected != UNLOCKED) return ev; | ||
ev->abandon(); | ||
return nullptr; | ||
} | ||
|
||
void unlock(LockAtom* lock) { | ||
SimpleLockEvent* expected = nullptr; | ||
auto next = UNLOCKED; | ||
while (!lock->compare_exchange_weak(expected, next, | ||
std::memory_order_acq_rel)) { | ||
next = expected ? expected->m_next.load(std::memory_order_relaxed) | ||
: UNLOCKED; | ||
} | ||
if (expected) expected->acquire(lock); | ||
} | ||
|
||
Object HHVM_FUNCTION(lock_mutex, const String& name) { | ||
auto const l = get_lock(name.toCppString()); | ||
if (auto ev = try_lock(l)) return Object{ev->getWaitHandle()}; | ||
|
||
tl_heldLocks->emplace(l); | ||
return Object{c_StaticWaitHandle::CreateSucceeded(make_tv<KindOfNull>())}; | ||
} | ||
|
||
bool HHVM_FUNCTION(try_lock_mutex, const String& name) { | ||
auto const l = get_lock(name.toCppString()); | ||
auto exp = UNLOCKED; | ||
if (l->compare_exchange_strong(exp, nullptr, std::memory_order_acq_rel)) { | ||
tl_heldLocks->emplace(l); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void HHVM_FUNCTION(unlock_mutex, const String& name) { | ||
auto const l = get_lock(name.toCppString()); | ||
if (!tl_heldLocks->erase(l)) { | ||
SystemLib::throwInvalidOperationExceptionObject( | ||
"cannot release unheld lock" | ||
); | ||
} | ||
unlock(l); | ||
} | ||
|
||
struct SimpleLockExtension final : Extension { | ||
SimpleLockExtension() : Extension("simplelock", "1.0", "sandbox_infra") {} | ||
|
||
bool moduleEnabled() const override { return !Cfg::Repo::Authoritative; } | ||
|
||
void moduleRegisterNative() override { | ||
HHVM_NAMED_FE(HH\\SimpleLock\\lock, HHVM_FN(lock_mutex)); | ||
HHVM_NAMED_FE(HH\\SimpleLock\\try_lock, HHVM_FN(try_lock_mutex)); | ||
HHVM_NAMED_FE_STR( | ||
"HH\\SimpleLock\\unlock", HHVM_FN(unlock_mutex), nativeFuncs() | ||
); | ||
} | ||
|
||
void requestShutdown() override { | ||
while (!tl_heldLocks->empty()) { | ||
hphp_fast_set<LockAtom*> locks; | ||
std::swap(*tl_heldLocks, locks); | ||
for (auto l : locks) unlock(l); | ||
} | ||
} | ||
|
||
} s_simple_lock_extension; | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?hh | ||
|
||
namespace HH\SimpleLock { | ||
|
||
/* | ||
* Acquire a cross-request mutual exclusion lock with a unique string name. | ||
* | ||
* If the same lock is acquired more than once in a request subsequent attempts | ||
* to acquire the lock will block until the it is unlocked. The returned | ||
* Awaitable will resolve when the lock has been acquired. | ||
* | ||
* @param string $name - the name of the lock | ||
*/ | ||
<<__Native>> | ||
function lock(string $name): Awaitable<void>; | ||
|
||
/* | ||
* Release a cross-request mutual exclusion lock with a unique name. | ||
* | ||
* Immediately release a held lock acquired using HH\SimpleLock\lock(), if the | ||
* lock was never acquired throws an InvalidOperationException. | ||
* | ||
* @param string $name - the name of the lock | ||
*/ | ||
<<__Native>> | ||
function unlock(string $name): void; | ||
|
||
/* | ||
* Attempt to acquired a cross-request mutual exclusion lock with a unique name. | ||
* | ||
* If the lock is unheld it is immediately acquired and true is returned, | ||
* otherwise we return false and no lock is acquired. | ||
* | ||
* @param string $name - the name of the lock | ||
* @return bool - whether the lock was acquired | ||
*/ | ||
<<__Native>> | ||
function try_lock(string $name): bool; | ||
|
||
/* | ||
* Acquired a cross-request mutual exclusion lock with a unique name and a | ||
* timeout. | ||
* | ||
* Attempts to acquire a lock within a fixed amount of microseconds, if the | ||
* timeout is reached without acquiring the lock, throws a RuntimeException. | ||
* | ||
* @param string $name - the name of the lock | ||
* @param int $timeout - the number of microseconds to wait for | ||
*/ | ||
async function lock_with_timeout(string $name, int $timeout): Awaitable<void> { | ||
$lwh = lock($name); | ||
|
||
if ($lwh->isFinished()) { | ||
await $lwh; | ||
return; | ||
} | ||
|
||
$swh = \HH\SleepWaitHandle::create($timeout); | ||
concurrent { | ||
await async { | ||
try { | ||
await $swh; | ||
\HH\Asio\cancel( | ||
$lwh, | ||
new \RuntimeException("Timed out waiting for lock $name"), | ||
); | ||
} catch (\Exception $_) {} | ||
}; | ||
await async { | ||
await $lwh; | ||
\HH\Asio\cancel($swh, new \Exception()); | ||
}; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?hh | ||
|
||
function thread_main() { | ||
HH\SimpleLock\lock('abandon'); | ||
HH\SimpleLock\lock('abandon'); | ||
HH\SimpleLock\lock('abandon'); | ||
HH\SimpleLock\lock('abandon'); | ||
HH\SimpleLock\lock('abandon'); | ||
|
||
$_ = true; | ||
$t = apc_inc('threads', 1, inout $_); | ||
} | ||
|
||
<<__EntryPoint>> | ||
async function main() { | ||
if (HH\execution_context() === "xbox") return; | ||
|
||
await HH\SimpleLock\lock('abandon'); | ||
apc_store('threads', 0); | ||
|
||
$funcs = vec[]; | ||
for ($i = 0; $i < 4; $i++) { | ||
$funcs[] = fb_call_user_func_async( | ||
__FILE__, | ||
'thread_main' | ||
); | ||
} | ||
|
||
$_ = true; | ||
while (apc_fetch('threads', inout $_) !== count($funcs)) usleep(10); | ||
|
||
HH\SimpleLock\unlock('abandon'); | ||
foreach ($funcs as $f) fb_end_user_func_async($f); | ||
|
||
await HH\SimpleLock\lock('abandon'); | ||
echo "Main done.\n"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Main done. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?hh | ||
|
||
<<__EntryPoint>> | ||
async function main() { | ||
await HH\SimpleLock\lock("hello"); | ||
await HH\SimpleLock\lock("goodbye"); | ||
HH\SimpleLock\unlock("hello"); | ||
HH\SimpleLock\unlock("bad"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
Fatal error: Uncaught exception 'InvalidOperationException' with message 'cannot release unheld lock' in %s/basic.php:8 | ||
Stack trace: | ||
#0 %s/basic.php(8): HH\SimpleLock\unlock() | ||
#1 (): main() | ||
#2 (): Closure$__SystemLib\enter_async_entry_point() | ||
#3 (): HH\Asio\join() | ||
#4 (): __SystemLib\enter_async_entry_point() | ||
#5 {main} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?hh | ||
|
||
async function thread_func() { | ||
echo "thread\n"; | ||
|
||
$_ = true; | ||
$t = apc_inc('threads', 1, inout $_); | ||
|
||
await HH\SimpleLock\lock("sync"); | ||
|
||
$t = apc_inc('counter', 1, inout $_); | ||
for ($i = 0; $i < 10; $i++) { | ||
echo "thread($t): $i\n"; | ||
await SleepWaitHandle::create(1000); | ||
} | ||
|
||
if ($t % 2) HH\SimpleLock\unlock("sync"); | ||
} | ||
|
||
function thread_main() { | ||
HH\Asio\join(thread_func()); | ||
} | ||
|
||
<<__EntryPoint>> | ||
async function main() { | ||
if (HH\execution_context() === "xbox") return; | ||
|
||
await HH\SimpleLock\lock("sync"); | ||
apc_store('counter', 0); | ||
apc_store('threads', 0); | ||
|
||
$funcs = vec[]; | ||
for ($i = 0; $i < 4; $i++) { | ||
$funcs[] = fb_call_user_func_async( | ||
__FILE__, | ||
'thread_main' | ||
); | ||
} | ||
|
||
$_ = true; | ||
while (apc_fetch('threads', inout $_) !== 4) usleep(10); | ||
|
||
HH\SimpleLock\unlock("sync"); | ||
foreach ($funcs as $f) fb_end_user_func_async($f); | ||
|
||
echo "Main done.\n"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
thread | ||
thread | ||
thread | ||
thread | ||
thread(1): 0 | ||
thread(1): 1 | ||
thread(1): 2 | ||
thread(1): 3 | ||
thread(1): 4 | ||
thread(1): 5 | ||
thread(1): 6 | ||
thread(1): 7 | ||
thread(1): 8 | ||
thread(1): 9 | ||
thread(2): 0 | ||
thread(2): 1 | ||
thread(2): 2 | ||
thread(2): 3 | ||
thread(2): 4 | ||
thread(2): 5 | ||
thread(2): 6 | ||
thread(2): 7 | ||
thread(2): 8 | ||
thread(2): 9 | ||
thread(3): 0 | ||
thread(3): 1 | ||
thread(3): 2 | ||
thread(3): 3 | ||
thread(3): 4 | ||
thread(3): 5 | ||
thread(3): 6 | ||
thread(3): 7 | ||
thread(3): 8 | ||
thread(3): 9 | ||
thread(4): 0 | ||
thread(4): 1 | ||
thread(4): 2 | ||
thread(4): 3 | ||
thread(4): 4 | ||
thread(4): 5 | ||
thread(4): 6 | ||
thread(4): 7 | ||
thread(4): 8 | ||
thread(4): 9 | ||
Main done. |
Empty file.
Oops, something went wrong.