Class: Futex

Inherits:
Object
  • Object
show all
Defined in:
lib/futex.rb

Overview

Futex (file mutex) is a fine-grained mutex that uses a file, not an entire thread, like Mutex does. Use it like this:

require 'futex'
Futex.new('/tmp/my-file.txt').open |f|
  IO.write(f, 'Hello, world!')
end

The file /tmp/my-file.txt.lock<tt> will be created and used as an entrance lock. If the file is already locked by another thread or another process, exception <tt>Futex::CantLock will be raised.

If you are not planning to write to the file, to speed things up, you may want to get a non-exclusive access to it, by providing false to the method open():

require 'futex'
Futex.new('/tmp/my-file.txt').open(false) |f|
  IO.read(f)
end

For more information read README file.

Author

Yegor Bugayenko ([email protected])

Copyright

Copyright © 2018 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: CantLock

Constant Summary collapse

COUNTS =

Global file for locks counting

File.join(Dir.tmpdir, 'futex.lock').freeze

Instance Method Summary collapse

Constructor Details

#initialize(path, log: STDOUT, timeout: 16, sleep: 0.005, lock: path + '.lock', logging: false) ⇒ Futex

Creates a new instance of the class.



74
75
76
77
78
79
80
81
82
# File 'lib/futex.rb', line 74

def initialize(path, log: STDOUT, timeout: 16, sleep: 0.005,
  lock: path + '.lock', logging: false)
  @path = path
  @log = log
  @logging = logging
  @timeout = timeout
  @sleep = sleep
  @lock = lock
end

Instance Method Details

#open(exclusive = true) ⇒ Object

Open the file. By default the file will be locked for exclusive access, which means that absolutely no other process will be able to do the same. This type of access (exclusive) is supposed to be used when you are making changes to the file. However, very often you may need just to read it and it’s OK to let many processes do the reading at the same time, provided none of them do the writing. In that case you should call this method open() with false first argument, which will mean “shared” access. Many threads and processes may have shared access to the same lock file, but they all will stop and wait if one of them will require an “exclusive” access. This mechanism is inherited from POSIX, read about it <a href=“man7.org/linux/man-pages/man2/flock.2.html”>here</a>.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/futex.rb', line 95

def open(exclusive = true)
  FileUtils.mkdir_p(File.dirname(@lock))
  step = (1 / @sleep).to_i
  start = Time.now
  prefix = exclusive ? '' : 'non-'
  b = badge(exclusive)
  Thread.current.thread_variable_set(:futex_lock, @lock)
  Thread.current.thread_variable_set(:futex_badge, b)
  open_synchronized(@lock) do |f|
    cycle = 0
    loop do
      if f.flock((exclusive ? File::LOCK_EX : File::LOCK_SH) | File::LOCK_NB)
        Thread.current.thread_variable_set(:futex_cycle, nil)
        Thread.current.thread_variable_set(:futex_time, nil)
        break
      end
      sleep(@sleep)
      cycle += 1
      Thread.current.thread_variable_set(:futex_cycle, cycle)
      Thread.current.thread_variable_set(:futex_time, Time.now - start)
      if Time.now - start > @timeout
        raise CantLock.new("#{b} can't get #{prefix}exclusive access \
to the file #{@path} because of the lock at #{@lock}, after #{age(start)} \
of waiting: #{IO.read(@lock)} (modified #{age(File.mtime(@lock))} ago)", start)
      end
      next unless (cycle % step).zero? && Time.now - start > @timeout / 2
      debug("#{b} still waiting for #{prefix}exclusive \
access to #{@path}, #{age(start)} already: #{IO.read(@lock)} \
(modified #{age(File.mtime(@lock))} ago)")
    end
    debug("Locked by #{b} in #{age(start)}, #{prefix}exclusive: \
#{@path} (attempt no.#{cycle})")
    File.write(@lock, b)
    acq = Time.now
    res = block_given? ? yield(@path) : nil
    debug("Unlocked by #{b} in #{age(acq)}, #{prefix}exclusive: #{@path}")
    res
  end
ensure
  Thread.current.thread_variable_set(:futex_cycle, nil)
  Thread.current.thread_variable_set(:futex_time, nil)
  Thread.current.thread_variable_set(:futex_lock, nil)
  Thread.current.thread_variable_set(:futex_badge, nil)
end