forked from TextureGroup/Texture
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce ASRecursiveUnfairLock and tests (TextureGroup#858)
* Introduce ASRecursiveUnfairLock and tests * Document it and put underscores to scare people away * Increment changelog * Integrate it with ASDN::Mutex behind experiment * Rename the experiment * Love these license headers oh so much * Move internal header because we have to * Address Jon's feedback
- Loading branch information
1 parent
cf1c3f6
commit 4bbbd72
Showing
11 changed files
with
426 additions
and
130 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
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
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
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
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
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
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
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,59 @@ | ||
// | ||
// ASRecursiveUnfairLock.h | ||
// Texture | ||
// | ||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <pthread/pthread.h> | ||
#import <os/lock.h> | ||
|
||
// Don't import C-only header if we're in a C++ file | ||
#ifndef __cplusplus | ||
#import <stdatomic.h> | ||
#endif | ||
|
||
// Note: We don't use ATOMIC_VAR_INIT here because C++ compilers don't like it, | ||
// and it literally does absolutely nothing. | ||
#define AS_RECURSIVE_UNFAIR_LOCK_INIT ((ASRecursiveUnfairLock){ OS_UNFAIR_LOCK_INIT, NULL, 0}) | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
OS_UNFAIR_LOCK_AVAILABILITY | ||
typedef struct { | ||
os_unfair_lock _lock; | ||
_Atomic(pthread_t) _thread; // Write-protected by lock | ||
int _count; // Protected by lock | ||
} ASRecursiveUnfairLock; | ||
|
||
CF_EXTERN_C_BEGIN | ||
|
||
/** | ||
* Lock, blocking if needed. | ||
*/ | ||
OS_UNFAIR_LOCK_AVAILABILITY | ||
void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l); | ||
|
||
/** | ||
* Try to lock without blocking. Returns whether we took the lock. | ||
*/ | ||
OS_UNFAIR_LOCK_AVAILABILITY | ||
BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l); | ||
|
||
/** | ||
* Unlock. Calling this on a thread that does not own | ||
* the lock will result in an assertion failure, and undefined | ||
* behavior if foundation assertions are disabled. | ||
*/ | ||
OS_UNFAIR_LOCK_AVAILABILITY | ||
void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l); | ||
|
||
CF_EXTERN_C_END | ||
|
||
NS_ASSUME_NONNULL_END |
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 @@ | ||
// | ||
// ASRecursiveUnfairLock.m | ||
// Texture | ||
// | ||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import "ASRecursiveUnfairLock.h" | ||
|
||
/** | ||
* For our atomic _thread, we use acquire/release memory order so that we can have | ||
* the minimum possible constraint on the hardware. The default, `memory_order_seq_cst` | ||
* demands that there be a total order of all such modifications as seen by all threads. | ||
* Acquire/release only requires that modifications to this specific atomic are | ||
* synchronized across acquire/release pairs. | ||
* http://en.cppreference.com/w/cpp/atomic/memory_order | ||
* | ||
* Note also that the unfair_lock involves a thread fence as well, so we don't need to | ||
* take care of synchronizing other values. Just the thread value. | ||
*/ | ||
#define rul_set_thread(l, t) atomic_store_explicit(&l->_thread, t, memory_order_release) | ||
#define rul_get_thread(l) atomic_load_explicit(&l->_thread, memory_order_acquire) | ||
|
||
void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l) | ||
{ | ||
// Just a cache for pthread_self so that we never call it twice. | ||
pthread_t s = NULL; | ||
|
||
// Try to lock without blocking. If we fail, check what thread owns it. | ||
// Note that the owning thread CAN CHANGE freely, but it can't become `self` | ||
// because only we are `self`. And if it's already `self` then we already have | ||
// the lock, because we reset it to NULL before we unlock. So (thread == self) is | ||
// invariant. | ||
|
||
if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { | ||
// Owned by other thread. Possibly other threads are waiting too. Block. | ||
os_unfair_lock_lock(&l->_lock); | ||
} | ||
// Now we've got the lock. Update the thread pointer and count. | ||
rul_set_thread(l, s ?: pthread_self()); | ||
l->_count++; | ||
} | ||
|
||
BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l) | ||
{ | ||
// Same logic as `Lock` function, see comments there. | ||
pthread_t s = NULL; | ||
|
||
if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { | ||
return NO; | ||
} | ||
rul_set_thread(l, s ?: pthread_self()); | ||
l->_count++; | ||
return YES; | ||
} | ||
|
||
void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l) | ||
{ | ||
// Ensure we have the lock. This check may miss some pathological cases, | ||
// but it'll catch 99.999999% of this serious programmer error. | ||
NSCAssert(rul_get_thread(l) == pthread_self(), @"Unlocking from a different thread than locked."); | ||
|
||
if (0 == --l->_count) { | ||
// Note that we have to clear this before unlocking because, if another thread | ||
// succeeds in locking above, but hasn't managed to update _thread, and we | ||
// try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly | ||
// think that we still own the lock and proceed without blocking. | ||
rul_set_thread(l, NULL); | ||
os_unfair_lock_unlock(&l->_lock); | ||
} | ||
} |
Oops, something went wrong.