Class: Agent::Lock::Store::FileSystemStore
- Inherits:
-
Object
- Object
- Agent::Lock::Store::FileSystemStore
- Defined in:
- lib/agent/lock/store/file_system_store.rb
Overview
Locks as files, which is the default and the one that needs nothing
installed. See Tree#store_dir for why they live inside .git.
Constant Summary collapse
- SUFFIX =
".lock.md"- MUTEX =
".mutex"- MUTEX_TIMEOUT =
Seconds. A claim's critical section is a directory scan and one write, so anything near this is somebody stuck, not somebody busy.
15
Instance Attribute Summary collapse
-
#tree ⇒ Object
readonly
Returns the value of attribute tree.
Instance Method Summary collapse
-
#all ⇒ Array<Record>
FNM_DOTMATCH is load-bearing.
-
#create(record) ⇒ Boolean
Atomic: two agents racing for one scope cannot both win, because only one File::EXCL create succeeds.
- #delete(record) ⇒ void
-
#describe ⇒ String
How a listing names this store.
- #dir ⇒ String
- #find(scope) ⇒ Record?
-
#initialize(tree) ⇒ FileSystemStore
constructor
A new instance of FileSystemStore.
-
#mutex_path ⇒ String
Named so that
all's*.lock.mdcan never match it, dot or no dot. - #path_for(id) ⇒ String
-
#synchronize { ... } ⇒ Object
Runs the block with every other process in this store shut out, so a scan for conflicts and the write it justifies cannot be interleaved.
-
#update(record) ⇒ void
Replace a lock in place.
Constructor Details
#initialize(tree) ⇒ FileSystemStore
Returns a new instance of FileSystemStore.
23 24 25 |
# File 'lib/agent/lock/store/file_system_store.rb', line 23 def initialize(tree) @tree = tree end |
Instance Attribute Details
#tree ⇒ Object (readonly)
Returns the value of attribute tree.
21 22 23 |
# File 'lib/agent/lock/store/file_system_store.rb', line 21 def tree @tree end |
Instance Method Details
#all ⇒ Array<Record>
FNM_DOTMATCH is load-bearing. A scope like .plans/** slugs to a
filename that starts with a dot, and a plain glob skips it, so the
lock was written, listed nowhere, and blocked nobody. Redis matches
those keys either way, and a store that enumerates less than it holds
is worse than no store at all.
40 41 42 43 44 |
# File 'lib/agent/lock/store/file_system_store.rb', line 40 def all Dir.glob(File.join(dir, "*#{SUFFIX}"), File::FNM_DOTMATCH) .sort .filter_map { |path| Record.read(path) } end |
#create(record) ⇒ Boolean
Atomic: two agents racing for one scope cannot both win, because only one File::EXCL create succeeds.
83 84 85 86 87 88 89 90 91 |
# File 'lib/agent/lock/store/file_system_store.rb', line 83 def create(record) FileUtils.mkdir_p(dir) File.open(path_for(record.id), File::WRONLY | File::CREAT | File::EXCL) do |file| file.write(record.to_markdown) end true rescue Errno::EEXIST false end |
#delete(record) ⇒ void
This method returns an undefined value.
108 |
# File 'lib/agent/lock/store/file_system_store.rb', line 108 def delete(record) = FileUtils.rm_f(record.path || path_for(record.id)) |
#describe ⇒ String
Returns how a listing names this store.
31 |
# File 'lib/agent/lock/store/file_system_store.rb', line 31 def describe = dir |
#dir ⇒ String
28 |
# File 'lib/agent/lock/store/file_system_store.rb', line 28 def dir = tree.store_dir |
#find(scope) ⇒ Record?
76 |
# File 'lib/agent/lock/store/file_system_store.rb', line 76 def find(scope) = Record.read(path_for(Record.id_for(tree, scope))) |
#mutex_path ⇒ String
Named so that all's *.lock.md can never match it, dot or no dot.
117 |
# File 'lib/agent/lock/store/file_system_store.rb', line 117 def mutex_path = File.join(dir, MUTEX) |
#path_for(id) ⇒ String
112 |
# File 'lib/agent/lock/store/file_system_store.rb', line 112 def path_for(id) = File.join(dir, "#{id}#{SUFFIX}") |
#synchronize { ... } ⇒ Object
Runs the block with every other process in this store shut out, so a
scan for conflicts and the write it justifies cannot be interleaved.
O_EXCL alone settles a race for one scope, but lib/** and
lib/a1.rb are two files, and two agents that both scanned an empty
store before either wrote both won.
An exclusive flock on .mutex beside the locks. The kernel drops it
when the holder exits, crash included, so a dead agent cannot leave
the store wedged. A live one that stops mid-claim can, which is why
the wait is bounded and ends in an error rather than a hung agent.
Not re-entrant. flock belongs to an open file, not to a process, so a nested call opens a second one, waits on the first, and raises once the timeout runs out.
64 65 66 67 68 69 70 71 72 |
# File 'lib/agent/lock/store/file_system_store.rb', line 64 def synchronize FileUtils.mkdir_p(dir) # Closing the file releases the flock, and the block form closes it # on the way out whether the critical section returned or raised. File.open(mutex_path, File::RDWR | File::CREAT, 0o644) do |file| wait_for(file) yield end end |
#update(record) ⇒ void
This method returns an undefined value.
Replace a lock in place. Written beside itself and renamed, so a reader never sees half a document, and so a crash mid-write leaves the previous version rather than nothing.
99 100 101 102 103 104 |
# File 'lib/agent/lock/store/file_system_store.rb', line 99 def update(record) path = record.path || path_for(record.id) temp = "#{path}.#{Process.pid}.tmp" File.write(temp, record.to_markdown) File.rename(temp, path) end |