futex 0.8.1 → 0.8.2

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
  SHA256:
3
- metadata.gz: d8a8d71051dbbfbccbf315a3ff9db4a8412d254a6f2076a759c5cc0ed27f1e34
4
- data.tar.gz: 6f89829eb5326e4152c8422e8d5057c4c54c9e74cab0593c959d52aa2ef76b1f
3
+ metadata.gz: 8a40f777d578131870588931a9f18fde1960ba7a5219b5b5cd50dd97a7b9fc07
4
+ data.tar.gz: 0bf2196fca978a7a75b63faccd92ca4115510fcedf24c639a2d1c1251bf420b4
5
5
  SHA512:
6
- metadata.gz: 379a44ca71ef3e5e76f92a89ee5a702177c4561e3c251a973caed8958182656bca60b469d46642f0cd753a9047b9daf96b43e759705f06af737141a97948011a
7
- data.tar.gz: e50ddd647eb79bd744a5f382be7600cff51779d6392feb699cc308bf59c1a67779a04675bf863f8d5bd70ded46835c9bfdc8300a95ba21e53c6c2c35176ac09f
6
+ metadata.gz: 49791caf45a0b5c250aee714870bd6315c359b6c5e32e93a69663d15490ec0d56b9ede3d0726e1fd90c5f8231b46bd29a3accc8a2bd6d4407834e8498174543c
7
+ data.tar.gz: 85296003d7fb88544f1365ca1b9f026ece2fc1eebf2f532bf82fe7a947f14f92b1d45054fff516cf1eabc2f800817c60b949773cdb686abee58d06cae8202f2a
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ Gemfile.lock
3
3
  .bundle/
4
4
  .DS_Store
5
5
  rdoc/
6
+ .idea/
data/futex.gemspec CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.rubygems_version = '2.3.3'
32
32
  s.required_ruby_version = '>=2.3'
33
33
  s.name = 'futex'
34
- s.version = '0.8.1'
34
+ s.version = '0.8.2'
35
35
  s.license = 'MIT'
36
36
  s.summary = 'File-based mutex'
37
37
  s.description = 'Ruby Gem for file-based locking'
@@ -43,6 +43,7 @@ Gem::Specification.new do |s|
43
43
  s.rdoc_options = ['--charset=UTF-8']
44
44
  s.extra_rdoc_files = ['README.md']
45
45
  s.add_development_dependency 'minitest', '5.11.3'
46
+ s.add_development_dependency 'openssl', '2.1.2'
46
47
  s.add_development_dependency 'rake', '12.3.1'
47
48
  s.add_development_dependency 'rdoc', '4.3.0'
48
49
  s.add_development_dependency 'rubocop', '0.60.0'
data/lib/futex.rb CHANGED
@@ -24,6 +24,8 @@
24
24
 
25
25
  require 'fileutils'
26
26
  require 'time'
27
+ require 'singleton'
28
+ require 'json'
27
29
 
28
30
  # Futex (file mutex) is a fine-grained mutex that uses a file, not an entire
29
31
  # thread, like <tt>Mutex</tt> does. Use it like this:
@@ -95,7 +97,7 @@ class Futex
95
97
  b = badge(exclusive)
96
98
  Thread.current.thread_variable_set(:futex_lock, @lock)
97
99
  Thread.current.thread_variable_set(:futex_badge, b)
98
- File.open(@lock, File::CREAT | File::RDWR) do |f|
100
+ open_synchronized(@lock) do |f|
99
101
  cycle = 0
100
102
  loop do
101
103
  if f.flock((exclusive ? File::LOCK_EX : File::LOCK_SH) | File::LOCK_NB)
@@ -157,4 +159,45 @@ access to #{@path}, #{age(start)} already: #{IO.read(@lock)} \
157
159
  @log.puts(msg)
158
160
  end
159
161
  end
162
+
163
+ def open_synchronized(path)
164
+ path = File.absolute_path(path)
165
+ file = nil
166
+ synchronized do |ref_counts_file|
167
+ file = File.open(path, File::CREAT | File::RDWR)
168
+ ref_counts = deserialize(File.read(ref_counts_file.path))
169
+ ref_counts[path] = (ref_counts[path] || 0) + 1
170
+ File.write(ref_counts_file.path, serialize(ref_counts))
171
+ end
172
+ yield file
173
+ ensure
174
+ synchronized do |ref_counts_file|
175
+ file.close
176
+ ref_counts = deserialize(File.read(ref_counts_file.path))
177
+ ref_counts[path] -= 1
178
+ if ref_counts[path].zero?
179
+ FileUtils.rm(path, force: true)
180
+ ref_counts.delete(path)
181
+ end
182
+ File.write(ref_counts_file.path, serialize(ref_counts))
183
+ end
184
+ end
185
+
186
+ def synchronized
187
+ ref_counts_file = File.join(Dir.tmpdir, '.futex.lock')
188
+ File.open(ref_counts_file, File::CREAT | File::RDWR) do |f|
189
+ f.flock(File::LOCK_EX)
190
+ yield f
191
+ end
192
+ end
193
+
194
+ def serialize(data)
195
+ # NOTE: JSON is just an example,
196
+ # use whatever serialization is more appropriate here
197
+ data.to_json
198
+ end
199
+
200
+ def deserialize(data)
201
+ data.empty? ? {} : JSON.parse(data)
202
+ end
160
203
  end
data/test/test_futex.rb CHANGED
@@ -93,32 +93,57 @@ class FutexTest < Minitest::Test
93
93
  end
94
94
  end
95
95
 
96
- def test_non_exclusive_locking
96
+ def test_exclusive_and_shared_locking
97
97
  Dir.mktmpdir do |dir|
98
98
  path = File.join(dir, 'g/e/f/file.txt')
99
99
  Threads.new(20).assert(1000) do |_, r|
100
100
  if (r % 50).zero?
101
101
  Futex.new(path).open do |f|
102
102
  text = SecureRandom.hex(1024)
103
- hash = Digest::SHA256.hexdigest(text)
103
+ hash = hash(text)
104
104
  IO.write(f, text + ' ' + hash)
105
105
  end
106
106
  end
107
107
  Futex.new(path).open(false) do |f|
108
108
  if File.exist?(f)
109
109
  text, hash = IO.read(f, text).split(' ')
110
- assert_equal(hash, Digest::SHA256.hexdigest(text))
110
+ assert_equal(hash, hash(text))
111
111
  end
112
112
  end
113
113
  end
114
114
  end
115
115
  end
116
116
 
117
- # This test doesn't work and I can't fix it. If I remove the file
118
- # afterwards, the flock() logic gets broken. More about it here:
119
- # https://stackoverflow.com/questions/53011200
117
+ def test_exclusive_and_shared_locking_in_processes
118
+ Dir.mktmpdir do |dir|
119
+ path = File.join(dir, 'g/e/f/file.txt')
120
+ 10.times do
121
+ Process.fork do
122
+ Threads.new(20).assert(1000) do |_, r|
123
+ if (r % 50).zero?
124
+ Futex.new(path).open do |f|
125
+ text = SecureRandom.hex(1024)
126
+ hash = hash(text)
127
+ IO.write(f, text + ' ' + hash)
128
+ end
129
+ end
130
+ Futex.new(path).open(false) do |f|
131
+ if File.exist?(f)
132
+ text, hash = IO.read(f, text).split(' ')
133
+ assert_equal(hash, hash(text))
134
+ end
135
+ end
136
+ end
137
+ exit!(0)
138
+ end
139
+ end
140
+ Process.waitall.each do |p, e|
141
+ raise "Failed in PID ##{p}: #{e}" unless e.exitstatus.zero?
142
+ end
143
+ end
144
+ end
145
+
120
146
  def test_cleans_up_the_mess
121
- skip
122
147
  Dir.mktmpdir do |dir|
123
148
  Futex.new(File.join(dir, 'hey.txt')).open do |f|
124
149
  IO.write(f, 'hey')
@@ -165,4 +190,10 @@ class FutexTest < Minitest::Test
165
190
  Futex.new(File.join(dir, 'hey.txt')).open
166
191
  end
167
192
  end
193
+
194
+ private
195
+
196
+ def hash(text)
197
+ Digest::SHA256.hexdigest(text)
198
+ end
168
199
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: futex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-16 00:00:00.000000000 Z
11
+ date: 2018-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.11.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: openssl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.1.2
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement