Fix lock-ordering during init (#237)

Reorders how locking is handled in `objc_send_initialize()` to prevent a deadlock.
Previously, contention on the low level spinlocks could cause a very intermittent deadlock:
  - Thread A : `objc_send_initialize()` holds the runtime lock, then tries to acquire the object lock on the metaclass, which needs to initialize the mutex for the new metaclass inside `referenceListForObject()`, so it tries to lock the `lock_for_pointer()` / `lock_spinlock()`
  - Thread B : `referenceListForObject()` holds a spinlock for an unrelated object while running `initHiddenClassForObject()` -> `allocateHiddenClass()`, which tries to acquire the runtime lock
If the metaclass object pointer in Thread A hashes to the same spinlock as the object in thread B, the runtime lock ends up deadlocked forever.
main
Earl Robsham 3 years ago committed by GitHub
parent 8c600f5b56
commit cd50e72f81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -683,11 +683,11 @@ PRIVATE void objc_send_initialize(id object)
objc_send_initialize((id)class->super_class); objc_send_initialize((id)class->super_class);
} }
// Lock the runtime while we're creating dtables and before we acquire any // Lock the runtime while we're creating dtables and before we acquire the
// other locks. This prevents a lock-order reversal when // init lock. This prevents a lock-order reversal when dtable_for_class is
// dtable_for_class is called from something holding the runtime lock while // called from something holding the runtime lock while we're still holding
// we're still holding the initialize lock. We should ensure that we never // the initialize lock. We should ensure that we never acquire the runtime
// acquire the runtime lock after acquiring the initialize lock. // lock after acquiring the initialize lock.
LOCK_RUNTIME(); LOCK_RUNTIME();
// Superclass +initialize might possibly send a message to this class, in // Superclass +initialize might possibly send a message to this class, in
@ -708,7 +708,16 @@ PRIVATE void objc_send_initialize(id object)
return; return;
} }
// We should try to acquire the class lock before any runtime/init locks.
// If another thread is in the middle of running `allocateHiddenClass()` it
// has acquired a spinlock and will be trying to acquire the runtime lock.
// When this happens there is a small chance we could hit the same spinlock
// and deadlock the process (as any further attempts to acquire the runtime
// will also block forever).
UNLOCK_RUNTIME();
LOCK_OBJECT_FOR_SCOPE((id)meta); LOCK_OBJECT_FOR_SCOPE((id)meta);
LOCK_RUNTIME();
LOCK(&initialize_lock); LOCK(&initialize_lock);
if (objc_test_class_flag(class, objc_class_flag_initialized)) if (objc_test_class_flag(class, objc_class_flag_initialized))
{ {

Loading…
Cancel
Save