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 }