001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.transaction.locking; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.commons.transaction.util.LoggerFacade; 030 031/** 032 * A generic implementain of a simple multi level lock. 033 * 034 * <p> 035 * The idea is to have an ascending number of lock levels ranging from 036 * <code>0</code> to <code>maxLockLevel</code> as specified in 037 * {@link #GenericLock(Object, int, LoggerFacade)}: the higher the lock level 038 * the stronger and more restrictive the lock. To determine which lock may 039 * coexist with other locks you have to imagine matching pairs of lock levels. 040 * For each pair both parts allow for all lock levels less than or equal to the 041 * matching other part. Pairs are composed by the lowest and highest level not 042 * yet part of a pair and successively applying this method until no lock level 043 * is left. For an even amount of levels each level is part of exactly one pair. 044 * For an odd amount the middle level is paired with itself. The highst lock 045 * level may coexist with the lowest one (<code>0</code>) which by 046 * definition means <code>NO LOCK</code>. This implies that you will have to 047 * specify at least one other lock level and thus set <code>maxLockLevel</code> 048 * to at least <code>1</code>. 049 * </p> 050 * 051 * <p> 052 * Although this may sound complicated, in practice this is quite simple. Let us 053 * imagine you have three lock levels: 054 * <ul> 055 * <li><code>0</code>:<code>NO LOCK</code> (always needed by the 056 * implementation of this lock) 057 * <li><code>1</code>:<code>SHARED</code> 058 * <li><code>2</code>:<code>EXCLUSIVE</code> 059 * </ul> 060 * Accordingly, you will have to set <code>maxLockLevel</code> to 061 * <code>2</code>. Now, there are two pairs of levels 062 * <ul> 063 * <li><code>NO LOCK</code> with <code>EXCLUSIVE</code> 064 * <li><code>SHARED</code> with <code>SHARED</code> 065 * </ul> 066 * This means when the current highest lock level is <code>NO LOCK</code> 067 * everything less or equal to <code>EXCLUSIVE</code> is allowed - which means 068 * every other lock level. On the other side <code>EXCLUSIVE</code> allows 069 * exacly for <code>NO LOCK</code>- which means nothing else. In conclusion, 070 * <code>SHARED</code> allows for <code>SHARED</code> or <code>NO 071 * LOCK</code>, 072 * but not for <code>EXCLUSIVE</code>. To make this very clear have a look at 073 * this table, where <code>o</code> means compatible or can coexist and 074 * <code>x</code> means incompatible or can not coexist: 075 * </p> 076 * <table><tbody> 077 * <tr> 078 * <td align="center"></td> 079 * <td align="center">NO LOCK</td> 080 * <td align="center">SHARED</td> 081 * <td align="center">EXCLUSIVE</td> 082 * </tr> 083 * <tr> 084 * <td align="center">NO LOCK</td> 085 * <td align="center">o</td> 086 * <td align="center">o</td> 087 * <td align="center">o</td> 088 * </tr> 089 * <tr> 090 * <td align="center">SHARED</td> 091 * <td align="center">o</td> 092 * <td align="center">o</td> 093 * <td align="center">x</td> 094 * </tr> 095 * <tr> 096 * <td align="center">EXCLUSIVE</td> 097 * <td align="center" align="center">o</td> 098 * <td align="center">x</td> 099 * <td align="center">x</td> 100 * </tr> 101 * </tbody> </table> 102 * 103 * </p> 104 * <p> 105 * Additionally, there are preferences for specific locks you can pass to 106 * {@link #acquire(Object, int, boolean, int, boolean, long)}. 107 * This means whenever more thanone party 108 * waits for a lock you can specify which one is to be preferred. This gives you 109 * every freedom you might need to specifcy e.g. 110 * <ul> 111 * <li>priority to parties either applying for higher or lower lock levels 112 * <li>priority not only to higher or lower locks, but to a specific level 113 * <li>completely random preferences 114 * </ul> 115 * </p> 116 * 117 * @version $Id: GenericLock.java 493628 2007-01-07 01:42:48Z joerg $ 118 */ 119public class GenericLock implements MultiLevelLock2 { 120 121 protected Object resourceId; 122 // XXX needs to be synchronized to allow for unsynchronized access for deadlock detection 123 // in getConflictingOwners to avoid deadlocks between lock to acquire and lock to check for 124 // deadlocks 125 protected Map owners = Collections.synchronizedMap(new HashMap()); 126 // XXX needs to be synchronized to allow for unsynchronized access for deadlock detection 127 // in getConflictingWaiters to avoid deadlocks between lock to acquire and lock to check for 128 // deadlocks 129 // Note: having this as a list allows for fair mechanisms in sub classes 130 protected List waitingOwners = Collections.synchronizedList(new ArrayList()); 131 private int maxLockLevel; 132 protected LoggerFacade logger; 133 protected int waiters = 0; 134 135 /** 136 * Creates a new lock. 137 * 138 * @param resourceId identifier for the resource associated to this lock 139 * @param maxLockLevel highest allowed lock level as described in class intro 140 * @param logger generic logger used for all kind of debug logging 141 */ 142 public GenericLock(Object resourceId, int maxLockLevel, LoggerFacade logger) { 143 if (maxLockLevel < 1) 144 throw new IllegalArgumentException( 145 "The maximum lock level must be at least 1 (" + maxLockLevel + " was specified)"); 146 this.resourceId = resourceId; 147 this.maxLockLevel = maxLockLevel; 148 this.logger = logger; 149 } 150 151 public boolean equals(Object o) { 152 if (o instanceof GenericLock) { 153 return ((GenericLock)o).resourceId.equals(resourceId); 154 } 155 return false; 156 } 157 158 public int hashCode() { 159 return resourceId.hashCode(); 160 } 161 162 /** 163 * @see MultiLevelLock2#test(Object, int, int) 164 */ 165 public boolean test(Object ownerId, int targetLockLevel, int compatibility) { 166 boolean success = tryLock(ownerId, targetLockLevel, compatibility, false, true); 167 return success; 168 } 169 170 /** 171 * @see MultiLevelLock2#has(Object, int) 172 */ 173 public boolean has(Object ownerId, int lockLevel) { 174 int level = getLockLevel(ownerId); 175 return (lockLevel <= level); 176 } 177 178 /** 179 * @see org.apache.commons.transaction.locking.MultiLevelLock#acquire(java.lang.Object, 180 * int, boolean, boolean, long) 181 */ 182 public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean wait, 183 boolean reentrant, long timeoutMSecs) throws InterruptedException { 184 return acquire(ownerId, targetLockLevel, wait, reentrant ? COMPATIBILITY_REENTRANT 185 : COMPATIBILITY_NONE, timeoutMSecs); 186 } 187 188 /** 189 * @see #acquire(Object, int, boolean, int, boolean, long) 190 */ 191 public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean wait, 192 int compatibility, long timeoutMSecs) throws InterruptedException { 193 return acquire(ownerId, targetLockLevel, wait, compatibility, false, timeoutMSecs); 194 } 195 196 /** 197 * Tries to blockingly acquire a lock which can be preferred. 198 * 199 * @see #acquire(Object, int, boolean, int, boolean, long) 200 * @since 1.1 201 */ 202 public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean preferred, 203 long timeoutMSecs) throws InterruptedException { 204 return acquire(ownerId, targetLockLevel, true, COMPATIBILITY_REENTRANT, preferred, 205 timeoutMSecs); 206 } 207 208 /** 209 * @see org.apache.commons.transaction.locking.MultiLevelLock2#acquire(Object, 210 * int, boolean, int, boolean, long) 211 * @since 1.1 212 */ 213 public synchronized boolean acquire( 214 Object ownerId, 215 int targetLockLevel, 216 boolean wait, 217 int compatibility, 218 boolean preferred, 219 long timeoutMSecs) 220 throws InterruptedException { 221 222 if (logger.isFinerEnabled()) { 223 logger.logFiner( 224 ownerId.toString() 225 + " trying to acquire lock for " 226 + resourceId.toString() 227 + " at level " 228 + targetLockLevel 229 + " at " 230 + System.currentTimeMillis()); 231 } 232 233 if (tryLock(ownerId, targetLockLevel, compatibility, preferred)) { 234 235 if (logger.isFinerEnabled()) { 236 logger.logFiner( 237 ownerId.toString() 238 + " actually acquired lock for " 239 + resourceId.toString() 240 + " at " 241 + System.currentTimeMillis()); 242 } 243 244 return true; 245 } else { 246 if (!wait) { 247 return false; 248 } else { 249 long started = System.currentTimeMillis(); 250 for (long remaining = timeoutMSecs; 251 remaining > 0; 252 remaining = timeoutMSecs - (System.currentTimeMillis() - started)) { 253 254 if (logger.isFinerEnabled()) { 255 logger.logFiner( 256 ownerId.toString() 257 + " waiting on " 258 + resourceId.toString() 259 + " for msecs " 260 + timeoutMSecs 261 + " at " 262 + System.currentTimeMillis()); 263 } 264 265 LockOwner waitingOwner = new LockOwner(ownerId, targetLockLevel, compatibility, 266 preferred); 267 try { 268 registerWaiter(waitingOwner); 269 if (preferred) { 270 // while waiting we already make our claim we are next 271 LockOwner oldLock = null; 272 try { 273 // we need to remember it to restore it after waiting 274 oldLock = (LockOwner) owners.get(ownerId); 275 // this creates a new owner, so we do not need to 276 // copy the old one 277 setLockLevel(ownerId, null, targetLockLevel, compatibility, 278 preferred); 279 280 // finally wait 281 wait(remaining); 282 283 } finally { 284 // we need to restore the old lock in order not to 285 // interfere with the intention lock in the 286 // following check 287 // and not to have it in case of success either 288 // as there will be an ordinary lock then 289 if (oldLock != null) { 290 owners.put(ownerId, oldLock); 291 } else { 292 owners.remove(ownerId); 293 } 294 } 295 296 } else { 297 wait(remaining); 298 } 299 } finally { 300 unregisterWaiter(waitingOwner); 301 } 302 303 if (tryLock(ownerId, targetLockLevel, compatibility, preferred)) { 304 305 if (logger.isFinerEnabled()) { 306 logger.logFiner( 307 ownerId.toString() 308 + " waiting on " 309 + resourceId.toString() 310 + " eventually got the lock at " 311 + System.currentTimeMillis()); 312 } 313 314 return true; 315 } 316 } 317 return false; 318 } 319 } 320 } 321 322 protected void registerWaiter(LockOwner waitingOwner) { 323 synchronized (waitingOwners) { 324 unregisterWaiter(waitingOwner); 325 waiters++; 326 waitingOwners.add(waitingOwner); 327 } 328 } 329 330 protected void unregisterWaiter(LockOwner waitingOwner) { 331 synchronized (waitingOwners) { 332 if (waitingOwners.remove(waitingOwner)) 333 waiters--; 334 } 335 } 336 337 /** 338 * @see org.apache.commons.transaction.locking.MultiLevelLock#release(Object) 339 */ 340 public synchronized boolean release(Object ownerId) { 341 if (owners.remove(ownerId) != null) { 342 if (logger.isFinerEnabled()) { 343 logger.logFiner( 344 ownerId.toString() 345 + " releasing lock for " 346 + resourceId.toString() 347 + " at " 348 + System.currentTimeMillis()); 349 } 350 notifyAll(); 351 return true; 352 } 353 return false; 354 } 355 356 /** 357 * @see org.apache.commons.transaction.locking.MultiLevelLock#getLockLevel(Object) 358 */ 359 public int getLockLevel(Object ownerId) { 360 LockOwner owner = (LockOwner) owners.get(ownerId); 361 if (owner == null) { 362 return 0; 363 } else { 364 return owner.lockLevel; 365 } 366 } 367 368 /** 369 * Gets the resource assotiated to this lock. 370 * 371 * @return identifier for the resource associated to this lock 372 */ 373 public Object getResourceId() { 374 return resourceId; 375 } 376 377 /** 378 * Gets the lowest lock level possible. 379 * 380 * @return minimum lock level 381 */ 382 public int getLevelMinLock() { 383 return 0; 384 } 385 386 /** 387 * Gets the highst lock level possible. 388 * 389 * @return maximum lock level 390 */ 391 public int getLevelMaxLock() { 392 return maxLockLevel; 393 } 394 395 public Object getOwner() { 396 LockOwner owner = getMaxLevelOwner(); 397 if (owner == null) 398 return null; 399 return owner.ownerId; 400 } 401 402 public synchronized String toString() { 403 StringBuffer buf = new StringBuffer(); 404 buf.append(resourceId.toString()).append(":\n"); 405 406 for (Iterator it = owners.values().iterator(); it.hasNext();) { 407 LockOwner owner = (LockOwner) it.next(); 408 buf.append("- ").append(owner.toString()).append("\n"); 409 } 410 411 if (waiters != 0) { 412 buf.append(waiters).append(" waiting:\n"); 413 for (Iterator it = waitingOwners.iterator(); it.hasNext();) { 414 LockOwner owner = (LockOwner) it.next(); 415 buf.append("- ").append(owner.toString()).append("\n"); 416 } 417 } 418 419 return buf.toString(); 420 } 421 422 protected synchronized LockOwner getMaxLevelOwner() { 423 return getMaxLevelOwner(null, -1, false); 424 } 425 426 protected synchronized LockOwner getMaxLevelOwner(LockOwner reentrantOwner, boolean preferred) { 427 return getMaxLevelOwner(reentrantOwner, -1, preferred); 428 } 429 430 protected synchronized LockOwner getMaxLevelOwner(int supportLockLevel, boolean preferred) { 431 return getMaxLevelOwner(null, supportLockLevel, preferred); 432 } 433 434 protected synchronized LockOwner getMaxLevelOwner(LockOwner reentrantOwner, 435 int supportLockLevel, boolean preferred) { 436 LockOwner maxOwner = null; 437 for (Iterator it = owners.values().iterator(); it.hasNext();) { 438 LockOwner owner = (LockOwner) it.next(); 439 if (owner.lockLevel != supportLockLevel && !owner.equals(reentrantOwner) 440 && (maxOwner == null || maxOwner.lockLevel < owner.lockLevel) 441 // if we are a preferred lock we must not interfere with other intention 442 // locks as we otherwise might mututally lock without resolvation 443 && !(preferred && owner.intention)) { 444 maxOwner = owner; 445 } 446 } 447 return maxOwner; 448 } 449 450 protected synchronized void setLockLevel(Object ownerId, LockOwner lock, int targetLockLevel, 451 int compatibility, boolean intention) { 452 // be sure there exists at most one lock per owner 453 if (lock != null) { 454 if (logger.isFinestEnabled()) { 455 logger.logFinest( 456 ownerId.toString() 457 + " upgrading lock for " 458 + resourceId.toString() 459 + " to level " 460 + targetLockLevel 461 + " at " 462 + System.currentTimeMillis()); 463 } 464 } else { 465 if (logger.isFinestEnabled()) { 466 logger.logFinest( 467 ownerId.toString() 468 + " getting new lock for " 469 + resourceId.toString() 470 + " at level " 471 + targetLockLevel 472 + " at " 473 + System.currentTimeMillis()); 474 } 475 } 476 owners.put(ownerId, new LockOwner(ownerId, targetLockLevel, compatibility, intention)); 477 } 478 479 protected boolean tryLock(Object ownerId, int targetLockLevel, int compatibility, 480 boolean preferred) { 481 return tryLock(ownerId, targetLockLevel, compatibility, preferred, false); 482 } 483 484 protected synchronized boolean tryLock(Object ownerId, int targetLockLevel, int compatibility, 485 boolean preferred, boolean tryOnly) { 486 487 LockOwner myLock = (LockOwner) owners.get(ownerId); 488 489 // determine highest owner 490 LockOwner highestOwner; 491 if (compatibility == COMPATIBILITY_REENTRANT) { 492 if (myLock != null && targetLockLevel <= myLock.lockLevel) { 493 // we already have it 494 return true; 495 } else { 496 // our own lock will not be compromised by ourself 497 highestOwner = getMaxLevelOwner(myLock, preferred); 498 } 499 } else if (compatibility == COMPATIBILITY_SUPPORT) { 500 // we are compatible with any other lock owner holding 501 // the same lock level 502 highestOwner = getMaxLevelOwner(targetLockLevel, preferred); 503 504 } else if (compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT) { 505 if (myLock != null && targetLockLevel <= myLock.lockLevel) { 506 // we already have it 507 return true; 508 } else { 509 // our own lock will not be compromised by ourself and same lock level 510 highestOwner = getMaxLevelOwner(myLock, targetLockLevel, preferred); 511 } 512 } else { 513 highestOwner = getMaxLevelOwner(); 514 } 515 516 int i; 517 // what is our current lock level? 518 int currentLockLevel; 519 if (highestOwner != null) { 520 currentLockLevel = highestOwner.lockLevel; 521 } else { 522 currentLockLevel = getLevelMinLock(); 523 } 524 525 // we are only allowed to acquire our locks if we do not compromise locks of any other lock owner 526 if (isCompatible(targetLockLevel, currentLockLevel)) { 527 if (!tryOnly) { 528 // if we really have the lock, it no longer is an intention 529 setLockLevel(ownerId, myLock, targetLockLevel, compatibility, false); 530 } 531 return true; 532 } else { 533 return false; 534 } 535 } 536 537 protected boolean isCompatible(int targetLockLevel, int currentLockLevel) { 538 return (targetLockLevel <= getLevelMaxLock() - currentLockLevel); 539 } 540 541 protected Set getConflictingOwners(Object ownerId, int targetLockLevel, int compatibility) { 542 543 LockOwner myLock = (LockOwner) owners.get(ownerId); 544 if (myLock != null && targetLockLevel <= myLock.lockLevel) { 545 // shortcut as we already have the lock 546 return null; 547 } 548 549 LockOwner testLock = new LockOwner(ownerId, targetLockLevel, compatibility, false); 550 List ownersCopy; 551 synchronized (owners) { 552 ownersCopy = new ArrayList(owners.values()); 553 } 554 return getConflictingOwners(testLock, ownersCopy); 555 556 } 557 558 protected Collection getConflictingWaiters(Object ownerId) { 559 LockOwner owner = (LockOwner) owners.get(ownerId); 560 if (owner != null) { 561 List waiterCopy; 562 synchronized (waitingOwners) { 563 waiterCopy = new ArrayList(waitingOwners); 564 } 565 Collection conflicts = getConflictingOwners(owner, waiterCopy); 566 return conflicts; 567 } 568 return null; 569 } 570 571 protected Set getConflictingOwners(LockOwner myOwner, Collection ownersToTest) { 572 573 if (myOwner == null) return null; 574 575 Set conflicts = new HashSet(); 576 577 // check if any locks conflict with ours 578 for (Iterator it = ownersToTest.iterator(); it.hasNext();) { 579 LockOwner owner = (LockOwner) it.next(); 580 581 // we do not interfere with ourselves, except when explicitely said so 582 if ((myOwner.compatibility == COMPATIBILITY_REENTRANT || myOwner.compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT) 583 && owner.ownerId.equals(myOwner.ownerId)) 584 continue; 585 586 // otherwise find out the lock level of the owner and see if we conflict with it 587 int onwerLockLevel = owner.lockLevel; 588 589 if (myOwner.compatibility == COMPATIBILITY_SUPPORT 590 || myOwner.compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT 591 && myOwner.lockLevel == onwerLockLevel) 592 continue; 593 594 if (!isCompatible(myOwner.lockLevel, onwerLockLevel)) { 595 conflicts.add(owner.ownerId); 596 } 597 } 598 return (conflicts.isEmpty() ? null : conflicts); 599 } 600 601 protected static class LockOwner { 602 public final Object ownerId; 603 public final int lockLevel; 604 public final boolean intention; 605 public final int compatibility; 606 607 public LockOwner(Object ownerId, int lockLevel, int compatibility, boolean intention) { 608 this.ownerId = ownerId; 609 this.lockLevel = lockLevel; 610 this.intention = intention; 611 this.compatibility = compatibility; 612 } 613 614 public String toString() { 615 StringBuffer buf = new StringBuffer(); 616 buf.append(ownerId.toString()).append(": level ").append(lockLevel).append(", complevel ") 617 .append(compatibility).append(intention ? ", intention/preferred" : ""); 618 return buf.toString(); 619 } 620 621 public boolean equals(Object o) { 622 if (o instanceof LockOwner) { 623 return ((LockOwner)o).ownerId.equals(ownerId); 624 } 625 return false; 626 } 627 628 public int hashCode() { 629 return ownerId.hashCode(); 630 } 631 } 632 633}