just_one_lock 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9cbc796000346c2d5e41e9808e7bf8b1e50ef421
4
- data.tar.gz: c919c93fdd387737c7349f2d2e1607550c235a24
3
+ metadata.gz: 6ac57ab6070d88fa87e098e760f0c8a85d9533b3
4
+ data.tar.gz: b22dab3d97237b42ad75ec74f75a9e6bf45ecaf4
5
5
  SHA512:
6
- metadata.gz: fdd1f6a2be4aea0132d54d0b05d29512cff1f913b2240ec6c99896cc93bfc8e9e04c5dc5d24bba11fc2a6e665b65d0528e3bf55581f32f97ed4de6bd8ac68eeb
7
- data.tar.gz: d9bb70c3362c62ac90ce25755199a375d76dd0e660c278691a1a37ddd56d6d903b0ec610a13d4e0752a1d3975e7d2e7fc5ed056c0448c12c445a6778ea79fa1f
6
+ metadata.gz: d3a2a7b9d9f81213e1e5bc40339c47050fcf2a08e41180817085802b13875e9761c98fadc211c8039ec0acfdb551e0900c1a323c7ba5cf0b152f6b9bee6f13ec
7
+ data.tar.gz: d07707b5948b99309c0d3de0abb09122de3312d643eabd4882a4c6c44187616d365fc10c68dbb1bc885aec1af3f9e73477de47233d70198ba97c653771491f92
data/bin/just_one_lock CHANGED
@@ -12,42 +12,29 @@ version JustOneLock::VERSION
12
12
 
13
13
  flag [:l,:lock_dir], default_value: '/tmp'
14
14
  flag [:s,:scope]
15
+ flag [:t,:timeout], desc: 'Timeout in seconds'
15
16
 
16
- pre do |global_options, command, options, args|
17
- $lock_dir = global_options[:lock_dir]
18
- $scope = global_options[:scope]
19
- $command_to_run = args.first
20
- end
21
-
22
- desc 'Execute system command using blocking lock'
23
- command :blocking do |c|
24
- c.flag [:t,:timeout], desc: 'Timeout in seconds'
25
-
17
+ desc 'Execute system command'
18
+ command :exec do |c|
26
19
  c.action do |global_options, options, args|
27
- timeout = options[:timeout]
28
- timeout = timeout.to_i if timeout
29
-
30
- JustOneLock::Blocking.prevent_multiple_executions(
31
- $lock_dir,
32
- $scope,
33
- output: $stdout,
34
- timeout: timeout
35
- ) do
36
- system $command_to_run
20
+ JustOneLock.world.directory = global_options[:lock_dir]
21
+ scope = global_options[:scope]
22
+ timeout = global_options[:timeout].to_f
23
+ command_to_run = args.first
24
+
25
+ if timeout > 0
26
+ locker = JustOneLock::BlockingLocker.new(timeout: timeout)
27
+ else
28
+ locker = JustOneLock::NonBlockingLocker.new
37
29
  end
38
- end
39
- end
40
30
 
41
- desc 'Execute system command'
42
- command :non_blocking do |c|
43
- c.action do |global_options, options, args|
44
- JustOneLock::NonBlocking.prevent_multiple_executions($lock_dir, $scope, output: $stdout) do
45
- system $command_to_run
31
+ JustOneLock::prevent_multiple_executions(locker, scope) do
32
+ system command_to_run
46
33
  end
47
34
  end
48
35
  end
49
36
 
50
- default_command :non_blocking
37
+ default_command :exec
51
38
 
52
39
  exit run(ARGV)
53
40
 
@@ -0,0 +1,27 @@
1
+ class JustOneLock::BaseLocker
2
+ def already_locked(scope)
3
+ msg = "Another process <#{scope}> already is running"
4
+ JustOneLock.puts msg
5
+ raise JustOneLock::AlreadyLocked, msg
6
+ end
7
+
8
+ private
9
+
10
+ def write_pid(f)
11
+ f.rewind
12
+ f.write(Process.pid)
13
+ f.flush
14
+ f.truncate(f.pos)
15
+ end
16
+
17
+ def run(f, path, &block)
18
+ write_pid(f)
19
+
20
+ JustOneLock.before_lock(path, f)
21
+ result = block.call
22
+ JustOneLock.after_lock(path, f)
23
+
24
+ result
25
+ end
26
+ end
27
+
@@ -0,0 +1,24 @@
1
+ require 'timeout'
2
+
3
+ class JustOneLock::BlockingLocker < JustOneLock::BaseLocker
4
+ DEFAULT_TIMEOUT = 1
5
+
6
+ attr_accessor :timeout
7
+
8
+ def initialize(timeout: DEFAULT_TIMEOUT)
9
+ @timeout = timeout
10
+ end
11
+
12
+ def lock(lock_path, &block)
13
+ result = nil
14
+
15
+ File.open(lock_path, File::RDWR|File::CREAT, 0644) do |f|
16
+ Timeout::timeout(@timeout, JustOneLock::AlreadyLocked) { f.flock(File::LOCK_EX) }
17
+
18
+ result = run(f, lock_path, &block)
19
+ end
20
+
21
+ result
22
+ end
23
+ end
24
+
@@ -0,0 +1,16 @@
1
+ class JustOneLock::NonBlockingLocker < JustOneLock::BaseLocker
2
+ def lock(lock_path, &block)
3
+ result = nil
4
+
5
+ File.open(lock_path, File::RDWR|File::CREAT, 0644) do |f|
6
+ if f.flock(File::LOCK_NB|File::LOCK_EX)
7
+ result = run(f, lock_path, &block)
8
+ else
9
+ fail JustOneLock::AlreadyLocked
10
+ end
11
+ end
12
+
13
+ result
14
+ end
15
+ end
16
+
@@ -1,3 +1,3 @@
1
1
  module JustOneLock
2
- VERSION = '0.0.9'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,42 @@
1
+ class JustOneLock::World
2
+ attr_accessor :output, :directory, :delete_files
3
+
4
+ def initialize
5
+ @files = {}
6
+ @output = $stdout
7
+ @directory = '/tmp'
8
+ @delete_files = true
9
+ end
10
+
11
+ def delete_unlocked_files
12
+ paths_to_delete = []
13
+
14
+ @files.each do |path, f|
15
+ if File.exists?(path) && f.closed?
16
+ paths_to_delete << path
17
+ end
18
+ end
19
+
20
+ paths_to_delete.each do |path|
21
+ File.delete(path)
22
+ @files.delete(path)
23
+ end
24
+ end
25
+
26
+ def before_lock(name, file)
27
+ @files[name] = file
28
+ end
29
+
30
+ def after_lock(name, file)
31
+ delete_unlocked_files if delete_files
32
+ end
33
+
34
+ def lock_paths
35
+ @files.keys
36
+ end
37
+
38
+ def puts(*args)
39
+ output.puts(*args)
40
+ end
41
+
42
+ end
data/lib/just_one_lock.rb CHANGED
@@ -1,55 +1,35 @@
1
1
  require 'just_one_lock/version'
2
- require 'just_one_lock/blocking'
3
- require 'just_one_lock/non_blocking'
2
+ require 'just_one_lock/world'
3
+ require 'just_one_lock/base_locker'
4
+ require 'just_one_lock/blocking_locker'
5
+ require 'just_one_lock/non_blocking_locker'
6
+ require 'forwardable'
4
7
 
5
8
  module JustOneLock
6
9
  class AlreadyLocked < StandardError; end
7
10
 
8
- class NullStream
9
- class << self
10
- def puts(str); end
11
- end
12
- end
13
-
14
- @files = {}
15
-
16
- def self.delete_unlocked_files
17
- paths_to_delete = []
18
-
19
- @files.each do |path, f|
20
- if File.exists?(path) && f.closed?
21
- paths_to_delete << path
22
- end
23
- end
24
-
25
- paths_to_delete.each do |path|
26
- File.delete(path)
27
- @files.delete(path)
28
- end
11
+ class << self
12
+ extend ::Forwardable
13
+ def_delegators :world, :before_lock, :after_lock, :delete_unlocked_files
29
14
  end
30
15
 
31
- def self.lock_paths
32
- @files.keys
16
+ def self.world
17
+ @world ||= JustOneLock::World.new
33
18
  end
34
19
 
35
- def self.already_locked(output, scope)
36
- msg = "Another process <#{scope}> already is running"
37
- output.puts msg
38
- raise JustOneLock::AlreadyLocked, msg
39
- end
40
-
41
- private
42
-
43
- def self.write_pid(f)
44
- f.rewind
45
- f.write(Process.pid)
46
- f.flush
47
- f.truncate(f.pos)
48
- end
49
-
50
- def self.run(f, lockname, &block)
51
- @files[lockname] = f
52
- write_pid(f)
53
- block.call
20
+ def self.prevent_multiple_executions(
21
+ locker,
22
+ scope,
23
+ &block
24
+ )
25
+ scope_name = scope.gsub(':', '_')
26
+ lock_path = File.join(world.directory, scope_name + '.lock')
27
+
28
+ begin
29
+ return locker.lock(lock_path, &block)
30
+ rescue JustOneLock::AlreadyLocked => e
31
+ locker.already_locked(scope)
32
+ end
54
33
  end
55
34
  end
35
+
@@ -3,38 +3,10 @@ require 'tempfile'
3
3
  require 'timeout'
4
4
  require 'just_one_lock/locking_object'
5
5
 
6
- describe JustOneLock::Blocking do
6
+ describe JustOneLock::BlockingLocker do
7
+ let(:locker) { JustOneLock::BlockingLocker.new }
7
8
  it_behaves_like 'a locking object'
8
9
 
9
- def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT, &block)
10
- Timeout::timeout(5) do
11
- dir, scope = dir_and_scope(lockpath)
12
- (1..n).map do
13
- Thread.new do
14
- JustOneLock::Blocking.prevent_multiple_executions(dir, scope, timeout: timeout, delete_files: false, &block)
15
- end
16
- end.map(&:join)
17
- end
18
-
19
- JustOneLock.delete_unlocked_files
20
- end
21
-
22
- def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT, &block)
23
- Timeout::timeout(5) do
24
- dir, scope = dir_and_scope(lockpath)
25
-
26
- (1..n).map do
27
- fork {
28
- JustOneLock::Blocking.prevent_multiple_executions(dir, scope, timeout: timeout, delete_files: false, &block)
29
- }
30
- end.map do |pid|
31
- Process.waitpid(pid)
32
- end
33
- end
34
-
35
- JustOneLock.delete_unlocked_files
36
- end
37
-
38
10
  # Java doesn't support forking
39
11
  if RUBY_PLATFORM != 'java'
40
12
  it 'should work for multiple processes' do
@@ -42,7 +14,7 @@ describe JustOneLock::Blocking do
42
14
 
43
15
  parallel_forks(6) do
44
16
  number = File.read('/tmp/number.txt').to_i
45
- sleep(JustOneLock::Blocking::DEFAULT_TIMEOUT / 100)
17
+ sleep(0.1)
46
18
  write('/tmp/number.txt', (number + 7).to_s)
47
19
  end
48
20
 
@@ -71,7 +43,7 @@ describe JustOneLock::Blocking do
71
43
 
72
44
  parallel(2) do
73
45
  value = answer
74
- sleep(JustOneLock::Blocking::DEFAULT_TIMEOUT / 2)
46
+ sleep(0.5)
75
47
  answer = value + 21
76
48
  end
77
49
 
@@ -6,7 +6,7 @@ shared_examples 'a locking object' do
6
6
  lockpath = File.join(lock_dir, 'sample.lock')
7
7
  answer = 0
8
8
 
9
- JustOneLock::Blocking.filelock lockpath do
9
+ locker.lock lockpath do
10
10
  answer += 42
11
11
  end
12
12
 
@@ -19,7 +19,7 @@ shared_examples 'a locking object' do
19
19
  lockpath = File.join(lock_dir, 'sample.lock')
20
20
  answer = 0
21
21
 
22
- answer = JustOneLock::Blocking.filelock lockpath do
22
+ answer = locker.lock lockpath do
23
23
  42
24
24
  end
25
25
 
@@ -40,13 +40,13 @@ shared_examples 'a locking object' do
40
40
  answer = 0
41
41
 
42
42
  begin
43
- JustOneLock::Blocking.filelock(lockpath) do
43
+ locker.lock(lockpath) do
44
44
  raise '42'
45
45
  end
46
46
  rescue RuntimeError
47
47
  end
48
48
 
49
- JustOneLock::Blocking.filelock(lockpath) do
49
+ locker.lock(lockpath) do
50
50
  answer += 42
51
51
  end
52
52
 
@@ -61,7 +61,7 @@ shared_examples 'a locking object' do
61
61
  locked = false
62
62
 
63
63
  Thread.new do
64
- JustOneLock::Blocking.filelock lockpath do
64
+ locker.lock lockpath do
65
65
  locked = true
66
66
  sleep 20
67
67
  end
@@ -74,7 +74,7 @@ shared_examples 'a locking object' do
74
74
  end
75
75
 
76
76
  expect do
77
- JustOneLock::Blocking.filelock lockpath, timeout: 0.001 do
77
+ locker.lock lockpath do
78
78
  answer = 0
79
79
  end
80
80
  end.to raise_error(JustOneLock::AlreadyLocked)
@@ -91,7 +91,7 @@ shared_examples 'a locking object' do
91
91
  dir, scope = dir_and_scope(lockpath)
92
92
 
93
93
  pid = fork {
94
- JustOneLock::Blocking.prevent_multiple_executions(lock_dir, scope) do
94
+ JustOneLock::prevent_multiple_executions(locker, scope) do
95
95
  sleep 10
96
96
  end
97
97
  }
@@ -105,7 +105,7 @@ shared_examples 'a locking object' do
105
105
  answer = 0
106
106
 
107
107
  thread = Thread.new {
108
- JustOneLock::Blocking.prevent_multiple_executions(dir, scope) do
108
+ JustOneLock::prevent_multiple_executions(locker, scope) do
109
109
  answer += 42
110
110
  end
111
111
  }
@@ -122,7 +122,7 @@ shared_examples 'a locking object' do
122
122
  lockpath = Pathname.new(File.join(lock_dir, 'sample.lock'))
123
123
 
124
124
  answer = 0
125
- JustOneLock::Blocking.filelock lockpath do
125
+ locker.lock lockpath do
126
126
  answer += 42
127
127
  end
128
128
 
@@ -135,7 +135,7 @@ shared_examples 'a locking object' do
135
135
  it 'works for Tempfile' do
136
136
  answer = 0
137
137
 
138
- JustOneLock::Blocking.filelock Tempfile.new(['sample', '.lock']) do
138
+ locker.lock Tempfile.new(['sample', '.lock']) do
139
139
  answer += 42
140
140
  end
141
141
 
@@ -146,7 +146,7 @@ shared_examples 'a locking object' do
146
146
  it 'creates file with exact path provided' do
147
147
  filename = "/tmp/awesome-lock-#{rand}.lock"
148
148
 
149
- JustOneLock::Blocking.filelock filename, delete_files: false do
149
+ locker.lock filename do
150
150
  end
151
151
 
152
152
  expect(File.exist?(filename)).to eq(true)
@@ -3,36 +3,8 @@ require 'tempfile'
3
3
  require 'timeout'
4
4
  require 'just_one_lock/locking_object'
5
5
 
6
- describe JustOneLock::NonBlocking do
6
+ describe JustOneLock::NonBlockingLocker do
7
+ let(:locker) { JustOneLock::NonBlockingLocker.new }
7
8
  it_behaves_like 'a locking object'
8
-
9
- def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
10
- Timeout::timeout(5) do
11
- dir, scope = dir_and_scope(lockpath)
12
- (1..n).map do
13
- Thread.new do
14
- JustOneLock::NonBlocking.prevent_multiple_executions(dir, scope, delete_files: false, &block)
15
- end
16
- end.map(&:join)
17
- end
18
-
19
- JustOneLock.delete_unlocked_files
20
- end
21
-
22
- def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
23
- Timeout::timeout(5) do
24
- dir, scope = dir_and_scope(lockpath)
25
-
26
- (1..n).map do
27
- fork {
28
- JustOneLock::NonBlocking.prevent_multiple_executions(dir, scope, delete_files: false, &block)
29
- }
30
- end.map do |pid|
31
- Process.waitpid(pid)
32
- end
33
- end
34
-
35
- JustOneLock.delete_unlocked_files
36
- end
37
9
  end
38
10
 
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,3 @@
1
- def lock_dir
2
- '/tmp'
3
- end
4
-
5
1
  # Helper because File.write won't work for older Ruby
6
2
  def write(filename, contents)
7
3
  File.open(filename.to_s, 'w') { |f| f.write(contents) }
@@ -14,3 +10,37 @@ def dir_and_scope(lockpath)
14
10
  dir = path_segments.join('/')
15
11
  [dir, scope]
16
12
  end
13
+
14
+ def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
15
+ Timeout::timeout(5) do
16
+ dir, scope = dir_and_scope(lockpath)
17
+ JustOneLock.world.directory = dir
18
+
19
+ (1..n).map do
20
+ Thread.new do
21
+ JustOneLock::prevent_multiple_executions(locker, scope, &block)
22
+ end
23
+ end.map(&:join)
24
+ end
25
+
26
+ JustOneLock.delete_unlocked_files
27
+ end
28
+
29
+ def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
30
+ Timeout::timeout(5) do
31
+ dir, scope = dir_and_scope(lockpath)
32
+ JustOneLock.world.directory = dir
33
+
34
+ (1..n).map do
35
+ fork {
36
+ JustOneLock::prevent_multiple_executions(locker, scope, &block)
37
+ }
38
+ end.map do |pid|
39
+ Process.waitpid(pid)
40
+ end
41
+ end
42
+
43
+ JustOneLock.delete_unlocked_files
44
+ end
45
+
46
+ JustOneLock.world.delete_files = false
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: just_one_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yury Kotov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-15 00:00:00.000000000 Z
11
+ date: 2014-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,9 +84,11 @@ files:
84
84
  - bin/just_one_lock
85
85
  - just_one_lock.gemspec
86
86
  - lib/just_one_lock.rb
87
- - lib/just_one_lock/blocking.rb
88
- - lib/just_one_lock/non_blocking.rb
87
+ - lib/just_one_lock/base_locker.rb
88
+ - lib/just_one_lock/blocking_locker.rb
89
+ - lib/just_one_lock/non_blocking_locker.rb
89
90
  - lib/just_one_lock/version.rb
91
+ - lib/just_one_lock/world.rb
90
92
  - spec/just_one_lock/blocking_spec.rb
91
93
  - spec/just_one_lock/locking_object.rb
92
94
  - spec/just_one_lock/non_blocking_spec.rb
@@ -1,41 +0,0 @@
1
- require 'timeout'
2
-
3
- module JustOneLock::Blocking
4
- DEFAULT_TIMEOUT = 1
5
-
6
- def self.filelock(
7
- lockname,
8
- timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT,
9
- delete_files: true,
10
- &block
11
- )
12
- result = nil
13
- File.open(lockname, File::RDWR|File::CREAT, 0644) do |f|
14
- Timeout::timeout(timeout, JustOneLock::AlreadyLocked) { f.flock(File::LOCK_EX) }
15
-
16
- result = JustOneLock.run(f, lockname, &block)
17
- end
18
-
19
- JustOneLock.delete_unlocked_files if delete_files
20
- result
21
- end
22
-
23
- def self.prevent_multiple_executions(
24
- lock_dir,
25
- scope,
26
- output: JustOneLock::NullStream,
27
- timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT,
28
- delete_files: true,
29
- &block
30
- )
31
- scope_name = scope.gsub(':', '_')
32
- lock_path = File.join(lock_dir, scope_name + '.lock')
33
-
34
- begin
35
- return filelock(lock_path, timeout: timeout, delete_files: delete_files, &block)
36
- rescue JustOneLock::AlreadyLocked => e
37
- JustOneLock.already_locked(output, scope)
38
- end
39
- end
40
- end
41
-
@@ -1,40 +0,0 @@
1
- module JustOneLock::NonBlocking
2
- def self.filelock(
3
- lockname,
4
- delete_files: true,
5
- &block
6
- )
7
- result = nil
8
-
9
- File.open(lockname, File::RDWR|File::CREAT, 0644) do |f|
10
- if f.flock(File::LOCK_NB|File::LOCK_EX)
11
- result = JustOneLock.run(f, lockname, &block)
12
- else
13
- fail JustOneLock::AlreadyLocked
14
- end
15
- end
16
-
17
- JustOneLock.delete_unlocked_files if delete_files
18
- result
19
- end
20
-
21
-
22
- def self.prevent_multiple_executions(
23
- lock_dir,
24
- scope,
25
- output: JustOneLock::NullStream,
26
- delete_files: true,
27
- &block
28
- )
29
- scope_name = scope.gsub(':', '_')
30
- lock_path = File.join(lock_dir, scope_name + '.lock')
31
-
32
- begin
33
- return filelock(lock_path, delete_files: delete_files, &block)
34
- rescue JustOneLock::AlreadyLocked => e
35
- JustOneLock.already_locked(output, scope)
36
- end
37
- end
38
- end
39
-
40
-