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 +4 -4
- data/README.md +72 -62
- data/Rakefile.rb +6 -1
- data/lib/{lock/file → lockf}/constants.rb +1 -1
- data/lib/lockf/ffi.rb +55 -0
- data/lib/lockf/version.rb +5 -0
- data/lib/lockf.rb +146 -1
- data/share/lockf.rb/examples/1_lock_file_blocking_variant.rb +2 -2
- data/share/lockf.rb/examples/2_lock_file_nonblocking_variant.rb +2 -2
- data/share/lockf.rb/examples/3_ffi_lockf_blocking_variant.rb +3 -3
- data/share/lockf.rb/examples/4_ffi_lockf_nonblocking_variant.rb +3 -3
- metadata +20 -23
- data/.bundle/config +0 -2
- data/.github/workflows/tests.yml +0 -23
- data/.gitignore +0 -4
- data/.projectile +0 -3
- data/.rubocop.yml +0 -34
- data/.yardopts +0 -4
- data/Gemfile +0 -2
- data/bin/test-runner +0 -6
- data/lib/lock/file/ffi.rb +0 -44
- data/lib/lock/file/version.rb +0 -5
- data/lib/lock/file.rb +0 -109
- data/lib/lockfile.rb +0 -1
- data/lockf.rb.gemspec +0 -21
- data/test/lock_file_test.rb +0 -81
- data/test/readme_test.rb +0 -42
- data/test/setup.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6002e2f48aae8711b6171d35f4458aab8144d287395c29c7072a71d0d7f5e5ca
|
|
4
|
+
data.tar.gz: f2d13d124949cb8bb12e2d3116fc24a9dc6b15df4d48212d51fc9adb52bf5878
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
4
|
-
|
|
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
|
-
###
|
|
38
|
+
### Synchronization
|
|
9
39
|
|
|
10
|
-
|
|
40
|
+
#### Synchronize
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 "
|
|
23
|
-
|
|
24
|
-
lockf =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
38
|
-
#
|
|
39
|
-
#
|
|
69
|
+
# Lock acquired (pid 12345)
|
|
70
|
+
# Lock acquired (pid 12346)
|
|
71
|
+
# ...
|
|
40
72
|
```
|
|
41
73
|
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 "
|
|
88
|
+
require "lockf"
|
|
52
89
|
|
|
53
|
-
lockf =
|
|
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
|
-
* [
|
|
117
|
-
* [
|
|
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
|
|
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]
|
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
|
data/lib/lockf.rb
CHANGED
|
@@ -1 +1,146 @@
|
|
|
1
|
-
|
|
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,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "
|
|
4
|
+
require "lockf"
|
|
5
5
|
require "tempfile"
|
|
6
6
|
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
|
7
|
-
|
|
7
|
+
Lockf::FFI.lockf(file, Lockf::F_LOCK, 0)
|
|
8
8
|
print "Lock acquired", "\n"
|
|
9
|
-
|
|
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 "
|
|
4
|
+
require "lockf"
|
|
5
5
|
require "tempfile"
|
|
6
6
|
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
|
7
|
-
|
|
7
|
+
Lockf::FFI.lockf(file, Lockf::F_TLOCK, 0)
|
|
8
8
|
print "Lock acquired", "\n"
|
|
9
|
-
|
|
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:
|
|
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:
|
|
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/
|
|
121
|
-
- lockf.rb
|
|
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.
|
|
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
data/.github/workflows/tests.yml
DELETED
|
@@ -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
data/.projectile
DELETED
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
data/Gemfile
DELETED
data/bin/test-runner
DELETED
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
|
data/lib/lock/file/version.rb
DELETED
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
|
data/test/lock_file_test.rb
DELETED
|
@@ -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