Class: Futex
- Inherits:
-
Object
- Object
- Futex
- 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
-
#initialize(path, log: STDOUT, timeout: 16, sleep: 0.005, lock: path + '.lock', logging: false) ⇒ Futex
constructor
Creates a new instance of the class.
-
#open(exclusive = true) ⇒ Object
Open the file.
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 |