Skip to content

Commit af57b48

Browse files
committed
#876 Fix hang when closing lots of statements
1 parent 97dd83d commit af57b48

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ For known issues, consult <<known-issues>>.
3737

3838
The following was fixed or changed since Jaybird 6.0.3:
3939

40-
* ...
40+
* Fixed: statement close could cause a hang of the connection (https://github.com/FirebirdSQL/jaybird/issues/876[#876])
41+
+
42+
A large amount (>= 64) of statement closes (or cleanup of leaked statements) without interleaving other server operations could cause a connection to Firebird 4.0 or higher to hang.
43+
This could also hang the cleaner thread used for closing leaked statements.
4144

4245
=== Jaybird 6.0.2
4346

src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.sql.SQLException;
4343
import java.util.Map;
4444
import java.util.WeakHashMap;
45+
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
4546
import java.util.function.Function;
4647

4748
import static java.util.Objects.requireNonNull;
@@ -253,54 +254,77 @@ private void deferredExceptionHandler(Exception exception) {
253254
}
254255
}
255256

257+
@SuppressWarnings("resource")
256258
private static final class CleanupAction implements Runnable, StatementListener, DatabaseListener {
257259

260+
private static final AtomicReferenceFieldUpdater<CleanupAction, FbWireDatabase> databaseUpdater =
261+
AtomicReferenceFieldUpdater.newUpdater(CleanupAction.class, FbWireDatabase.class, "database");
262+
263+
private static final DeferredAction CLEANUP_FREE_DEFERRED_ACTION = new DeferredAction() {
264+
@Override
265+
public void processResponse(Response response) {
266+
// nothing to do
267+
}
268+
269+
@Override
270+
public boolean requiresSync() {
271+
return true;
272+
}
273+
};
274+
258275
private final int handle;
259-
@SuppressWarnings("java:S3077")
260276
private volatile FbWireDatabase database;
261277

262278
private CleanupAction(AbstractFbWireStatement statement) {
263279
// NOTE: Care should be taken not to retain a handle to statement itself here
264280
handle = statement.getHandle();
265-
database = statement.getDatabase();
281+
FbWireDatabase database = statement.getDatabase();
282+
databaseUpdater.set(this, database);
266283
database.addWeakDatabaseListener(this);
267284
statement.addWeakStatementListener(this);
268285
}
269286

270287
@Override
271288
public void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState) {
272289
if (newState == StatementState.CLOSING) {
273-
FbDatabase database = this.database;
274-
if (database != null) {
275-
release(database);
276-
}
290+
releaseDatabaseReference();
277291
sender.removeStatementListener(this);
278292
}
279293
}
280294

281295
@Override
282296
public void detaching(FbDatabase database) {
283-
release(database);
297+
releaseDatabaseReference();
284298
}
285299

286-
private void release(FbDatabase database) {
287-
this.database = null;
288-
database.removeDatabaseListener(this);
300+
private void releaseDatabaseReference() {
301+
FbWireDatabase database = databaseUpdater.getAndSet(this, null);
302+
if (database != null) {
303+
database.removeDatabaseListener(this);
304+
}
305+
}
306+
307+
private FbWireDatabase releaseAndGetDatabaseReference() {
308+
FbWireDatabase database = databaseUpdater.getAndSet(this, null);
309+
if (database != null) {
310+
database.removeDatabaseListener(this);
311+
}
312+
return database;
289313
}
290314

291315
@Override
292316
public void run() {
293-
FbWireDatabase database = this.database;
317+
FbWireDatabase database = releaseAndGetDatabaseReference();
294318
if (database == null) return;
295-
release(database);
296319
try (LockCloseable ignored = database.withLock()) {
297320
if (!database.isAttached()) return;
298321
XdrOutputStream xdrOut = database.getXdrStreamAccess().getXdrOut();
299322
xdrOut.writeInt(WireProtocolConstants.op_free_statement); // p_operation
300323
xdrOut.writeInt(handle); // p_sqlfree_statement
301324
xdrOut.writeInt(ISCConstants.DSQL_drop); // p_sqlfree_option
302325
xdrOut.flush();
303-
database.enqueueDeferredAction(DeferredAction.NO_OP_INSTANCE);
326+
// TODO: This may process deferred actions on the cleaner thread, we may want to change that
327+
database.enqueueDeferredAction(CLEANUP_FREE_DEFERRED_ACTION);
304328
} catch (SQLException | IOException e) {
305329
System.getLogger(getClass().getName()).log(System.Logger.Level.TRACE,
306330
"Ignored exception during statement clean up", e);

src/main/org/firebirdsql/gds/ng/wire/version11/V11Statement.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ public WarningMessageCallback getWarningMessageCallback() {
269269

270270
@Override
271271
public boolean requiresSync() {
272-
return option == ISCConstants.DSQL_close;
272+
// DSQL_close requires sync as it isn't flushed,
273+
// other options require sync because server defers response
274+
return true;
273275
}
274276
});
275277
} catch (IOException e) {

0 commit comments

Comments
 (0)