futex 0.3.1 → 0.4.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
  SHA256:
3
- metadata.gz: c7d43b57ed9b9c1976f33c1da3d39e9646bbb5e890594c339ca7178da830db81
4
- data.tar.gz: 7584b99738962f3526367c4310a5a626e8bcec599e5064350825f7a784ab0fac
3
+ metadata.gz: e19f93d6754a7535a3a7824b2ba55aa6fb8fcf00e63b04011af936865e26895f
4
+ data.tar.gz: da61f106a45c5da30d76891f88f3adf389c1610c852604326b3d182eadfe1215
5
5
  SHA512:
6
- metadata.gz: 8af3cb3b44c1e665d0a7744b5321b2084844d1b8c31b73cc37ea1abe47cc6e9c30ace004fd6abac823998a3584385ac18de237bebd53fc6702df186e2646b0bf
7
- data.tar.gz: c99a473b9ecf837cfc95d852ce2e5886b492ce19cba9929cc3dbaef70f80d8bccbb182f9ae08006f6e5ae35611af2819ef0ae4532e00670f6c91c75016a52c90
6
+ metadata.gz: d1adb27a04e721ff4ba1356afd44e19731b7193938b4fba03805eab9722c1b986a20bd4e5f0343de3f685e71b95b0d4e94f34213a49007839fc4243090cc87a0
7
+ data.tar.gz: 29748f308f7790b67d3e33cf1827bcaea200a2b2a9f7aa977db77f08e289176fc5f65b9927ef5f88309559f59886a414bc12a0988cc0a40a236ba52edc674cc5
data/.rubocop.yml CHANGED
@@ -2,6 +2,8 @@ AllCops:
2
2
  DisplayCopNames: true
3
3
  TargetRubyVersion: 2.3.3
4
4
 
5
+ Layout/EmptyLineAfterGuardClause:
6
+ Enabled: false
5
7
  Layout/MultilineMethodCallIndentation:
6
8
  Enabled: false
7
9
  Metrics/AbcSize:
data/.rultor.yml CHANGED
@@ -20,4 +20,7 @@ architect:
20
20
  - yegor256
21
21
  merge:
22
22
  commanders: []
23
+ script: |-
24
+ bundle install
25
+ rake
23
26
  deploy: {}
data/README.md CHANGED
@@ -1,9 +1,12 @@
1
+ [![EO principles respected here](http://www.elegantobjects.org/badge.svg)](http://www.elegantobjects.org)
1
2
  [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/futex)](http://www.rultor.com/p/yegor256/futex)
2
3
  [![We recommend RubyMine](http://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)
3
4
 
4
5
  [![Build Status](https://travis-ci.org/yegor256/futex.svg)](https://travis-ci.org/yegor256/futex)
6
+ [![Build status](https://ci.appveyor.com/api/projects/status/po1mn8ca96jk0llr?svg=true)](https://ci.appveyor.com/project/yegor256/futex)
5
7
  [![Gem Version](https://badge.fury.io/rb/futex.svg)](http://badge.fury.io/rb/futex)
6
8
  [![Maintainability](https://api.codeclimate.com/v1/badges/5528e182bb5e4a2ecc1f/maintainability)](https://codeclimate.com/github/yegor256/futex/maintainability)
9
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/yegor256/futex/master/frames)
7
10
 
8
11
  Sometimes you need to synchronize your block of code, but `Mutex` is too coarse-grained,
9
12
  because it _always locks_, no matter what objects your code accesses. The
@@ -21,12 +24,23 @@ Then, use it like this:
21
24
  ```ruby
22
25
  require 'futex'
23
26
  Futex.new('/tmp/my-file.txt').open |f|
24
- File.write(f, 'Hello, world!')
27
+ IO.write(f, 'Hello, world!')
25
28
  end
26
29
  ```
27
30
 
28
31
  The file `/tmp/my-file.txt.lock` will be created and used as an entrance lock.
29
- It will be deleted afterwards.
32
+ It <del>will</del> [won't](https://github.com/yegor256/futex/issues/5) be deleted afterwards.
33
+
34
+ If you are not planning to write to the file, it is recommended to get
35
+ a non-exclusive/shared access to it, by providing `false` to the method
36
+ `open()`:
37
+
38
+ ```ruby
39
+ require 'futex'
40
+ Futex.new('/tmp/my-file.txt').open(false) |f|
41
+ IO.read(f)
42
+ end
43
+ ```
30
44
 
31
45
  For better traceability you can provide a few arguments to the
32
46
  constructor of the `Futex` class, including:
data/Rakefile CHANGED
@@ -44,6 +44,13 @@ Rake::TestTask.new(:test) do |test|
44
44
  test.verbose = false
45
45
  end
46
46
 
47
+ require 'rdoc/task'
48
+ RDoc::Task.new do |rdoc|
49
+ rdoc.main = 'README.md'
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.rdoc_files.include('README.md', 'lib/**/*.rb')
52
+ end
53
+
47
54
  require 'rubocop/rake_task'
48
55
  desc 'Run RuboCop on all directories'
49
56
  RuboCop::RakeTask.new(:rubocop) do |task|
data/appveyor.yml ADDED
@@ -0,0 +1,38 @@
1
+ version: '{build}'
2
+ skip_tags: true
3
+ clone_depth: 10
4
+ branches:
5
+ only:
6
+ - master
7
+ except:
8
+ - gh-pages
9
+ os: Windows Server 2012
10
+ environment:
11
+ matrix:
12
+ - ruby_version: "23-x64"
13
+ - ruby_version: "24-x64"
14
+ - ruby_version: "25-x64"
15
+ install:
16
+ - ps: |
17
+ $Env:PATH = "C:\Ruby${Env:ruby_version}\bin;${Env:PATH}"
18
+ if ($Env:ruby_version -match "^23" ) {
19
+ # RubyInstaller; download OpenSSL headers from OpenKnapsack Project
20
+ $Env:openssl_dir = "C:\Ruby${Env:ruby_version}"
21
+ appveyor DownloadFile http://dl.bintray.com/oneclick/OpenKnapsack/x64/openssl-1.0.2j-x64-windows.tar.lzma
22
+ 7z e openssl-1.0.2j-x64-windows.tar.lzma
23
+ 7z x -y -oC:\Ruby${Env:ruby_version} openssl-1.0.2j-x64-windows.tar
24
+ } else {
25
+ # RubyInstaller2; openssl package seems to be installed already
26
+ $Env:openssl_dir = "C:\msys64\mingw64"
27
+ }
28
+ - bundle config --local path vendor/bundle
29
+ - bundle config build.openssl --with-openssl-dir=%openssl_dir%
30
+ - ruby -v
31
+ - bundle -v
32
+ build_script:
33
+ - bundle update
34
+ - bundle install
35
+ test_script:
36
+ - bundle exec rake --quiet
37
+ cache:
38
+ - vendor/bundle
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.3.1'
34
+ s.version = '0.4.0'
35
35
  s.license = 'MIT'
36
36
  s.summary = 'File-based mutex'
37
37
  s.description = 'Ruby Gem for file-based locking'
@@ -42,10 +42,10 @@ Gem::Specification.new do |s|
42
42
  s.test_files = s.files.grep(%r{^(test)/})
43
43
  s.rdoc_options = ['--charset=UTF-8']
44
44
  s.extra_rdoc_files = ['README.md']
45
- s.add_development_dependency 'minitest', '~>5'
46
- s.add_development_dependency 'rake', '~>12'
47
- s.add_development_dependency 'rdoc', '~>4'
48
- s.add_development_dependency 'rubocop', '0.58.1'
49
- s.add_development_dependency 'rubocop-rspec', '~>1'
50
- s.add_development_dependency 'threads', '~>0'
45
+ s.add_development_dependency 'minitest', '5.11.3'
46
+ s.add_development_dependency 'rake', '12.3.1'
47
+ s.add_development_dependency 'rdoc', '4.3.0'
48
+ s.add_development_dependency 'rubocop', '0.60.0'
49
+ s.add_development_dependency 'rubocop-rspec', '1.30.1'
50
+ s.add_development_dependency 'threads', '0.3.0'
51
51
  end
data/lib/futex.rb CHANGED
@@ -25,37 +25,55 @@
25
25
  require 'fileutils'
26
26
  require 'time'
27
27
 
28
- # Futex.
28
+ # Futex (file mutex) is a fine-grained mutex that uses a file, not an entire
29
+ # thread, like <tt>Mutex</tt> does. Use it like this:
30
+ #
31
+ # require 'futex'
32
+ # Futex.new('/tmp/my-file.txt').open |f|
33
+ # IO.write(f, 'Hello, world!')
34
+ # end
35
+ #
36
+ # The file <tt>/tmp/my-file.txt.lock<tt> will be created and
37
+ # used as an entrance lock.
38
+ #
39
+ # If you are not planning to write to the file, to speed things up, you may
40
+ # want to get a non-exclusive access to it, by providing <tt>false</tt> to
41
+ # the method <tt>open()</tt>:
42
+ #
43
+ # require 'futex'
44
+ # Futex.new('/tmp/my-file.txt').open(false) |f|
45
+ # IO.read(f)
46
+ # end
47
+ #
48
+ # For more information read
49
+ # {README}[https://github.com/yegor256/futex/blob/master/README.md] file.
50
+ #
29
51
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
52
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
31
53
  # License:: MIT
32
54
  class Futex
55
+ # Creates a new instance of the class.
33
56
  def initialize(path, log: STDOUT, timeout: 16, sleep: 0.005,
34
57
  lock: path + '.lock', logging: false)
35
- raise "File path can't be nil" if path.nil?
36
58
  @path = path
37
- raise "Log can't be nil" if log.nil?
38
59
  @log = log
39
- raise "Logging can't be nil" if logging.nil?
40
60
  @logging = logging
41
- raise "Timeout can't be nil" if timeout.nil?
42
- raise "Timeout must be positive: #{timeout}" unless timeout.positive?
43
61
  @timeout = timeout
44
- raise "Sleep can't be nil" if sleep.nil?
45
- raise "Sleep can't be negative or zero: #{sleep}" unless sleep.positive?
46
62
  @sleep = sleep
47
- raise "Lock path can't be nil" if lock.nil?
48
63
  @lock = lock
49
64
  end
50
65
 
51
- def open
66
+ # Open the file.
67
+ def open(exclusive = true)
52
68
  FileUtils.mkdir_p(File.dirname(@lock))
53
69
  step = (1 / @sleep).to_i
54
70
  start = Time.now
55
71
  File.open(@lock, File::CREAT | File::RDWR) do |f|
56
72
  cycle = 0
57
73
  loop do
58
- break if f.flock(File::LOCK_EX | File::LOCK_NB)
74
+ if f.flock((exclusive ? File::LOCK_EX : File::LOCK_SH) | File::LOCK_NB)
75
+ break
76
+ end
59
77
  sleep(@sleep)
60
78
  cycle += 1
61
79
  if Time.now - start > @timeout
data/test/test_futex.rb CHANGED
@@ -25,6 +25,8 @@
25
25
  require 'minitest/autorun'
26
26
  require 'tmpdir'
27
27
  require 'threads'
28
+ require 'digest'
29
+ require 'securerandom'
28
30
  require_relative '../lib/futex'
29
31
 
30
32
  # Futex test.
@@ -59,6 +61,27 @@ class FutexTest < Minitest::Test
59
61
  end
60
62
  end
61
63
 
64
+ def test_non_exclusive_locking
65
+ Dir.mktmpdir do |dir|
66
+ path = File.join(dir, 'g/e/f/file.txt')
67
+ Threads.new(20).assert(1000) do |_, r|
68
+ if (r % 50).zero?
69
+ Futex.new(path).open do |f|
70
+ text = SecureRandom.hex(1024)
71
+ hash = Digest::SHA256.hexdigest(text)
72
+ IO.write(f, text + ' ' + hash)
73
+ end
74
+ end
75
+ Futex.new(path).open(false) do |f|
76
+ if File.exist?(f)
77
+ text, hash = IO.read(f, text).split(' ')
78
+ assert_equal(hash, Digest::SHA256.hexdigest(text))
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
62
85
  # This test doesn't work and I can't fix it. If I remove the file
63
86
  # afterwards, the flock() logic gets broken. More about it here:
64
87
  # https://stackoverflow.com/questions/53011200
metadata CHANGED
@@ -1,99 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: futex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
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-10-26 00:00:00.000000000 Z
11
+ date: 2018-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: '5'
19
+ version: 5.11.3
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: '5'
26
+ version: 5.11.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '12'
33
+ version: 12.3.1
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '12'
40
+ version: 12.3.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rdoc
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: '4'
47
+ version: 4.3.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: '4'
54
+ version: 4.3.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.58.1
61
+ version: 0.60.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 0.58.1
68
+ version: 0.60.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop-rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: '1'
75
+ version: 1.30.1
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: '1'
82
+ version: 1.30.1
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: threads
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: 0.3.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - '='
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: 0.3.0
97
97
  description: Ruby Gem for file-based locking
98
98
  email: yegor256@gmail.com
99
99
  executables: []
@@ -110,6 +110,7 @@ files:
110
110
  - Gemfile
111
111
  - README.md
112
112
  - Rakefile
113
+ - appveyor.yml
113
114
  - futex.gemspec
114
115
  - lib/futex.rb
115
116
  - test/test_futex.rb