View Javadoc

1   /*
2    * Copyright 2007-2008 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.util;
17  
18  import java.util.concurrent.Semaphore;
19  
20  import org.apache.log4j.Logger;
21  
22  /**
23   * Utility class that can be used to diagnose concurrency issues.  When concurrency is detected
24   * the default {@link #onConcurrencyDetected()} implementation logs an error and optionally the
25   * stack traces (if trackStacktraces is enabled).
26   * 
27   * E.g.
28   * 
29   * private static final ConcurrencyDetector detector = new ConcurrencyDetector("Concurrency in Foo class");
30   * 
31   * <tt>
32   * public void questionableMethod() {
33   *   detector.enter();
34   *   try {
35   *     // method impl
36   *   } finally {
37   *     detector.exit();
38   *   }
39   * }
40   * </tt>
41   *
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class ConcurrencyDetector {
45      // keeps track of whether the last enter (in the thread) was able to acquire
46      // the semaphore (in which case the next exit should release it)
47      private ThreadLocal<Boolean> ACQUIRED_SEMAPHORE = new ThreadLocal<Boolean>() {
48          @Override
49          protected Boolean initialValue() {
50              return Boolean.FALSE;
51          }
52      };
53      private final Logger log;
54      private final String name;
55      private final boolean trackStacktraces;
56      private final Semaphore semaphore = new Semaphore(1);
57  
58      private Throwable entryPoint;
59  
60      public ConcurrencyDetector() {
61          this(ConcurrencyDetector.class.getName());
62      }
63      
64      public ConcurrencyDetector(String name) {
65          this(name, true);
66      }
67      
68      public ConcurrencyDetector(String name, boolean trackStacktraces) {
69          this.log = Logger.getLogger(name);
70          this.name = name;
71          this.trackStacktraces = trackStacktraces;
72      }
73      
74      public synchronized boolean enter() {
75          boolean acquired = semaphore.tryAcquire();
76          ACQUIRED_SEMAPHORE.set(Boolean.valueOf(acquired));
77          if (!acquired) {
78              onConcurrencyDetected();
79          } else {
80              if (trackStacktraces) {
81                  entryPoint = new Throwable("Initial entry");
82              }
83          }
84          return acquired;
85      }
86  
87      public synchronized void exit() {
88          if (ACQUIRED_SEMAPHORE.get()) {
89              if (trackStacktraces) {
90                  entryPoint = null;
91              }
92              semaphore.release();
93          }
94      }
95  
96      /**
97       * Logs an error (and optionally stack traces) when concurrency is detected.
98       * Subclasses may override for custom behavior.
99       */
100     protected void onConcurrencyDetected() {
101         log.error("Concurrency was detected");
102         if (trackStacktraces) {
103             entryPoint.printStackTrace();
104             new Throwable("Second entry").printStackTrace();
105         }
106     }
107 }