View Javadoc

1   package org.apache.ojb.broker.locking;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.io.Serializable;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.Hashtable;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import org.apache.ojb.broker.util.logging.Logger;
26  import org.apache.ojb.broker.util.logging.LoggerFactory;
27  import org.apache.commons.lang.SystemUtils;
28  
29  /**
30   * This implementation of the {@link LockManager} interface supports a simple, fast, non-blocking
31   * pessimistic locking for single JVM applications.
32   *
33   * @version $Id: LockManagerInMemoryImpl.java,v 1.1 2007-08-24 22:17:41 ewestfal Exp $
34   */
35  public class LockManagerInMemoryImpl implements LockManager
36  {
37      private Logger log = LoggerFactory.getLogger(LockManagerInMemoryImpl.class);
38      private static long CLEANUP_FREQUENCY = 1000; // 1000 milliseconds.
39      private static int MAX_LOCKS_TO_CLEAN = 300;
40      /**
41       * MBAIRD: a LinkedHashMap returns objects in the order you put them in,
42       * while still maintaining an O(1) lookup like a normal hashmap. We can then
43       * use this to get the oldest entries very quickly, makes cleanup a breeze.
44       */
45      private HashMap locktable = new HashMap();
46      private LockIsolationManager lockStrategyManager = new LockIsolationManager();
47      private long m_lastCleanupAt = System.currentTimeMillis();
48      private long lockTimeout;
49      private long timeoutCounterRead;
50      private long timeoutCounterWrite;
51  
52      public LockManagerInMemoryImpl()
53      {
54          this.lockTimeout = DEFAULT_LOCK_TIMEOUT;
55      }
56  
57      public long getLockTimeout()
58      {
59          return lockTimeout;
60      }
61  
62      public void setLockTimeout(long timeout)
63      {
64          this.lockTimeout = timeout;
65      }
66  
67      /**
68       * NOOP
69       * @return Always '0'
70       */
71      public long getBlockTimeout()
72      {
73          return 0;
74      }
75  
76      /**
77       * NOOP
78       */ 
79      public void setBlockTimeout(long timeout)
80      {
81      }
82  
83      public String getLockInfo()
84      {
85          String eol = SystemUtils.LINE_SEPARATOR;
86          StringBuffer msg = new StringBuffer("Class: " + LockManagerInMemoryImpl.class.getName() + eol);
87          msg.append("lock timeout: " + getLockTimeout() + " [ms]" + eol);
88          msg.append("concurrent lock owners: " + locktable.size() + eol);
89          msg.append("timed out write locks: " + timeoutCounterWrite + eol);
90          msg.append("timed out read locks: " + timeoutCounterRead + eol);
91          return msg.toString();
92      }
93  
94      public boolean readLock(Object key, Object resourceId, int isolationLevel)
95      {
96          if(log.isDebugEnabled()) log.debug("LM.readLock(tx-" + key + ", " + resourceId + ")");
97          checkTimedOutLocks();
98          LockEntry reader = new LockEntry(resourceId,
99                  key,
100                 System.currentTimeMillis(),
101                 isolationLevel,
102                 LockEntry.LOCK_READ);
103         LockIsolation ls = lockStrategyManager.getStrategyFor(isolationLevel);
104         return addReaderIfPossibleInternal(reader, ls.allowMultipleRead(), ls.allowReadWhenWrite());
105     }
106 
107     private boolean addReaderIfPossibleInternal(LockEntry reader, boolean allowMultipleReader,
108                                                 boolean allowReaderWhenWriteLock)
109     {
110         boolean result = false;
111         ObjectLocks objectLocks = null;
112         Object oid = reader.getResourceId();
113         /**
114          * MBAIRD: We need to synchronize the get/put so we don't have two threads
115          * competing to check if something is locked and double-locking it.
116          */
117         synchronized(locktable)
118         {
119             objectLocks = (ObjectLocks) locktable.get(oid);
120             if(objectLocks == null)
121             {
122                 // no write or read lock, go on
123                 objectLocks = new ObjectLocks();
124                 locktable.put(oid, objectLocks);
125                 objectLocks.addReader(reader);
126                 result = true;
127             }
128             else
129             {
130                 // ObjectLocks exist, first check for a write lock
131                 LockEntry writer = objectLocks.getWriter();
132                 if(writer != null)
133                 {
134                     // if writer is owned by current entity, read lock is
135                     // successful (we have an write lock)
136                     if(writer.isOwnedBy(reader.getKey()))
137                     {
138                         result = true;
139                     }
140                     else
141                     {
142                         // if read lock is allowed when different entity hold write lock
143                         // go on if multiple reader allowed, else do nothing
144                         if(allowReaderWhenWriteLock && allowMultipleReader)
145                         {
146                             objectLocks.addReader(reader);
147                             result = true;
148                         }
149                         else
150                         {
151                             result = false;
152                         }
153                     }
154                 }
155                 else
156                 {
157                     // no write lock exist, check for existing read locks
158                     if(objectLocks.getReaders().size() > 0)
159                     {
160                         // if we have already an read lock, do nothing
161                         if(objectLocks.getReader(reader.getKey()) != null)
162                         {
163                             result = true;
164                         }
165                         else
166                         {
167                             // we have read locks of other entities, add read lock
168                             // if allowed
169                             if(allowMultipleReader)
170                             {
171                                 objectLocks.addReader(reader);
172                                 result = true;
173                             }
174                         }
175                     }
176                     else
177                     {
178                         // no read locks exist, so go on
179                         objectLocks.addReader(reader);
180                         result = true;
181                     }
182                 }
183             }
184         }
185         return result;
186     }
187 
188     /**
189      * Remove an read lock.
190      */
191     public boolean removeReader(Object key, Object resourceId)
192     {
193         boolean result = false;
194         ObjectLocks objectLocks = null;
195         synchronized(locktable)
196         {
197             objectLocks = (ObjectLocks) locktable.get(resourceId);
198             if(objectLocks != null)
199             {
200                 /**
201                  * MBAIRD, last one out, close the door and turn off the lights.
202                  * if no locks (readers or writers) exist for this object, let's remove
203                  * it from the locktable.
204                  */
205                 Map readers = objectLocks.getReaders();
206                 result = readers.remove(key) != null;
207                 if((objectLocks.getWriter() == null) && (readers.size() == 0))
208                 {
209                     locktable.remove(resourceId);
210                 }
211             }
212         }
213         return result;
214     }
215 
216     /**
217      * Remove an write lock.
218      */
219     public boolean removeWriter(Object key, Object resourceId)
220     {
221         boolean result = false;
222         ObjectLocks objectLocks = null;
223         synchronized(locktable)
224         {
225             objectLocks = (ObjectLocks) locktable.get(resourceId);
226             if(objectLocks != null)
227             {
228                 /**
229                  * MBAIRD, last one out, close the door and turn off the lights.
230                  * if no locks (readers or writers) exist for this object, let's remove
231                  * it from the locktable.
232                  */
233                 LockEntry entry = objectLocks.getWriter();
234                 if(entry != null && entry.isOwnedBy(key))
235                 {
236                     objectLocks.setWriter(null);
237                     result = true;
238 
239                     // no need to check if writer is null, we just set it.
240                     if(objectLocks.getReaders().size() == 0)
241                     {
242                         locktable.remove(resourceId);
243                     }
244                 }
245             }
246         }
247         return result;
248     }
249 
250     public boolean releaseLock(Object key, Object resourceId)
251     {
252         if(log.isDebugEnabled()) log.debug("LM.releaseLock(tx-" + key + ", " + resourceId + ")");
253         boolean result = removeReader(key, resourceId);
254         // if no read lock could be removed, try write lock
255         if(!result)
256         {
257             result = removeWriter(key, resourceId);
258         }
259         return result;
260     }
261 
262     /**
263      * @see LockManager#releaseLocks(Object)
264      */
265     public void releaseLocks(Object key)
266     {
267         if(log.isDebugEnabled()) log.debug("LM.releaseLocks(tx-" + key + ")");
268         checkTimedOutLocks();
269         releaseLocksInternal(key);
270     }
271 
272     private void releaseLocksInternal(Object key)
273     {
274         synchronized(locktable)
275         {
276             Collection values = locktable.values();
277             ObjectLocks entry;
278             for(Iterator iterator = values.iterator(); iterator.hasNext();)
279             {
280                 entry = (ObjectLocks) iterator.next();
281                 entry.removeReader(key);
282                 if(entry.getWriter() != null && entry.getWriter().isOwnedBy(key))
283                 {
284                     entry.setWriter(null);
285                 }
286             }
287         }
288     }
289 
290     public boolean writeLock(Object key, Object resourceId, int isolationLevel)
291     {
292         if(log.isDebugEnabled()) log.debug("LM.writeLock(tx-" + key + ", " + resourceId + ")");
293         checkTimedOutLocks();
294         LockEntry writer = new LockEntry(resourceId,
295                 key,
296                 System.currentTimeMillis(),
297                 isolationLevel,
298                 LockEntry.LOCK_WRITE);
299         LockIsolation ls = lockStrategyManager.getStrategyFor(isolationLevel);
300         return setWriterIfPossibleInternal(writer, ls.allowWriteWhenRead());
301     }
302 
303     private boolean setWriterIfPossibleInternal(LockEntry writer, boolean allowReaders)
304     {
305         boolean result = false;
306         ObjectLocks objectLocks = null;
307         /**
308          * MBAIRD: We need to synchronize the get/put so we don't have two threads
309          * competing to check if something is locked and double-locking it.
310          */
311         synchronized(locktable)
312         {
313             objectLocks = (ObjectLocks) locktable.get(writer.getResourceId());
314             // if we don't upgrade, go on
315             if(objectLocks == null)
316             {
317                 // no locks for current entity exist, so go on
318                 objectLocks = new ObjectLocks();
319                 objectLocks.setWriter(writer);
320                 locktable.put(writer.getResourceId(), objectLocks);
321                 result = true;
322             }
323             else
324             {
325                 // the ObjectLock exist, check if there is already a write lock
326                 LockEntry oldWriter = objectLocks.getWriter();
327                 if(oldWriter != null)
328                 {
329                     // if already a write lock exists, check owner
330                     if(oldWriter.isOwnedBy(writer.getKey()))
331                     {
332                         // if current entity has already a write lock
333                         // signal success
334                         result = true;
335                     }
336                 }
337                 else
338                 {
339                     // current ObjectLock has no write lock, so check for readers
340                     int readerSize = objectLocks.getReaders().size();
341                     if(readerSize > 0)
342                     {
343                         // does current entity have already an read lock
344                         if(objectLocks.getReader(writer.getKey()) != null)
345                         {
346                             if(readerSize == 1)
347                             {
348                                 // only current entity has a read lock, so go on
349                                 objectLocks.readers.remove(writer.getKey());
350                                 objectLocks.setWriter(writer);
351                                 result = true;
352                             }
353                             else
354                             {
355                                 // current entity and others have already a read lock
356                                 // if aquire a write is allowed, go on
357                                 if(allowReaders)
358                                 {
359                                     objectLocks.readers.remove(writer.getKey());
360                                     objectLocks.setWriter(writer);
361                                     result = true;
362                                 }
363                             }
364                         }
365                         else
366                         {
367                             // current entity has no read lock, but others
368                             // if aquire a write is allowed, go on
369                             if(allowReaders)
370                             {
371                                 objectLocks.setWriter(writer);
372                                 result = true;
373                             }
374                         }
375                     }
376                     else
377                     {
378                         // no readers and writers, so go on if we don't upgrade
379                         objectLocks.setWriter(writer);
380                         result = true;
381                     }
382                 }
383             }
384         }
385         return result;
386     }
387 
388     public boolean upgradeLock(Object key, Object resourceId, int isolationLevel)
389     {
390         if(log.isDebugEnabled()) log.debug("LM.upgradeLock(tx-" + key + ", " + resourceId + ")");
391         return writeLock(key, resourceId, isolationLevel);
392     }
393 
394     /**
395      * @see LockManager#hasWrite(Object, Object)
396      */
397     public boolean hasWrite(Object key, Object resourceId)
398     {
399         if(log.isDebugEnabled()) log.debug("LM.hasWrite(tx-" + key + ", " + resourceId + ")");
400         checkTimedOutLocks();
401         return hasWriteLockInternal(resourceId, key);
402     }
403 
404     private boolean hasWriteLockInternal(Object resourceId, Object key)
405     {
406         boolean result = false;
407         ObjectLocks objectLocks = null;
408         synchronized(locktable)
409         {
410             objectLocks = (ObjectLocks) locktable.get(resourceId);
411             if(objectLocks != null)
412             {
413                 LockEntry writer = objectLocks.getWriter();
414                 if(writer != null)
415                 {
416                     result = writer.isOwnedBy(key);
417                 }
418             }
419         }
420         return result;
421     }
422 
423     public boolean hasUpgrade(Object key, Object resourceId)
424     {
425         if(log.isDebugEnabled()) log.debug("LM.hasUpgrade(tx-" + key + ", " + resourceId + ")");
426         return hasWrite(key, resourceId);
427     }
428 
429     /**
430      * @see LockManager#hasRead(Object, Object)
431      */
432     public boolean hasRead(Object key, Object resourceId)
433     {
434         if(log.isDebugEnabled()) log.debug("LM.hasRead(tx-" + key + ", " + resourceId + ')');
435         checkTimedOutLocks();
436         return hasReadLockInternal(resourceId, key);
437     }
438 
439     private boolean hasReadLockInternal(Object resourceId, Object key)
440     {
441         boolean result = false;
442         ObjectLocks objectLocks = null;
443         synchronized(locktable)
444         {
445             objectLocks = (ObjectLocks) locktable.get(resourceId);
446             if(objectLocks != null)
447             {
448                 LockEntry reader = objectLocks.getReader(key);
449                 if(reader != null || (objectLocks.getWriter() != null && objectLocks.getWriter().isOwnedBy(key)))
450                 {
451                     result = true;
452                 }
453             }
454         }
455         return result;
456     }
457 
458     /**
459      *
460      */
461     public int lockedObjects()
462     {
463         return locktable.size();
464     }
465 
466     private void checkTimedOutLocks()
467     {
468         if(System.currentTimeMillis() - m_lastCleanupAt > CLEANUP_FREQUENCY)
469         {
470             removeTimedOutLocks(getLockTimeout());
471             m_lastCleanupAt = System.currentTimeMillis();
472         }
473     }
474 
475     /**
476      * removes all timed out lock entries from the persistent storage.
477      * The timeout value can be set in the OJB properties file.
478      */
479     private void removeTimedOutLocks(long timeout)
480     {
481         int count = 0;
482         long maxAge = System.currentTimeMillis() - timeout;
483         boolean breakFromLoop = false;
484         ObjectLocks temp = null;
485         synchronized(locktable)
486         {
487             Iterator it = locktable.values().iterator();
488             /**
489              * run this loop while:
490              * - we have more in the iterator
491              * - the breakFromLoop flag hasn't been set
492              * - we haven't removed more than the limit for this cleaning iteration.
493              */
494             while(it.hasNext() && !breakFromLoop && (count <= MAX_LOCKS_TO_CLEAN))
495             {
496                 temp = (ObjectLocks) it.next();
497                 if(temp.getWriter() != null)
498                 {
499                     if(temp.getWriter().getTimestamp() < maxAge)
500                     {
501                         // writer has timed out, set it to null
502                         temp.setWriter(null);
503                         ++timeoutCounterWrite;
504                     }
505                 }
506                 if(temp.getYoungestReader() < maxAge)
507                 {
508                     // all readers are older than timeout.
509                     temp.getReaders().clear();
510                     ++timeoutCounterRead;
511                     if(temp.getWriter() == null)
512                     {
513                         // all readers and writer are older than timeout,
514                         // remove the objectLock from the iterator (which
515                         // is backed by the map, so it will be removed.
516                         it.remove();
517                     }
518                 }
519                 else
520                 {
521                     // we need to walk each reader.
522                     Iterator readerIt = temp.getReaders().values().iterator();
523                     LockEntry readerLock = null;
524                     while(readerIt.hasNext())
525                     {
526                         readerLock = (LockEntry) readerIt.next();
527                         if(readerLock.getTimestamp() < maxAge)
528                         {
529                             // this read lock is old, remove it.
530                             readerIt.remove();
531                         }
532                     }
533                 }
534                 count++;
535             }
536         }
537     }
538 
539 
540     //===============================================================
541     // inner class
542     //===============================================================
543     static final class ObjectLocks
544     {
545         private LockEntry writer;
546         private Hashtable readers;
547         private long m_youngestReader = 0;
548 
549         ObjectLocks()
550         {
551             this(null);
552         }
553 
554         ObjectLocks(LockEntry writer)
555         {
556             this.writer = writer;
557             readers = new Hashtable();
558         }
559 
560         LockEntry getWriter()
561         {
562             return writer;
563         }
564 
565         void setWriter(LockEntry writer)
566         {
567             this.writer = writer;
568         }
569 
570         Hashtable getReaders()
571         {
572             return readers;
573         }
574 
575         void addReader(LockEntry reader)
576         {
577             /**
578              * MBAIRD:
579              * we want to track the youngest reader so we can remove all readers at timeout
580              * if the youngestreader is older than the timeoutperiod.
581              */
582             if((reader.getTimestamp() < m_youngestReader) || (m_youngestReader == 0))
583             {
584                 m_youngestReader = reader.getTimestamp();
585             }
586             this.readers.put(reader.getKey(), reader);
587         }
588 
589         long getYoungestReader()
590         {
591             return m_youngestReader;
592         }
593 
594         LockEntry getReader(Object key)
595         {
596             return (LockEntry) this.readers.get(key);
597         }
598 
599         LockEntry removeReader(Object key)
600         {
601             return (LockEntry) this.readers.remove(key);
602         }
603     }
604 
605 
606     //===============================================================
607     // inner class
608     //===============================================================
609     /**
610      * A lock entry encapsulates locking information.
611      */
612     final class LockEntry implements Serializable
613     {
614         /**
615          * marks a Read Lock.
616          */
617         static final int LOCK_READ = 0;
618 
619         /**
620          * marks a Write Lock.
621          */
622         static final int LOCK_WRITE = 1;
623 
624         /**
625          * the object to be locked.
626          */
627         private Object resourceId;
628 
629         /**
630          * key for locked object
631          */
632         private Object key;
633 
634         /**
635          * the timestamp marking the time of acquisition of this lock
636          */
637         private long timestamp;
638 
639         /**
640          * the isolationlevel for this lock.
641          */
642         private int isolationLevel;
643 
644         /**
645          * marks if this is a read or a write lock.
646          * LOCK_READ = 0;
647          * LOCK_WRITE = 1;
648          */
649         private int lockType;
650 
651         /**
652          * Multiargument constructor for fast loading of LockEntries by OJB.
653          */
654         public LockEntry(Object resourceId,
655                          Object key,
656                          long timestamp,
657                          int isolationLevel,
658                          int lockType)
659         {
660             this.resourceId = resourceId;
661             this.key = key;
662             this.timestamp = timestamp;
663             this.isolationLevel = isolationLevel;
664             this.lockType = lockType;
665 
666         }
667 
668         /**
669          * Returns the resource id of the locked object (or the locked object itself).
670          */
671         public Object getResourceId()
672         {
673             return resourceId;
674         }
675 
676         /**
677          * Returns lock key.
678          */
679         public Object getKey()
680         {
681             return key;
682         }
683 
684         /**
685          * returns the timestamp of the acqusition of the lock.
686          */
687         public long getTimestamp()
688         {
689             return timestamp;
690         }
691 
692         /**
693          * returns the isolation level of this lock
694          */
695         public int getIsolationLevel()
696         {
697             return isolationLevel;
698         }
699 
700         /**
701          * returns the locktype of this lock.
702          *
703          * @return LOCK_READ if lock is a readlock,
704          *         LOCK_WRITE if lock is a Write lock.
705          */
706         public int getLockType()
707         {
708             return lockType;
709         }
710 
711         /**
712          * sets the locktype of this lockentry.
713          *
714          * @param locktype LOCK_READ for read, LOCK_WRITE for write lock.
715          */
716         public void setLockType(int locktype)
717         {
718             this.lockType = locktype;
719         }
720 
721         /**
722          * Returns true if this lock is owned by the specified key.
723          */
724         public boolean isOwnedBy(Object key)
725         {
726             return this.getKey().equals(key);
727         }
728 
729 
730         /**
731          * Sets the isolationLevel.
732          *
733          * @param isolationLevel The isolationLevel to set
734          */
735         public void setIsolationLevel(int isolationLevel)
736         {
737             this.isolationLevel = isolationLevel;
738         }
739 
740         /**
741          * Sets the resourceId.
742          *
743          * @param resourceId The resourceId to set
744          */
745         public void setresourceId(String resourceId)
746         {
747             this.resourceId = resourceId;
748         }
749 
750         /**
751          * Sets the timestamp.
752          *
753          * @param timestamp The timestamp to set
754          */
755         public void setTimestamp(long timestamp)
756         {
757             this.timestamp = timestamp;
758         }
759 
760         /**
761          * Sets the key.
762          *
763          * @param key The key to set
764          */
765         public void setKey(Object key)
766         {
767             this.key = key;
768         }
769     }
770 }