def with_transaction(options=nil)
      # Non-configurable 120 second timeout for the entire operation
      deadline = Time.now + 120
      transaction_in_progress = false
      loop do
        commit_options = {}
        if options
          commit_options[:write_concern] = options[:write_concern]
        end
        start_transaction(options)
        transaction_in_progress = true
        begin
          rv = yield self
        rescue Exception => e
          if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
            abort_transaction
            transaction_in_progress = false
          end

          if Time.now >= deadline
            transaction_in_progress = false
            raise
          end

          if e.is_a?(Mongo::Error) && e.label?(Mongo::Error::TRANSIENT_TRANSACTION_ERROR_LABEL)
            next
          end

          raise
        else
          if within_states?(TRANSACTION_ABORTED_STATE, NO_TRANSACTION_STATE, TRANSACTION_COMMITTED_STATE)
            transaction_in_progress = false
            return rv
          end

          begin
            commit_transaction(commit_options)
            transaction_in_progress = false
            return rv
          rescue Mongo::Error => e
            if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
              # WriteConcernFailed
              if e.is_a?(Mongo::Error::OperationFailure) && e.code == 64 && e.wtimeout?
                transaction_in_progress = false
                raise
              end
              if Time.now >= deadline
                transaction_in_progress = false
                raise
              end
              wc_options = case v = commit_options[:write_concern]
                when WriteConcern::Base
                  v.options
                when nil
                  {}
                else
                  v
                end
              commit_options[:write_concern] = wc_options.merge(w: :majority)
              retry
            elsif e.label?(Mongo::Error::TRANSIENT_TRANSACTION_ERROR_LABEL)
              if Time.now >= deadline
                transaction_in_progress = false
                raise
              end
              next
            else
              transaction_in_progress = false
              raise
            end
          end
        end
      end
    ensure
      if transaction_in_progress
        log_warn('with_transaction callback altered with_transaction loop, aborting transaction')
        begin
          abort_transaction
        rescue Error::OperationFailure, Error::InvalidTransactionOperation
        end
      end
    end
def with_transaction(options=nil)
      # Non-configurable 120 second timeout for the entire operation
      deadline = Time.now + 120
      transaction_in_progress = false
      loop do
        commit_options = {}
        if options
          commit_options[:write_concern] = options[:write_concern]
        end
        start_transaction(options)
        transaction_in_progress = true
        begin
          rv = yield self
        rescue Exception => e
          if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
            abort_transaction
            transaction_in_progress = false
          end

          if Time.now >= deadline
            transaction_in_progress = false
            raise
          end

          if e.is_a?(Mongo::Error) && e.label?(Mongo::Error::TRANSIENT_TRANSACTION_ERROR_LABEL)
            next
          end

          raise
        else
          if within_states?(TRANSACTION_ABORTED_STATE, NO_TRANSACTION_STATE, TRANSACTION_COMMITTED_STATE)
            transaction_in_progress = false
            return rv
          end

          begin
            commit_transaction(commit_options)
            transaction_in_progress = false
            return rv
          rescue Mongo::Error => e
            if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
              # WriteConcernFailed
              if e.is_a?(Mongo::Error::OperationFailure) && e.code == 64 && e.wtimeout?
                transaction_in_progress = false
                raise
              end
              if Time.now >= deadline
                transaction_in_progress = false
                raise
              end
              wc_options = case v = commit_options[:write_concern]
                when WriteConcern::Base
                  v.options
                when nil
                  {}
                else
                  v
                end
              commit_options[:write_concern] = wc_options.merge(w: :majority)
              retry
            elsif e.label?(Mongo::Error::TRANSIENT_TRANSACTION_ERROR_LABEL)
              if Time.now >= deadline
                transaction_in_progress = false
                raise
              end
              next
            else
              transaction_in_progress = false
              raise
            end
          end
        end
      end
    ensure
      if transaction_in_progress
        log_warn('with_transaction callback altered with_transaction loop, aborting transaction')
        begin
          abort_transaction
        rescue Error::OperationFailure, Error::InvalidTransactionOperation
        end
      end
    end
Executes the provided block in a transaction, retrying as necessary.

 Returns the return value of the block.

 Exact number of retries and when they are performed are implementation
 details of the driver; the provided block should be idempotent, and
 should be prepared to be called more than once. The driver may retry
 the commit command within an active transaction or it may repeat the
 transaction and invoke the block again, depending on the error
 encountered if any. Note also that the retries may be executed against
 different servers.

 Transactions cannot be nested - InvalidTransactionOperation will be raised
 if this method is called when the session already has an active transaction.

 Exceptions raised by the block which are not derived from Mongo::Error
 stop processing, abort the transaction and are propagated out of
 with_transaction. Exceptions derived from Mongo::Error may be
 handled by with_transaction, resulting in retries of the process.

 Currently, with_transaction will retry commits and block invocations
 until at least 120 seconds have passed since with_transaction started
 executing. This timeout is not configurable and may change in a future
 driver version.

 @note with_transaction contains a loop, therefore the if with_transaction
   itself is placed in a loop, its block should not call next or break to
   control the outer loop because this will instead affect the loop in
   with_transaction. The driver will warn and abort the transaction
   if it detects this situation.

 @example Execute a statement in a transaction
   session.with_transaction(write_concern: {w: :majority}) do
     collection.update_one({ id: 3 }, { '$set' => { status: 'Inactive'} },
                           session: session)

   end

 @example Execute a statement in a transaction, limiting total time consumed
   Timeout.timeout(5) do
     session.with_transaction(write_concern: {w: :majority}) do
       collection.update_one({ id: 3 }, { '$set' => { status: 'Inactive'} },
                             session: session)

     end
   end

 @param [ Hash ] options The options for the transaction being started.
   These are the same options that start_transaction accepts.

 @raise [ Error::InvalidTransactionOperation ] If a transaction is already in
   progress or if the write concern is unacknowledged.

 @since 2.7.0