just_one_lock 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-