Skip to content

Commit f29174b

Browse files
RUBY-3770 Implement makeTimeoutError semantics in withTransaction
Per DRIVERS-3391 / transactions-convenient-api spec, withTransaction must propagate a TimeoutError (wrapping the last transient error as .cause) when the CSOT deadline is exhausted, instead of re-raising the raw error. Changes to lib/mongo/session.rb: - Callback raises TransientTransactionError + deadline expired: raise TimeoutError in CSOT mode, re-raise original in non-CSOT mode. - Commit raises UnknownTransactionCommitResult + deadline expired: same. - Commit raises TransientTransactionError + deadline expired: same. - Backoff (regular and overload) would exceed deadline: use new make_timeout_error_from helper — CSOT wraps last_error with cause, non-CSOT raises last_error directly (fixes incorrect TimeoutError that was raised in non-CSOT mode before this change). - Commit overload backoff would exceed deadline: raise TimeoutError in CSOT mode, re-raise in non-CSOT mode. - Add make_timeout_error_from private helper implementing the makeTimeoutError(lastError) pseudocode from the spec. Uses an inner begin/rescue to wire .cause when called outside a rescue block. New spec/mongo/session/with_transaction_timeout_spec.rb covers all eight code paths as unit tests with stubbed time control (no real server needed).
1 parent 680e19e commit f29174b

2 files changed

Lines changed: 410 additions & 6 deletions

File tree

lib/mongo/session.rb

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ def with_transaction(options = nil)
475475
if overload_encountered
476476
delay = @client.retry_policy.backoff_delay(overload_error_count)
477477
if backoff_would_exceed_deadline?(deadline, delay)
478-
raise Mongo::Error::TimeoutError, 'CSOT timeout expired waiting to retry withTransaction'
478+
make_timeout_error_from(last_error)
479479
end
480480
unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
481481
raise(last_error)
@@ -484,7 +484,7 @@ def with_transaction(options = nil)
484484
else
485485
backoff = backoff_seconds_for_retry(transaction_attempt)
486486
if backoff_would_exceed_deadline?(deadline, backoff)
487-
raise Mongo::Error::TimeoutError, 'CSOT timeout expired waiting to retry withTransaction'
487+
make_timeout_error_from(last_error)
488488
end
489489
sleep(backoff)
490490
end
@@ -513,7 +513,11 @@ def with_transaction(options = nil)
513513

514514
if deadline_expired?(deadline)
515515
transaction_in_progress = false
516-
raise
516+
if @with_transaction_timeout_ms
517+
raise Mongo::Error::TimeoutError, 'CSOT timeout expired during withTransaction callback'
518+
else
519+
raise
520+
end
517521
end
518522

519523
if e.is_a?(Mongo::Error) && e.label?('TransientTransactionError')
@@ -554,7 +558,11 @@ def with_transaction(options = nil)
554558
e.is_a?(Error::OperationFailure::Family) && e.max_time_ms_expired?
555559
then
556560
transaction_in_progress = false
557-
raise
561+
if @with_transaction_timeout_ms && deadline_expired?(deadline)
562+
raise Mongo::Error::TimeoutError, 'CSOT timeout expired during withTransaction commit'
563+
else
564+
raise
565+
end
558566
end
559567

560568
if e.label?('SystemOverloadedError')
@@ -569,7 +577,11 @@ def with_transaction(options = nil)
569577
delay = @client.retry_policy.backoff_delay(overload_error_count)
570578
if backoff_would_exceed_deadline?(deadline, delay)
571579
transaction_in_progress = false
572-
raise
580+
if @with_transaction_timeout_ms
581+
raise Mongo::Error::TimeoutError, 'CSOT timeout expired during withTransaction commit'
582+
else
583+
raise
584+
end
573585
end
574586
unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
575587
transaction_in_progress = false
@@ -591,7 +603,11 @@ def with_transaction(options = nil)
591603
elsif e.label?('TransientTransactionError')
592604
if Utils.monotonic_time >= deadline
593605
transaction_in_progress = false
594-
raise
606+
if @with_transaction_timeout_ms
607+
raise Mongo::Error::TimeoutError, 'CSOT timeout expired during withTransaction commit'
608+
else
609+
raise
610+
end
595611
end
596612
last_error = e
597613
if e.label?('SystemOverloadedError')
@@ -1436,5 +1452,25 @@ def backoff_would_exceed_deadline?(deadline, backoff_seconds)
14361452

14371453
Utils.monotonic_time + backoff_seconds >= deadline
14381454
end
1455+
1456+
# Implements makeTimeoutError(lastError) from the transactions-convenient-api spec.
1457+
# Called when the withTransaction retry loop cannot continue because backoff would
1458+
# exceed the deadline.
1459+
#
1460+
# - CSOT mode: raises TimeoutError with lastError as Ruby's .cause
1461+
# - non-CSOT mode: re-raises lastError directly
1462+
#
1463+
# Must be called from outside a rescue block; raises unconditionally.
1464+
def make_timeout_error_from(last_error)
1465+
if @with_transaction_timeout_ms
1466+
begin
1467+
raise last_error
1468+
rescue
1469+
raise Mongo::Error::TimeoutError, 'CSOT timeout expired waiting to retry withTransaction'
1470+
end
1471+
else
1472+
raise last_error
1473+
end
1474+
end
14391475
end
14401476
end

0 commit comments

Comments
 (0)