Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* Fixed a race in `coordination.Session` where `lastGoodResponseTime` could be zero when the keep-alive loop started, causing the keep-alive timer to fire immediately and triggering a spurious reconnect that would fail non-idempotent operations such as `CreateSemaphore`

## v3.135.10
* Fixed the SDK's `database/sql` driver to consistently map session-invalidating YDB errors to `driver.ErrBadConn` where possible, so `database/sql` can detect and discard bad connections

Expand Down
6 changes: 5 additions & 1 deletion internal/coordination/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,12 @@ func (s *session) receiveLoop( //nolint:funlen

return
case *Ydb_Coordination.SessionResponse_SessionStarted_:
sessionStarted <- message.GetSessionStarted()
// Update the keep-alive timestamp BEFORE signaling mainLoop. If the update happened after
// the channel send, mainLoop could race into the keep-alive inner loop and see a zero
// lastGoodResponseTime, causing the keep-alive timer to fire immediately and triggering a
// spurious reconnect that would fail any non-idempotent operations in the queue.
s.updateLastGoodResponseTime()
sessionStarted <- message.GetSessionStarted()
case *Ydb_Coordination.SessionResponse_SessionStopped_:
sessionStopped <- message.GetSessionStopped()
s.cancel()
Expand Down
6 changes: 4 additions & 2 deletions tests/integration/coordination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestCoordinationSemaphore(sourceTest *testing.T) {
err = db.Coordination().CreateNode(ctx, nodePath, coordination.NodeConfig{
Path: "",
SelfCheckPeriodMillis: 1000,
SessionGracePeriodMillis: 1000,
SessionGracePeriodMillis: 10000, //nolint:mnd
ReadConsistencyMode: coordination.ConsistencyModeStrict,
AttachConsistencyMode: coordination.ConsistencyModeStrict,
RatelimiterCountersMode: coordination.RatelimiterCountersModeDetailed,
Expand All @@ -64,7 +64,9 @@ func TestCoordinationSemaphore(sourceTest *testing.T) {
}
fmt.Printf("node description: %+v\nnode config: %+v\n", e, c)

s, err := db.Coordination().Session(ctx, nodePath)
// Use a zero reconnect delay so the client re-establishes the gRPC stream immediately after
// Reconnect(), ensuring the session is restored well within the server's SessionGracePeriodMillis.
s, err := db.Coordination().Session(ctx, nodePath, options.WithSessionReconnectDelay(0))
if err != nil {
t.Fatalf("failed to create session: %v\n", err)
}
Expand Down
Loading