1 package com.google.inject.internal; 2 3 import com.google.common.base.Preconditions; 4 import com.google.common.base.Supplier; 5 import com.google.common.collect.ArrayListMultimap; 6 import com.google.common.collect.ImmutableListMultimap; 7 import com.google.common.collect.LinkedHashMultimap; 8 import com.google.common.collect.ListMultimap; 9 import com.google.common.collect.Lists; 10 import com.google.common.collect.Maps; 11 import com.google.common.collect.Multimap; 12 import com.google.common.collect.Multimaps; 13 14 import java.util.Collection; 15 import java.util.LinkedHashMap; 16 import java.util.List; 17 import java.util.Map; 18 import java.util.concurrent.locks.Lock; 19 import java.util.concurrent.locks.ReentrantLock; 20 21 /** 22 * Simplified version of {@link Lock} that is special due to how it handles deadlocks detection. 23 * 24 * <p>Is an inherent part of {@link SingletonScope}, moved into a upper level class due 25 * to its size and complexity. 26 * 27 * @paramLock identification provided by the client, is returned unmodified to the client 28 * when lock cycle is detected to identify it. Only toString() needs to be implemented. 29 * Lock references this object internally, 30 * for the purposes of Garbage Collection you should not use heavy IDs. 31 * Lock is referenced by a lock factory as long as it's owned by a thread. 32 * 33 * @see SingletonScope 34 * @see com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory 35 * 36 * @author timofeyb (Timothy Basanov) 37 */ 38 interface CycleDetectingLock<ID> { 39 40 /** 41 * Takes a lock in a blocking fashion in case no potential deadlocks are detected. 42 * If the lock was successfully owned, returns an empty map indicating no detected potential 43 * deadlocks. 44 * 45 * Otherwise, a map indicating threads involved in a potential deadlock are returned. 46 * Map is ordered by dependency cycle and lists locks for each thread that are part of 47 * the loop in order. Returned map is created atomically. 48 * 49 * In case no cycle is detected performance is O(threads creating singletons), 50 * in case cycle is detected performance is O(singleton locks). 51 */ 52 ListMultimap<Long, ID> lockOrDetectPotentialLocksCycle(); 53 54 /** 55 * Unlocks previously locked lock. 56 */ 57 void unlock(); 58 59 /** 60 * Wraps locks so they would never cause a deadlock. On each 61 * {@link CycleDetectingLock#lockOrDetectPotentialLocksCycle} we check for dependency cycles 62 * within locks created by the same factory. Either we detect a cycle and return it 63 * or take it atomically. 64 * 65 * <p>Important to note that we do not prevent deadlocks in the client code. As an example: 66 * Thread A takes lock L and creates singleton class CA depending on the singleton class CB. 67 * Meanwhile thread B is creating class CB and is waiting on the lock L. Issue happens 68 * due to client code creating interdependent classes and using locks, where 69 * no guarantees on the creation order from Guice are provided. 70 * 71 * <p>Instances of these locks are not intended to be exposed outside of {@link SingletonScope}. 72 */ 73 class CycleDetectingLockFactory<ID> { 74 75 /** 76 * Specifies lock that thread is currently waiting on to own it. 77 * Used only for purposes of locks cycle detection. 78 * 79 * Key: thread id 80 * Value: lock that is being waited on 81 * 82 * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} before {@link Lock#lock} 83 * is called. Element is removed inside {@link #lockOrDetectPotentialLocksCycle()} after 84 * {@link Lock#lock} and synchronously with adding it to {@link #locksOwnedByThread}. 85 * 86 * Same lock can be added for several threads in case all of them are trying to 87 * take it. 88 * 89 * Guarded by {@code this}. 90 */ 91 private Map<Long, ReentrantCycleDetectingLock> lockThreadIsWaitingOn = Maps.newHashMap(); 92 93 /** 94 * Lists locks that thread owns. 95 * Used only to populate locks in a potential cycle when it is detected. 96 * 97 * Key: thread id 98 * Value: stack of locks that were owned. 99 * 100 * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} after {@link Lock#lock} 101 * is called. Element is removed inside {@link #unlock()} synchronously with 102 * {@link Lock#unlock()} call. 103 * 104 * Same lock can only be present several times for the same thread as locks are 105 * reentrant. Lock can not be owned by several different threads as the same time. 106 * 107 * Guarded by {@code this}. 108 */ 109 private final Multimap<Long, ReentrantCycleDetectingLock> locksOwnedByThread = 110 LinkedHashMultimap.create(); 111 112 /** 113 * Creates new lock within this factory context. We can guarantee that locks created by 114 * the same factory would not deadlock. 115 * 116 * @param newLockId lock id that would be used to report lock cycles if detected 117 */ 118 CycleDetectingLock<ID> create(ID newLockId) { 119 return new ReentrantCycleDetectingLock(newLockId, new ReentrantLock()); 120 } 121 122 /** The implementation for {@link CycleDetectingLock}. */ 123 class ReentrantCycleDetectingLock implements CycleDetectingLock<ID> { 124 125 /** Underlying lock used for actual waiting when no potential deadlocks are detected. */ 126 private final Lock lockImplementation; 127 /** User id for this lock. */ 128 private final ID userLockId; 129 /** 130 * Thread id for the thread that owned this lock. Nullable. 131 * Guarded by {@code CycleDetectingLockFactory.this}. 132 */ 133 private Long lockOwnerThreadId = null; 134 /** 135 * Number of times that thread owned this lock. 136 * Guarded by {@code CycleDetectingLockFactory.this}. 137 */ 138 private int lockReentranceCount = 0; 139 140 ReentrantCycleDetectingLock(ID userLockId, Lock lockImplementation) { 141 this.userLockId = Preconditions.checkNotNull(userLockId, "userLockId"); 142 this.lockImplementation = Preconditions.checkNotNull( 143 lockImplementation, "lockImplementation"); 144 } 145 146 @Override public ListMultimap<Long, ID> lockOrDetectPotentialLocksCycle() { 147 final long currentThreadId = Thread.currentThread().getId(); 148 synchronized (CycleDetectingLockFactory.this) { 149 checkState(); 150 ListMultimap<Long, ID> locksInCycle = detectPotentialLocksCycle(); 151 if (!locksInCycle.isEmpty()) { 152 // potential deadlock is found, we don't try to take this lock 153 return locksInCycle; 154 } 155 156 lockThreadIsWaitingOn.put(currentThreadId, this); 157 } 158 159 // this may be blocking, but we don't expect it to cause a deadlock 160 lockImplementation.lock(); 161 162 synchronized (CycleDetectingLockFactory.this) { 163 // current thread is no longer waiting on this lock 164 lockThreadIsWaitingOn.remove(currentThreadId); 165 checkState(); 166 167 // mark it as owned by us 168 lockOwnerThreadId = currentThreadId; 169 lockReentranceCount++; 170 // add this lock to the list of locks owned by a current thread 171 locksOwnedByThread.put(currentThreadId, this); 172 } 173 // no deadlock is found, locking successful 174 return ImmutableListMultimap.of(); 175 } 176 177 @Override public void unlock() { 178 final long currentThreadId = Thread.currentThread().getId(); 179 synchronized (CycleDetectingLockFactory.this) { 180 checkState(); 181 Preconditions.checkState(lockOwnerThreadId != null, 182 "Thread is trying to unlock a lock that is not locked"); 183 Preconditions.checkState(lockOwnerThreadId == currentThreadId, 184 "Thread is trying to unlock a lock owned by another thread"); 185 186 // releasing underlying lock 187 lockImplementation.unlock(); 188 189 // be sure to release the lock synchronously with updating internal state 190 lockReentranceCount--; 191 if (lockReentranceCount == 0) { 192 // we no longer own this lock 193 lockOwnerThreadId = null; 194 Preconditions.checkState(locksOwnedByThread.remove(currentThreadId, this), 195 "Internal error: Can not find this lock in locks owned by a current thread"); 196 if (locksOwnedByThread.get(currentThreadId).isEmpty()) { 197 // clearing memory 198 locksOwnedByThread.removeAll(currentThreadId); 199 } 200 } 201 } 202 } 203 204 /** Check consistency of an internal state. */ 205 void checkState() throws IllegalStateException { 206 final long currentThreadId = Thread.currentThread().getId(); 207 Preconditions.checkState(!lockThreadIsWaitingOn.containsKey(currentThreadId), 208 "Internal error: Thread should not be in a waiting thread on a lock now"); 209 if (lockOwnerThreadId != null) { 210 // check state of a locked lock 211 Preconditions.checkState(lockReentranceCount >= 0, 212 "Internal error: Lock ownership and reentrance count internal states do not match"); 213 Preconditions.checkState(locksOwnedByThread.get(lockOwnerThreadId).contains(this), 214 "Internal error: Set of locks owned by a current thread and lock " 215 + "ownership status do not match"); 216 } else { 217 // check state of a non locked lock 218 Preconditions.checkState(lockReentranceCount == 0, 219 "Internal error: Reentrance count of a non locked lock is expect to be zero"); 220 Preconditions.checkState(!locksOwnedByThread.values().contains(this), 221 "Internal error: Non locked lock should not be owned by any thread"); 222 } 223 } 224 225 /** 226 * Algorithm to detect a potential lock cycle. 227 * 228 * For lock's thread owner check which lock is it trying to take. 229 * Repeat recursively. When current thread is found a potential cycle is detected. 230 * 231 * @see CycleDetectingLock#lockOrDetectPotentialLocksCycle() 232 */ 233 private ListMultimap<Long, ID> detectPotentialLocksCycle() { 234 final long currentThreadId = Thread.currentThread().getId(); 235 if (lockOwnerThreadId == null || lockOwnerThreadId == currentThreadId) { 236 // if nobody owns this lock, lock cycle is impossible 237 // if a current thread owns this lock, we let Guice to handle it 238 return ImmutableListMultimap.of(); 239 } 240 241 ListMultimap<Long, ID> potentialLocksCycle = Multimaps.newListMultimap( 242 new LinkedHashMap<Long, Collection<ID>>(), 243 new Supplier<List<ID>>() { 244 @Override 245 public List<ID> get() { 246 return Lists.newArrayList(); 247 } 248 }); 249 // lock that is a part of a potential locks cycle, starts with current lock 250 ReentrantCycleDetectingLock lockOwnerWaitingOn = this; 251 // try to find a dependency path between lock's owner thread and a current thread 252 while (lockOwnerWaitingOn != null && lockOwnerWaitingOn.lockOwnerThreadId != null) { 253 Long threadOwnerThreadWaits = lockOwnerWaitingOn.lockOwnerThreadId; 254 // in case locks cycle exists lock we're waiting for is part of it 255 potentialLocksCycle.putAll(threadOwnerThreadWaits, 256 getAllLockIdsAfter(threadOwnerThreadWaits, lockOwnerWaitingOn)); 257 258 if (threadOwnerThreadWaits == currentThreadId) { 259 // owner thread depends on current thread, cycle detected 260 return potentialLocksCycle; 261 } 262 // going for the next thread we wait on indirectly 263 lockOwnerWaitingOn = lockThreadIsWaitingOn.get(threadOwnerThreadWaits); 264 } 265 // no dependency path from an owner thread to a current thread 266 return ImmutableListMultimap.of(); 267 } 268 269 /** Return locks owned by a thread after a lock specified, inclusive. */ 270 private List<ID> getAllLockIdsAfter(long threadId, ReentrantCycleDetectingLock lock) { 271 List<ID> ids = Lists.newArrayList(); 272 boolean found = false; 273 Collection<ReentrantCycleDetectingLock> ownedLocks = locksOwnedByThread.get(threadId); 274 Preconditions.checkNotNull(ownedLocks, 275 "Internal error: No locks were found taken by a thread"); 276 for (ReentrantCycleDetectingLock ownedLock : ownedLocks) { 277 if (ownedLock == lock) { 278 found = true; 279 } 280 if (found) { 281 ids.add(ownedLock.userLockId); 282 } 283 } 284 Preconditions.checkState(found, "Internal error: We can not find locks that " 285 + "created a cycle that we detected"); 286 return ids; 287 } 288 289 @Override public String toString() { 290 // copy is made to prevent a data race 291 // no synchronization is used, potentially stale data, should be good enough 292 Long localLockOwnerThreadId = this.lockOwnerThreadId; 293 if (localLockOwnerThreadId != null) { 294 return String.format("CycleDetectingLock[%s][locked by %s]", 295 userLockId, localLockOwnerThreadId); 296 } else { 297 return String.format("CycleDetectingLock[%s][unlocked]", userLockId); 298 } 299 } 300 } 301 } 302 } 303