lockf.rb 2.1.0 → 3.0.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: b0f68d54d3ad30fc3c9c6316910adebcd74645876546aec01c86402261275ff6
4
- data.tar.gz: ed9cbe6b94840dd3987d16825737b3e98c42efb4904f15fd2ef206ebd0ab431a
3
+ metadata.gz: 6002e2f48aae8711b6171d35f4458aab8144d287395c29c7072a71d0d7f5e5ca
4
+ data.tar.gz: f2d13d124949cb8bb12e2d3116fc24a9dc6b15df4d48212d51fc9adb52bf5878
5
5
  SHA512:
6
- metadata.gz: 663fb2919e2560b75307ce6ea01291582e61fca0fa14f9c86f54dfd7ab598a2301b361be09952800a93089a5010f53dcd8ee76870ba1cc44ff7470ef1295abb0
7
- data.tar.gz: a249a8c95037378796bbc8cb4fd3224da60137117902ac189546860791bfa9b1256a26636895762ffbcd3a74037e4bb38c26d6aefe57721ca6d603a0022f1128
6
+ metadata.gz: 8000aea494d1af0f6ce3f17b68cd37e35fc486638338038684c0ce29a8ff99f507df065100fbd9a8a49131db263b18999235807e5e8f4155c72a1e54370dda66
7
+ data.tar.gz: 70fa6fc1d3251d991124aebf9dccd07b6e7f449d2140d0bc173c551bfb86f4416a11af12bde81b5dc15aee0ed9b340ff651b1fbc02fa7cdd54701b3d0536effc
data/README.md CHANGED
@@ -1,56 +1,93 @@
1
1
  ## About
2
2
 
3
- lockf.rb provides Ruby bindings for
4
- [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3).
3
+ lockf.rb offers Ruby bindings for the advisory-mode lock
4
+ provided by the
5
+ [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
6
+ function. It is similar to flock(2) in spirit but it also has semantic
7
+ differences that can be desirable when used across the fork(2) boundary
8
+ and that is usually the main reason to use this library.
9
+
10
+ ## Background
11
+
12
+ The primary difference between lockf(3) and flock(2) in practical terms
13
+ is that locks created with lockf(3) persist across fork(2). That is to
14
+ say, if a parent process acquires a lock and then forks a child process,
15
+ the child process will have to wait until the parent process releases
16
+ the lock before it can acquire the same lock. This is not the case with
17
+ flock(2).
18
+
19
+ The technical explanation is that lockf(3) creates a lock that is owned
20
+ by the process rather than the open file description (as is the case with
21
+ flock(2)). Since a lock belongs to the process, it cannot be acquired
22
+ by more than one process at a time, and with flock(2) the acquisition
23
+ of a lock won't block as long as the lock is held by the same open file
24
+ description, which is the case after fork(2).
25
+
26
+ To the best of my knowledge, Ruby does not provide built-in support for
27
+ lockf(3) so the library fills that gap.
28
+
29
+ ## Features
30
+
31
+ * Pure Ruby bindings with zero dependencies outside Ruby's standard library
32
+ * Temporary, unlinked locks backed by a Tempfile
33
+ * Blocking and non-blocking locks
34
+ * Low-level abstraction
5
35
 
6
36
  ## Examples
7
37
 
8
- ### Lock::File
38
+ ### Synchronization
9
39
 
10
- __Blocking__
40
+ #### Synchronize
11
41
 
12
- [Lock::File#lock](http://0x1eef.github.io/x/lockf.rb/Lock/File.html#lock-instance_method)
13
- can be used to acquire a lock.
14
- [Lock::File.temporary_file](http://0x1eef.github.io/x/lockf.rb/Lock/File.html#temporary_file-class_method)
15
- returns a lock for an unlinked temporary file.
16
- [Lock::File#lock](http://0x1eef.github.io/x/lockf.rb/Lock/File.html#lock-instance_method)
17
- will block when another
18
- process has acquired a lock beforehand:
42
+ The `synchronize` and `synchronize!` methods provide both a locking,
43
+ and non-blocking lock that is released automatically when the
44
+ block yields and returns control to the caller. The non-blocking
45
+ form (`synchronize!`) raises a subclass of SystemCallError if the
46
+ lock cannot be acquired right away.
47
+
48
+ With all that said, the next example uses the blocking-form of
49
+ these two methods, the synchronize method, to create a critical
50
+ section that is executed serially by multiple processes. The example
51
+ creates a lock that is backed by a randomly named, unlinked temporary
52
+ file:
19
53
 
20
54
  ```ruby
21
55
  #!/usr/bin/env ruby
22
- require "lock/file"
23
-
24
- lockf = Lock::File.temporary_file
25
- lockf.lock
26
- print "Lock acquired by parent process (#{Time.now.utc})", "\n"
27
- fork do
28
- print "Child process waiting on lock (#{Time.now.utc})", "\n"
29
- lockf.lock
30
- print "Lock acquired by child process (#{Time.now.utc})", "\n"
56
+ require "lockf"
57
+
58
+ lockf = Lockf.unlinked
59
+ 5.times do
60
+ Process.detach fork {
61
+ lockf.synchronize do
62
+ print "Lock acquired (pid #{Process.pid})", "\n"
63
+ sleep 5
64
+ end
65
+ }
31
66
  end
32
- sleep(3)
33
- lockf.release
34
- Process.wait
35
67
 
36
68
  ##
37
- # Lock acquired by parent process (2023-02-11 16:43:15 UTC)
38
- # Child process waiting on lock (2023-02-11 16:43:15 UTC)
39
- # Lock acquired by child process (2023-02-11 16:43:18 UTC)
69
+ # Lock acquired (pid 12345)
70
+ # Lock acquired (pid 12346)
71
+ # ...
40
72
  ```
41
73
 
42
- __Non-blocking__
74
+ #### Procedural
75
+
76
+ When more control over the lock and release process is required,
77
+ the `lock`, `lock_nonblock`, and `release` methods can be used
78
+ to acquire and release locks procedurally without a block.
43
79
 
44
- [Lock::File#lock_nonblock](http://0x1eef.github.io/x/lockf.rb/Lock/File.html#lock_nonblock-instance_method)
45
- can be used to acquire a lock without blocking. When it is found
46
- that acquiring a lock would block the method will raise an
47
- exception (`Errno::EAGAIN` / `Errno::EWOULDBLOCK`) instead:
80
+ The following example is different from the last one in that it
81
+ uses the procedural style, and rather than acquiring a blocking
82
+ lock the acquire in this example is non-blocking. When the lock
83
+ is found to block, a system-specific subclass of SystemCallError
84
+ will be raised:
48
85
 
49
86
  ```ruby
50
87
  #!/usr/bin/env ruby
51
- require "lock/file"
88
+ require "lockf"
52
89
 
53
- lockf = Lock::File.temporary_file
90
+ lockf = Lockf.unlinked
54
91
  lockf.lock_nonblock
55
92
  print "Lock acquired by parent process (#{Time.now.utc})", "\n"
56
93
  fork do
@@ -73,33 +110,6 @@ Process.wait
73
110
  # Lock acquired by child process (2023-02-11 19:03:08 UTC)
74
111
  ```
75
112
 
76
- ### Lock::File::FFI
77
-
78
- __lockf__
79
-
80
- [Lock::File::FFI.lockf](http://0x1eef.freebsd.home.network/x/lockf.rb/Lock/File/FFI.html#lockf-instance_method)
81
- provides a direct interface to
82
- [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
83
- that is more or less equivalent to how the function would be called
84
- from C:
85
-
86
- ```ruby
87
- #!/usr/bin/env ruby
88
- require "lock/file"
89
- require "tempfile"
90
-
91
- file = Tempfile.new("lockf-ffi").tap(&:unlink)
92
- Lock::File::FFI.lockf(file, Lock::File::F_LOCK, 0)
93
- print "Lock acquired", "\n"
94
- Lock::File::FFI.lockf(file, Lock::File::F_ULOCK, 0)
95
- print "Lock released", "\n"
96
- file.close
97
-
98
- ##
99
- # Lock acquired
100
- # Lock released
101
- ```
102
-
103
113
  ## Documentation
104
114
 
105
115
  A complete API reference is available at
@@ -113,8 +123,8 @@ lockf.rb can be installed via rubygems.org:
113
123
 
114
124
  ## Sources
115
125
 
116
- * [GitHub](https://github.com/0x1eef/lockf.rb#readme)
117
- * [GitLab](https://gitlab.com/0x1eef/lockf.rb#about)
126
+ * [github.com/@0x1eef](https://github.com/0x1eef/lockf.rb#readme)
127
+ * [gitlab.com/@0x1eef](https://gitlab.com/0x1eef/lockf.rb#about)
118
128
 
119
129
  ## License
120
130
 
data/Rakefile.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "bundler/setup"
2
+ require "bundler/gem_tasks"
2
3
 
3
4
  namespace :format do
4
5
  desc "Apply rubocop fixes"
@@ -18,6 +19,10 @@ task ci: %i[format:check test]
18
19
 
19
20
  desc "Run tests"
20
21
  task :test do
21
- sh "bin/test-runner"
22
+ sh <<-SHELL
23
+ for t in test/*_test.rb; do
24
+ bundle exec ruby ${t} || exit 1
25
+ done
26
+ SHELL
22
27
  end
23
28
  task default: %w[test]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Lock::File
3
+ class Lockf
4
4
  # The constants found in this module are defined
5
5
  # by unistd.h. Their documentation can be found in
6
6
  # the
data/lib/lockf/ffi.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lockf
4
+ module FFI
5
+ require "fiddle"
6
+ include Fiddle::Types
7
+ extend self
8
+
9
+ ##
10
+ # The common superclass for FFI errors
11
+ Error = Class.new(RuntimeError)
12
+
13
+ ##
14
+ # The error raised when dlopen(3) fails
15
+ LinkError = Class.new(Error)
16
+
17
+ @libc ||= begin
18
+ globs = %w[
19
+ /lib/libc.so.*
20
+ /usr/lib/libc.so.*
21
+ /lib/x86_64-linux-gnu/libc.so.*
22
+ /lib/i386-linux-gnu/libc.so.*
23
+ ]
24
+ Fiddle.dlopen(Dir[*globs].first)
25
+ rescue => ex
26
+ raise LinkError, "link of libc had an error", cause: ex
27
+ end unless defined?(@libc)
28
+
29
+ @function = Fiddle::Function.new(
30
+ @libc["lockf"],
31
+ [INT, INT, INT],
32
+ INT
33
+ ) unless defined?(@function)
34
+
35
+ ##
36
+ # Provides a Ruby interface for lockf(3)
37
+ # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
38
+ # @param [Integer, #fileno] fd
39
+ # @param [Integer] fn
40
+ # @param [Integer] size
41
+ # @raise [SystemCallError]
42
+ # Might raise a subclass of SystemCallError
43
+ # @return [Boolean]
44
+ # Returns true when successful
45
+ def lockf(fd, fn, size = 0)
46
+ fileno = fd.respond_to?(:fileno) ? fd.fileno : fd
47
+ Lockf::FFI.function.call(fileno, fn, size).zero? ||
48
+ raise(SystemCallError.new("lockf", Fiddle.last_error))
49
+ end
50
+
51
+ ##
52
+ # @api private
53
+ def self.function = @function
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lockf
4
+ VERSION = "3.0.0"
5
+ end
data/lib/lockf.rb CHANGED
@@ -1 +1,146 @@
1
- require_relative "lock/file"
1
+ ##
2
+ # {Lockf Lockf} provides an object-oriented
3
+ # [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
4
+ # interface
5
+ class Lockf
6
+ require_relative "lockf/version"
7
+ require_relative "lockf/ffi"
8
+ require_relative "lockf/constants"
9
+
10
+ include FFI
11
+ include Constants
12
+
13
+ ##
14
+ # Accepts the same parameters as Tempfile.new
15
+ # @example
16
+ # lockf = Lockf.unlinked
17
+ # lockf.lock
18
+ # # ...
19
+ # @return [Lockf]
20
+ # Returns a {Lockf Lockf} for a random,
21
+ # unlinked temporary file
22
+ def self.unlinked(...)
23
+ require "tempfile" unless defined?(Tempfile)
24
+ Lockf.new Tempfile.new(...).tap(&:unlink)
25
+ end
26
+
27
+ ##
28
+ # @return [<#fileno>]
29
+ # Returns a file handle
30
+ attr_reader :file
31
+
32
+ ##
33
+ # @param [<#fileno>] file
34
+ # @param [Integer] size
35
+ # @return [Lockf]
36
+ # Returns an instance of {Lockf Lockf}
37
+ def initialize(file, size = 0)
38
+ @file = file
39
+ @size = size
40
+ end
41
+
42
+ ##
43
+ # Acquire lock (blocking)
44
+ # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
45
+ # @raise [SystemCallError]
46
+ # Might raise a subclass of SystemCallError
47
+ # @return [Boolean]
48
+ # Returns true when successful
49
+ def lock
50
+ try(function: F_LOCK)
51
+ end
52
+
53
+ ##
54
+ # Acquire lock (non-blocking)
55
+ # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
56
+ # @raise [SystemCallError]
57
+ # Might raise a subclass of SystemCallError
58
+ # @return [Boolean]
59
+ # Returns true when successful
60
+ def lock_nonblock
61
+ try(function: F_TLOCK)
62
+ end
63
+
64
+ ##
65
+ # Release lock
66
+ # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
67
+ # @raise [SystemCallError]
68
+ # Might raise a subclass of SystemCallError
69
+ # @return [Boolean]
70
+ # Returns true when successful
71
+ def release
72
+ try(function: F_ULOCK)
73
+ end
74
+
75
+ ##
76
+ # Acquire a blocking lock, yield, and finally release the lock
77
+ # @example
78
+ # lockf = Lockf.unlinked
79
+ # lockf.synchronize do
80
+ # # critical section
81
+ # end
82
+ # @raise [SystemCallError]
83
+ # Might raise a subclass of SystemCallError
84
+ # @return [Object]
85
+ # Returns the return value of the block
86
+ def synchronize
87
+ locked = false
88
+ lock
89
+ locked = true
90
+ yield
91
+ ensure
92
+ release if locked
93
+ end
94
+
95
+ ##
96
+ # Acquire a non-blocking lock, yield, and finally release the lock
97
+ # @note
98
+ # If the lock cannot be acquired, an exception is raised immediately
99
+ # @example
100
+ # lockf = Lockf.unlinked
101
+ # lockf.synchronize! do
102
+ # # critical section
103
+ # end
104
+ # @raise [SystemCallError]
105
+ # Might raise a subclass of SystemCallError
106
+ # @return [Object]
107
+ # Returns the return value of the block
108
+ def synchronize!
109
+ locked = false
110
+ lock_nonblock
111
+ locked = true
112
+ yield
113
+ ensure
114
+ release if locked
115
+ end
116
+
117
+ ##
118
+ # @return [Boolean]
119
+ # Returns true when lock can be acquired
120
+ def lockable?
121
+ try(function: F_TEST)
122
+ true
123
+ rescue Errno::EACCES, Errno::EAGAIN, Errno::EWOULDBLOCK
124
+ false
125
+ end
126
+
127
+ ##
128
+ # Closes {Lockf#file Lockf#file}
129
+ # @example
130
+ # # Equivalent to:
131
+ # lockf = Lockf.unlinked
132
+ # lockf.file.close
133
+ # @return [void]
134
+ def close
135
+ @file.respond_to?(:close) ? @file.close : nil
136
+ end
137
+
138
+ private
139
+
140
+ def try(function: F_LOCK, attempts: 3)
141
+ lockf(@file, function, @size)
142
+ rescue Errno::EINTR => ex
143
+ attempts -= 1
144
+ (attempts == 0) ? raise(ex) : retry
145
+ end
146
+ end
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "lock/file"
5
- lockf = Lock::File.temporary_file
4
+ require "lockf"
5
+ lockf = Lockf.unlinked
6
6
  lockf.lock
7
7
  print "Lock acquired by parent process (#{Time.now.utc})", "\n"
8
8
  fork do
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "lock/file"
5
- lockf = Lock::File.temporary_file
4
+ require "lockf"
5
+ lockf = Lockf.unlinked
6
6
  lockf.lock_nonblock
7
7
  print "Lock acquired by parent process (#{Time.now.utc})", "\n"
8
8
  fork do
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "lock/file"
4
+ require "lockf"
5
5
  require "tempfile"
6
6
  file = Tempfile.new("lockf-ffi").tap(&:unlink)
7
- Lock::File::FFI.lockf(file, Lock::File::F_LOCK, 0)
7
+ Lockf::FFI.lockf(file, Lockf::F_LOCK, 0)
8
8
  print "Lock acquired", "\n"
9
- Lock::File::FFI.lockf(file, Lock::File::F_ULOCK, 0)
9
+ Lockf::FFI.lockf(file, Lockf::F_ULOCK, 0)
10
10
  print "Lock released", "\n"
11
11
  file.close
12
12
 
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "lock/file"
4
+ require "lockf"
5
5
  require "tempfile"
6
6
  file = Tempfile.new("lockf-ffi").tap(&:unlink)
7
- Lock::File::FFI.lockf(file, Lock::File::F_TLOCK, 0)
7
+ Lockf::FFI.lockf(file, Lockf::F_TLOCK, 0)
8
8
  print "Lock acquired", "\n"
9
- Lock::File::FFI.lockf(file, Lock::File::F_ULOCK, 0)
9
+ Lockf::FFI.lockf(file, Lockf::F_ULOCK, 0)
10
10
  print "Lock released", "\n"
11
11
  file.close
12
12
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lockf.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - '0x1eef'
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-06-29 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: fiddle
@@ -24,6 +23,20 @@ dependencies:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
25
  version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '='
31
+ - !ruby/object:Gem::Version
32
+ version: 13.0.6
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 13.0.6
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: yard
29
42
  requirement: !ruby/object:Gem::Requirement
@@ -101,36 +114,21 @@ executables: []
101
114
  extensions: []
102
115
  extra_rdoc_files: []
103
116
  files:
104
- - ".bundle/config"
105
- - ".github/workflows/tests.yml"
106
- - ".gitignore"
107
- - ".projectile"
108
- - ".rubocop.yml"
109
- - ".yardopts"
110
- - Gemfile
111
117
  - LICENSE
112
118
  - README.md
113
119
  - Rakefile.rb
114
- - bin/test-runner
115
- - lib/lock/file.rb
116
- - lib/lock/file/constants.rb
117
- - lib/lock/file/ffi.rb
118
- - lib/lock/file/version.rb
119
120
  - lib/lockf.rb
120
- - lib/lockfile.rb
121
- - lockf.rb.gemspec
121
+ - lib/lockf/constants.rb
122
+ - lib/lockf/ffi.rb
123
+ - lib/lockf/version.rb
122
124
  - share/lockf.rb/examples/1_lock_file_blocking_variant.rb
123
125
  - share/lockf.rb/examples/2_lock_file_nonblocking_variant.rb
124
126
  - share/lockf.rb/examples/3_ffi_lockf_blocking_variant.rb
125
127
  - share/lockf.rb/examples/4_ffi_lockf_nonblocking_variant.rb
126
- - test/lock_file_test.rb
127
- - test/readme_test.rb
128
- - test/setup.rb
129
128
  homepage: https://github.com/0x1eef/lockf.rb#readme
130
129
  licenses:
131
130
  - 0BSD
132
131
  metadata: {}
133
- post_install_message:
134
132
  rdoc_options: []
135
133
  require_paths:
136
134
  - lib
@@ -145,8 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
143
  - !ruby/object:Gem::Version
146
144
  version: '0'
147
145
  requirements: []
148
- rubygems_version: 3.5.11
149
- signing_key:
146
+ rubygems_version: 3.6.9
150
147
  specification_version: 4
151
148
  summary: Ruby bindings for lockf(3)
152
149
  test_files: []
data/.bundle/config DELETED
@@ -1,2 +0,0 @@
1
- ---
2
- BUNDLE_PATH: ".gems"
@@ -1,23 +0,0 @@
1
- name: lockf.rb
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- specs:
11
- strategy:
12
- fail-fast: false
13
- matrix:
14
- os: [ubuntu-latest, macos-latest]
15
- ruby: [3.2, 3.3]
16
- runs-on: ${{ matrix.os }}
17
- steps:
18
- - uses: actions/checkout@v2
19
- - uses: ruby/setup-ruby@v1
20
- with:
21
- ruby-version: ${{ matrix.ruby }}
22
- - run: bundle install
23
- - run: ruby -S rake ci
data/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- /*.lock
2
- /.yardoc/
3
- /.gems/
4
- /doc/
data/.projectile DELETED
@@ -1,3 +0,0 @@
1
- +./
2
- +.github/
3
- -.gems/
data/.rubocop.yml DELETED
@@ -1,34 +0,0 @@
1
- ##
2
- # Plugins
3
- require:
4
- - standard
5
-
6
- ##
7
- # Defaults: standard-rb
8
- inherit_gem:
9
- standard: config/base.yml
10
-
11
- ##
12
- # Disabled cops
13
- Layout/MultilineMethodCallIndentation:
14
- Enabled: false
15
- Layout/ArgumentAlignment:
16
- Enabled: false
17
- Layout/MultilineArrayBraceLayout:
18
- Enabled: false
19
- Layout/ExtraSpacing:
20
- Enabled: false
21
- Style/MultilineIfModifier:
22
- Enabled: false
23
- Layout/ArrayAlignment:
24
- Enabled: false
25
-
26
- ##
27
- # Options for all cops.
28
- AllCops:
29
- TargetRubyVersion: 3.2
30
- Include:
31
- - lib/*.rb
32
- - lib/**/*.rb
33
- - test/*.rb
34
- - share/lockf.rb/examples/*.rb
data/.yardopts DELETED
@@ -1,4 +0,0 @@
1
- -m markdown -M redcarpet --no-private
2
- -
3
- README.md
4
- LICENSE
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source "https://rubygems.org"
2
- gemspec
data/bin/test-runner DELETED
@@ -1,6 +0,0 @@
1
- #!/bin/sh
2
- set -e
3
-
4
- for t in test/*_test.rb; do
5
- ruby -Itest "${t}"
6
- done
data/lib/lock/file/ffi.rb DELETED
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lock::File
4
- module FFI
5
- require "fiddle"
6
- include Fiddle::Types
7
- extend self
8
-
9
- ##
10
- # Provides a Ruby interface for lockf(3)
11
- #
12
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
13
- # @param [Integer, #fileno] fd
14
- # @param [Integer] function
15
- # @param [Integer] size
16
- # @raise [SystemCallError]
17
- # Might raise a subclass of SystemCallError
18
- # @return [Boolean]
19
- # Returns true when successful
20
- def lockf(fd, function, size = 0)
21
- fileno = fd.respond_to?(:fileno) ? fd.fileno : fd
22
- Fiddle::Function.new(
23
- libc["lockf"],
24
- [INT, INT, INT],
25
- INT
26
- ).call(fileno, function, size)
27
- .zero? || raise(SystemCallError.new("lockf", Fiddle.last_error))
28
- end
29
-
30
- ##
31
- # @return [Fiddle::Handle]
32
- def libc
33
- @libc ||= begin
34
- globs = %w[
35
- /lib/libc.so.*
36
- /usr/lib/libc.so.*
37
- /lib/x86_64-linux-gnu/libc.so.*
38
- /lib/i386-linux-gnu/libc.so.*
39
- ]
40
- Fiddle.dlopen(Dir[*globs].first)
41
- end
42
- end
43
- end
44
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lock::File
4
- VERSION = "2.1.0"
5
- end
data/lib/lock/file.rb DELETED
@@ -1,109 +0,0 @@
1
- module Lock
2
- end unless defined?(Lock)
3
-
4
- ##
5
- # {Lock::File Lock::File} provides an object-oriented
6
- # [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
7
- # interface
8
- class Lock::File
9
- require_relative "file/version"
10
- require_relative "file/ffi"
11
- require_relative "file/constants"
12
-
13
- include FFI
14
- include Constants
15
-
16
- ##
17
- # Accepts the same parameters as Tempfile.new
18
- #
19
- # @example
20
- # lockf = Lock::File.temporary_file
21
- # lockf.lock
22
- # # ...
23
- #
24
- # @return [Lock::File]
25
- # Returns a {Lock::File Lock::File} for a random,
26
- # unlinked temporary file
27
- def self.temporary_file(...)
28
- require "tempfile" unless defined?(Tempfile)
29
- Lock::File.new Tempfile.new(...).tap(&:unlink)
30
- end
31
-
32
- ##
33
- # @return [<#fileno>]
34
- # Returns a file handle
35
- attr_reader :file
36
-
37
- ##
38
- # @param [<#fileno>] file
39
- # @param [Integer] size
40
- # @return [Lock::File]
41
- # Returns an instance of {Lock::File Lock::File}
42
- def initialize(file, size = 0)
43
- @file = file
44
- @size = size
45
- end
46
-
47
- ##
48
- # Acquire lock (blocking)
49
- #
50
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
51
- # @raise [SystemCallError]
52
- # Might raise a subclass of SystemCallError
53
- # @return [Boolean]
54
- # Returns true when successful
55
- def lock
56
- tries ||= 0
57
- lockf(@file, F_LOCK, @size)
58
- rescue Errno::EINTR => ex
59
- tries += 1
60
- (tries == 3) ? raise(ex) : retry
61
- end
62
-
63
- ##
64
- # Acquire lock (non-blocking)
65
- #
66
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
67
- # @raise [SystemCallError]
68
- # Might raise a subclass of SystemCallError
69
- # @return [Boolean]
70
- # Returns true when successful
71
- def lock_nonblock
72
- lockf(@file, F_TLOCK, @size)
73
- end
74
-
75
- ##
76
- # Release lock
77
- #
78
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
79
- # @raise [SystemCallError]
80
- # Might raise a subclass of SystemCallError
81
- # @return [Boolean]
82
- # Returns true when successful
83
- def release
84
- lockf(@file, F_ULOCK, @size)
85
- end
86
-
87
- ##
88
- # @return [Boolean]
89
- # Returns true when lock is held by another process
90
- def locked?
91
- lockf(@file, F_TEST, @size)
92
- false
93
- rescue Errno::EACCES, Errno::EAGAIN
94
- true
95
- end
96
-
97
- ##
98
- # Closes {Lock::File#file Lock::File#file}
99
- #
100
- # @example
101
- # # Equivalent to:
102
- # lockf = Lock::File.temporary_file
103
- # lockf.file.close
104
- # @return [void]
105
- def close
106
- return unless @file.respond_to?(:close)
107
- @file.close
108
- end
109
- end
data/lib/lockfile.rb DELETED
@@ -1 +0,0 @@
1
- require_relative "lock/file"
data/lockf.rb.gemspec DELETED
@@ -1,21 +0,0 @@
1
- require_relative "lib/lockfile"
2
-
3
- Gem::Specification.new do |gem|
4
- gem.name = "lockf.rb"
5
- gem.authors = ["0x1eef"]
6
- gem.email = ["0x1eef@protonmail.com"]
7
- gem.homepage = "https://github.com/0x1eef/lockf.rb#readme"
8
- gem.version = Lock::File::VERSION
9
- gem.licenses = ["0BSD"]
10
- gem.files = `git ls-files`.split($/)
11
- gem.require_paths = ["lib"]
12
- gem.summary = "Ruby bindings for lockf(3)"
13
- gem.description = gem.summary
14
-
15
- gem.add_runtime_dependency "fiddle", "~> 1.1"
16
- gem.add_development_dependency "yard", "~> 0.9"
17
- gem.add_development_dependency "standard", "~> 1.39"
18
- gem.add_development_dependency "rubocop", "~> 1.29"
19
- gem.add_development_dependency "test-unit", "~> 3.5"
20
- gem.add_development_dependency "test-cmd.rb", "~> 0.12"
21
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "setup"
4
-
5
- class Lock::File::Test < Test::Unit::TestCase
6
- attr_reader :file
7
- attr_reader :lockf
8
-
9
- def setup
10
- @file = Tempfile.new("lockf-test").tap(&:unlink)
11
- @lockf = Lock::File.new(file)
12
- end
13
-
14
- def teardown
15
- file.close
16
- end
17
-
18
- ##
19
- # LockFile#lock
20
- def test_lock
21
- assert_equal true, lockf.lock
22
- ensure
23
- lockf.release
24
- end
25
-
26
- def test_lock_in_fork
27
- pid = fork_sleep { lockf.lock }
28
- sleep(0.1)
29
- assert_raises(Errno::EWOULDBLOCK) { lockf.lock_nonblock }
30
- ensure
31
- Process.kill("KILL", pid)
32
- lockf.release
33
- end
34
-
35
- ##
36
- # LockFile#lock_nonblock
37
- def test_lock_nonblock
38
- assert_equal true, lockf.lock_nonblock
39
- ensure
40
- lockf.release
41
- end
42
-
43
- def test_lock_nonblock_in_fork
44
- pid = fork_sleep { lockf.lock_nonblock }
45
- sleep(0.1)
46
- assert_raises(Errno::EWOULDBLOCK) { lockf.lock_nonblock }
47
- ensure
48
- Process.kill("KILL", pid)
49
- lockf.release
50
- end
51
-
52
- ##
53
- # LockFile#locked?
54
- def test_locked?
55
- pid = fork_sleep { lockf.lock }
56
- sleep(0.1)
57
- assert_equal true, lockf.locked?
58
- ensure
59
- Process.kill("KILL", pid)
60
- lockf.release
61
- end
62
-
63
- ##
64
- # LockFile.temporary_file
65
- def test_temporary_file
66
- lockf = Lock::File.temporary_file
67
- assert_equal true, lockf.lock
68
- assert_equal true, lockf.release
69
- ensure
70
- lockf.file.close
71
- end
72
-
73
- private
74
-
75
- def fork_sleep
76
- fork do
77
- yield
78
- sleep
79
- end
80
- end
81
- end
data/test/readme_test.rb DELETED
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "setup"
4
- require "test/cmd"
5
-
6
- class Lock::File::ReadmeTest < Test::Unit::TestCase
7
- def test_lockfile_blocking_variant
8
- r = ruby(readme_example("1_lock_file_blocking_variant.rb"))
9
- ["Lock acquired by parent process (.+)\n",
10
- "Child process waiting on lock (.+)\n",
11
- "Lock acquired by child process (.+)\n"
12
- ].each { assert_match Regexp.new(_1), r.stdout }
13
- end
14
-
15
- def test_lockfile_nonblocking_variant
16
- r = ruby(readme_example("2_lock_file_nonblocking_variant.rb"))
17
- ["Lock acquired by parent process (.+)\n",
18
- "(Lock would block\n){3,4}",
19
- "Lock acquired by child process (.+)\n"
20
- ].each { assert_match Regexp.new(_1), r.stdout }
21
- end
22
-
23
- def test_ffi_lockf_blocking_variant
24
- assert_equal "Lock acquired\nLock released\n",
25
- ruby(readme_example("3_ffi_lockf_blocking_variant.rb")).stdout
26
- end
27
-
28
- def test_ffi_lockf_nonblocking_variant
29
- assert_equal "Lock acquired\nLock released\n",
30
- ruby(readme_example("4_ffi_lockf_nonblocking_variant.rb")).stdout
31
- end
32
-
33
- private
34
-
35
- def ruby(*argv)
36
- cmd("ruby", *argv)
37
- end
38
-
39
- def readme_example(example_name)
40
- File.join(__dir__, "..", "share", "lockf.rb", "examples", example_name)
41
- end
42
- end
data/test/setup.rb DELETED
@@ -1,4 +0,0 @@
1
- require "bundler/setup"
2
- require "test/unit"
3
- require "lock/file"
4
- require "tempfile"