Class: Procrastinator::FileTransaction

Inherits:
Object
  • Object
show all
Defined in:
lib/procrastinator/task_store/file_transaction.rb

Overview

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

Class Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ FileTransaction

Returns a new instance of FileTransaction.



28
29
30
# File 'lib/procrastinator/task_store/file_transaction.rb', line 28

def initialize(path)
   @path = ensure_path(path)
end

Class Attribute Details

.file_mutexObject (readonly)

Returns the value of attribute file_mutex.



25
26
27
# File 'lib/procrastinator/task_store/file_transaction.rb', line 25

def file_mutex
  @file_mutex
end

Instance Method Details

#read(&block) ⇒ Object

Alias for transact(writable: false)



33
34
35
# File 'lib/procrastinator/task_store/file_transaction.rb', line 33

def read(&block)
   transact(writable: false, &block)
end

#transact(writable: false) ⇒ Object

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.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 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) ⇒ Object

Alias for transact(writable: true)



38
39
40
# File 'lib/procrastinator/task_store/file_transaction.rb', line 38

def write(&block)
   transact(writable: true, &block)
end