Home | History | Annotate | Line # | Download | only in libobjc
      1   1.1  mrg /* GNU Objective C Runtime @synchronized implementation
      2  1.10  mrg    Copyright (C) 2010-2022 Free Software Foundation, Inc.
      3   1.1  mrg    Contributed by Nicola Pero <nicola.pero (at) meta-innovation.com>
      4   1.1  mrg 
      5   1.1  mrg This file is part of GCC.
      6   1.1  mrg 
      7   1.1  mrg GCC is free software; you can redistribute it and/or modify it under the
      8   1.1  mrg terms of the GNU General Public License as published by the Free Software
      9   1.1  mrg Foundation; either version 3, or (at your option) any later version.
     10   1.1  mrg 
     11   1.1  mrg GCC is distributed in the hope that it will be useful, but WITHOUT ANY
     12   1.1  mrg WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     13   1.1  mrg FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
     14   1.1  mrg details.
     15   1.1  mrg 
     16   1.1  mrg Under Section 7 of GPL version 3, you are granted additional
     17   1.1  mrg permissions described in the GCC Runtime Library Exception, version
     18   1.1  mrg 3.1, as published by the Free Software Foundation.
     19   1.1  mrg 
     20   1.1  mrg You should have received a copy of the GNU General Public License and
     21   1.1  mrg a copy of the GCC Runtime Library Exception along with this program;
     22   1.1  mrg see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
     23   1.1  mrg <http://www.gnu.org/licenses/>.  */
     24   1.1  mrg 
     25   1.1  mrg /* This file implements objc_sync_enter() and objc_sync_exit(), the
     26   1.1  mrg    two functions required to support @synchronized().
     27   1.1  mrg 
     28   1.1  mrg    objc_sync_enter(object) needs to get a recursive lock associated
     29   1.1  mrg    with 'object', and lock it.
     30   1.1  mrg 
     31   1.1  mrg    objc_sync_exit(object) needs to get the recursive lock associated
     32   1.1  mrg    with 'object', and unlock it.  */
     33   1.1  mrg 
     34   1.1  mrg /* To avoid the overhead of continuously allocating and deallocating
     35   1.1  mrg    locks, we implement a pool of locks.  When a lock is needed for an
     36   1.1  mrg    object, we get a lock from the pool and associate it with the
     37   1.1  mrg    object.
     38   1.1  mrg 
     39   1.1  mrg    The lock pool need to be protected by its own lock (the
     40   1.1  mrg    "protection" lock), which has to be locked then unlocked each time
     41   1.1  mrg    objc_sync_enter() and objc_sync_exit() are called.  To reduce the
     42   1.1  mrg    contention on the protection lock, instead of a single pool with a
     43   1.1  mrg    single (global) protection lock we use a number of smaller pools,
     44   1.1  mrg    each with its own pool protection lock.  To decide which lock pool
     45   1.1  mrg    to use for each object, we compute a hash from the object pointer.
     46   1.1  mrg 
     47   1.1  mrg    The implementation of each lock pool uses a linked list of all the
     48   1.1  mrg    locks in the pool (both unlocked, and locked); this works in the
     49   1.1  mrg    assumption that the number of locks concurrently required is very
     50   1.1  mrg    low.  In practice, it seems that you rarely see more than a few
     51   1.1  mrg    locks ever concurrently required.
     52   1.1  mrg 
     53   1.1  mrg    A standard case is a thread acquiring a lock recursively, over and
     54   1.1  mrg    over again: for example when most methods of a class are protected
     55   1.1  mrg    by @synchronized(self) but they also call each other.  We use
     56   1.1  mrg    thread-local storage to implement a cache and optimize this case.
     57   1.1  mrg    The cache stores locks that the thread successfully acquired,
     58   1.1  mrg    allowing objc_sync_enter() and objc_sync_exit() to locate a lock
     59   1.1  mrg    which is already held by the current thread without having to use
     60   1.1  mrg    any protection lock or synchronization mechanism.  It can so detect
     61   1.1  mrg    recursive locks/unlocks, and transform them into no-ops that
     62   1.1  mrg    require no actual locking or synchronization mechanisms at all.  */
     63   1.1  mrg 
     64   1.1  mrg /* You can disable the thread-local cache (most likely to benchmark
     65   1.1  mrg    the code with and without it) by compiling with
     66   1.1  mrg    -DSYNC_CACHE_DISABLE, or commenting out the following line.  */
     67   1.1  mrg /* #define SYNC_CACHE_DISABLE */
     68   1.1  mrg 
     69   1.1  mrg /* If thread-local storage is not available, automatically disable the
     70   1.1  mrg    cache.  */
     71   1.1  mrg #ifndef HAVE_TLS
     72   1.1  mrg # define SYNC_CACHE_DISABLE
     73   1.1  mrg #endif
     74   1.1  mrg 
     75   1.1  mrg #include "objc-private/common.h"
     76   1.1  mrg #include "objc/objc-sync.h"         /* For objc_sync_enter(), objc_sync_exit() */
     77   1.1  mrg #include "objc/runtime.h"           /* For objc_malloc() */
     78   1.1  mrg #include "objc/thr.h"               /* For objc_mutex_loc() and similar */
     79   1.1  mrg #include "objc-private/objc-sync.h" /* For __objc_sync_init() */
     80   1.1  mrg 
     81   1.1  mrg /* We have 32 pools of locks, each of them protected by its own
     82   1.1  mrg    protection lock.  It's tempting to increase this number to reduce
     83   1.1  mrg    contention; but in our tests it is high enough.  */
     84   1.1  mrg #define SYNC_NUMBER_OF_POOLS 32
     85   1.1  mrg 
     86   1.1  mrg /* Given an object, it determines which pool contains the associated
     87   1.1  mrg    lock.  */
     88   1.1  mrg #define SYNC_OBJECT_HASH(OBJECT) ((((size_t)OBJECT >> 8) ^ (size_t)OBJECT) & (SYNC_NUMBER_OF_POOLS - 1))
     89   1.1  mrg 
     90   1.1  mrg /* The locks protecting each pool.  */
     91   1.1  mrg static objc_mutex_t sync_pool_protection_locks[SYNC_NUMBER_OF_POOLS];
     92   1.1  mrg 
     93   1.1  mrg /* The data structure (linked list) holding the locks.  */
     94   1.1  mrg typedef struct lock_node
     95   1.1  mrg {
     96   1.1  mrg   /* Pointer to next entry on the list.  NULL indicates end of list.
     97   1.1  mrg      You need to hold the appropriate sync_pool_protection_locks[N] to
     98   1.1  mrg      read or write this variable.  */
     99   1.1  mrg   struct lock_node *next;
    100   1.1  mrg 
    101   1.1  mrg   /* The (recursive) lock.  Allocated when the node is created, and
    102   1.1  mrg      always not-NULL, and unchangeable, after that.  */
    103   1.1  mrg   objc_mutex_t lock;
    104   1.1  mrg 
    105   1.1  mrg   /* This is how many times the objc_mutex_lock() has been called on
    106   1.1  mrg      the lock (it is 0 when the lock is unused).  Used to track when
    107   1.1  mrg      the lock is no longer associated with an object and can be reused
    108   1.1  mrg      for another object.  It records "real" locks, potentially (but
    109   1.1  mrg      not necessarily) by multiple threads.  You need to hold the
    110   1.1  mrg      appropriate sync_pool_protection_locks[N] to read or write this
    111   1.1  mrg      variable.  */
    112   1.1  mrg   unsigned int usage_count;
    113   1.1  mrg 
    114   1.1  mrg   /* The object that the lock is associated with.  This variable can
    115   1.1  mrg      only be written when holding the sync_pool_protection_locks[N]
    116   1.1  mrg      and when node->usage_count == 0, ie, the lock is not being used.
    117   1.1  mrg      You can read this variable either when you hold the
    118   1.1  mrg      sync_pool_protection_locks[N] or when you hold node->lock,
    119   1.1  mrg      because in that case you know that node->usage_count can't get to
    120   1.1  mrg      zero until you release the lock.  It is valid to have usage_count
    121   1.1  mrg      == 0 and object != nil; in that case, the lock is not currently
    122   1.1  mrg      being used, but is still currently associated with the
    123   1.1  mrg      object.  */
    124   1.1  mrg   id object;
    125   1.1  mrg 
    126   1.1  mrg   /* This is a counter reserved for use by the thread currently
    127   1.1  mrg      holding the lock.  So, you need to hold node->lock to read or
    128   1.1  mrg      write this variable.  It is normally 0, and if the cache is not
    129   1.1  mrg      being used, it is kept at 0 (even if recursive locks are being
    130   1.1  mrg      done; in that case, no difference is made between recursive and
    131   1.1  mrg      non-recursive locks: they all increase usage_count, and call
    132   1.1  mrg      objc_mutex_lock()).  When the cache is being used, a thread may
    133   1.1  mrg      be able to find a lock that it already holds using the cache; in
    134   1.1  mrg      that case, to perform additional locks/unlocks it can
    135   1.1  mrg      increase/decrease the recursive_usage_count (which does not
    136   1.1  mrg      require any synchronization with other threads, since it's
    137   1.1  mrg      protected by the node->lock itself) instead of the usage_count
    138   1.1  mrg      (which requires locking the pool protection lock).  And it can
    139   1.1  mrg      skip the call to objc_mutex_lock/unlock too.  */
    140   1.1  mrg   unsigned int recursive_usage_count;
    141   1.1  mrg } *lock_node_ptr;
    142   1.1  mrg 
    143   1.1  mrg 
    144   1.1  mrg /* The pools of locks.  Each of them is a linked list of lock_nodes.
    145   1.1  mrg    In the list we keep both unlocked and locked nodes.  */
    146   1.1  mrg static lock_node_ptr sync_pool_array[SYNC_NUMBER_OF_POOLS];
    147   1.1  mrg 
    148   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    149   1.1  mrg /* We store a cache of locks acquired by each thread in thread-local
    150   1.1  mrg    storage.  */
    151   1.1  mrg static __thread lock_node_ptr *lock_cache = NULL;
    152   1.1  mrg 
    153   1.1  mrg /* This is a conservative implementation that uses a static array of
    154   1.1  mrg    fixed size as cache.  Because the cache is an array that we scan
    155   1.1  mrg    linearly, the bigger it is, the slower it gets.  This does not
    156   1.1  mrg    matter much at small sizes (eg, the overhead of checking 8 cache
    157   1.1  mrg    slots instead of 4 is very small compared to the other overheads
    158   1.1  mrg    involved such as function calls and lock/unlock operations), but at
    159   1.1  mrg    large sizes it becomes important as obviously there is a size over
    160   1.1  mrg    which using the cache backfires: the lookup is so slow that the
    161   1.1  mrg    cache slows down the software instead of speeding it up.  In
    162   1.1  mrg    practice, it seems that most threads use a small number of
    163   1.1  mrg    concurrent locks, so we have a conservative implementation with a
    164   1.1  mrg    fixed-size cache of 8 locks which gives a very predictable
    165   1.1  mrg    behaviour.  If a thread locks lots of different locks, only the
    166   1.1  mrg    first 8 get the speed benefits of the cache, but the cache remains
    167   1.1  mrg    always small, fast and predictable.
    168   1.1  mrg 
    169   1.1  mrg    SYNC_CACHE_SIZE is the size of the lock cache for each thread.  */
    170   1.1  mrg #define SYNC_CACHE_SIZE 8
    171   1.1  mrg #endif /* SYNC_CACHE_DISABLE */
    172   1.1  mrg 
    173   1.1  mrg /* Called at startup by init.c.  */
    174   1.1  mrg void
    175   1.1  mrg __objc_sync_init (void)
    176   1.1  mrg {
    177   1.1  mrg   int i;
    178   1.1  mrg 
    179   1.1  mrg   for (i = 0; i < SYNC_NUMBER_OF_POOLS; i++)
    180   1.1  mrg     {
    181   1.1  mrg       lock_node_ptr new_node;
    182   1.1  mrg 
    183   1.1  mrg       /* Create a protection lock for each pool.  */
    184   1.1  mrg       sync_pool_protection_locks[i] = objc_mutex_allocate ();
    185   1.1  mrg 
    186   1.1  mrg       /* Preallocate a lock per pool.  */
    187   1.1  mrg       new_node = objc_malloc (sizeof (struct lock_node));
    188   1.1  mrg       new_node->lock = objc_mutex_allocate ();
    189   1.1  mrg       new_node->object = nil;
    190   1.1  mrg       new_node->usage_count = 0;
    191   1.1  mrg       new_node->recursive_usage_count = 0;
    192   1.1  mrg       new_node->next = NULL;
    193   1.1  mrg 
    194   1.1  mrg       sync_pool_array[i] = new_node;
    195   1.1  mrg     }
    196   1.1  mrg }
    197   1.1  mrg 
    198   1.1  mrg int
    199   1.1  mrg objc_sync_enter (id object)
    200   1.1  mrg {
    201   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    202   1.1  mrg   int free_cache_slot;
    203   1.1  mrg #endif
    204   1.1  mrg   int hash;
    205   1.1  mrg   lock_node_ptr node;
    206   1.1  mrg   lock_node_ptr unused_node;
    207   1.1  mrg 
    208   1.1  mrg   if (object == nil)
    209   1.1  mrg     return OBJC_SYNC_SUCCESS;
    210   1.1  mrg 
    211   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    212   1.1  mrg   if (lock_cache == NULL)
    213   1.1  mrg     {
    214   1.1  mrg       /* Note that this calloc only happen only once per thread, the
    215   1.1  mrg 	 very first time a thread does a objc_sync_enter().  */
    216   1.1  mrg       lock_cache = objc_calloc (SYNC_CACHE_SIZE, sizeof (lock_node_ptr));
    217   1.1  mrg     }
    218   1.1  mrg 
    219   1.1  mrg   /* Check the cache to see if we have a record of having already
    220   1.1  mrg      locked the lock corresponding to this object.  While doing so,
    221   1.1  mrg      keep track of the first free cache node in case we need it
    222   1.1  mrg      later.  */
    223   1.1  mrg   node = NULL;
    224   1.1  mrg   free_cache_slot = -1;
    225   1.1  mrg 
    226   1.1  mrg   {
    227   1.1  mrg     int i;
    228   1.1  mrg     for (i = 0; i < SYNC_CACHE_SIZE; i++)
    229   1.1  mrg       {
    230   1.1  mrg 	lock_node_ptr locked_node = lock_cache[i];
    231   1.1  mrg 
    232   1.1  mrg 	if (locked_node == NULL)
    233   1.1  mrg 	  {
    234   1.1  mrg 	    if (free_cache_slot == -1)
    235   1.1  mrg 	      free_cache_slot = i;
    236   1.1  mrg 	  }
    237   1.1  mrg 	else if (locked_node->object == object)
    238   1.1  mrg 	  {
    239   1.1  mrg 	    node = locked_node;
    240   1.1  mrg 	    break;
    241   1.1  mrg 	  }
    242   1.1  mrg       }
    243   1.1  mrg   }
    244   1.1  mrg 
    245   1.1  mrg   if (node != NULL)
    246   1.1  mrg     {
    247   1.1  mrg       /* We found the lock.  Increase recursive_usage_count, which is
    248   1.1  mrg 	 protected by node->lock, which we already hold.  */
    249   1.1  mrg       node->recursive_usage_count++;
    250   1.1  mrg 
    251   1.1  mrg       /* There is no need to actually lock anything, since we already
    252   1.1  mrg 	 hold the lock.  Correspondingly, objc_sync_exit() will just
    253   1.1  mrg 	 decrease recursive_usage_count and do nothing to unlock.  */
    254   1.1  mrg       return OBJC_SYNC_SUCCESS;
    255   1.1  mrg     }
    256   1.1  mrg #endif /* SYNC_CACHE_DISABLE */
    257   1.1  mrg 
    258   1.1  mrg   /* The following is the standard lookup for the lock in the standard
    259   1.1  mrg      pool lock.  It requires a pool protection lock.  */
    260   1.1  mrg   hash = SYNC_OBJECT_HASH(object);
    261   1.1  mrg 
    262   1.1  mrg   /* Search for an existing lock for 'object'.  While searching, make
    263   1.1  mrg      note of any unused lock if we find any.  */
    264   1.1  mrg   unused_node = NULL;
    265   1.1  mrg 
    266   1.1  mrg   objc_mutex_lock (sync_pool_protection_locks[hash]);
    267   1.1  mrg 
    268   1.1  mrg   node = sync_pool_array[hash];
    269   1.1  mrg 
    270   1.1  mrg   while (node != NULL)
    271   1.1  mrg     {
    272   1.1  mrg       if (node->object == object)
    273   1.1  mrg 	{
    274   1.1  mrg 	  /* We found the lock.  */
    275   1.1  mrg 	  node->usage_count++;
    276   1.1  mrg 	  objc_mutex_unlock (sync_pool_protection_locks[hash]);
    277   1.1  mrg 
    278   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    279   1.1  mrg 	  /* Put it in the cache.  */
    280   1.1  mrg 	  if (free_cache_slot != -1)
    281   1.1  mrg 	    lock_cache[free_cache_slot] = node;
    282   1.1  mrg #endif
    283   1.1  mrg 
    284   1.1  mrg 	  /* Lock it.  */
    285   1.1  mrg 	  objc_mutex_lock (node->lock);
    286   1.1  mrg 
    287   1.1  mrg 	  return OBJC_SYNC_SUCCESS;
    288   1.1  mrg 	}
    289   1.1  mrg 
    290   1.1  mrg       if (unused_node == NULL  &&  node->usage_count == 0)
    291   1.1  mrg 	{
    292   1.1  mrg 	  /* We found the first unused node.  Record it.  */
    293   1.1  mrg 	  unused_node = node;
    294   1.1  mrg 	}
    295   1.1  mrg 
    296   1.1  mrg       node = node->next;
    297   1.1  mrg     }
    298   1.1  mrg 
    299   1.1  mrg   /* An existing lock for 'object' could not be found.  */
    300   1.1  mrg   if (unused_node != NULL)
    301   1.1  mrg     {
    302   1.1  mrg       /* But we found a unused lock; use it.  */
    303   1.1  mrg       unused_node->object = object;
    304   1.1  mrg       unused_node->usage_count = 1;
    305   1.1  mrg       unused_node->recursive_usage_count = 0;
    306   1.1  mrg       objc_mutex_unlock (sync_pool_protection_locks[hash]);
    307   1.1  mrg 
    308   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    309   1.1  mrg       if (free_cache_slot != -1)
    310   1.1  mrg 	lock_cache[free_cache_slot] = unused_node;
    311   1.1  mrg #endif
    312   1.1  mrg 
    313   1.1  mrg       objc_mutex_lock (unused_node->lock);
    314   1.1  mrg 
    315   1.1  mrg       return OBJC_SYNC_SUCCESS;
    316   1.1  mrg     }
    317   1.1  mrg   else
    318   1.1  mrg     {
    319   1.1  mrg       /* There are no unused nodes; allocate a new node.  */
    320   1.1  mrg       lock_node_ptr new_node;
    321   1.1  mrg 
    322   1.1  mrg       /* Create the node.  */
    323   1.1  mrg       new_node = objc_malloc (sizeof (struct lock_node));
    324   1.1  mrg       new_node->lock = objc_mutex_allocate ();
    325   1.1  mrg       new_node->object = object;
    326   1.1  mrg       new_node->usage_count = 1;
    327   1.1  mrg       new_node->recursive_usage_count = 0;
    328   1.1  mrg 
    329   1.1  mrg       /* Attach it at the beginning of the pool.  */
    330   1.1  mrg       new_node->next = sync_pool_array[hash];
    331   1.1  mrg       sync_pool_array[hash] = new_node;
    332   1.1  mrg       objc_mutex_unlock (sync_pool_protection_locks[hash]);
    333   1.1  mrg 
    334   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    335   1.1  mrg       if (free_cache_slot != -1)
    336   1.1  mrg 	lock_cache[free_cache_slot] = new_node;
    337   1.1  mrg #endif
    338   1.1  mrg 
    339   1.1  mrg       objc_mutex_lock (new_node->lock);
    340   1.1  mrg 
    341   1.1  mrg       return OBJC_SYNC_SUCCESS;
    342   1.1  mrg     }
    343   1.1  mrg }
    344   1.1  mrg 
    345   1.1  mrg int
    346   1.1  mrg objc_sync_exit (id object)
    347   1.1  mrg {
    348   1.1  mrg   int hash;
    349   1.1  mrg   lock_node_ptr node;
    350   1.1  mrg 
    351   1.1  mrg   if (object == nil)
    352   1.1  mrg     return OBJC_SYNC_SUCCESS;
    353   1.1  mrg 
    354   1.1  mrg #ifndef SYNC_CACHE_DISABLE
    355   1.1  mrg   if (lock_cache != NULL)
    356   1.1  mrg     {
    357   1.1  mrg       int i;
    358   1.1  mrg 
    359   1.1  mrg       /* Find the lock in the cache.  */
    360   1.1  mrg       node = NULL;
    361   1.1  mrg       for (i = 0; i < SYNC_CACHE_SIZE; i++)
    362   1.1  mrg 	{
    363   1.1  mrg 	  lock_node_ptr locked_node = lock_cache[i];
    364   1.1  mrg 
    365   1.1  mrg 	  if (locked_node != NULL  &&  locked_node->object == object)
    366   1.1  mrg 	    {
    367   1.1  mrg 	      node = locked_node;
    368   1.1  mrg 	      break;
    369   1.1  mrg 	    }
    370   1.1  mrg 	}
    371   1.1  mrg       /* Note that, if a node was found in the cache, the variable i
    372   1.1  mrg 	 now holds the index where it was found, which will be used to
    373   1.1  mrg 	 remove it from the cache.  */
    374   1.1  mrg       if (node != NULL)
    375   1.1  mrg 	{
    376   1.1  mrg 	  if (node->recursive_usage_count > 0)
    377   1.1  mrg 	    {
    378   1.1  mrg 	      node->recursive_usage_count--;
    379   1.1  mrg 	      return OBJC_SYNC_SUCCESS;
    380   1.1  mrg 	    }
    381   1.1  mrg 	  else
    382   1.1  mrg 	    {
    383   1.1  mrg 	      /* We need to do a real unlock.  */
    384   1.1  mrg 	      hash = SYNC_OBJECT_HASH(object);
    385   1.1  mrg 
    386   1.1  mrg 	      /* TODO: If we had atomic increase/decrease operations
    387   1.1  mrg 		 with memory barriers, we could avoid the lock
    388   1.1  mrg 		 here!  */
    389   1.1  mrg 	      objc_mutex_lock (sync_pool_protection_locks[hash]);
    390   1.1  mrg 	      node->usage_count--;
    391   1.1  mrg 	      /* Normally, we do not reset object to nil here.  We'll
    392   1.1  mrg 		 leave the lock associated with that object, at zero
    393   1.3  mrg 		 usage count.  This makes it slightly more efficient to
    394   1.1  mrg 		 provide a lock for that object if (as likely)
    395   1.1  mrg 		 requested again.  If the object is deallocated, we
    396   1.1  mrg 		 don't care.  It will never match a new lock that is
    397   1.1  mrg 		 requested, and the node will be reused at some point.
    398   1.1  mrg 
    399   1.1  mrg 		 But, if garbage collection is enabled, leaving a
    400   1.1  mrg 		 pointer to the object in memory might prevent the
    401   1.1  mrg 		 object from being released.  In that case, we remove
    402   1.1  mrg 		 it (TODO: maybe we should avoid using the garbage
    403   1.1  mrg 		 collector at all ?  Nothing is ever deallocated in
    404   1.1  mrg 		 this file).  */
    405   1.1  mrg #if OBJC_WITH_GC
    406   1.1  mrg 	      node->object = nil;
    407   1.1  mrg #endif
    408   1.1  mrg 	      objc_mutex_unlock (sync_pool_protection_locks[hash]);
    409   1.1  mrg 
    410   1.1  mrg 	      /* PS: Between objc_mutex_unlock
    411   1.1  mrg 		 (sync_pool_protection_locks[hash]) and
    412   1.1  mrg 		 objc_mutex_unlock (node->lock), the pool is unlocked
    413   1.1  mrg 		 so other threads may allocate this same lock to
    414   1.1  mrg 		 another object (!).  This is not a problem, but it is
    415   1.1  mrg 		 curious.  */
    416   1.1  mrg 	      objc_mutex_unlock (node->lock);
    417   1.1  mrg 
    418   1.1  mrg 	      /* Remove the node from the cache.  */
    419   1.1  mrg 	      lock_cache[i] = NULL;
    420   1.1  mrg 
    421   1.1  mrg 	      return OBJC_SYNC_SUCCESS;
    422   1.1  mrg 	    }
    423   1.1  mrg 	}
    424   1.1  mrg     }
    425   1.1  mrg #endif
    426   1.1  mrg 
    427   1.1  mrg   /* The cache either wasn't there, or didn't work (eg, we overflowed
    428   1.1  mrg      it at some point and stopped recording new locks in the cache).
    429   1.1  mrg      Proceed with a full search of the lock pool.  */
    430   1.1  mrg   hash = SYNC_OBJECT_HASH(object);
    431   1.1  mrg 
    432   1.1  mrg   objc_mutex_lock (sync_pool_protection_locks[hash]);
    433   1.1  mrg 
    434   1.1  mrg   /* Search for an existing lock for 'object'.  */
    435   1.1  mrg   node = sync_pool_array[hash];
    436   1.1  mrg 
    437   1.1  mrg   while (node != NULL)
    438   1.1  mrg     {
    439   1.1  mrg       if (node->object == object)
    440   1.1  mrg 	{
    441   1.1  mrg 	  /* We found the lock.  */
    442   1.1  mrg 	  node->usage_count--;
    443   1.1  mrg 	  objc_mutex_unlock (sync_pool_protection_locks[hash]);
    444   1.1  mrg 
    445   1.1  mrg 	  objc_mutex_unlock (node->lock);
    446   1.1  mrg 
    447   1.1  mrg 	  /* No need to remove the node from the cache, since it
    448   1.1  mrg 	     wasn't found in the cache when we looked for it!  */
    449   1.1  mrg 	  return OBJC_SYNC_SUCCESS;
    450   1.1  mrg 	}
    451   1.1  mrg 
    452   1.1  mrg       node = node->next;
    453   1.1  mrg     }
    454   1.1  mrg 
    455   1.1  mrg   objc_mutex_unlock (sync_pool_protection_locks[hash]);
    456   1.1  mrg 
    457   1.1  mrg   /* A lock for 'object' to unlock could not be found (!!).  */
    458   1.1  mrg   return OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
    459   1.1  mrg }
    460