Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1410934
fix: send upsert with the UPSERT opcode
aensidhe Sep 25, 2026
e618633
fix: encode DmoOperation::insert with the ! operator
aensidhe Sep 25, 2026
0d2d90f
fix: keep the writer queue non-empty for small request thresholds
aensidhe Sep 25, 2026
abe74c6
fix: bound error description length by the remaining frame bytes
aensidhe Sep 25, 2026
1c768ef
fix: decode a frame length as soon as it is fully received
aensidhe Sep 25, 2026
a6bbf68
fix: keep the last salt character and accept padded or unpadded base64
aensidhe Sep 25, 2026
dab7f8a
fix: pass connect_timeout rather than the request timeout to the disp…
aensidhe Sep 25, 2026
de7283f
fix: wrap the request sync counter instead of overflowing
aensidhe Sep 25, 2026
30380bd
fix: report a failed writer task join as a connection error
aensidhe Sep 25, 2026
8649b04
fix: reject an auth response whose sync does not match the request
aensidhe Sep 25, 2026
7f41b26
fix: skip preparing SQL statements while another prepare is in flight
aensidhe Sep 25, 2026
af38d01
fix: drop the partial frame when request encoding fails
aensidhe Sep 25, 2026
80622f6
docs: correct the SQL cache default and drop the orphan call response…
aensidhe Sep 25, 2026
faff9f6
refactor: remove the dead resend machinery
aensidhe Sep 25, 2026
1474168
feat: fail stale streams and transactions after reconnect
aensidhe Sep 25, 2026
e452dff
fix: evict timed-out requests from the in-flight map
aensidhe Sep 25, 2026
161403f
fix: stop reconnecting once the last client handle is gone
aensidhe Sep 25, 2026
349ced8
fix: never send a second rollback for a finished transaction
aensidhe Sep 25, 2026
c6cb82f
fix: warn about a disabled reconnect interval and fix UniqueIdNameMap…
aensidhe Sep 25, 2026
00e9b16
fix: invalidate the SQL statement cache after a reconnect
aensidhe Sep 25, 2026
777272a
chore: ignore the repo-local tmp directory
aensidhe Sep 26, 2026
dda167c
ci: follow the plugin template lint gates and fix the audit
aensidhe Sep 26, 2026
7015a34
fix: accept any u32 response frame and reject requests over 2 GiB
aensidhe Sep 25, 2026
be81d62
fix: carry 64-bit syncs end to end
aensidhe Sep 26, 2026
9b73334
fix: pipeline AUTH and ID and drop the unread feature state
aensidhe Sep 26, 2026
38ae108
perf: batch writer flushes and stop the writer by abort
aensidhe Sep 26, 2026
7381544
fix: cancel every dropped request through a dedicated channel
aensidhe Sep 26, 2026
176f64c
fix: close the connection when the last handle drops
aensidhe Sep 26, 2026
48de993
fix: advance the generation as soon as a connection is lost
aensidhe Sep 26, 2026
48b8b67
feat: hand the transaction back when commit or rollback fails on the …
aensidhe Sep 26, 2026
9dad7ca
fix: keep the drop-time rollback from holding the connection alive
aensidhe Sep 26, 2026
72f8519
fix: guard the SQL statement cache by generation and evict rejected ids
aensidhe Sep 26, 2026
cdefcff
fix: bound connection setup, clamp the threshold and normalize the ba…
aensidhe Sep 26, 2026
01d2e91
fix: give DmoOperation::delete a count and return nothing from upsert
aensidhe Sep 27, 2026
aecadb1
refactor: use testcontainers' ImageExt helpers in the test container
aensidhe Sep 27, 2026
c3389b7
build: check the minimum dependency versions with a dated nightly
aensidhe Sep 27, 2026
a548ad0
fix: send no PREPARE through a finished transaction or a stale stream
aensidhe Sep 27, 2026
7e6a4d9
test: pin the handshake, writer and dispatcher edge cases
aensidhe Sep 27, 2026
1ed33a3
test: reach the outage in the outage tests and commit as an authentic…
aensidhe Sep 27, 2026
b406b5f
fix: spell transaction correctly in logs and docs
aensidhe Sep 27, 2026
9c9fb5d
docs: add changelog entries for the review fixes
aensidhe Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- run: cargo check --all-targets
- run: cargo fmt --all -- --check
- run: cargo clippy --all-targets -- -Dclippy::all -Wclippy::pedantic --deny warnings --no-deps
- run: cargo clippy --all-targets -- -Dclippy::all -Wclippy::pedantic --deny warnings

test:
name: Test Suite
Expand All @@ -40,6 +39,19 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@v2
with:
tool: cargo-audit,cargo-deny
tool: cargo-audit
- run: cargo audit
- run: cargo deny check advisories

minimal-versions:
name: Minimal versions
runs-on: ubuntu-latest
# A too-low floor can hang rustc instead of failing, and GitHub's default limit is 6 hours.
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
- uses: taiki-e/install-action@v2
with:
tool: just
- name: Install the nightly pinned in the justfile
run: just --evaluate nightly | xargs rustup toolchain install --profile minimal
- run: just minimal-versions
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@

# These are backup files generated by rustfmt
**/*.rs.bk

tmp
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- `Error::ConnectionReset`, returned when a request is made through a stream or transaction whose connection was lost since it was created. Its message reads "Connection was lost; stream or transaction state on the server is gone".
- `TransactionError`, returned by `commit` and `rollback` on failure; after a client-side failure it carries the transaction back unfinished, and the outcome of a COMMIT is then unknown.

### Changed
- Breaking: `Transaction::commit` and `Transaction::rollback`, and the `commit` and `rollback` wrappers on `Space<Transaction>` and `OwnedIndex<Transaction>`, return `Result<(), TransactionError>`. Success and server errors consume the transaction. A client-side failure, such as a timeout or a closed connection, returns the transaction inside `TransactionError`, unfinished; dropping that error, or converting it into `Error`, sends a ROLLBACK unless the connection was lost. After such a failure the outcome of a COMMIT is unknown: it may have been applied, may have failed on the server, or may never have been sent. A retried COMMIT and that ROLLBACK succeed in every one of these cases, because Tarantool treats COMMIT and ROLLBACK without an active transaction as successful no-ops, so neither proves what happened.
- Breaking: `DmoOperation::delete` takes a count and encodes the 3-element operation every Tarantool version requires.
- Breaking: `upsert` returns `Result<()>`; UPSERT never returns a tuple.
- Breaking: `Error::DuplicatedSync` carries a `u64`.
- Streams and transactions no longer silently continue on the new connection after a reconnect. A request made through one fails with `Error::ConnectionReset` as soon as the loss of its connection is observed, even mid-reconnect, instead of running against the wrong server session. A stream or transaction created during the outage works after the reconnect.
- IPROTO_ID is sent on every connect and reconnect, so every server session enables streams, transactions and the error extension. AUTH and ID share one round trip.
- A request whose caller stops waiting (timeout, dropped future, lost `select!` branch) leaves the in-flight map.
- The dispatcher closes the connection and exits as soon as the last client handle is dropped, whether connected or reconnecting.
- The SQL statement cache is invalidated after a reconnect; a cached prepared statement no longer fails permanently with Tarantool's `ER_WRONG_QUERY_ID`. A cached statement id that the server rejects with `ER_SQL_EXECUTE` (for example "statement has expired" after DDL) or `ER_WRONG_QUERY_ID` is evicted; the error is returned and the next call prepares the statement again.
- `reconnect_interval(None)` is documented as a hot reconnect loop with a real CPU cost.
- The exponential reconnect backoff no longer uses the `backoff` crate. Ordinary parameters behave as before; degenerate ones are normalized instead of panicking or retrying back to back.
- Requests queued together are written to the socket in one batch.
- A request whose frame would exceed Tarantool's 2 GiB request limit fails on the client with `EncodingError::MessagePack`, instead of being sent and making the server close the connection.
- The minimum versions are tokio 1.22, anyhow 1.0.47, async-trait 0.1.43, rmp 0.8.11, serde 1.0.136 and tracing 0.1.7.

### Removed
- Dead resend machinery, which never resent a request.

### Fixed
- `upsert` sends the `UPSERT` opcode instead of `REPLACE`.
- `DmoOperation::insert` is encoded with the `!` operator instead of `|`.
- A grab-bag of protocol and handshake hardening: frame length decoding, error description bounds, auth/ID response sync validation, and writer task join failures reported as connection errors.
- The client reserves at most 64 MiB up front for a response frame and grows the buffer as bytes arrive; any length Tarantool can send is accepted.
- Request syncs are 64-bit, as on the wire.
- `connect_timeout` bounds the whole connection setup (TCP connect, greeting, AUTH, ID) of the first connection and every reconnect attempt. When it is unset, the request timeout does, as before. Before, `connect_timeout` was ignored. Users who set both now get `connect_timeout` for setup.
- `internal_simultaneous_requests_threshold` outside its range is clamped instead of panicking in `build`.

## [picodata-0.12] - 2026-09-02

### Fixed
Expand Down
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ readme = "README.md"
repository = "https://github.com/Flowneee/tarantool-rs"

[dependencies]
anyhow = "1"
async-trait = "0.1"
anyhow = "1.0.47"
async-trait = "0.1.43"
base64 = "0.23"
bytes = "1"
fastrand = "2"
futures = "0.3"
lru = "0.18.2"
parking_lot = { version = "0.12", features = ["send_guard"] }
rmp = "0.8"
rmp = "0.8.11"
rmp-serde = "1"
rmpv = { version = "1", features = ["with-serde"] }
serde = { version = "1", features = ["derive"] }
serde = { version = "1.0.136", features = ["derive"] }
sha1 = "0.11"
thiserror = "2"
tokio = { version = "1", features = ["rt", "net", "io-util", "macros", "time"] }
tokio-stream = "0.1"
tokio = { version = "1.22", features = ["rt", "net", "io-util", "macros", "time", "sync"] }
tokio-util = { version = "0.7", default-features = false, features = ["codec"] }
tracing = { version = "0.1", features = ["log"] }
tracing = { version = "0.1.7", features = ["log"] }

[dev-dependencies]
assert_matches = "1.5"
Expand All @@ -39,7 +38,7 @@ pretty_env_logger = "0.5"
rustyline = "18"
serde_json = "1"
tokio = { version = "1", features = ["full"] }
tracing-test = { version = "0.2", features = ["no-env-filter"] }
tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
tarantool-test-container = { path = "tarantool-test-container" }

[[example]]
Expand Down
10 changes: 4 additions & 6 deletions examples/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ async fn main() -> Result<(), anyhow::Error> {
.select::<(i64, String), _>(None, None, Some(IteratorType::All), ())
.await?
);
info!(
"UPSERT: {:?}",
space
.upsert((0, "Name"), DmoOperation::string_splice("name", 2, 2, "!!"),)
.await?
);
space
.upsert((0, "Name"), DmoOperation::string_splice("name", 2, 2, "!!"))
.await?;
info!("UPSERT done");
info!(
"UPDATE: {:?}",
space
Expand Down
38 changes: 38 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# The one nightly the minimal-versions check uses; CI installs it from this line.
nightly := "nightly-2026-09-26"

# The library must build with the lowest versions of its normal dependencies
# that Cargo.toml allows. The check leaves the dev-dependencies out: the path
# dev-dependency tarantool-test-container pulls in testcontainers, whose
# bollard needs tokio 1.47. With them in the resolution, the check would pass
# only once the library's published floors rose to what such test-only crates
# need. Without the dev-dependencies no test can build, so the check builds
# the library and runs no tests. At the floors, rustc prints two
# mismatched_lifetime_syntaxes warnings, at `prepare_sql` and `space` in
# src/client/executor_ext.rs: async-trait releases before 0.1.82 print them,
# and 0.1.82 and later do not. They are expected; the check does not deny
# warnings. The recipe needs the pinned nightly: it never installs a toolchain
# and never skips the check.
[doc('Check that the library builds with its normal dependencies at their minimum versions')]
minimal-versions:
#!/usr/bin/env bash
set -euo pipefail
# Never let rustup install the toolchain on its own.
export RUSTUP_AUTO_INSTALL=0
if ! rustup run {{nightly}} cargo --version > /dev/null 2>&1; then
echo "error: toolchain {{nightly}} is not installed" >&2
echo "install it with: rustup toolchain install {{nightly}} --profile minimal" >&2
exit 1
fi
# Resolve in a copy of the library, so the committed Cargo.lock stays as
# it is. The copy's manifest drops the dev-dependency tables, and the
# example, bench and test targets, whose files are not copied.
work=target/minimal-versions
rm -rf "$work/src" "$work/Cargo.lock"
mkdir -p "$work"
cp -R README.md src "$work"/
awk '/^\[/ { skip = ($0 ~ /^\[dev-dependencies[].]|^\[\[(example|bench|test)\]\]/) } !skip' \
Cargo.toml > "$work/Cargo.toml"
cd "$work"
cargo +{{nightly}} generate-lockfile -Z direct-minimal-versions
cargo +{{nightly}} check --lib --all-features
1 change: 0 additions & 1 deletion rustfmt.toml

This file was deleted.

Loading
Loading