Skip to content

Commit e8fc172

Browse files
Merge pull request #88 from oracle/85-more-results-leak
Workaround ResultSet memory leak
2 parents bf030b2 + 375618f commit e8fc172

File tree

1 file changed

+38
-6
lines changed

1 file changed

+38
-6
lines changed

src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java

+38-6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.sql.Connection;
4040
import java.sql.PreparedStatement;
4141
import java.sql.ResultSet;
42+
import java.sql.SQLException;
4243
import java.sql.SQLType;
4344
import java.sql.SQLWarning;
4445
import java.time.Duration;
@@ -55,6 +56,7 @@
5556
import java.util.function.Function;
5657
import java.util.stream.IntStream;
5758

59+
import static java.sql.Statement.CLOSE_ALL_RESULTS;
5860
import static java.sql.Statement.KEEP_CURRENT_RESULT;
5961
import static java.sql.Statement.RETURN_GENERATED_KEYS;
6062
import static java.util.Objects.requireNonNullElse;
@@ -1158,26 +1160,56 @@ private OracleResultImpl getWarnings(OracleResultImpl result) {
11581160
*/
11591161
private Publisher<Void> deallocate(Collection<OracleResultImpl> results) {
11601162

1161-
// Close the statement after all results are consumed
1163+
// Set up a counter that is decremented as each result is consumed.
11621164
AtomicInteger unconsumed = new AtomicInteger(results.size());
1165+
1166+
// Set up a publisher that decrements the counter, and closes the
1167+
// statement when it reaches zero
11631168
Publisher<Void> closeStatement = adapter.getLock().run(() -> {
11641169
if (unconsumed.decrementAndGet() == 0)
1165-
preparedStatement.close();
1170+
closeStatement();
11661171
});
11671172

1173+
// Tell each unconsumed result to decrement the unconsumed count, and then
1174+
// close the statement when the count reaches zero.
11681175
for (OracleResultImpl result : results) {
11691176
if (!result.onConsumed(closeStatement))
11701177
unconsumed.decrementAndGet();
11711178
}
11721179

1173-
// If all results have already been consumed, the returned
1174-
// publisher closes the statement
1180+
// If there are no results, or all results have already been consumed,
1181+
// then the returned publisher closes the statement.
11751182
if (unconsumed.get() == 0)
1176-
addDeallocation(adapter.getLock().run(preparedStatement::close));
1183+
addDeallocation(adapter.getLock().run(this::closeStatement));
11771184

11781185
return deallocators;
11791186
}
11801187

1188+
/**
1189+
* Closes the JDBC {@link #preparedStatement}. This method should only be
1190+
* called while holding the
1191+
* {@linkplain ReactiveJdbcAdapter#getLock() connection lock}
1192+
* @throws SQLException If the statement fails to close.
1193+
*/
1194+
private void closeStatement() throws SQLException {
1195+
try {
1196+
// Workaround Oracle JDBC bug #34545179: ResultSet references are
1197+
// retained even when the statement is closed. Calling getMoreResults
1198+
// with the CLOSE_ALL_RESULTS argument forces the driver to
1199+
// de-reference them.
1200+
preparedStatement.getMoreResults(CLOSE_ALL_RESULTS);
1201+
}
1202+
catch (SQLException sqlException) {
1203+
// It may be the case that the JDBC connection was closed, and so the
1204+
// statement was closed with it. Check for this, and ignore the
1205+
// SQLException if so.
1206+
if (!jdbcConnection.isClosed())
1207+
throw sqlException;
1208+
}
1209+
1210+
preparedStatement.close();
1211+
}
1212+
11811213
/**
11821214
* Sets the {@code value} of a {@code preparedStatement} parameter at the
11831215
* specified {@code index}. If a non-null {@code type} is provided, then it is
@@ -1454,7 +1486,7 @@ private JdbcBatch(
14541486
*/
14551487
@Override
14561488
protected Publisher<Void> bind() {
1457-
@SuppressWarnings({"unchecked","rawtypes"})
1489+
@SuppressWarnings({"unchecked"})
14581490
Publisher<Void>[] bindPublishers = new Publisher[batchSize];
14591491
for (int i = 0; i < batchSize; i++) {
14601492
bindPublishers[i] = Flux.concat(

0 commit comments

Comments
 (0)