// // ASLocking.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 #import NS_ASSUME_NONNULL_BEGIN @protocol ASLocking /// Try to take lock without blocking. Returns whether the lock was taken. - (BOOL)tryLock; @end /** * Take multiple locks "simultaneously," avoiding deadlocks * caused by lock ordering. * * We use an explicit count argument to handle nil locks. * If you pass a nil lock, obviously we ignore it. * * TODO: Implement a scoped version. The scope variable would be * a struct containing the lock count and the locks to unlock. */ NS_INLINE void ASLockMany(unsigned count, id _Nullable arg0, ...) { if (count == 0) { return; } BOOL done = NO; while (!done) { // Don't retain/release locks. Arguments are retained by caller. __unsafe_unretained id ownedLocks[count]; va_list locks; va_start(locks, arg0); for (int i = 0; i < count; i++) { __unsafe_unretained id lock = va_arg(locks, id); // Attempt to lock, or pass if nil. BOOL locked = (lock ? [lock tryLock] : YES); if (!locked) { // If we failed to lock, release what we have, yield, and start over. for (int j = 0; j < i; j++) { [ownedLocks[j] unlock]; } sched_yield(); break; } else if (i == count - 1) { // If we suceeded and this was the last, we're done. done = YES; } else { // Otherwise note that we have this lock and keep going. ownedLocks[i] = lock; } } va_end(locks); } } NS_INLINE void ASUnlockMany(unsigned count, id arg0, ...) { va_list locks; va_start(locks, arg0); for (int i = 0; i < count; i++) { __unsafe_unretained id lock = va_arg(locks, id); [lock unlock]; } va_end(locks); } @interface NSLock (ASLocking) @end @interface NSRecursiveLock (ASLocking) @end @interface NSConditionLock (ASLocking) @end NS_ASSUME_NONNULL_END