1717package org .labkey .api .data ;
1818
1919import org .apache .commons .beanutils .ConvertUtils ;
20+ import java .lang .ref .Cleaner ;
2021import org .apache .commons .collections4 .IteratorUtils ;
2122import org .apache .logging .log4j .LogManager ;
2223import org .apache .logging .log4j .Logger ;
2526import org .labkey .api .collections .RowMap ;
2627import org .labkey .api .dataiterator .DataIterator ;
2728import org .labkey .api .miniprofiler .MiniProfiler ;
28- import org .labkey .api .settings .AppProps ;
2929import org .labkey .api .util .ExceptionUtil ;
3030import org .labkey .api .util .MemTracker ;
3131import org .labkey .api .util .ResultSetUtil ;
@@ -77,27 +77,78 @@ public class CachedResultSet implements ResultSet, TableResultSet
7777 // data
7878 private final ArrayList <RowMap <Object >> _rowMaps ;
7979 private final boolean _isComplete ;
80- @ Nullable
81- private final StackTraceElement [] _stackTrace ;
82- private final String _threadName ;
8380
8481 private boolean _wasClosed = false ;
85- private boolean _requireClose = true ;
86- private String _url = null ;
8782
8883 // state
8984 private int _row = -1 ;
9085 private int _direction = 1 ;
9186 private int _fetchSize = 1 ;
9287 private Object _lastObject = null ;
9388
89+ private static final Cleaner CLEANER = Cleaner .create ();
90+
91+ private static class CachedResultSetState implements Runnable
92+ {
93+ private final boolean _requireClose ;
94+ private final @ Nullable StackTraceElement [] _stackTrace ;
95+ private final String _threadName ;
96+ private final String _url ;
97+ private final Logger _log ;
98+
99+ private boolean _wasClosed = false ;
100+
101+ private CachedResultSetState (boolean requireClose , @ Nullable StackTraceElement [] stackTrace , String threadName , String url , Logger log )
102+ {
103+ _requireClose = requireClose ;
104+ _stackTrace = stackTrace ;
105+ _threadName = threadName ;
106+ _url = url ;
107+ _log = log ;
108+ }
109+
110+ @ Override
111+ public void run ()
112+ {
113+ if (!_wasClosed )
114+ {
115+ if (_requireClose )
116+ {
117+ StringBuilder error = new StringBuilder ("CachedResultSet was not closed." );
118+ if (null != _threadName )
119+ {
120+ error .append (" Created by thread " ).append (_threadName );
121+ }
122+ if (null != _url )
123+ {
124+ error .append (" for URL " ).append (_url );
125+ }
126+ if (null != _stackTrace )
127+ {
128+ error .append ("\n " ).append (ExceptionUtil .renderStackTrace (_stackTrace ));
129+ }
130+ _log .error (error );
131+ }
132+ _wasClosed = true ;
133+ }
134+ }
135+
136+ private void close ()
137+ {
138+ _wasClosed = true ;
139+ }
140+ }
141+
142+ private final CachedResultSetState _state ;
143+ private final Cleaner .Cleanable _cleanable ;
144+
94145
95146 /*
96147 Constructor is not normally used... see CachedResultSets for static factory methods.
97148
98149 stackTrace is used to set an alternate stack trace -- good for async queries, to indicate original creation stack trace
99150 */
100- CachedResultSet (ResultSetMetaData md , ArrayList <RowMap <Object >> maps , boolean isComplete , @ Nullable StackTraceElement [] stackTrace )
151+ CachedResultSet (ResultSetMetaData md , ArrayList <RowMap <Object >> maps , boolean isComplete , boolean requireClose , @ Nullable StackTraceElement [] stackTrace )
101152 {
102153 _rowMaps = maps ;
103154 _isComplete = isComplete ;
@@ -120,50 +171,35 @@ public class CachedResultSet implements ResultSet, TableResultSet
120171 throw new RuntimeSQLException (x );
121172 }
122173
174+ String url = null ;
175+ String threadName = null ;
123176 if (MiniProfiler .isCollectTroubleshootingStackTraces ())
124177 {
125178 // Stash stack trace that created this CachedRowSet
126- if (null ! = stackTrace )
179+ if (null = = stackTrace )
127180 {
128- _stackTrace = stackTrace ;
129- }
130- else
131- {
132- _stackTrace = MiniProfiler .getTroubleshootingStackTrace ();
181+ stackTrace = MiniProfiler .getTroubleshootingStackTrace ();
133182 }
134183
135- _threadName = Thread .currentThread ().getName ();
184+ threadName = Thread .currentThread ().getName ();
136185
137186 if (HttpView .getStackSize () > 0 )
138187 {
139188 try
140189 {
141- _url = ViewServlet .getOriginalURL ();
190+ url = ViewServlet .getOriginalURL ();
142191 }
143192 catch (Exception x )
144193 {
145194 // we might not be in a view thread...
146195 }
147196 }
148197 }
149- else
150- {
151- _stackTrace = null ;
152- _threadName = null ;
153- }
154198
155- MemTracker .getInstance ().put (this );
156- }
157-
158- public boolean isRequireClose ()
159- {
160- return _requireClose ;
161- }
199+ _state = new CachedResultSetState (requireClose , stackTrace , threadName , url , _log );
200+ _cleanable = CLEANER .register (this , _state );
162201
163- public CachedResultSet setRequireClose (boolean requireClose )
164- {
165- _requireClose = requireClose ;
166- return this ;
202+ MemTracker .getInstance ().put (this );
167203 }
168204
169205 @ Override
@@ -223,6 +259,8 @@ public boolean next()
223259 public void close ()
224260 {
225261 _wasClosed = true ;
262+ _state .close ();
263+ _cleanable .clean ();
226264 }
227265
228266 @ Override
@@ -729,28 +767,6 @@ public void afterLast()
729767 _row = _rowMaps .size ();
730768 }
731769
732- @ Override
733- protected void finalize () throws Throwable
734- {
735- if (!_wasClosed )
736- {
737- close ();
738-
739- if (_requireClose && AppProps .getInstance ().isDevMode ())
740- {
741- StringBuilder error = new StringBuilder ("CachedResultSet was not closed." );
742- if (null != _url )
743- error .append ("\n URL: " ).append (_url );
744- else if (_threadName != null )
745- error .append ("\n threadName: " ).append (_threadName );
746- error .append ("\n Stack trace from the creation:" );
747- error .append (ExceptionUtil .renderStackTrace (_stackTrace ));
748-
749- _log .error (error );
750- }
751- }
752- super .finalize ();
753- }
754770
755771 @ Override
756772 public boolean first ()
0 commit comments