Class: ActiveRecord::ConnectionAdapters::TransactionManager

Inherits:
Object
  • Object
show all
Defined in:
activerecord/lib/active_record/connection_adapters/abstract/transaction.rb

Overview

:nodoc:

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ TransactionManager

Returns a new instance of TransactionManager.


291
292
293
294
295
296
297
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 291

def initialize(connection)
  @stack = []
  @connection = connection
  @has_unmaterialized_transactions = false
  @materializing_transactions = false
  @lazy_transactions_enabled = true
end

Instance Method Details

#begin_transaction(isolation: nil, joinable: true, _lazy: true) ⇒ Object


299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 299

def begin_transaction(isolation: nil, joinable: true, _lazy: true)
  @connection.lock.synchronize do
    run_commit_callbacks = !current_transaction.joinable?
    transaction =
      if @stack.empty?
        RealTransaction.new(
          @connection,
          isolation: isolation,
          joinable: joinable,
          run_commit_callbacks: run_commit_callbacks
        )
      elsif current_transaction.restartable?
        RestartParentTransaction.new(
          @connection,
          current_transaction,
          isolation: isolation,
          joinable: joinable,
          run_commit_callbacks: run_commit_callbacks
        )
      else
        SavepointTransaction.new(
          @connection,
          "active_record_#{@stack.size}",
          current_transaction,
          isolation: isolation,
          joinable: joinable,
          run_commit_callbacks: run_commit_callbacks
        )
      end

    unless transaction.materialized?
      if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
        @has_unmaterialized_transactions = true
      else
        transaction.materialize!
      end
    end
    @stack.push(transaction)
    transaction
  end
end

#commit_transactionObject


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 395

def commit_transaction
  @connection.lock.synchronize do
    transaction = @stack.last

    begin
      transaction.before_commit_records
    ensure
      @stack.pop
    end

    dirty_current_transaction if transaction.dirty?

    if current_transaction.open?
      current_transaction.written_indirectly ||= transaction.written || transaction.written_indirectly
    end

    transaction.commit
    transaction.commit_records
  end
end

#current_transactionObject


488
489
490
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 488

def current_transaction
  @stack.last || NULL_TRANSACTION
end

#dirty_current_transactionObject


354
355
356
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 354

def dirty_current_transaction
  current_transaction.dirty!
end

#disable_lazy_transactions!Object


341
342
343
344
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 341

def disable_lazy_transactions!
  materialize_transactions
  @lazy_transactions_enabled = false
end

#enable_lazy_transactions!Object


346
347
348
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 346

def enable_lazy_transactions!
  @lazy_transactions_enabled = true
end

#lazy_transactions_enabled?Boolean

Returns:

  • (Boolean)

350
351
352
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 350

def lazy_transactions_enabled?
  @lazy_transactions_enabled
end

#materialize_transactionsObject


372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 372

def materialize_transactions
  return if @materializing_transactions

  # As a logical simplification for now, we assume anything that requests
  # materialization is about to dirty the transaction. Note this is just
  # an assumption about the caller, not a direct property of this method.
  # It can go away later when callers are able to handle dirtiness for
  # themselves.
  dirty_current_transaction

  return unless @has_unmaterialized_transactions

  @connection.lock.synchronize do
    begin
      @materializing_transactions = true
      @stack.each { |t| t.materialize! unless t.materialized? }
    ensure
      @materializing_transactions = false
    end
    @has_unmaterialized_transactions = false
  end
end

#open_transactionsObject


484
485
486
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 484

def open_transactions
  @stack.size
end

#restorable?Boolean

Returns:

  • (Boolean)

368
369
370
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 368

def restorable?
  @stack.none?(&:dirty?)
end

#restore_transactionsObject


358
359
360
361
362
363
364
365
366
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 358

def restore_transactions
  return false unless restorable?

  @stack.each(&:restore!)

  materialize_transactions unless @lazy_transactions_enabled

  true
end

#rollback_transaction(transaction = nil) ⇒ Object


416
417
418
419
420
421
422
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 416

def rollback_transaction(transaction = nil)
  @connection.lock.synchronize do
    transaction ||= @stack.pop
    transaction.rollback
    transaction.rollback_records
  end
end

#within_new_transaction(isolation: nil, joinable: true) ⇒ Object


424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'activerecord/lib/active_record/connection_adapters/abstract/transaction.rb', line 424

def within_new_transaction(isolation: nil, joinable: true)
  @connection.lock.synchronize do
    transaction = begin_transaction(isolation: isolation, joinable: joinable)
    ret = yield
    completed = true
    ret
  rescue Exception => error
    if transaction
      if error.is_a?(ActiveRecord::TransactionRollbackError) &&
          @connection.savepoint_errors_invalidate_transactions?
        transaction.state.invalidate!
      end
      rollback_transaction
      after_failure_actions(transaction, error)
    end

    raise
  ensure
    if transaction
      if error
        # @connection still holds an open or invalid transaction, so we must not
        # put it back in the pool for reuse.
        @connection.throw_away! unless transaction.state.rolledback?
      else
        if Thread.current.status == "aborting"
          rollback_transaction
        elsif !completed && transaction.written
          # This was deprecated in 6.1, and has now changed to a rollback
          rollback_transaction
        elsif !completed && !transaction.written_indirectly
          # This was a silent commit in 6.1, but now becomes a rollback; we skipped
          # the warning because (having not been written) the change generally won't
          # have any effect
          rollback_transaction
        else
          if !completed && transaction.written_indirectly
            # This is the case that was missed in the 6.1 deprecation, so we have to
            # do it now
            ActiveSupport::Deprecation.warn(<<~EOW)
              Using `return`, `break` or `throw` to exit a transaction block is
              deprecated without replacement. If the `throw` came from
              `Timeout.timeout(duration)`, pass an exception class as a second
              argument so it doesn't use `throw` to abort its block. This results
              in the transaction being committed, but in the next release of Rails
              it will rollback.
            EOW
          end

          begin
            commit_transaction
          rescue Exception
            rollback_transaction(transaction) unless transaction.state.completed?
            raise
          end
        end
      end
    end
  end
end