Class: Woods::Coordination::PipelineLock

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/coordination/pipeline_lock.rb

Overview

File-based lock for preventing concurrent pipeline operations.

Creates a lock file with PID and timestamp. Supports stale lock detection for crashed processes.

Examples:

lock = PipelineLock.new(lock_dir: '/tmp', name: 'extraction')
lock.with_lock do
  # extraction runs here
end

Constant Summary collapse

DEFAULT_STALE_TIMEOUT =

1 hour

3600

Instance Method Summary collapse

Constructor Details

#initialize(lock_dir:, name:, stale_timeout: DEFAULT_STALE_TIMEOUT) ⇒ PipelineLock

Returns a new instance of PipelineLock.

Parameters:

  • lock_dir (String)

    Directory for lock files

  • name (String)

    Lock name (used as filename prefix)

  • stale_timeout (Integer) (defaults to: DEFAULT_STALE_TIMEOUT)

    Seconds after which a lock is considered stale



27
28
29
30
31
32
33
# File 'lib/woods/coordination/pipeline_lock.rb', line 27

def initialize(lock_dir:, name:, stale_timeout: DEFAULT_STALE_TIMEOUT)
  @lock_dir = lock_dir
  @name = name
  @stale_timeout = stale_timeout
  @lock_path = File.join(lock_dir, "#{name}.lock")
  @held = false
end

Instance Method Details

#acquireBoolean

Attempt to acquire the lock.

Returns:

  • (Boolean)

    true if lock acquired, false if already held



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/woods/coordination/pipeline_lock.rb', line 38

def acquire
  FileUtils.mkdir_p(@lock_dir)

  # Check for stale lock first (separate from atomic creation)
  if File.exist?(@lock_path)
    return false unless stale?

    # Remove stale lock
    FileUtils.rm_f(@lock_path)
  end

  # Atomic lock creation: File::EXCL ensures this fails if file already exists
  File.open(@lock_path, File::WRONLY | File::CREAT | File::EXCL) do |f|
    f.write(lock_content)
  end
  @held = true
  true
rescue Errno::EEXIST
  false
end

#locked?Boolean

Whether the lock is currently held by this instance.

Returns:

  • (Boolean)


85
86
87
# File 'lib/woods/coordination/pipeline_lock.rb', line 85

def locked?
  @held && File.exist?(@lock_path)
end

#releasevoid

This method returns an undefined value.

Release the lock.



62
63
64
65
# File 'lib/woods/coordination/pipeline_lock.rb', line 62

def release
  FileUtils.rm_f(@lock_path) if @held
  @held = false
end

#with_lock { ... } ⇒ Object

Execute a block while holding the lock.

Yields:

  • Block to execute

Returns:

  • (Object)

    Return value of the block

Raises:



72
73
74
75
76
77
78
79
80
# File 'lib/woods/coordination/pipeline_lock.rb', line 72

def with_lock(&block)
  raise LockError, "Cannot acquire lock '#{@name}' — another process is running" unless acquire

  begin
    block.call
  ensure
    release
  end
end