-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split up what this library was originally trying to achieve into simp…
…ler parts. Added some rspec tests for a few concurrency scenarios.
- Loading branch information
1 parent
373ae02
commit f486e28
Showing
8 changed files
with
157 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tmp/spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,6 @@ | ||
A simple class to determine if another process has control over a lock-file on a unix filesystem. | ||
A set of simple multiprocess concurrency examples originally designed to allow many worker processes to self organise and identify a leader process. | ||
|
||
Example: | ||
If you are looking for the process_lock gem, go here: https://github.com/ianheggie/ruby-process-lock/ | ||
|
||
IRB 1>> | ||
p = ProcessLock.new('example.tmp') | ||
p.owner? | ||
=> false | ||
p.aquire! | ||
=> true | ||
p.owner? | ||
=> true | ||
|
||
IRB 2>> | ||
q = ProcessLock.new('example.tmp') | ||
p.owner? | ||
=> false | ||
p.aquire! | ||
=> false | ||
p.owner? | ||
=> false | ||
p.alive? | ||
=> true | ||
|
||
example.tmp will contain the pid of the running process | ||
|
||
|
||
|
||
Copyright (c) 2008 Simon Engledew, released under the MIT license | ||
Copyright (c) 2008-2014 Simon Engledew, released under the MIT license | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
require 'rspec/core/rake_task' | ||
|
||
RSpec::Core::RakeTask.new('spec') | ||
|
||
task :default => :spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
class File | ||
# open a file with exclusive write permissions, blocking between processes | ||
# until the lock is released | ||
def self.open_exclusive(path, mode='r+') | ||
File.open(path, mode) do |handle| | ||
begin | ||
handle.flock(File::LOCK_EX) | ||
return yield(handle) | ||
ensure | ||
handle.flock(File::LOCK_UN) | ||
end | ||
end | ||
end | ||
end | ||
|
||
module Process | ||
# check to see if a process is alive using a null signal | ||
def self.alive?(pid) | ||
return pid > 0 && Process.kill(0, pid) > 0 | ||
rescue Errno::ESRCH | ||
return false | ||
end | ||
end | ||
|
||
class PidFile | ||
def initialize(filename) | ||
FileUtils.touch(@filename = filename) | ||
end | ||
|
||
# attempt to atomically write your PID into a file when many processes are starting | ||
# simultaneously | ||
def acquire! | ||
File.open_exclusive(@filename) do |handle| | ||
pid = handle.read.to_i | ||
return true if pid == Process.pid | ||
if not Process.alive?(pid) | ||
handle.truncate(0) | ||
handle.write(Process.pid) | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
# truncate the pidfile and allow another processes to write theirs | ||
def release! | ||
File.open_exclusive(@filename) do |handle| | ||
pid = handle.read.to_i | ||
if pid == 0 || pid == Process.pid | ||
handle.truncate(0) | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
require File.dirname(__FILE__) + '/spec_helper' | ||
|
||
describe File do | ||
it 'can acquire a multiprocess critical region' do | ||
expect { |b| File.open_exclusive(@path, &b) }.to yield_control | ||
end | ||
|
||
it 'cannot acquire from a forked process' do | ||
pid = nil | ||
|
||
File.open_exclusive(@path) do | ||
expect { | ||
pid = Process.fork do | ||
expect { |b| File.open_exclusive(@path, &b) }.to yield_control | ||
end | ||
Process.wait(pid) | ||
}.to time_out(0.5) | ||
end | ||
|
||
expect(pid).not_to be_nil | ||
Process.wait(pid) | ||
end | ||
end | ||
|
||
describe Process do | ||
it 'can successfully determine if another process is alive or dead' do | ||
pid = nil | ||
|
||
File.open_exclusive(@path) do | ||
expect { | ||
pid = Process.fork do | ||
expect { |b| File.open_exclusive(@path, &b) }.to yield_control | ||
end | ||
expect(Process.alive?(pid)).to be_true | ||
Process.wait(pid) | ||
}.to time_out(0.5) | ||
end | ||
|
||
expect(pid).not_to be_nil | ||
Process.wait(pid) | ||
|
||
expect(Process.alive?(pid)).to be_false | ||
end | ||
end | ||
|
||
describe PidFile do | ||
it 'can successfully acquire a pidfile' do | ||
pid = PidFile.new(@path) | ||
expect(pid.acquire!).to be_true | ||
expect(pid.release!).to be_true | ||
end | ||
|
||
it 'cannot acquire a pidfile if it is being used by another process' do | ||
pid = PidFile.new(@path) | ||
expect(pid.acquire!).to be_true | ||
|
||
Process.wait(Process.fork do | ||
fork_pid = PidFile.new(@path) | ||
expect(fork_pid.acquire!).to be_false | ||
expect(fork_pid.release!).to be_false | ||
end) | ||
|
||
expect(pid.release!).to be_true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
require 'process_lock' | ||
require 'timeout' | ||
|
||
module TimeoutMatcher | ||
extend RSpec::Matchers::DSL | ||
|
||
matcher :time_out do |value| | ||
match do |block| | ||
begin | ||
Timeout.timeout(value) do | ||
block.call | ||
false | ||
end | ||
rescue TimeoutError | ||
true | ||
end | ||
end | ||
end | ||
end | ||
|
||
RSpec.configure do |c| | ||
c.include TimeoutMatcher | ||
c.before(:all) do | ||
FileUtils.touch(@path = 'tmp/spec') | ||
File.truncate(@path, 0) | ||
end | ||
end |
Empty file.