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 +4 -4
- data/.gitignore +1 -0
- data/futex.gemspec +2 -1
- data/lib/futex.rb +44 -1
- data/test/test_futex.rb +38 -7
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a40f777d578131870588931a9f18fde1960ba7a5219b5b5cd50dd97a7b9fc07
|
4
|
+
data.tar.gz: 0bf2196fca978a7a75b63faccd92ca4115510fcedf24c639a2d1c1251bf420b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49791caf45a0b5c250aee714870bd6315c359b6c5e32e93a69663d15490ec0d56b9ede3d0726e1fd90c5f8231b46bd29a3accc8a2bd6d4407834e8498174543c
|
7
|
+
data.tar.gz: 85296003d7fb88544f1365ca1b9f026ece2fc1eebf2f532bf82fe7a947f14f92b1d45054fff516cf1eabc2f800817c60b949773cdb686abee58d06cae8202f2a
|
data/.gitignore
CHANGED
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.
|
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
|
-
|
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
|
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 =
|
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,
|
110
|
+
assert_equal(hash, hash(text))
|
111
111
|
end
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
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.
|
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-
|
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
|