001/** 002 * Copyright (C) 2012 FuseSource, Inc. 003 * http://fusesource.com 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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 */ 017 018package org.fusesource.hawtdispatch; 019 020import java.io.InputStream; 021import java.io.PrintWriter; 022import java.io.StringWriter; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashSet; 026import java.util.Properties; 027import java.util.concurrent.atomic.AtomicInteger; 028 029import static java.lang.String.format; 030 031/** 032 * Base class that implements the {@link Retained} interface. 033 * 034 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 035 */ 036public class BaseRetained implements Retained { 037 038 private static final int MAX_TRACES = Integer.getInteger("org.fusesource.hawtdispatch.BaseRetained.MAX_TRACES", 100); 039 private static final boolean TRACE = Boolean.getBoolean("org.fusesource.hawtdispatch.BaseRetained.TRACE"); 040 041 final private AtomicInteger retained = new AtomicInteger(1); 042 volatile private Task disposer; 043 /** 044 * <p> 045 * Adds a disposer runnable that is executed once the object is disposed. 046 * </p><p> 047 * A dispatch object's disposer runnable will be invoked on the object's target queue 048 * once the object's retain counter reaches zero. This disposer may be 049 * used by the application to release any resources associated with the object. 050 * </p> 051 * 052 * @param disposer 053 */ 054 final public void setDisposer(final Runnable disposer) { 055 this.setDisposer(new TaskWrapper(disposer)); 056 } 057 058 /** 059 * <p> 060 * Adds a disposer runnable that is executed once the object is disposed. 061 * </p><p> 062 * A dispatch object's disposer runnable will be invoked on the object's target queue 063 * once the object's retain counter reaches zero. This disposer may be 064 * used by the application to release any resources associated with the object. 065 * </p> 066 * 067 * @param disposer 068 */ 069 final public void setDisposer(Task disposer) { 070 assertRetained(); 071 this.disposer = disposer; 072 } 073 074 final public Task getDisposer() { 075 return disposer; 076 } 077 078 /** 079 * <p> 080 * Increment the reference count of this object. 081 * </p> 082 * 083 * Calls to {@link #retain()} must be balanced with calls to 084 * {@link #release()}. 085 */ 086 final public void retain() { 087 if( TRACE ) { 088 synchronized(traces) { 089 assertRetained(); 090 final int x = retained.incrementAndGet(); 091 trace("retained", x); 092 } 093 } else { 094 assertRetained(); 095 retained.getAndIncrement(); 096 } 097 } 098 099 /** 100 * <p> 101 * Decrement the reference count of this object. 102 * </p><p> 103 * An object is asynchronously disposed once all references are 104 * released. Using a disposed object will cause undefined errors. 105 * The system does not guarantee that a given client is the last or 106 * only reference to a given object. 107 * </p> 108 */ 109 final public void release() { 110 if( TRACE ) { 111 synchronized(traces) { 112 assertRetained(); 113 final int x = retained.decrementAndGet(); 114 trace("released", x); 115 if (x == 0) { 116 dispose(); 117 trace("disposed", x); 118 } 119 } 120 } else { 121 assertRetained(); 122 if (retained.decrementAndGet() == 0) { 123 dispose(); 124 } 125 } 126 } 127 128 /** 129 * <p> 130 * Decrements the reference count by n. 131 * </p><p> 132 * An object is asynchronously disposed once all references are 133 * released. Using a disposed object will cause undefined errors. 134 * The system does not guarantee that a given client is the last or 135 * only reference to a given object. 136 * </p> 137 * @param n 138 */ 139 final protected void release(int n) { 140 if( TRACE ) { 141 synchronized(traces) { 142 assertRetained(); 143 int x = retained.addAndGet(-n); 144 trace("released "+n, x); 145 if ( x == 0) { 146 trace("disposed", x); 147 dispose(); 148 } 149 } 150 } else { 151 assertRetained(); 152 if (retained.addAndGet(-n) == 0) { 153 dispose(); 154 } 155 } 156 } 157 158 /** 159 * Subclasses can use this method to validate that the object has not yet been released. 160 * it will throw an IllegalStateException if it has been released. 161 * 162 * @throws IllegalStateException if the object has been released. 163 */ 164 final protected void assertRetained() { 165 if( TRACE ){ 166 synchronized(traces) { 167 if( retained.get() <= 0 ) { 168 throw new AssertionError(format("%s: Use of object not allowed after it has been released. %s", this.toString(), traces)); 169 } 170 } 171 } else { 172 assert retained.get() > 0 : format("%s: Use of object not allowed after it has been released.", this.toString()); 173 } 174 } 175 176 /** 177 * @return the retained counter 178 */ 179 final public int retained() { 180 return retained.get(); 181 } 182 183 /** 184 * <p> 185 * This method will be called once the release retained reaches zero. It causes 186 * the set disposer runnabled to be executed if not null. 187 * <p></p> 188 * Subclasses should override if they want to implement a custom disposing action in 189 * addition to the actions performed by the disposer object. 190 * </p> 191 */ 192 protected void dispose() { 193 Runnable disposer = this.disposer; 194 if( disposer!=null ) { 195 disposer.run(); 196 } 197 } 198 199 final private ArrayList<String> traces = TRACE ? new ArrayList<String>(MAX_TRACES+1) : null; 200 final private void trace(final String action, final int counter) { 201 if( traces.size() < MAX_TRACES) { 202 Exception ex = new Exception() { 203 public String toString() { 204 return "Trace "+(traces.size()+1)+": "+action+", counter: "+counter+", thread: "+Thread.currentThread().getName(); 205 } 206 }; 207 208 String squashed = squash(ex.getStackTrace()); 209 if( squashed == null ) { 210 StringWriter sw = new StringWriter(); 211 ex.printStackTrace(new PrintWriter(sw)); 212 traces.add("\n"+sw); 213 } 214// else { 215// traces.add("\n"+ex.toString()+"\n at "+squashed+"\n"); 216// } 217 } else if (traces.size() == MAX_TRACES) { 218 traces.add("MAX_TRACES reached... no more traces will be recorded."); 219 } 220 } 221 222 // 223 // Hide system generated balanced calls to retain/release since the tracing facility is 224 // here to help end users figure out where THEY are failing to pair up the calls. 225 // 226 static private String squash(StackTraceElement[] st) { 227 if( st.length > 2) { 228 final String traceData = st[2].getClassName()+"."+st[2].getMethodName(); 229 if( CALLERS.contains(traceData) ) { 230 return traceData; 231 } 232 } 233 return null; 234 } 235 236 237 static HashSet<String> CALLERS = new HashSet<String>(); 238 static { 239 if( TRACE ) { 240 Properties p = new Properties(); 241 final InputStream is = BaseRetained.class.getResourceAsStream("BaseRetained.CALLERS"); 242 try { 243 p.load(is); 244 } catch (Exception ignore) { 245 ignore.printStackTrace(); 246 } finally { 247 try { 248 is.close(); 249 } catch (Exception ignore) { 250 } 251 } 252 for (Object key : Collections.list(p.keys())) { 253 CALLERS.add((String) key); 254 } 255 } 256 } 257 258}