How can I make a non-blocking request for an exclusive lock using File#flock?
Use Timeout Module with Exclusive Locks
You can use the Timeout module to set a duration for #flock to acquire an exclusive lock. The following example will raise Timeout::Error: execution expired
, which can then be rescued in whatever way seems appropriate for the application. Returning nil when the timer expires allows the #flock expression to be tested for truth.
require 'timeout'
f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0
f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
Timeout::timeout(0.001) { f2.flock(File::LOCK_EX) } rescue nil
# => nil
Use Bitwise OR for Non-Blocking Lock Attempts
The documentation for File#flock says:
Locks or unlocks a file according to locking_constant (a logical or of the values in the table below). Returns false if File::LOCK_NB is specified and the operation would otherwise have blocked.
However, the method actually expects a Bitwise OR operator, rather than a Logical OR keyword as defined in parse.y by the tOROP parser token. As a result, the correct argument that allows #flock to return false
when an exclusive lock fails is actually File::LOCK_NB|File::LOCK_EX
. For example:
f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX|File::LOCK_NB)
# => 0
f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_NB|File::LOCK_EX)
# => false
f1.close; f2.close
# => nil
This will consistently generate an exclusive lock when available; otherwise, it immediately returns a falsy value without the overhead of raising or rescuing exceptions. This is obviously the way the module is intended to be used, but the documentation could use some clarification and additional examples to make it easier to understand.
flock() doesn't preventing other process to get exclusive lock
Try:
if ((fd1 = open( "file1", O_RDWR | O_CREAT | O_TRUNC)) == -1)
// ^ ^
As what you have written is the same as:
if (open( "file1", O_RDWR | O_CREAT | O_TRUNC) == -1)
fd1 = TRUE;
else
fd1 = FALSE;
Therefore you are attempting to lock stdin
or stdout
(depending on the result of open()
).
File locking and unlocking from input
I don't have a windows machine w/perl installed to check if that works there, but documentation on Term::ReadKey implies that it should. Term::ReadKey is a module that provides non-blocking and timed read functionality. It has some limited Windows support.
use Time::HiRes qw(time sleep);
use Term::ReadKey;
sub wait_for_key {
my $timeout = shift;
my $started = time();
while (1) {
last if $started + $timeout < time();
my $str = '';
while (my $char = ReadKey(-1)) {
$str .= $char;
};
last if $str =~ m/\n/s;
sleep 0.1;
}
}
I'm sure there are better ways to do that, though. Maybe someone with perl-on-windows experience will show up.
Curse on you, Windows. On any other system the code above would look like this:
sub wait_for_key { ReadLine(shift) }
Can Linux flock(fd, LOCK_EX|LOCK_NB) fail spuriously?
Reading man flock(2):
EWOULDBLOCK
The file is locked and the LOCK_NB flag was selected.
So getting EWOULDBLOCK means the file is already locked. If it is guaranteed that your two processes are the only ones involved, they will never get EWOULDBLOCK on the same file at the same time.
Please note that threads is a different story. Threads normally share file descriptors, so several threads within the same process can call flock() successfully on the same file.
Related Topics
How to Stop God from Leaving Stale Resque Worker Processes
Memory Usage Increase with Ruby 2.1 Versus Ruby 2.0 or 1.9
Remove Rails Model After Migration
How to Rescue Timeout Issues (Ruby, Rails)
Dynamically Create Index with Mongoid
Good Explanation of Ruby Object Model -- Mainly, 'Classes Are Objects'
Ruby: Merge Two Hash as One and with Value Connected
Capistrano 3 + Sprockets 3 + Rails 4.2.1 Won't Deploy
Setting Environment Variables with Puppet
How to Use Rails 5.1.0 and Jquery
How to Do Sti and Still Use Polymorphic Path Helpers
Running Webrick Server in Background
Using Acts_As_List with Has_Many :Through in Rails
How to Resolve Deprecation Warnings for Openssl::Cipher::Cipher#Encrypt