|
| 1 | += jdp-2026-01: Additional locking for sending in wire protocol |
| 2 | + |
| 3 | +// SPDX-FileCopyrightText: Copyright 2026 Mark Rotteveel |
| 4 | +// SPDX-License-Identifier: LicenseRef-PDL-1.0 |
| 5 | + |
| 6 | +== Status |
| 7 | + |
| 8 | +* Draft |
| 9 | +* Proposed for: Jaybird 7 |
| 10 | + |
| 11 | +== Type |
| 12 | + |
| 13 | +* Feature-Specification |
| 14 | + |
| 15 | +== Context |
| 16 | + |
| 17 | +With Jaybird 3, we implemented support for protocol 12 and cancellation. |
| 18 | +We wanted to avoid an overly complex solution with overlapping locks to perform separate locking for sending and receiving, or switching to NIO. |
| 19 | + |
| 20 | +We decided to take the chance and implement cancellation in a way that is not thread-safe, by sending the cancellation message without taking out the connection lock. |
| 21 | +(The connection lock covers sending and receiving, making cancellation only occur after the operation if you try it under that lock.) |
| 22 | +This can lead to the cancellation message being sent in the middle of a normal message. |
| 23 | +This then results in the server receiving a corrupt message, with undefined results (e.g. errors, the server doing the wrong thing, or the connection being terminated). |
| 24 | +We tried to reduce this risk by first constructing the message on a separate `XdrOutputStream`, and then sending the resulting byte array at once. |
| 25 | + |
| 26 | +We've now received reports that indicate this can occur when performing rapid cancellation, where the cancellation might overlap with the statement execute (or another action). |
| 27 | +We've been able to produce a broken connection with rapid cancellation; |
| 28 | +this was a different issue than reported, though. |
| 29 | + |
| 30 | +The native implementation is not affected (and if it were, it is probably not an issue we can address within Jaybird). |
| 31 | + |
| 32 | +== Decision |
| 33 | + |
| 34 | +As we now have a clear case that our not thread-safe solution can actually fail in production, we will need to address the thread-safety of cancellation. |
| 35 | + |
| 36 | +For the wire protocol, we will add a "`transmit`"-lock which should _only_ cover transmitting (sending) messages to the server. |
| 37 | +This lock should _not_ cover receiving messages, and preferably not cover other actions unrelated to sending messages to the server. |
| 38 | +The "`transmit`"-lock should be held for the sending of an entire message. |
| 39 | +When multiple messages are batched without an interleaved receive of responses, it should be considered carefully if the lock should cover individual messages or the entire batch. |
| 40 | + |
| 41 | +For normal operations, the "`transmit`"-lock should be taken out while the normal connection lock is held (i.e. within a try-with-resources calling `withLock()`). |
| 42 | +For the cancellation operation, and possibly other "`out-of-band`" transmissions, only the "`transmit`"-lock should be taken out; |
| 43 | +this can probably be done within implementations of `FbWireOperations.writeDirect(byte[])`. |
| 44 | + |
| 45 | +The lock will use a `java.util.concurrent.locks.ReentrantLock` (see also <<rejected>>). |
| 46 | + |
| 47 | +Exact details of use will be fleshed out during implementation, but `XdrStreamAccess` is probably the right place to provide access to this lock. |
| 48 | +We will provide two methods of taking out the lock: |
| 49 | + |
| 50 | +. `LockCloseable withTransmitLock()` -- for use with try-with-resources in the same fashion as the `withLock()` method(s) for the connection lock |
| 51 | +. `void withTransmitLock(TransmitAction transmitAction) throws SQLException, IOException` where `TransmitAction` is a functional interface with a method `void transmit(XdrOutputStream xdrOut) throws SQLException, IOException` |
| 52 | ++ |
| 53 | +If possible, the `IOException` should be left out of the throws lists of `withTransmitLock(TransmitAction)` and maybe `transmit(XdrOutputStream)` by using `FbExcepionBuilder.ioWriteError(IOException)`, but right now we can't oversee if that is always possible. |
| 54 | +On the other hand, we could always fall back on the first option if there are cases where an `IOException` must be thrown. |
| 55 | + |
| 56 | +If needed, implementation classes (but not the `FbWireXXX` interfaces) may provide the same methods (e.g. as `protected`) for purposes of code simplication, as long as they ultimately call the actual locking methods. |
| 57 | + |
| 58 | +The latter should also be viewed as an experiment if in the future we can use a similar alternative for `withLock()`. |
| 59 | +If during implementation this turns out to be too cumbersome, this may need to be revisited, and maybe we'll only implement the first option. |
| 60 | + |
| 61 | +During implementation, this will be done first in the `FbWireStatement` implementations and the `FbWireOperations.writeDirect(byte[])` method as a proof of concept. |
| 62 | +If this proof of concept fails to resolve the broken connection issue we can reproduce, we will need to rethink this approach. |
| 63 | + |
| 64 | +Given the invasiveness of this change, it will not be backported to Jaybird 5 and 6. |
| 65 | +We will consider a reduced solution, only covering `FbWireStatement` implementations, to address the immediate thread-safety problem of statement cancellation. |
| 66 | +Such a reduced solution could still cause problems if statement cancellation is done while the connection is performing a non-statement operation. |
| 67 | +If we decide to go for such a solution for Jaybird 5 and/or 6, it will be covered by a separate JDP. |
| 68 | + |
| 69 | +[#rejected] |
| 70 | +=== Rejected options |
| 71 | + |
| 72 | +As the lock is generally uncontested, we considered using a simple spinlock on an `AtomicBoolean` or a volatile `int` field (with help of `AtomicIntegerFieldUpdater`) instead of a full-blown lock. |
| 73 | +Unfortunately, there may be cases where we need reentrancy of the lock due to implementation of protocol versioning (e.g. an overloaded method calling its parent and then doing something that should be done within the "`transmit`"-lock). |
| 74 | + |
| 75 | +If during implementation it turns out that this is not a problem (e.g. because the lock can be taken out by the callers of such overloaded methods), we can revisit this decision. |
| 76 | + |
| 77 | +== Consequences |
| 78 | + |
| 79 | +The entire wire protocol implementation is revised to take out the "`transmit`"-lock during sending. |
| 80 | + |
| 81 | +The javadoc of `FbWireOperations.writeDirect(byte[])` is revised to remove mention of race conditions. |
| 82 | +The javadoc of other methods, etc., are revised as needed. |
| 83 | + |
| 84 | +"`Normal`" users of the JDBC driver and the internal GDS-ng API, are not affected -- there is no change in the methods they call, or the operation or behaviour of the driver (except cancellation is now thread-safe). |
| 85 | + |
| 86 | +Implementers of Jaybird forks or alternative wire protocol versions building on the `org.firebirdsql.gds.ng.wire` package and related packages and classes will need to change their code to correctly take out the "`transmit`"-lock during transmission to the server. |
| 87 | + |
| 88 | +It is possible that this change will not fully address cancellation issues (e.g. we suspect deferred response handling might need additional work, and result sets may need to be invalidated/closed by cancellation); |
| 89 | +this will be subject to further investigation after this thread-safety issue has been addressed. |
| 90 | + |
| 91 | +[appendix] |
| 92 | +== License Notice |
| 93 | + |
| 94 | +The contents of this Documentation are subject to the Public Documentation License Version 1.0 (the “License”); |
| 95 | +you may only use this Documentation if you comply with the terms of this License. |
| 96 | +A copy of the License is available at https://firebirdsql.org/en/public-documentation-license/. |
| 97 | + |
| 98 | +The Original Documentation is "`jdp-2026-01: Additional locking for sending in wire protocol`". |
| 99 | +The Initial Writer of the Original Documentation is Mark Rotteveel, Copyright © 2026. |
| 100 | +All Rights Reserved. |
| 101 | +(Initial Writer contact(s): mark (at) lawinegevaar (dot) nl). |
| 102 | + |
| 103 | +//// |
| 104 | +Contributor(s): ______________________________________. |
| 105 | +Portions created by ______ are Copyright © _________ [Insert year(s)]. |
| 106 | +All Rights Reserved. |
| 107 | +(Contributor contact(s): ________________ [Insert hyperlink/alias]). |
| 108 | +//// |
| 109 | + |
| 110 | +The exact file history is recorded in our Git repository; |
| 111 | +see https://github.com/FirebirdSQL/jaybird |
0 commit comments