Module: MalawiHivProgramReports::Utils::ConcurrencyUtils

Included in:
Clinic::PatientsAliveAndOnTreatment, Clinic::RegimensByWeightAndGender, Moh::ArtCohort
Defined in:
app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb

Constant Summary collapse

LOCK_FILES_DIR_PATH =
Rails.root.join('tmp', 'locks')

Instance Method Summary collapse

Instance Method Details

#with_lock(lock_file_path, blocking: true) ⇒ Object

Acquire a lock and run the given block of code.

The locking mechanism uses files to allow for blocking across processes. This is useful for example in situations where you only want to run one instance of a report.

Parameters:

lock_file_path: A relative path to the lock file (allows for namespacing your locks, eg art_service/regimens.lock)
blocking: If lock can not be acquired wait else throw an error (defaults to true)

Raises:

FailedToAcquireLock: When lock couldn't be acquired and blocking is set to false

Usage:

class Someclass
  include ModelUtils

  def do_something
    with_lock('mylockfile.lock') do
      # Run some task requiring exclusive access to some resource
    end
  end
end

SomeClass.new.do_something


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb', line 34

def with_lock(lock_file_path, blocking: true)
  path = LOCK_FILES_DIR_PATH.join(lock_file_path)

  unless Dir.exist?(path.dirname)
    Rails.logger.debug("Creating lock file directory: #{path.dirname}")
    ::FileUtils.mkdir_p(path.dirname)
  end

  File.open(path, 'w') do |lock_file|
    Rails.logger.debug("Attempting to acquire lock: #{lock_file_path}")
    locked = lock_file.flock(blocking ? File::LOCK_EX : File::LOCK_NB | File::LOCK_EX)
    raise ::FailedToAcquireLock, "Lock #{lock_file_path} is locked by another process" if !locked && !blocking

    lock_file.write("Locked by process ##{Process.pid} at #{Time.now}")
    yield
  end
end