futex 0.8.1 → 0.8.2

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
  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