We will achieve this by creating a block accepting method to optionally create and then lock a .lock
File of the underlying accessed file.
Why create a .lock
file?
- The main advantage of creating a
.lock
file is that#flock
might block some operations and require the index node of the file to be consistent. Some operations might change that index node. - In some cases it might also be convenient to just read/write the lock file first and update the other file afterwards or vice versa, such that breaking of a process does not affect the other version.
That being said the shown process also works without creating a lock file by calling File.open(@file_path) { ... }
on the file directly instead. I will also include an example on how to use this for directories instead for files at the end.
The method
You can define the following method to lock a file from read and write access by methods like File.open
and File.read
.
def exclusively_locked_access
begin
@file_locked = true
File.open(F"#{file_path}.lock"), 'w+') do |f|
f.flock(File::LOCK_EX)
yield
end
ensure
@file_locked = false
end
end
-
#flock
requests a lock and waits until it can lock the file.- Note that exiting the block from open will also unlock the file.
- The passed
LOCK_EX
forflock
will create an exclusive lock for that file. You can look up the doc for #flock Show archive.org snapshot to find all available locking options.
Now we can pass any block to the method to safely every file access you want to have locked like so
exclusively_locked_access do
file_content = File.read(@file_path)
end
Variations
When saving the file in a variable
- Locking the file in the block passed to
File.open
will take care of closing and unlocking the file. - If you don't use that block make sure to do that manually and preferably wrap it it with
begin
andrescue
if something might go wrong:
begin
f = File.open(file, File::CREAT)
f.flock(File::LOCK_EX)
yield
ensure
f.flock(File::LOCK_UN)
f.close
end
Waiting for a timeout
If you might run into deadlocks it can be safer to ensure a timeout with using Timeout::timeout(0.1) { f.flock(File::LOCK_EX) }
Locking directories
- If one process might change several files within one folder you can indeed make the method from above write in a way that it accepts the path.
- But when files are read and written at the same time and their content is changed in dependent on each other, it might be a better idea to flock the complete folder instead.
This can also achieved similarly:
def exclusively_folder_locked_access
directory = File.open(Paths::GEM_STORAGE)
directory.flock(File::LOCK_EX)
yield
directory.flock(File::LOCK_UN)
end
Since you can't write a directory you also don't have to call #close
on it.