Statement.cancel() causes lockup

15 views
Skip to first unread message

Stefan Schmaltz

unread,
Sep 17, 2025, 5:47:46 AM (12 days ago) Sep 17
to firebird-java
FB 5.0.3 Windows / Jaybird 6.0.3 / Java 21.0.7
Hi,
we implemented a user search function in this way:
User enters text ->starts search thread with SQL Query (PreparedStatement)
Now user writes more text -> we call Statement.cancel(), change parameter and exec SQL again.
When the user is typing slow, everything is good, but when the user types fast, cancel is called where the SQL is not completed. Firebird did this well, but java has a problem: The socket waits endless for more data. Stacktrace:

java...@21.0.7/sun.nio.ch.Net.poll(Native Method)
java...@21.0.7/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:191)
java...@21.0.7/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:280)
java...@21.0.7/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:304)
java...@21.0.7/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:346)
java...@21.0.7/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:796)
java...@21.0.7/java.net.Socket$SocketInputStream.read(Socket.java:1099)
java...@21.0.7/java.io.BufferedInputStream.fill(BufferedInputStream.java:291)
java...@21.0.7/java.io.BufferedInputStream.read1(BufferedInputStream.java:347)
java...@21.0.7/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420)
java...@21.0.7/java.io.BufferedInputStream.read(BufferedInputStream.java:399)
java...@21.0.7/java.io.FilterInputStream.read(FilterInputStream.java:95)
java...@21.0.7/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:146)
java...@21.0.7/javax.crypto.CipherInputStream.read(CipherInputStream.java:234)
java...@21.0.7/java.io.FilterInputStream.read(FilterInputStream.java:71)
app//org.firebirdsql.gds.impl.wire.XdrInputStream.readInt(XdrInputStream.java:183)
app//org.firebirdsql.gds.ng.wire.WireConnection.readNextOperation(WireConnection.java:524)
app//org.firebirdsql.gds.ng.wire.AbstractWireOperations.readNextOperation(AbstractWireOperations.java:185)
app//org.firebirdsql.gds.ng.wire.AbstractWireOperations.readResponse(AbstractWireOperations.java:169)
app//org.firebirdsql.gds.ng.wire.version11.V11WireOperations.processDeferredActions(V11WireOperations.java:64)
app//org.firebirdsql.gds.ng.wire.AbstractWireOperations.readNextOperation(AbstractWireOperations.java:184)
app//org.firebirdsql.gds.ng.wire.AbstractWireOperations.readResponse(AbstractWireOperations.java:169)
app//org.firebirdsql.gds.ng.wire.AbstractFbWireDatabase.readResponse(AbstractFbWireDatabase.java:222)
app//org.firebirdsql.gds.ng.wire.version10.V10Statement.execute(V10Statement.java:273)
app//org.firebirdsql.jdbc.FBStatement.internalExecute(FBStatement.java:970)
app//org.firebirdsql.jdbc.FBPreparedStatement.internalExecute(FBPreparedStatement.java:563)
app//org.firebirdsql.jdbc.FBPreparedStatement.executeQuery(FBPreparedStatement.java:189)
app//org.firebirdsql.jdbc.FBPreparedStatement.executeQuery(FBPreparedStatement.java:181)
app//komet3.DB.Komet3PreparedStatement.executeQuery(Komet3PreparedStatement.java:109)

Is this a known problem? Solution? 

Best regards
Stefan

Mark Rotteveel

unread,
Sep 17, 2025, 5:57:57 AM (12 days ago) Sep 17
to firebi...@googlegroups.com
On 17/09/2025 10:33, Stefan Schmaltz wrote:
> FB 5.0.3 Windows / Jaybird 6.0.3 / Java 21.0.7
> Hi,
> we implemented a user search function in this way:
> User enters text ->starts search thread with SQL Query (PreparedStatement)
> Now user writes more text -> we call Statement.cancel(), change
> parameter and exec SQL again.
> When the user is typing slow, everything is good, but when the user
> types fast, cancel is called where the SQL is not completed. Firebird
> did this well, but java has a problem: The socket waits endless for more
> data. Stacktrace:
>
> Is this a known problem? Solution?
Do you have a reproducible testcase I can use for investigation?
Cancellation in Jaybird is not entirely thread-safe unfortunately, and I
guess this might be one of the side-effects.

Mark
--
Mark Rotteveel

Stefan Schmaltz

unread,
Sep 17, 2025, 7:25:13 AM (12 days ago) Sep 17
to firebird-java
Looks like a test case is not providable. Running search in AWT-Eventqueue, executing query in separate thread.

But I found a solution:

      stmt.cancel();
      if (stmt instanceof FBStatement fbstmt)
      {
        FbDatabase fbDatabase = fbstmt.getConnection().getFbDatabase();
        if (fbDatabase instanceof AbstractFbWireDatabase wireDb)
          try
          {
            wireDb.getXdrStreamAccess().getXdrIn().close();
          }
          catch (IOException | SQLException e)
          {
            e.printStackTrace();
          }
      }

Maybe add this to your cancel() code? 

Stefan Schmaltz

unread,
Sep 17, 2025, 11:32:57 AM (12 days ago) Sep 17
to firebird-java
A Better way:

stmt.cancel();

if (stmt instanceof FBStatement fbstmt)

{

FbDatabase fbDatabase = fbstmt.getConnection().getFbDatabase();

if (fbDatabase instanceof AbstractFbWireDatabase wireDb)

{

try

{

XdrOutputStream xdrOut = wireDb.getXdrStreamAccess().getXdrOut();

xdrOut.writeInt(WireProtocolConstants.op_detach);

xdrOut.writeInt(0);

xdrOut.writeInt(WireProtocolConstants.op_disconnect);

xdrOut.writeInt(0);

xdrOut.flush();

}

catch (SQLException | IOException e)

{

//e.printStackTrace();

}

}

}


Mark Rotteveel

unread,
Sep 18, 2025, 2:59:56 AM (12 days ago) Sep 18
to firebi...@googlegroups.com
On 17/09/2025 13:25, Stefan Schmaltz wrote:
> Looks like a test case is not providable. Running search in AWT-
> Eventqueue, executing query in separate thread.
>
> But I found a solution:
>
>       stmt.cancel();
>       if (stmt instanceof FBStatement fbstmt)
>       {
>         FbDatabase fbDatabase = fbstmt.getConnection().getFbDatabase();
>         if (fbDatabase instanceof AbstractFbWireDatabase wireDb)
>           try
>           {
>             wireDb.getXdrStreamAccess().getXdrIn().close();
>           }
>           catch (IOException | SQLException e)
>           {
>             e.printStackTrace();
>           }
>       }
>
> Maybe add this to your cancel() code?

That will terminate the entire connection, not just cancel the statement.

I'll see if I can find the problem or make it somehow reproducible.
There are two possibilities I can think of:

1) as I said the cancellation is not entirely thread-safe, because
making it entirely thread-safe would require using separate
(overlapping) locks for sending and receiving data. Maybe the send of
the cancellation happened in the middle of sending other data (though
the fact server-side the cancellation was handled seems to exclude that).

2) The hang happens when processing deferred operations, maybe one of
the deferred operations wasn't properly removed from the queue when
cancellation was received, or I missed an operation that would also be
affected by cancellation.

However, based on your description, you may want to consider - as a
workaround at least - to accumulate user input for at least X
milliseconds before cancelling the previous statement. As long as the
user adds characters (i.e. the previous search is the prefix of the next
search), you could even consider waiting for the query result and
post-process and filter it locally (or even filter a previously received
result locally).

I've created https://github.com/FirebirdSQL/jaybird/issues/892 to
investigate and hopefully fix this.

Mark
--
Mark Rotteveel
Reply all
Reply to author
Forward
0 new messages