Skip to content

Commit 897ffed

Browse files
Require Java 25 (#7305)
1 parent 7d620db commit 897ffed

21 files changed

Lines changed: 440 additions & 237 deletions

api/src/org/labkey/api/data/CachedResultSet.java

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.labkey.api.data;
1818

1919
import org.apache.commons.beanutils.ConvertUtils;
20+
import java.lang.ref.Cleaner;
2021
import org.apache.commons.collections4.IteratorUtils;
2122
import org.apache.logging.log4j.LogManager;
2223
import org.apache.logging.log4j.Logger;
@@ -25,7 +26,6 @@
2526
import org.labkey.api.collections.RowMap;
2627
import org.labkey.api.dataiterator.DataIterator;
2728
import org.labkey.api.miniprofiler.MiniProfiler;
28-
import org.labkey.api.settings.AppProps;
2929
import org.labkey.api.util.ExceptionUtil;
3030
import org.labkey.api.util.MemTracker;
3131
import 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("\nURL: ").append(_url);
744-
else if (_threadName != null)
745-
error.append("\nthreadName: ").append(_threadName);
746-
error.append("\nStack 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()

api/src/org/labkey/api/data/CachedResultSets.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@
3232
*/
3333
public class CachedResultSets
3434
{
35-
public static CachedResultSet create(ResultSet rs, boolean cacheMetaData, int maxRows) throws SQLException
35+
public static CachedResultSet create(ResultSet rs, boolean cacheMetaData, boolean requireClose, int maxRows) throws SQLException
3636
{
37-
return create(rs, cacheMetaData, maxRows, null, QueryLogging.emptyQueryLogging());
37+
return create(rs, cacheMetaData, requireClose, maxRows, null, QueryLogging.emptyQueryLogging());
3838
}
3939

40-
public static CachedResultSet create(ResultSet rsIn, boolean cacheMetaData, int maxRows, @Nullable StackTraceElement[] stackTrace, QueryLogging queryLogging) throws SQLException
40+
public static CachedResultSet create(ResultSet rsIn, boolean cacheMetaData, boolean requireClose, int maxRows, @Nullable StackTraceElement[] stackTrace, QueryLogging queryLogging) throws SQLException
4141
{
4242
try (ResultSet rs = new LoggingResultSetWrapper(rsIn, queryLogging)) // TODO: avoid if we're passed a read-only and empty one??
4343
{
@@ -58,13 +58,13 @@ public static CachedResultSet create(ResultSet rsIn, boolean cacheMetaData, int
5858
// If we have another row, then we're not complete
5959
boolean isComplete = !rs.next();
6060

61-
return new CachedResultSet(md, list, isComplete, stackTrace);
61+
return new CachedResultSet(md, list, isComplete, requireClose, stackTrace);
6262
}
6363
}
6464

6565
public static CachedResultSet create(ResultSetMetaData md, List<Map<String, Object>> maps, boolean isComplete)
6666
{
67-
return new CachedResultSet(md, convertToRowMaps(md, maps), isComplete, null);
67+
return new CachedResultSet(md, convertToRowMaps(md, maps), isComplete, true, null);
6868
}
6969

7070
public static CachedResultSet create(List<Map<String, Object>> maps)
@@ -88,7 +88,7 @@ public static CachedResultSet create(List<Map<String, Object>> maps, Collection<
8888
ResultSetMetaData md = createMetaData(columnNames);
8989

9090
// Avoid error message from CachedResultSet.finalize() about unclosed CachedResultSet.
91-
try (CachedResultSet crs = new CachedResultSet(md, convertToRowMaps(md, maps), true, null))
91+
try (CachedResultSet crs = new CachedResultSet(md, convertToRowMaps(md, maps), true, true, null))
9292
{
9393
return crs;
9494
}

api/src/org/labkey/api/data/ConnectionWrapper.java

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.labkey.api.data;
1818

1919
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
20+
import java.lang.ref.Cleaner;
2021
import org.apache.logging.log4j.LogManager;
2122
import org.apache.logging.log4j.Logger;
2223
import org.apache.logging.log4j.core.LoggerContext;
@@ -78,6 +79,59 @@ public class ConnectionWrapper implements java.sql.Connection
7879
private static final Logger packageLogger = LogManager.getLogger(ConnectionWrapper.class.getPackageName());
7980
private static final Logger LOG = LogHelper.getLogger(ConnectionWrapper.class, "All JDBC metadata and SQL execution calls being made");
8081

82+
private static final Cleaner CLEANER = Cleaner.create();
83+
84+
private static class ConnectionState implements Runnable
85+
{
86+
private Connection _connection;
87+
private final DbScope _scope;
88+
private final ConnectionWrapper _wrapper; // This is risky, but we need toString()
89+
private final Logger _log;
90+
91+
private boolean _closed = false;
92+
93+
private ConnectionState(Connection connection, DbScope scope, ConnectionWrapper wrapper, Logger log)
94+
{
95+
_connection = connection;
96+
_scope = scope;
97+
_wrapper = wrapper;
98+
_log = log;
99+
}
100+
101+
@Override
102+
public void run()
103+
{
104+
if (_connection != null && !_closed)
105+
{
106+
_log.error("Connection was not closed! " + _wrapper.toString());
107+
realClose();
108+
}
109+
}
110+
111+
private void realClose()
112+
{
113+
if (!_closed)
114+
{
115+
try
116+
{
117+
_wrapper.realCloseInternal();
118+
}
119+
catch (SQLException e)
120+
{
121+
_log.error("Failed to close connection", e);
122+
}
123+
finally
124+
{
125+
_closed = true;
126+
_connection = null;
127+
}
128+
}
129+
}
130+
}
131+
132+
private final ConnectionState _state;
133+
private final Cleaner.Cleanable _cleanable;
134+
81135
private static final Set<ConnectionWrapper> _openConnections = Collections.synchronizedSet(new HashSet<>());
82136
private static final Set<ConnectionWrapper> _loggedLeaks = new HashSet<>();
83137
private static final boolean _explicitLogger = initializeExplicitLogger();
@@ -165,6 +219,8 @@ public ConnectionWrapper(Connection conn, DbScope scope, Integer spid, Connectio
165219
_openConnections.add(this);
166220

167221
_log = log != null ? log : getConnectionLogger();
222+
_state = new ConnectionState(_connection, _scope, this, _log);
223+
_cleanable = CLEANER.register(this, _state);
168224
}
169225

170226
/** this is a best guess logger, pass one in to be predictable */
@@ -457,7 +513,13 @@ void internalClose() throws SQLException
457513
_type.close(_scope, this, this::realClose);
458514
}
459515

460-
private void realClose() throws SQLException
516+
private void realClose()
517+
{
518+
_state.realClose();
519+
_cleanable.clean();
520+
}
521+
522+
private void realCloseInternal() throws SQLException
461523
{
462524
_openConnections.remove(this);
463525
_loggedLeaks.remove(this);
@@ -938,18 +1000,6 @@ public boolean isWrapperFor(Class<?> iface)
9381000
return iface.isAssignableFrom(_connection.getClass());
9391001
}
9401002

941-
@Override
942-
protected void finalize() throws Throwable
943-
{
944-
// If the thread was banned from getting a connection, _connection will be null, and we shouldn't complain that it wasn't closed
945-
if (_connection != null && !isClosed())
946-
{
947-
LOG.error("Connection was not closed! " + this);
948-
realClose();
949-
}
950-
951-
super.finalize();
952-
}
9531003

9541004
public @NotNull Closer getRunOnClose()
9551005
{

api/src/org/labkey/api/data/DbScope.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1878,7 +1878,7 @@ private static void detectUnexpectedConnections(Connection conn, LabKeyDataSourc
18781878
stmt.setString(1, databaseName);
18791879
stmt.setString(2, applicationName);
18801880

1881-
try (CachedResultSet rs = CachedResultSets.create(stmt.executeQuery(), true, 1000))
1881+
try (CachedResultSet rs = CachedResultSets.create(stmt.executeQuery(), true, true, 1000))
18821882
{
18831883
count = rs.getSize();
18841884
if (count != 0)

0 commit comments

Comments
 (0)