objc-sync.c 15.5 KB
Newer Older
1
/* GNU Objective C Runtime @synchronized implementation
2
   Copyright (C) 2010-2013 Free Software Foundation, Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   Contributed by Nicola Pero <nicola.pero@meta-innovation.com>

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

25 26
/* This file implements objc_sync_enter() and objc_sync_exit(), the
   two functions required to support @synchronized().
27

28 29 30 31 32
   objc_sync_enter(object) needs to get a recursive lock associated
   with 'object', and lock it.
   
   objc_sync_exit(object) needs to get the recursive lock associated
   with 'object', and unlock it.  */
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

/* To avoid the overhead of continuously allocating and deallocating
   locks, we implement a pool of locks.  When a lock is needed for an
   object, we get a lock from the pool and associate it with the
   object.
 
   The lock pool need to be protected by its own lock (the
   "protection" lock), which has to be locked then unlocked each time
   objc_sync_enter() and objc_sync_exit() are called.  To reduce the
   contention on the protection lock, instead of a single pool with a
   single (global) protection lock we use a number of smaller pools,
   each with its own pool protection lock.  To decide which lock pool
   to use for each object, we compute a hash from the object pointer.
 
   The implementation of each lock pool uses a linked list of all the
   locks in the pool (both unlocked, and locked); this works in the
   assumption that the number of locks concurrently required is very
   low.  In practice, it seems that you rarely see more than a few
   locks ever concurrently required.
 
   A standard case is a thread acquiring a lock recursively, over and
   over again: for example when most methods of a class are protected
   by @synchronized(self) but they also call each other.  We use
   thread-local storage to implement a cache and optimize this case.
   The cache stores locks that the thread successfully acquired,
   allowing objc_sync_enter() and objc_sync_exit() to locate a lock
   which is already held by the current thread without having to use
   any protection lock or synchronization mechanism.  It can so detect
   recursive locks/unlocks, and transform them into no-ops that
62
   require no actual locking or synchronization mechanisms at all.  */
63 64 65

/* You can disable the thread-local cache (most likely to benchmark
   the code with and without it) by compiling with
66
   -DSYNC_CACHE_DISABLE, or commenting out the following line.  */
67 68 69
/* #define SYNC_CACHE_DISABLE */

/* If thread-local storage is not available, automatically disable the
70
   cache.  */
71 72 73 74
#ifndef HAVE_TLS
# define SYNC_CACHE_DISABLE
#endif

75
#include "objc-private/common.h"
76
#include "objc/objc-sync.h"         /* For objc_sync_enter(), objc_sync_exit() */
77
#include "objc/runtime.h"           /* For objc_malloc() */
78 79 80 81 82
#include "objc/thr.h"               /* For objc_mutex_loc() and similar */
#include "objc-private/objc-sync.h" /* For __objc_sync_init() */

/* We have 32 pools of locks, each of them protected by its own
   protection lock.  It's tempting to increase this number to reduce
83
   contention; but in our tests it is high enough.  */
84 85 86
#define SYNC_NUMBER_OF_POOLS 32

/* Given an object, it determines which pool contains the associated
87
   lock.  */
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
#define SYNC_OBJECT_HASH(OBJECT) ((((size_t)OBJECT >> 8) ^ (size_t)OBJECT) & (SYNC_NUMBER_OF_POOLS - 1))

/* The locks protecting each pool.  */
static objc_mutex_t sync_pool_protection_locks[SYNC_NUMBER_OF_POOLS];

/* The data structure (linked list) holding the locks.  */
typedef struct lock_node
{
  /* Pointer to next entry on the list.  NULL indicates end of list.
     You need to hold the appropriate sync_pool_protection_locks[N] to
     read or write this variable.  */
  struct lock_node *next;

  /* The (recursive) lock.  Allocated when the node is created, and
     always not-NULL, and unchangeable, after that.  */
  objc_mutex_t lock;

  /* This is how many times the objc_mutex_lock() has been called on
     the lock (it is 0 when the lock is unused).  Used to track when
     the lock is no longer associated with an object and can be reused
     for another object.  It records "real" locks, potentially (but
     not necessarily) by multiple threads.  You need to hold the
     appropriate sync_pool_protection_locks[N] to read or write this
     variable.  */
  unsigned int usage_count;

  /* The object that the lock is associated with.  This variable can
     only be written when holding the sync_pool_protection_locks[N]
     and when node->usage_count == 0, ie, the lock is not being used.
     You can read this variable either when you hold the
     sync_pool_protection_locks[N] or when you hold node->lock,
     because in that case you know that node->usage_count can't get to
     zero until you release the lock.  It is valid to have usage_count
     == 0 and object != nil; in that case, the lock is not currently
122 123
     being used, but is still currently associated with the
     object.  */
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  id object;

  /* This is a counter reserved for use by the thread currently
     holding the lock.  So, you need to hold node->lock to read or
     write this variable.  It is normally 0, and if the cache is not
     being used, it is kept at 0 (even if recursive locks are being
     done; in that case, no difference is made between recursive and
     non-recursive locks: they all increase usage_count, and call
     objc_mutex_lock()).  When the cache is being used, a thread may
     be able to find a lock that it already holds using the cache; in
     that case, to perform additional locks/unlocks it can
     increase/decrease the recursive_usage_count (which does not
     require any synchronization with other threads, since it's
     protected by the node->lock itself) instead of the usage_count
     (which requires locking the pool protection lock).  And it can
139
     skip the call to objc_mutex_lock/unlock too.  */
140 141 142 143 144
  unsigned int recursive_usage_count;
} *lock_node_ptr;


/* The pools of locks.  Each of them is a linked list of lock_nodes.
145
   In the list we keep both unlocked and locked nodes.  */
146 147 148 149
static lock_node_ptr sync_pool_array[SYNC_NUMBER_OF_POOLS];

#ifndef SYNC_CACHE_DISABLE
/* We store a cache of locks acquired by each thread in thread-local
150
   storage.  */
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
static __thread lock_node_ptr *lock_cache = NULL;

/* This is a conservative implementation that uses a static array of
   fixed size as cache.  Because the cache is an array that we scan
   linearly, the bigger it is, the slower it gets.  This does not
   matter much at small sizes (eg, the overhead of checking 8 cache
   slots instead of 4 is very small compared to the other overheads
   involved such as function calls and lock/unlock operations), but at
   large sizes it becomes important as obviously there is a size over
   which using the cache backfires: the lookup is so slow that the
   cache slows down the software instead of speeding it up.  In
   practice, it seems that most threads use a small number of
   concurrent locks, so we have a conservative implementation with a
   fixed-size cache of 8 locks which gives a very predictable
   behaviour.  If a thread locks lots of different locks, only the
   first 8 get the speed benefits of the cache, but the cache remains
   always small, fast and predictable.
 
169
   SYNC_CACHE_SIZE is the size of the lock cache for each thread.  */
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
#define SYNC_CACHE_SIZE 8
#endif /* SYNC_CACHE_DISABLE */

/* Called at startup by init.c.  */
void
__objc_sync_init (void)
{
  int i;

  for (i = 0; i < SYNC_NUMBER_OF_POOLS; i++)
    {
      lock_node_ptr new_node;
      
      /* Create a protection lock for each pool.  */
      sync_pool_protection_locks[i] = objc_mutex_allocate ();

      /* Preallocate a lock per pool.  */
      new_node = objc_malloc (sizeof (struct lock_node));
      new_node->lock = objc_mutex_allocate ();
      new_node->object = nil;
      new_node->usage_count = 0;
      new_node->recursive_usage_count = 0;
      new_node->next = NULL;

      sync_pool_array[i] = new_node;
    }
}  

int
objc_sync_enter (id object)
{
#ifndef SYNC_CACHE_DISABLE
  int free_cache_slot;
#endif
  int hash;
  lock_node_ptr node;
  lock_node_ptr unused_node;

  if (object == nil)
209
    return OBJC_SYNC_SUCCESS;
210 211 212 213 214

#ifndef SYNC_CACHE_DISABLE
  if (lock_cache == NULL)
    {
      /* Note that this calloc only happen only once per thread, the
215
	 very first time a thread does a objc_sync_enter().  */
216 217 218 219 220
      lock_cache = objc_calloc (SYNC_CACHE_SIZE, sizeof (lock_node_ptr));
    }

  /* Check the cache to see if we have a record of having already
     locked the lock corresponding to this object.  While doing so,
221 222
     keep track of the first free cache node in case we need it
     later.  */ 
223 224 225 226 227 228 229 230 231 232 233 234
  node = NULL;
  free_cache_slot = -1;

  {
    int i;
    for (i = 0; i < SYNC_CACHE_SIZE; i++)
      {
	lock_node_ptr locked_node = lock_cache[i];
	
	if (locked_node == NULL)
	  {
	    if (free_cache_slot == -1)
235
	      free_cache_slot = i;
236 237 238 239 240 241 242 243 244 245 246 247
	  }
	else if (locked_node->object == object)
	  {
	    node = locked_node;
	    break;
	  }
      }
  }

  if (node != NULL)
    {
      /* We found the lock.  Increase recursive_usage_count, which is
248
	 protected by node->lock, which we already hold.  */
249 250 251 252
      node->recursive_usage_count++;
      
      /* There is no need to actually lock anything, since we already
	 hold the lock.  Correspondingly, objc_sync_exit() will just
253
	 decrease recursive_usage_count and do nothing to unlock.  */
254 255 256 257 258
      return OBJC_SYNC_SUCCESS;
    }
#endif /* SYNC_CACHE_DISABLE */

  /* The following is the standard lookup for the lock in the standard
259
     pool lock.  It requires a pool protection lock.  */
260 261 262
  hash = SYNC_OBJECT_HASH(object);

  /* Search for an existing lock for 'object'.  While searching, make
263
     note of any unused lock if we find any.  */
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
  unused_node = NULL;

  objc_mutex_lock (sync_pool_protection_locks[hash]);

  node = sync_pool_array[hash];

  while (node != NULL)
    {
      if (node->object == object)
	{
	  /* We found the lock.  */
	  node->usage_count++;
	  objc_mutex_unlock (sync_pool_protection_locks[hash]);

#ifndef SYNC_CACHE_DISABLE
	  /* Put it in the cache.  */
	  if (free_cache_slot != -1)
281
	    lock_cache[free_cache_slot] = node;
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
#endif

	  /* Lock it.  */
	  objc_mutex_lock (node->lock);

	  return OBJC_SYNC_SUCCESS;
	}

      if (unused_node == NULL  &&  node->usage_count == 0)
	{
	  /* We found the first unused node.  Record it.  */
	  unused_node = node;
	}
      
      node = node->next;
    }

  /* An existing lock for 'object' could not be found.  */
  if (unused_node != NULL)
    {
      /* But we found a unused lock; use it.  */
      unused_node->object = object;
      unused_node->usage_count = 1;
      unused_node->recursive_usage_count = 0;
      objc_mutex_unlock (sync_pool_protection_locks[hash]);

#ifndef SYNC_CACHE_DISABLE
      if (free_cache_slot != -1)
310
	lock_cache[free_cache_slot] = unused_node;
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
#endif

      objc_mutex_lock (unused_node->lock);

      return OBJC_SYNC_SUCCESS;
    }
  else
    {
      /* There are no unused nodes; allocate a new node.  */
      lock_node_ptr new_node;

      /* Create the node.  */
      new_node = objc_malloc (sizeof (struct lock_node));
      new_node->lock = objc_mutex_allocate ();
      new_node->object = object;
      new_node->usage_count = 1;
      new_node->recursive_usage_count = 0;

      /* Attach it at the beginning of the pool.  */
      new_node->next = sync_pool_array[hash];
      sync_pool_array[hash] = new_node;
      objc_mutex_unlock (sync_pool_protection_locks[hash]);

#ifndef SYNC_CACHE_DISABLE
      if (free_cache_slot != -1)
336
	lock_cache[free_cache_slot] = new_node;
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
#endif

      objc_mutex_lock (new_node->lock);

      return OBJC_SYNC_SUCCESS;
    }
}

int
objc_sync_exit (id object)
{
  int hash;
  lock_node_ptr node;

  if (object == nil)
352
    return OBJC_SYNC_SUCCESS;
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
  
#ifndef SYNC_CACHE_DISABLE
  if (lock_cache != NULL)
    {
      int i;
    
      /* Find the lock in the cache.  */
      node = NULL;
      for (i = 0; i < SYNC_CACHE_SIZE; i++)
	{
	  lock_node_ptr locked_node = lock_cache[i];
	  
	  if (locked_node != NULL  &&  locked_node->object == object)
	    {
	      node = locked_node;
	      break;
	    }
	}
      /* Note that, if a node was found in the cache, the variable i
	 now holds the index where it was found, which will be used to
	 remove it from the cache.  */
      if (node != NULL)
	{
	  if (node->recursive_usage_count > 0)
	    {
	      node->recursive_usage_count--;
	      return OBJC_SYNC_SUCCESS;
	    }
	  else
	    {
	      /* We need to do a real unlock.  */
	      hash = SYNC_OBJECT_HASH(object);
	      
	      /* TODO: If we had atomic increase/decrease operations
387 388
		 with memory barriers, we could avoid the lock
		 here!  */
389 390 391 392
	      objc_mutex_lock (sync_pool_protection_locks[hash]);
	      node->usage_count--;
	      /* Normally, we do not reset object to nil here.  We'll
		 leave the lock associated with that object, at zero
Ondřej Bílka committed
393
		 usage count.  This makes it slightly more efficient to
394 395 396 397 398 399 400 401 402 403
		 provide a lock for that object if (as likely)
		 requested again.  If the object is deallocated, we
		 don't care.  It will never match a new lock that is
		 requested, and the node will be reused at some point.

		 But, if garbage collection is enabled, leaving a
		 pointer to the object in memory might prevent the
		 object from being released.  In that case, we remove
		 it (TODO: maybe we should avoid using the garbage
		 collector at all ?  Nothing is ever deallocated in
404
		 this file).  */
405 406 407 408 409 410 411 412 413 414
#if OBJC_WITH_GC
	      node->object = nil;
#endif
	      objc_mutex_unlock (sync_pool_protection_locks[hash]);
	    
	      /* PS: Between objc_mutex_unlock
		 (sync_pool_protection_locks[hash]) and
		 objc_mutex_unlock (node->lock), the pool is unlocked
		 so other threads may allocate this same lock to
		 another object (!).  This is not a problem, but it is
415
		 curious.  */
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
	      objc_mutex_unlock (node->lock);
	      
	      /* Remove the node from the cache.  */
	      lock_cache[i] = NULL;
	      
	      return OBJC_SYNC_SUCCESS;
	    }
	}
    }
#endif	  

  /* The cache either wasn't there, or didn't work (eg, we overflowed
     it at some point and stopped recording new locks in the cache).
     Proceed with a full search of the lock pool.  */
  hash = SYNC_OBJECT_HASH(object);

  objc_mutex_lock (sync_pool_protection_locks[hash]);

  /* Search for an existing lock for 'object'.  */
  node = sync_pool_array[hash];

  while (node != NULL)
    {
      if (node->object == object)
	{
	  /* We found the lock.  */
	  node->usage_count--;
	  objc_mutex_unlock (sync_pool_protection_locks[hash]);

	  objc_mutex_unlock (node->lock);

	  /* No need to remove the node from the cache, since it
448
	     wasn't found in the cache when we looked for it!  */
449 450 451 452 453 454 455 456 457 458 459
	  return OBJC_SYNC_SUCCESS;
	}
      
      node = node->next;
    }

  objc_mutex_unlock (sync_pool_protection_locks[hash]);

  /* A lock for 'object' to unlock could not be found (!!).  */
  return OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}