Skip to content

Commit c7690ba

Browse files
committed
#905 Fixed incomplete detection of fatal connection errors for deferred actions
Also fixes an NPE when the connection was terminated due to a fatal connection error.
1 parent 26efcc1 commit c7690ba

File tree

7 files changed

+107
-8
lines changed

7 files changed

+107
-8
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ This fix was backported from Jaybird 6.0.4.
4242
+
4343
This is a partial backport from Jaybird 7.
4444
See also <<breaking-changes-internal-api-5-0-11>>.
45+
* Fixed: Incomplete detection of fatal connection errors for deferred actions (https://github.com/FirebirdSQL/jaybird/issues/905[#905])
46+
+
47+
This change also fixes a `NullPointerException` when the connection was terminated due to a fatal connection error.
48+
A minor breaking change was made in the internal GDS-ng wire API, which should only be relevant to maintainers of Jaybird forks, see <<breaking-changes-internal-api-5-0-11>>.
49+
+
50+
This fix was backported from Jaybird 6.0.4.
4551
4652
[#jaybird-5-0-10-changelog]
4753
=== Jaybird 5.0.10
@@ -1504,6 +1510,13 @@ We have avoided most of the breaking changes that we made in Jaybird 7, but the
15041510
+
15051511
If you do continue to override `sendExecute(int, RowValue)`, make sure the transmit lock covers the entire message.
15061512
1513+
Other breaking changes to the internal API in Jaybird 6.0.4 unrelated to the transmit lock:
1514+
1515+
* `WireConnection`
1516+
** `readNextOperation` now also throws `SQLException` -- if the connection is closed.
1517+
* `FbWireOperations`
1518+
** `readNextOperation` now also throws `SQLException` -- if the connection is closed.
1519+
15071520
[#breaking-changes-unlikely]
15081521
=== Unlikely breaking changes
15091522

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,50 @@ public final Response readResponse(WarningMessageCallback warningCallback) throw
222222
return wireOperations.readResponse(warningCallback);
223223
}
224224

225+
/**
226+
* {@inheritDoc}
227+
* <p>
228+
* The implementation decorates {@code deferredAction} to ensure this database instance is notified of exceptions.
229+
* This can result in double notifications if the caller also notifies exceptions and this database instance is
230+
* registered as an exception listener for those notifications (this is not harmful).
231+
* </p>
232+
*/
225233
@Override
226234
public final void enqueueDeferredAction(DeferredAction deferredAction) {
227-
wireOperations.enqueueDeferredAction(deferredAction);
235+
wireOperations.enqueueDeferredAction(decorateWithExceptionNotification(deferredAction));
236+
}
237+
238+
private DeferredAction decorateWithExceptionNotification(DeferredAction deferredAction) {
239+
return deferredAction instanceof ExceptionNotifyingDeferredAction
240+
// Don't decorate again
241+
? deferredAction
242+
: new ExceptionNotifyingDeferredAction(deferredAction);
243+
}
244+
245+
private final class ExceptionNotifyingDeferredAction extends DeferredAction.DelegatingDeferredAction {
246+
247+
ExceptionNotifyingDeferredAction(DeferredAction delegate) {
248+
super(delegate);
249+
}
250+
251+
@Override
252+
public void onException(Exception exception) {
253+
try {
254+
super.onException(exception);
255+
} finally {
256+
exceptionListenerDispatcher.errorOccurred(toReadSQLException(exception));
257+
}
258+
}
259+
260+
private SQLException toReadSQLException(Exception exception) {
261+
if (exception instanceof SQLException) {
262+
return (SQLException) exception;
263+
} else if (exception instanceof IOException) {
264+
return FbExceptionBuilder.forException(ISCConstants.isc_net_read_err).cause(exception).toSQLException();
265+
}
266+
return new SQLException(exception);
267+
}
268+
228269
}
229270

230271
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public final Response readOperationResponse(int operationCode, WarningMessageCal
179179
}
180180

181181
@Override
182-
public final int readNextOperation() throws IOException {
182+
public final int readNextOperation() throws SQLException, IOException {
183183
try (LockCloseable ignored = withLock()) {
184184
processDeferredActions();
185185
return connection.readNextOperation();

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import java.util.function.Consumer;
3232
import java.util.function.Function;
3333

34+
import static java.util.Objects.requireNonNull;
35+
3436
/**
3537
* Interface for processing deferred responses from the server.
3638
* <p>
@@ -110,4 +112,38 @@ public WarningMessageCallback getWarningMessageCallback() {
110112
}
111113
};
112114
}
115+
116+
/**
117+
* Deferred action implementation that delegates to another deferred action.
118+
* <p>
119+
* This class is intended as a base class for implementations that want to decorate method calls. To decorate it,
120+
* subclass this class, and override the method, and ensure you call {@code super.<overridden-method>} in such a
121+
* way that it is always called, even if the decoration fails.
122+
* </p>
123+
* @since 5.0.11
124+
*/
125+
abstract class DelegatingDeferredAction implements DeferredAction {
126+
127+
private final DeferredAction delegate;
128+
129+
DelegatingDeferredAction(DeferredAction delegate) {
130+
this.delegate = requireNonNull(delegate, "delegate");
131+
}
132+
133+
@Override
134+
public void processResponse(Response response) {
135+
delegate.processResponse(response);
136+
}
137+
138+
@Override
139+
public void onException(Exception exception) {
140+
delegate.onException(exception);
141+
}
142+
143+
@Override
144+
public WarningMessageCallback getWarningMessageCallback() {
145+
return delegate.getWarningMessageCallback();
146+
}
147+
148+
}
113149
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,13 @@ public interface FbWireOperations {
7575
* </p>
7676
*
7777
* @return next operation
78+
* @throws SQLException
79+
* when the connection is closed
7880
* @throws java.io.IOException
7981
* for errors reading the operation from the connection
8082
* @since 5.0.7
8183
*/
82-
int readNextOperation() throws IOException;
84+
int readNextOperation() throws SQLException, IOException;
8385

8486
/**
8587
* Reads the response from the server when the operation code has already been read.

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,11 +529,14 @@ private FbWireOperations getCryptKeyCallbackWireOperations() {
529529
* Reads the next operation code. Skips all {@link org.firebirdsql.gds.impl.wire.WireProtocolConstants#op_dummy}
530530
* codes received.
531531
*
532-
* @return Operation code
532+
* @return operation code
533+
* @throws SQLException
534+
* when the connection is closed
533535
* @throws IOException
534536
* if an error occurs while reading from the underlying InputStream
535537
*/
536-
public final int readNextOperation() throws IOException {
538+
public final int readNextOperation() throws SQLException, IOException {
539+
XdrInputStream xdrIn = getXdrStreamAccess().getXdrIn();
537540
int op;
538541
do {
539542
op = xdrIn.readInt();

src/test/org/firebirdsql/management/FBMaintenanceManagerTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.firebirdsql.common.extension.UsesDatabaseExtension;
2323
import org.firebirdsql.gds.ISCConstants;
2424
import org.firebirdsql.gds.TransactionParameterBuffer;
25-
import org.firebirdsql.gds.impl.GDSType;
2625
import org.firebirdsql.gds.ng.FbDatabase;
2726
import org.firebirdsql.gds.ng.FbTransaction;
2827
import org.firebirdsql.jdbc.FBConnection;
@@ -53,8 +52,11 @@
5352
import static org.firebirdsql.common.matchers.MatcherAssume.assumeThat;
5453
import static org.firebirdsql.common.matchers.SQLExceptionMatchers.errorCode;
5554
import static org.firebirdsql.common.matchers.SQLExceptionMatchers.errorCodeEquals;
55+
import static org.firebirdsql.common.matchers.SQLExceptionMatchers.message;
5656
import static org.hamcrest.CoreMatchers.not;
57+
import static org.hamcrest.CoreMatchers.startsWith;
5758
import static org.hamcrest.MatcherAssert.assertThat;
59+
import static org.hamcrest.Matchers.anyOf;
5860
import static org.hamcrest.Matchers.oneOf;
5961
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
6062
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -202,8 +204,10 @@ void testForcedShutdown(boolean autoCommit) throws Exception {
202204
maintenanceManager.shutdownDatabase(MaintenanceManager.SHUTDOWN_FORCE, 0);
203205

204206
SQLException exception = assertThrows(SQLException.class, () -> stmt.executeQuery(sql));
205-
assertThat(exception, errorCode(oneOf(
206-
ISCConstants.isc_shutdown, ISCConstants.isc_att_shutdown, ISCConstants.isc_net_read_err)));
207+
assertThat(exception, anyOf(
208+
errorCode(oneOf(ISCConstants.isc_shutdown, ISCConstants.isc_att_shutdown,
209+
ISCConstants.isc_net_read_err)),
210+
message(startsWith("Connection closed"))));
207211
} finally {
208212
closeQuietly(stmt);
209213
}

0 commit comments

Comments
 (0)