11#include " ClassBuilder.h"
22
3+ #include < mutex>
4+
5+ #include " UnfairLock.h"
6+
37namespace tns {
48
9+ namespace {
10+ // objc_allocateClassPair only rejects names that are already *registered*, so
11+ // two threads extending the same class name concurrently can both allocate it
12+ // and register duplicate same-named classes (objc keeps both and name lookups
13+ // become ambiguous). Serialize the whole allocate -> register window.
14+ //
15+ // This lock is a leaf — the section never acquires a v8::Locker — so it cannot
16+ // form a cycle with the isolate locks.
17+ UnfairMutex extendedClassRegistrationMutex;
18+
19+ // Registered objc class names are never reclaimed and the ladder below
20+ // allocates suffixes densely under the lock, so a name's taken suffixes form a
21+ // contiguous prefix. Gallop + binary-search with objc_getClass probes to find
22+ // its end, so heavy same-name reuse (worker tests, HMR re-extends of one
23+ // class) stays O(log N) per extend with no side state to grow.
24+ int FirstFreeSuffix (const std::string& initialName) {
25+ auto taken = [&initialName](int i) {
26+ return objc_getClass ((initialName + std::to_string (i)).c_str ()) != nil;
27+ };
28+ int hi = 1 ;
29+ while (taken (hi)) {
30+ hi *= 2 ;
31+ }
32+ int lo = hi / 2 ;
33+ while (lo + 1 < hi) {
34+ int mid = lo + (hi - lo) / 2 ;
35+ if (taken (mid)) {
36+ lo = mid;
37+ } else {
38+ hi = mid;
39+ }
40+ }
41+ return hi;
42+ }
43+
44+ // Bounds only *consecutive* failures past the probed free position, i.e.
45+ // allocation failing for reasons other than an ordinary name collision (which
46+ // would otherwise loop forever holding the mutex).
47+ constexpr int kMaxConsecutiveAllocFailures = 100 ;
48+ } // namespace
49+
550// Moved this method in a separate .cpp file because ARC destroys the class
651// created with objc_allocateClassPair when the control leaves this method scope
7- // TODO: revist this. Maybe a lock is needed regardless
852Class ClassBuilder::GetExtendedClass (std::string baseClassName,
953 std::string staticClassName,
1054 std::string suffix) {
@@ -14,19 +58,23 @@ Class ClassBuilder::GetExtendedClass(std::string baseClassName,
1458 ? staticClassName
1559 : baseClassName + suffix + " _" +
1660 std::to_string (++ClassBuilder::classNameCounter_);
17- // here we could either call objc_getClass with the name to see if the class
18- // already exists or we can just try allocating it, which will return nil if
19- // the class already exists so we try allocating it every time to avoid race
20- // conditions in case this method is being executed by multiple threads
61+ // Allocation failure is the collision signal (objc_getClass beforehand
62+ // would race), but that only detects *registered* names — hence the lock
63+ // spanning allocate -> register.
64+ std::lock_guard<UnfairMutex> lock (extendedClassRegistrationMutex);
2165 Class clazz = objc_allocateClassPair (baseClass, name.c_str (), 0 );
2266
2367 if (clazz == nil) {
24- int i = 1 ;
2568 std::string initialName = name;
26- while (clazz == nil) {
27- name = initialName + std::to_string (i++);
69+ int next = FirstFreeSuffix (initialName);
70+ for (int attempts = 0 ;
71+ clazz == nil && attempts < kMaxConsecutiveAllocFailures ; attempts++) {
72+ name = initialName + std::to_string (next++);
2873 clazz = objc_allocateClassPair (baseClass, name.c_str (), 0 );
2974 }
75+ if (clazz == nil) {
76+ return nil;
77+ }
3078 }
3179
3280 objc_registerClassPair (clazz);
0 commit comments