class Procrastinator::FileTransaction
The general idea is that there may be two threads that need to do these actions on the same file:
thread A: read thread B: read thread A/B: write thread A/B: write
When this sequence happens, the second file write is based on old information and loses the info from the prior write. Using a global mutex per file path prevents this case.
This situation can also occur with multi processing, so file locking is also used for solitary access. File locking is only advisory in some systems, though, so it may only work against other applications that request a lock.
@author Robin Miller
Attributes
Public Class Methods
# File lib/procrastinator/task_store/file_transaction.rb, line 28 def initialize(path) @path = ensure_path(path) end
Public Instance Methods
Alias for transact(writable: false)
# File lib/procrastinator/task_store/file_transaction.rb, line 33 def read(&block) transact(writable: false, &block) end
Completes the given block as an atomic transaction locked using a global mutex table. The block is provided the current file contents. The block’s result is written to the file.
# File lib/procrastinator/task_store/file_transaction.rb, line 45 def transact(writable: false) semaphore = FileTransaction.file_mutex[@path.to_s] ||= Mutex.new semaphore.synchronize do @path.open(writable ? 'r+' : 'r') do |file| file.flock(File::LOCK_EX) yield_result = yield(file.read) if writable file.rewind file.write yield_result file.truncate(file.pos) end yield_result end end end
Alias for transact(writable: true)
# File lib/procrastinator/task_store/file_transaction.rb, line 38 def write(&block) transact(writable: true, &block) end
Private Instance Methods
# File lib/procrastinator/task_store/file_transaction.rb, line 65 def ensure_path(path) path = Pathname.new path unless path.exist? path.dirname.mkpath FileUtils.touch path end path end