reentrant_flock 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6d20c3216b95cf824f521614caca8e048843f649
4
+ data.tar.gz: 8957df239f692bfa3703545431070fd8f311c606
5
+ SHA512:
6
+ metadata.gz: 178c166ac4fc276c43ebc1c59160e6787ddfb92b52ad9dc3715343c2c61819f2de1a1e8270c02191071ba2f96cbfbdc0e56133b641c9559838f5c3ed4ada9a2d
7
+ data.tar.gz: bf0f978871509dab41c0192f7191530fc07eeec7f349dc33cf3d92cc84433ef96ef1064d5932c20105541fdd72fed0ca48605adfc2e86a1f756cd4b07ad891ee
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.*
5
+ - 2.3.*
6
+ - 2.4.*
7
+ before_install: gem install bundler -v 1.15.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0 (2017-09-05)
2
+
3
+ initial version
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in reentrant_flock.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # ReentrantFlock
2
+
3
+ A reentrant/recursive flock.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'reentrant_flock'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install reentrant_flock
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'reentrant_flock'
25
+
26
+ File.open('/tmp/lock', File::RDWR | File::CREAT) do |fp|
27
+ ReentrantFlock.synchronize(fp, File::LOCK_EX) do
28
+ ReentrantFlock.synchronize(fp, File::LOCK_EX) do
29
+ # not to be blocked by myself
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ ```ruby
36
+ require 'reentrant_flock'
37
+
38
+ def with_lock(&block)
39
+ File.open('/tmp/lock', File::RDWR | File::CREAT) do |fp|
40
+ ReentrantFlock.synchronize(fp, File::LOCK_EX, &block)
41
+ end
42
+ end
43
+
44
+ with_lock do
45
+ with_lock do
46
+ # not to be blocked by myself
47
+ end
48
+ end
49
+ ```
50
+
51
+ `ReentrantFlock.synchronize` would raise `ReentrantFlock::AlreadyLocked` if `File::LOCK_NB` is given and already locked by somebody else.
52
+
53
+ See [File#flock](https://ruby-doc.org/core-2.2.0/File.html#method-i-flock) for flags.
54
+
55
+ ## Development
56
+
57
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
58
+
59
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sonots/reentrant_flock.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "reentrant_flock"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ class ReentrantFlock
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,179 @@
1
+ require "reentrant_flock/version"
2
+
3
+ class ReentrantFlock
4
+ class AlreadyLocked < StandardError; end
5
+ end
6
+
7
+ class ReentrantFlock
8
+ class << self
9
+ # Note that File#flock is automatically unlocked when a file is closed,
10
+ # but ReentrantFlock.flock cannot automatically reset internal counts
11
+ # when a file is closed. Use ReentrantFlock.synchronize to assure
12
+ # decrementing internal counts.
13
+ #
14
+ # @param fp [File]
15
+ # @param operation [PARAM]
16
+ # @see File#flock for more details
17
+ # @param [PARAM] operation See File#flock
18
+ # @return see File#flock
19
+ def flock(fp, operation)
20
+ if (operation & File::LOCK_UN) > 0
21
+ unlock(fp)
22
+ else
23
+ lock(fp, operation)
24
+ end
25
+ end
26
+
27
+ # @param fp [File]
28
+ # @param [PARAM] operation See File#flock
29
+ # @yield
30
+ # @raise [AlreadyLocked] if already locked and LOCK_NB operation is specified
31
+ # @return [Object] the result of block
32
+ def synchronize(fp, operation)
33
+ raise 'Must be called with a block' unless block_given?
34
+
35
+ begin
36
+ raise AlreadyLocked if lock(fp, operation) == false
37
+ yield
38
+ ensure
39
+ unlock(fp)
40
+ end
41
+ end
42
+
43
+ # @param fp [File]
44
+ # @return [Boolean] true if locked by self
45
+ def self_locked?(fp)
46
+ k = key(fp)
47
+ Thread.current.key?(k) and Thread.current[k] >= 1
48
+ end
49
+
50
+ private
51
+
52
+ # @return [0] if current thread holds the lock
53
+ # @return [false] if current thread holds the lock
54
+ def lock(fp, operation)
55
+ c = incr(key(fp))
56
+ if c <= 1
57
+ # flock returns 0 if successfully locked.
58
+ # LOCK_NB returns false if somebody else already locked
59
+ fp.flock(operation)
60
+ else
61
+ 0
62
+ end
63
+ end
64
+
65
+ def unlock(fp)
66
+ k = key(fp)
67
+ c = decr(k)
68
+ if c <= 0
69
+ fp.flock(File::LOCK_UN)
70
+ del(k)
71
+ end
72
+ end
73
+
74
+ def key(fp)
75
+ "reentrant_flock_#{fp.path}"
76
+ end
77
+
78
+ def incr(key)
79
+ Thread.current[key] ||= 0
80
+ Thread.current[key] += 1
81
+ end
82
+
83
+ def decr(key)
84
+ Thread.current[key] ||= 0
85
+ Thread.current[key] -= 1
86
+ end
87
+
88
+ def del(key)
89
+ Thread.current[key] = nil
90
+ end
91
+ end
92
+ end
93
+
94
+ # There is a fact that
95
+ #
96
+ # ```
97
+ # fp = File.open('a', 'w')
98
+ # fp.flock(File::LOCK_EX)
99
+ # fp.flock(File::LOCK_EX) # does not block
100
+ # fp = File.open('a', 'w')
101
+ # fp.flock(File::LOCK_EX) # block
102
+ # ```
103
+ #
104
+ # That is, File#flock is orginally reentrant for the same
105
+ # file object because its file descriptor has lock information
106
+ # on kernel layer. So, this instance version, which holds an
107
+ # instance of File, may not worth to be prepared.
108
+ class ReentrantFlock
109
+ attr_reader :fp
110
+
111
+ def initialize(fp)
112
+ @fp = fp
113
+ @mutex = Mutex.new
114
+ @counts = Hash.new(0)
115
+ end
116
+
117
+ # @param [PARAM] operation See File#flock
118
+ # @return see File#flock
119
+ def flock(operation)
120
+ if (operation & File::LOCK_UN) > 0
121
+ unlock
122
+ else
123
+ lock(operation)
124
+ end
125
+ end
126
+
127
+ # @param [PARAM] operation See File#flock
128
+ # @yield
129
+ # @raise [AlreadyLocked] if already locked and LOCK_NB operation is specified
130
+ # @return [Object] the result of block
131
+ def synchronize(operation)
132
+ raise 'Must be called with a block' unless block_given?
133
+
134
+ begin
135
+ raise AlreadyLocked if lock(operation) == false
136
+ yield
137
+ ensure
138
+ unlock
139
+ end
140
+ end
141
+
142
+ # @return [Boolean] true if locked by self
143
+ def self_locked?
144
+ @mutex.synchronize { @counts[Thread.current] >= 1 }
145
+ end
146
+
147
+ private
148
+
149
+ def lock(operation)
150
+ c = incr
151
+ if c <= 1
152
+ # flock returns 0 if successfully locked.
153
+ # LOCK_NB returns false if somebody else already locked
154
+ fp.flock(operation)
155
+ else
156
+ 0
157
+ end
158
+ end
159
+
160
+ def unlock
161
+ c = decr
162
+ if c <= 0
163
+ fp.flock(File::LOCK_UN)
164
+ del
165
+ end
166
+ end
167
+
168
+ def incr
169
+ @mutex.synchronize { @counts[Thread.current] += 1 }
170
+ end
171
+
172
+ def decr
173
+ @mutex.synchronize { @counts[Thread.current] -= 1 }
174
+ end
175
+
176
+ def del
177
+ @mutex.synchronize { @counts.delete(Thread.current) }
178
+ end
179
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "reentrant_flock/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "reentrant_flock"
8
+ spec.version = ReentrantFlock::VERSION
9
+ spec.authors = ["Naotoshi Seo"]
10
+ spec.email = ["sonots@gmail.com"]
11
+
12
+ spec.summary = %q{A reentrant/recursive flock.}
13
+ spec.description = %q{A reentrant/recursive flock.}
14
+ spec.homepage = "https://github.com/sonots/reentrant_flock"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.15"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "test-unit"
26
+ spec.add_development_dependency "test-unit-power_assert"
27
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reentrant_flock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Naotoshi Seo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-09-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit-power_assert
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A reentrant/recursive flock.
70
+ email:
71
+ - sonots@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - lib/reentrant_flock.rb
85
+ - lib/reentrant_flock/version.rb
86
+ - reentrant_flock.gemspec
87
+ homepage: https://github.com/sonots/reentrant_flock
88
+ licenses: []
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.6.13
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A reentrant/recursive flock.
110
+ test_files: []