Class: Philiprehberger::LockKit::ReadWriteLock

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/lock_kit/read_write_lock.rb

Overview

Read-write lock supporting shared reads and exclusive writes

Read locks are shared — multiple readers can hold the lock concurrently. Write locks are exclusive — no readers or other writers are allowed. Uses a ‘.readers` counter file alongside the lock file to track state.

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ ReadWriteLock

Returns a new instance of ReadWriteLock.

Parameters:

  • path (String)

    base path for the lock files



12
13
14
15
16
17
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 12

def initialize(path)
  @path = path
  @write_lock_path = "#{path}.write"
  @readers_path = "#{path}.readers"
  @write_file = nil
end

Instance Method Details

#acquire_read(timeout: nil) ⇒ true

Acquire a shared read lock

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    seconds to wait before raising

Returns:

  • (true)

    when the lock is acquired

Raises:



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 24

def acquire_read(timeout: nil)
  deadline = timeout ? Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout : nil

  loop do
    # Check if a write lock is held
    unless write_locked?
      increment_readers
      # Double-check no writer snuck in
      unless write_locked?
        return true
      end

      decrement_readers
    end

    raise Error, "Could not acquire read lock on #{@path}" unless deadline

    remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
    if remaining <= 0
      raise Error, "Timeout acquiring read lock on #{@path} after #{timeout}s"
    end

    sleep [0.05, remaining].min
  end
end

#acquire_write(timeout: nil) ⇒ true

Acquire an exclusive write lock

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    seconds to wait before raising

Returns:

  • (true)

    when the lock is acquired

Raises:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 62

def acquire_write(timeout: nil)
  @write_file = File.open(@write_lock_path, File::CREAT | File::RDWR)
  deadline = timeout ? Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout : nil

  # First, acquire the write file lock
  loop do
    break if @write_file.flock(File::LOCK_EX | File::LOCK_NB)

    if deadline
      remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
      if remaining <= 0
        close_write_file
        raise Error, "Timeout acquiring write lock on #{@path} after #{timeout}s"
      end

      sleep [0.05, remaining].min
    else
      close_write_file
      raise Error, "Could not acquire write lock on #{@path}"
    end
  end

  # Then, wait for all readers to finish
  loop do
    break if reader_count.zero?

    if deadline
      remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
      if remaining <= 0
        @write_file.flock(File::LOCK_UN)
        close_write_file
        raise Error, "Timeout acquiring write lock on #{@path} after #{timeout}s — readers still active"
      end

      sleep [0.05, remaining].min
    else
      @write_file.flock(File::LOCK_UN)
      close_write_file
      raise Error, "Could not acquire write lock on #{@path} — readers still active"
    end
  end

  true
end

#reader_countInteger

Return the current number of active readers

Returns:

  • (Integer)


120
121
122
123
124
125
126
127
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 120

def reader_count
  return 0 unless File.exist?(@readers_path)

  count = File.read(@readers_path).strip.to_i
  count.negative? ? 0 : count
rescue Errno::ENOENT
  0
end

#release_readvoid

This method returns an undefined value.

Release the shared read lock



53
54
55
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 53

def release_read
  decrement_readers
end

#release_writevoid

This method returns an undefined value.

Release the exclusive write lock



110
111
112
113
114
115
# File 'lib/philiprehberger/lock_kit/read_write_lock.rb', line 110

def release_write
  return unless @write_file

  @write_file.flock(File::LOCK_UN)
  close_write_file
end