Class: Farce::ReadWriteLock

Inherits:
Object
  • Object
show all
Includes:
Internal::Copyable, Shareable::Unfreezable, Internal::MarshalSupport::Reject
Defined in:
lib/farce/read_write_lock.rb

Overview

A Ractor-shareable read/write lock with fiber-scheduler-aware waiting.

Multiple execution contexts may hold the read lock concurrently. The write lock is exclusive and may be acquired while the current context holds a read lock. One concurrent upgrader retains its read lock continuously. Additional upgraders yield their read slot to avoid deadlock and restore it before returning to the outer read-lock block.

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods included from Internal::Copyable

#duplicable?

Constructor Details

#initialize ⇒ ReadWriteLock

Returns a new instance of ReadWriteLock.



32
33
34
35
36
# File 'lib/farce/read_write_lock.rb', line 32

def initialize
  @state  = Internal::Vector.new([0])
  @signal = Internal::Signal.new
  super
end

Instance Method Details

#with_read_lock { ... } ⇒ Object

Runs the block while holding a shared read lock.

Yields:

  • the block to run while holding the read lock

Returns:

  • (Object) —

    the block result

Raises:

  • (LocalJumpError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/farce/read_write_lock.rb', line 42

def with_read_lock
  raise LocalJumpError, "no block given" unless block_given?

  local = local_state
  Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
    global = local[READ_DEPTH].zero? && local[WRITE_DEPTH].zero?
    acquire_read_lock if global
    local[READ_DEPTH] += 1

    begin
      # Block capture allocates on every lock operation.
      # rubocop:disable-next Style/ExplicitBlockArgument
      Thread.handle_interrupt(INTERRUPTS_IMMEDIATE) { yield }
    ensure
      local[READ_DEPTH] -= 1
      release_read_lock if global
      clear_local_state(local)
    end
  end
end

#with_write_lock { ... } ⇒ Object

Runs the block while holding an exclusive write lock.

Calling this method from inside #with_read_lock upgrades the read lock.

Yields:

  • the block to run while holding the write lock

Returns:

  • (Object) —

    the block result

Raises:

  • (LocalJumpError)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/farce/read_write_lock.rb', line 69

def with_write_lock
  raise LocalJumpError, "no block given" unless block_given?

  local = local_state
  Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
    reentrant = local[WRITE_DEPTH].positive?
    upgraded  = !reentrant && local[READ_DEPTH].positive?
    acquire_write_lock(upgraded) unless reentrant
    local[WRITE_DEPTH] += 1

    begin
      # Block capture allocates on every lock operation.
      # rubocop:disable-next Style/ExplicitBlockArgument
      Thread.handle_interrupt(INTERRUPTS_IMMEDIATE) { yield }
    ensure
      local[WRITE_DEPTH] -= 1
      unless reentrant
        upgraded ? downgrade_write_lock : release_write_lock
      end
      clear_local_state(local)
    end
  end
end