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

file_mutex[R]

Public Class Methods

new(path) click to toggle source
# File lib/procrastinator/task_store/file_transaction.rb, line 28
def initialize(path)
   @path = ensure_path(path)
end

Public Instance Methods

read(&block) click to toggle source

Alias for transact(writable: false)

# File lib/procrastinator/task_store/file_transaction.rb, line 33
def read(&block)
   transact(writable: false, &block)
end
transact(writable: false) { |read| ... } click to toggle source

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
write(&block) click to toggle source

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

ensure_path(path) click to toggle source
# 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