objc-sync.mm 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /*
  2. * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
  3. *
  4. * @APPLE_LICENSE_HEADER_START@
  5. *
  6. * This file contains Original Code and/or Modifications of Original Code
  7. * as defined in and that are subject to the Apple Public Source License
  8. * Version 2.0 (the 'License'). You may not use this file except in
  9. * compliance with the License. Please obtain a copy of the License at
  10. * http://www.opensource.apple.com/apsl/ and read it before using this
  11. * file.
  12. *
  13. * The Original Code and all software distributed under the License are
  14. * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  15. * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  16. * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  18. * Please see the License for the specific language governing rights and
  19. * limitations under the License.
  20. *
  21. * @APPLE_LICENSE_HEADER_END@
  22. */
  23. #include "objc-private.h"
  24. #include "objc-sync.h"
  25. //
  26. // Allocate a lock only when needed. Since few locks are needed at any point
  27. // in time, keep them on a single list.
  28. //
  29. typedef struct alignas(CacheLineSize) SyncData {
  30. struct SyncData* nextData;
  31. DisguisedPtr<objc_object> object;
  32. int32_t threadCount; // number of THREADS using this block
  33. recursive_mutex_t mutex;
  34. } SyncData;
  35. typedef struct {
  36. SyncData *data;
  37. unsigned int lockCount; // number of times THIS THREAD locked this block
  38. } SyncCacheItem;
  39. typedef struct SyncCache {
  40. unsigned int allocated;
  41. unsigned int used;
  42. SyncCacheItem list[0];
  43. } SyncCache;
  44. /*
  45. Fast cache: two fixed pthread keys store a single SyncCacheItem.
  46. This avoids malloc of the SyncCache for threads that only synchronize
  47. a single object at a time.
  48. SYNC_DATA_DIRECT_KEY == SyncCacheItem.data
  49. SYNC_COUNT_DIRECT_KEY == SyncCacheItem.lockCount
  50. */
  51. struct SyncList {
  52. SyncData *data;
  53. spinlock_t lock;
  54. constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
  55. };
  56. // Use multiple parallel lists to decrease contention among unrelated objects.
  57. #define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
  58. #define LIST_FOR_OBJ(obj) sDataLists[obj].data
  59. static StripedMap<SyncList> sDataLists;
  60. enum usage { ACQUIRE, RELEASE, CHECK };
  61. static SyncCache *fetch_cache(bool create)
  62. {
  63. _objc_pthread_data *data;
  64. data = _objc_fetch_pthread_data(create);
  65. if (!data) return NULL;
  66. if (!data->syncCache) {
  67. if (!create) {
  68. return NULL;
  69. } else {
  70. int count = 4;
  71. data->syncCache = (SyncCache *)
  72. calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
  73. data->syncCache->allocated = count;
  74. }
  75. }
  76. // Make sure there's at least one open slot in the list.
  77. if (data->syncCache->allocated == data->syncCache->used) {
  78. data->syncCache->allocated *= 2;
  79. data->syncCache = (SyncCache *)
  80. realloc(data->syncCache, sizeof(SyncCache)
  81. + data->syncCache->allocated * sizeof(SyncCacheItem));
  82. }
  83. return data->syncCache;
  84. }
  85. void _destroySyncCache(struct SyncCache *cache)
  86. {
  87. if (cache) free(cache);
  88. }
  89. static SyncData* id2data(id object, enum usage why)
  90. {
  91. spinlock_t *lockp = &LOCK_FOR_OBJ(object);
  92. SyncData **listp = &LIST_FOR_OBJ(object);
  93. SyncData* result = NULL;
  94. #if SUPPORT_DIRECT_THREAD_KEYS
  95. // Check per-thread single-entry fast cache for matching object
  96. bool fastCacheOccupied = NO;
  97. SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
  98. if (data) {
  99. fastCacheOccupied = YES;
  100. if (data->object == object) {
  101. // Found a match in fast cache.
  102. uintptr_t lockCount;
  103. result = data;
  104. lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
  105. if (result->threadCount <= 0 || lockCount <= 0) {
  106. _objc_fatal("id2data fastcache is buggy");
  107. }
  108. switch(why) {
  109. case ACQUIRE: {
  110. lockCount++;
  111. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  112. break;
  113. }
  114. case RELEASE:
  115. lockCount--;
  116. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  117. if (lockCount == 0) {
  118. // remove from fast cache
  119. tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
  120. // atomic because may collide with concurrent ACQUIRE
  121. OSAtomicDecrement32Barrier(&result->threadCount);
  122. }
  123. break;
  124. case CHECK:
  125. // do nothing
  126. break;
  127. }
  128. return result;
  129. }
  130. }
  131. #endif
  132. // Check per-thread cache of already-owned locks for matching object
  133. SyncCache *cache = fetch_cache(NO);
  134. if (cache) {
  135. unsigned int i;
  136. for (i = 0; i < cache->used; i++) {
  137. SyncCacheItem *item = &cache->list[i];
  138. if (item->data->object != object) continue;
  139. // Found a match.
  140. result = item->data;
  141. if (result->threadCount <= 0 || item->lockCount <= 0) {
  142. _objc_fatal("id2data cache is buggy");
  143. }
  144. switch(why) {
  145. case ACQUIRE:
  146. item->lockCount++;
  147. break;
  148. case RELEASE:
  149. item->lockCount--;
  150. if (item->lockCount == 0) {
  151. // remove from per-thread cache
  152. cache->list[i] = cache->list[--cache->used];
  153. // atomic because may collide with concurrent ACQUIRE
  154. OSAtomicDecrement32Barrier(&result->threadCount);
  155. }
  156. break;
  157. case CHECK:
  158. // do nothing
  159. break;
  160. }
  161. return result;
  162. }
  163. }
  164. // Thread cache didn't find anything.
  165. // Walk in-use list looking for matching object
  166. // Spinlock prevents multiple threads from creating multiple
  167. // locks for the same new object.
  168. // We could keep the nodes in some hash table if we find that there are
  169. // more than 20 or so distinct locks active, but we don't do that now.
  170. lockp->lock();
  171. {
  172. SyncData* p;
  173. SyncData* firstUnused = NULL;
  174. for (p = *listp; p != NULL; p = p->nextData) {
  175. if ( p->object == object ) {
  176. result = p;
  177. // atomic because may collide with concurrent RELEASE
  178. OSAtomicIncrement32Barrier(&result->threadCount);
  179. goto done;
  180. }
  181. if ( (firstUnused == NULL) && (p->threadCount == 0) )
  182. firstUnused = p;
  183. }
  184. // no SyncData currently associated with object
  185. if ( (why == RELEASE) || (why == CHECK) )
  186. goto done;
  187. // an unused one was found, use it
  188. if ( firstUnused != NULL ) {
  189. result = firstUnused;
  190. result->object = (objc_object *)object;
  191. result->threadCount = 1;
  192. goto done;
  193. }
  194. }
  195. // Allocate a new SyncData and add to list.
  196. // XXX allocating memory with a global lock held is bad practice,
  197. // might be worth releasing the lock, allocating, and searching again.
  198. // But since we never free these guys we won't be stuck in allocation very often.
  199. posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
  200. result->object = (objc_object *)object;
  201. result->threadCount = 1;
  202. new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
  203. result->nextData = *listp;
  204. *listp = result;
  205. done:
  206. lockp->unlock();
  207. if (result) {
  208. // Only new ACQUIRE should get here.
  209. // All RELEASE and CHECK and recursive ACQUIRE are
  210. // handled by the per-thread caches above.
  211. if (why == RELEASE) {
  212. // Probably some thread is incorrectly exiting
  213. // while the object is held by another thread.
  214. return nil;
  215. }
  216. if (why != ACQUIRE) _objc_fatal("id2data is buggy");
  217. if (result->object != object) _objc_fatal("id2data is buggy");
  218. #if SUPPORT_DIRECT_THREAD_KEYS
  219. if (!fastCacheOccupied) {
  220. // Save in fast thread cache
  221. tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
  222. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
  223. } else
  224. #endif
  225. {
  226. // Save in thread cache
  227. if (!cache) cache = fetch_cache(YES);
  228. cache->list[cache->used].data = result;
  229. cache->list[cache->used].lockCount = 1;
  230. cache->used++;
  231. }
  232. }
  233. return result;
  234. }
  235. BREAKPOINT_FUNCTION(
  236. void objc_sync_nil(void)
  237. );
  238. // Begin synchronizing on 'obj'.
  239. // Allocates recursive mutex associated with 'obj' if needed.
  240. // Returns OBJC_SYNC_SUCCESS once lock is acquired.
  241. int objc_sync_enter(id obj)
  242. {
  243. int result = OBJC_SYNC_SUCCESS;
  244. if (obj) {
  245. SyncData* data = id2data(obj, ACQUIRE);
  246. assert(data);
  247. data->mutex.lock();
  248. } else {
  249. // @synchronized(nil) does nothing
  250. if (DebugNilSync) {
  251. _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
  252. }
  253. objc_sync_nil();
  254. }
  255. return result;
  256. }
  257. // End synchronizing on 'obj'.
  258. // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
  259. int objc_sync_exit(id obj)
  260. {
  261. int result = OBJC_SYNC_SUCCESS;
  262. if (obj) {
  263. SyncData* data = id2data(obj, RELEASE);
  264. if (!data) {
  265. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
  266. } else {
  267. bool okay = data->mutex.tryUnlock();
  268. if (!okay) {
  269. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
  270. }
  271. }
  272. } else {
  273. // @synchronized(nil) does nothing
  274. }
  275. return result;
  276. }