lockf.rb 0.12.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 +7 -0
- data/.bundle/config +2 -0
- data/.github/workflows/tests.yml +23 -0
- data/.gitignore +10 -0
- data/.projectile +3 -0
- data/.rubocop.yml +21 -0
- data/.uncrustify.cfg +2 -0
- data/.yardopts +4 -0
- data/Gemfile +3 -0
- data/LICENSE +15 -0
- data/README.md +165 -0
- data/Rakefile.rb +24 -0
- data/ext/lockf.rb/extconf.rb +12 -0
- data/ext/lockf.rb/lockf.c +36 -0
- data/ext/lockf.rb/lockf.h +1 -0
- data/lib/lockf/version.rb +3 -0
- data/lib/lockf.rb +140 -0
- data/lockf.rb.gemspec +24 -0
- data/share/lockf.rb/examples/1_lockfile_blocking_variant.rb +19 -0
- data/share/lockf.rb/examples/2_lockfile_nonblocking_variant.rb +24 -0
- data/share/lockf.rb/examples/3_ffilockf_blocking_variant.rb +13 -0
- data/share/lockf.rb/examples/4_ffilockf_nonblocking_variant.rb +13 -0
- data/test/lock_file_test.rb +92 -0
- data/test/readme_test.rb +42 -0
- data/test/setup.rb +6 -0
- metadata +194 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cc7a6787621983c5bcea9dc35a899eb064d8d09f20d905b90b8391e8fc018f64
|
4
|
+
data.tar.gz: e32dcc7d52786b2c1c2acdb652264b450506debd976583a2ff8f46a367b2efe9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b275ee5e97c06f6dfcfbf32074139b1514b3fe4795ab1669bdbb188336f0039aee368846146b6e4b6ef15fc41d0077816844cfcf772e52039584b5816d5e8cbb
|
7
|
+
data.tar.gz: ffab08187b049fd42ad5549e060c0ba5da31228212c1e5419c3c9640a8842c96617a20cd54ee6f14edc265a5b28ddf50bc00e8dc343493fce0ec6a96a388a3cb
|
data/.bundle/config
ADDED
@@ -0,0 +1,23 @@
|
|
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]
|
15
|
+
ruby: [3.2]
|
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: bundle exec rake
|
data/.gitignore
ADDED
data/.projectile
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
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
|
+
|
18
|
+
##
|
19
|
+
# Options for all cops.
|
20
|
+
AllCops:
|
21
|
+
TargetRubyVersion: 2.7
|
data/.uncrustify.cfg
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright (C) 2023 by 0x1eef <0x1eef@protonmail.com>
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this
|
4
|
+
software for any purpose with or without fee is hereby
|
5
|
+
granted.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
8
|
+
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
9
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
10
|
+
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
11
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
12
|
+
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
13
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
14
|
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
15
|
+
OF THIS SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
## About
|
2
|
+
|
3
|
+
lockf.rb is a C extension that provides a Ruby interface to
|
4
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3).
|
5
|
+
The
|
6
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
|
7
|
+
function implements an advisory-mode lock that can be placed on select
|
8
|
+
regions of a file, or on the entire contents of a file.
|
9
|
+
|
10
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
|
11
|
+
can be used to synchronize access to a file between multiple
|
12
|
+
processes, or be used more generally to synchronize access to a shared
|
13
|
+
resource being accessed by multiple processes at the same time. When used
|
14
|
+
generally,
|
15
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
|
16
|
+
can provide something similar to a mutex that works across multiple
|
17
|
+
processes rather than multiple threads.
|
18
|
+
|
19
|
+
## Examples
|
20
|
+
|
21
|
+
### LockFile
|
22
|
+
|
23
|
+
The
|
24
|
+
[`LockFile`](https://0x1eef.github.io/x/lockf.rb/LockFile.html)
|
25
|
+
class provides an abstract, Ruby-oriented interface to
|
26
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3).
|
27
|
+
|
28
|
+
__Blocking lock__
|
29
|
+
|
30
|
+
The `LockFile#lock` method can be used to acquire a lock. The method will
|
31
|
+
block when another process has acquired a lock beforehand:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require "lockf"
|
35
|
+
|
36
|
+
lockf = LockFile.temporary_file
|
37
|
+
lockf.lock
|
38
|
+
print "Lock acquired by parent process (#{Time.now.utc})", "\n"
|
39
|
+
pid = fork do
|
40
|
+
print "Child process waiting on lock (#{Time.now.utc})", "\n"
|
41
|
+
lockf.lock
|
42
|
+
print "Lock acquired by child process (#{Time.now.utc})", "\n"
|
43
|
+
end
|
44
|
+
sleep(3)
|
45
|
+
lockf.release
|
46
|
+
Process.wait(pid)
|
47
|
+
lockf.file.close
|
48
|
+
|
49
|
+
##
|
50
|
+
# Lock acquired by parent process (2023-02-11 16:43:15 UTC)
|
51
|
+
# Child process waiting on lock (2023-02-11 16:43:15 UTC)
|
52
|
+
# Lock acquired by child process (2023-02-11 16:43:18 UTC)
|
53
|
+
```
|
54
|
+
|
55
|
+
__Non-blocking lock__
|
56
|
+
|
57
|
+
The `LockFile#lock_nonblock` method can be used to acquire a lock
|
58
|
+
without blocking. When it is found that acquiring a lock would block
|
59
|
+
the method will raise an exception (ie `Errno::EAGAIN` /`Errno::EWOULDBLOCK`)
|
60
|
+
instead:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
require "lockf"
|
64
|
+
|
65
|
+
lockf = LockFile.temporary_file
|
66
|
+
lockf.lock_nonblock
|
67
|
+
print "Lock acquired by parent process (#{Time.now.utc})", "\n"
|
68
|
+
pid = fork do
|
69
|
+
lockf.lock_nonblock
|
70
|
+
print "Lock acquired by child process (#{Time.now.utc})", "\n"
|
71
|
+
rescue Errno::EWOULDBLOCK
|
72
|
+
print "Lock would block", "\n"
|
73
|
+
sleep 1
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
sleep 3
|
77
|
+
lockf.release
|
78
|
+
Process.wait(pid)
|
79
|
+
lockf.file.close
|
80
|
+
|
81
|
+
##
|
82
|
+
# Lock acquired by parent process (2023-02-11 19:03:05 UTC)
|
83
|
+
# Lock would block
|
84
|
+
# Lock would block
|
85
|
+
# Lock would block
|
86
|
+
# Lock acquired by child process (2023-02-11 19:03:08 UTC)
|
87
|
+
```
|
88
|
+
|
89
|
+
### LockFile.lockf
|
90
|
+
|
91
|
+
The
|
92
|
+
[`LockFile.lockf`](https://0x1eef.github.io/x/lockf.rb/LockFile.html#lockf-class_method)
|
93
|
+
method provides a direct interface to
|
94
|
+
[lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
|
95
|
+
that is more or less equivalent to how the function would be called
|
96
|
+
from C.
|
97
|
+
|
98
|
+
__Blocking lock__
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
require "lockf"
|
102
|
+
require "tempfile"
|
103
|
+
|
104
|
+
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
105
|
+
LockFile.lockf(file.fileno, LockFile::F_LOCK, 0)
|
106
|
+
print "Lock acquired", "\n"
|
107
|
+
LockFile.lockf(file.fileno, LockFile::F_ULOCK, 0)
|
108
|
+
print "Lock released", "\n"
|
109
|
+
file.close
|
110
|
+
|
111
|
+
##
|
112
|
+
# Lock acquired
|
113
|
+
# Lock released
|
114
|
+
```
|
115
|
+
|
116
|
+
__Non-blocking lock__
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
require "lockf"
|
120
|
+
require "tempfile"
|
121
|
+
|
122
|
+
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
123
|
+
LockFile.lockf(file.fileno, LockFile::F_TLOCK, 0)
|
124
|
+
print "Lock acquired", "\n"
|
125
|
+
LockFile.lockf(file.fileno, LockFile::F_ULOCK, 0)
|
126
|
+
print "Lock released", "\n"
|
127
|
+
file.close
|
128
|
+
|
129
|
+
##
|
130
|
+
# Lock acquired
|
131
|
+
# Lock released
|
132
|
+
```
|
133
|
+
|
134
|
+
## Sources
|
135
|
+
|
136
|
+
* [Source code (GitHub)](https://github.com/0x1eef/lockf.rb#readme)
|
137
|
+
* [Source code (GitLab)](https://gitlab.com/0x1eef/lockf.rb#about)
|
138
|
+
|
139
|
+
## Install
|
140
|
+
|
141
|
+
**Git**
|
142
|
+
|
143
|
+
lockf.rb is distributed as a RubyGem through its git repositories. <br>
|
144
|
+
[GitHub](https://github.com/0x1eef/lockf.rb),
|
145
|
+
and
|
146
|
+
[GitLab](https://gitlab.com/0x1eef/lockf.rb)
|
147
|
+
are available as sources.
|
148
|
+
|
149
|
+
``` ruby
|
150
|
+
# Gemfile
|
151
|
+
gem "lock.fb", github: "0x1eef/lockf.rb", tag: "v0.12.0"
|
152
|
+
```
|
153
|
+
|
154
|
+
**Rubygems.org**
|
155
|
+
|
156
|
+
lock.rb can also be installed via rubygems.org.
|
157
|
+
|
158
|
+
gem install lockf.rb
|
159
|
+
|
160
|
+
## License
|
161
|
+
|
162
|
+
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/).
|
163
|
+
<br>
|
164
|
+
See [LICENSE](./LICENSE).
|
165
|
+
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rake/extensiontask"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
namespace :linters do
|
5
|
+
desc "Run the C linter"
|
6
|
+
task :c do
|
7
|
+
sh "uncrustify -c .uncrustify.cfg --no-backup --replace ext/lockf.rb/*.c"
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Run the Ruby linter"
|
11
|
+
task :ruby do
|
12
|
+
sh "bundle exec rubocop -A Rakefile.rb lib/**/*.rb spec/**/*.rb"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
task lint: ["linters:c", "linters:ruby"]
|
16
|
+
|
17
|
+
Rake::ExtensionTask.new("lockf.rb")
|
18
|
+
|
19
|
+
Rake::TestTask.new do |t|
|
20
|
+
t.test_files = FileList["test/*_test.rb"]
|
21
|
+
t.verbose = true
|
22
|
+
t.warning = false
|
23
|
+
end
|
24
|
+
task default: %w[clobber compile test]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <unistd.h>
|
3
|
+
#include <errno.h>
|
4
|
+
#include "lockf.h"
|
5
|
+
|
6
|
+
static VALUE
|
7
|
+
lockf_lock(VALUE self, VALUE fd, VALUE cmd, VALUE len)
|
8
|
+
{
|
9
|
+
int result;
|
10
|
+
|
11
|
+
Check_Type(fd, T_FIXNUM);
|
12
|
+
Check_Type(cmd, T_FIXNUM);
|
13
|
+
Check_Type(len, T_FIXNUM);
|
14
|
+
errno = 0;
|
15
|
+
result = lockf(NUM2INT(fd), NUM2INT(cmd), NUM2INT(len));
|
16
|
+
if (result == -1) {
|
17
|
+
rb_syserr_fail(errno, "lockf");
|
18
|
+
} else {
|
19
|
+
return INT2NUM(result);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
void
|
24
|
+
Init_lockf(void)
|
25
|
+
{
|
26
|
+
VALUE cLockf, mFcntl;
|
27
|
+
|
28
|
+
rb_require("fcntl");
|
29
|
+
cLockf = rb_define_class("LockFile", rb_cObject);
|
30
|
+
mFcntl = rb_const_get(rb_cObject, rb_intern("Fcntl"));
|
31
|
+
rb_define_const(mFcntl, "F_LOCK", INT2NUM(F_LOCK));
|
32
|
+
rb_define_const(mFcntl, "F_TLOCK", INT2NUM(F_TLOCK));
|
33
|
+
rb_define_const(mFcntl, "F_ULOCK", INT2NUM(F_ULOCK));
|
34
|
+
rb_define_const(mFcntl, "F_TEST", INT2NUM(F_TEST));
|
35
|
+
rb_define_singleton_method(cLockf, "lockf", lockf_lock, 3);
|
36
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
void Init_lockf(void);
|
data/lib/lockf.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
##
|
2
|
+
# The
|
3
|
+
# [`LockFile`](https://0x1eef.github.io/x/lockf.rb/LockFile.html)
|
4
|
+
# class provides a Ruby-oriented interface to the C function
|
5
|
+
# [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3).
|
6
|
+
class LockFile
|
7
|
+
require "tmpdir"
|
8
|
+
require_relative "lockf.rb.so"
|
9
|
+
include Fcntl
|
10
|
+
|
11
|
+
##
|
12
|
+
# @!method self.lockf(fd, cmd, len)
|
13
|
+
# @example
|
14
|
+
# LockFile.lockf(5, LockFile::F_LOCK, 0)
|
15
|
+
#
|
16
|
+
# @param [Integer] fd
|
17
|
+
# A number that represents a file descriptor.
|
18
|
+
#
|
19
|
+
# @param [Integer] cmd
|
20
|
+
# {LockFile::F_LOCK}, {LockFile::F_TLOCK}, {LockFile::F_ULOCK}, or
|
21
|
+
# {LockFile::F_TEST}.
|
22
|
+
#
|
23
|
+
# @param [Integer] len
|
24
|
+
# The number of bytes to place a lock on.
|
25
|
+
# A value of "0" covers the entire file.
|
26
|
+
#
|
27
|
+
# @raise [SystemCallError]
|
28
|
+
# Might raise a number of Errno exception classes.
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
# Returns 0 on success.
|
32
|
+
#
|
33
|
+
# @see (https://man7.org/linux/man-pages/man3/lockf.3.html) lockf man page (Linux)
|
34
|
+
# @see (https://man.openbsd.org/lockf.3) lockf man page (OpenBSD)
|
35
|
+
# @see (https://www.freebsd.org/cgi/man.cgi?query=lockf) lockf man page (FreeBSD)
|
36
|
+
|
37
|
+
##
|
38
|
+
# @example
|
39
|
+
# lockf = LockFile.temporary_file
|
40
|
+
# lockf.lock
|
41
|
+
# lockf.release
|
42
|
+
# lockf.file.close
|
43
|
+
#
|
44
|
+
# @param [String] basename
|
45
|
+
# The basename of the temporary file.
|
46
|
+
#
|
47
|
+
# @param [String] tmpdir
|
48
|
+
# The path to the parent directory of the temporary file.
|
49
|
+
#
|
50
|
+
# @return [LockFile]
|
51
|
+
# Returns an instance of {LockFile LockFile} backed by an
|
52
|
+
# unlinked instance of Tempfile.
|
53
|
+
def self.from_temporary_file(basename: "lockf", tmpdir: Dir.tmpdir)
|
54
|
+
require "tempfile" unless defined?(Tempfile)
|
55
|
+
file = Tempfile.new(basename, tmpdir:).tap(&:unlink)
|
56
|
+
LockFile.new(file)
|
57
|
+
end
|
58
|
+
class << self
|
59
|
+
alias_method :temporary_file, :from_temporary_file
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# @return [<File, Tempfile, #fileno>]
|
64
|
+
# Returns a file object.
|
65
|
+
attr_reader :file
|
66
|
+
|
67
|
+
##
|
68
|
+
# @param [<File, TempFile, String, #fileno>] file
|
69
|
+
# The file to place a lock on.
|
70
|
+
#
|
71
|
+
# @param [Integer] len
|
72
|
+
# The number of bytes to place a lock on.
|
73
|
+
# A value of "0" covers the entire file.
|
74
|
+
#
|
75
|
+
# @return [LockFile]
|
76
|
+
# Returns an instance of {LockFile LockFile}.
|
77
|
+
def initialize(file, len = 0)
|
78
|
+
@file = String === file ? File.open(file, "r+") : file
|
79
|
+
@len = len
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Acquire a lock (blocking)
|
84
|
+
# @raise [Errno::EBADF]
|
85
|
+
# @raise [Errno::EDEADLK]
|
86
|
+
# @raise [Errno::EINTR]
|
87
|
+
# @raise [Errno::ENOLCK]
|
88
|
+
# @return [Integer]
|
89
|
+
def lock
|
90
|
+
attempts ||= 0
|
91
|
+
LockFile.lockf(@file.fileno, F_LOCK, @len)
|
92
|
+
rescue Errno::EINTR => ex
|
93
|
+
attempts += 1
|
94
|
+
attempts == 3 ? raise(ex) : retry
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Acquire a lock (non-blocking)
|
99
|
+
# @raise [Errno::EAGAIN]
|
100
|
+
# @raise [Errno::EBADF]
|
101
|
+
# @raise [Errno::ENOLCK]
|
102
|
+
# @raise [Errno::EINVAL]
|
103
|
+
# @return [Integer]
|
104
|
+
def lock_nonblock
|
105
|
+
LockFile.lockf(@file.fileno, F_TLOCK, @len)
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Release a lock
|
110
|
+
# @raise [Errno::EBADF]
|
111
|
+
# @raise [Errno::ENOLCK]
|
112
|
+
# @return [Integer]
|
113
|
+
def release
|
114
|
+
LockFile.lockf(@file.fileno, F_ULOCK, @len)
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# @return [Boolean]
|
119
|
+
# Returns true when a lock has been acquired by another process.
|
120
|
+
def locked?
|
121
|
+
LockFile.lockf(@file.fileno, F_TEST, @len)
|
122
|
+
false
|
123
|
+
rescue Errno::EACCES, Errno::EAGAIN
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Closes {LockFile#file LockFile#file}.
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# # Equivalent to:
|
132
|
+
# lockf = LockFile.temporary_file
|
133
|
+
# lockf.file.close
|
134
|
+
#
|
135
|
+
# @return [void]
|
136
|
+
def close
|
137
|
+
return unless @file.respond_to?(:close)
|
138
|
+
@file.close
|
139
|
+
end
|
140
|
+
end
|
data/lockf.rb.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "./lib/lockf/version"
|
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 = LockFile::VERSION
|
9
|
+
gem.licenses = ["0BSD"]
|
10
|
+
gem.files = `git ls-files`.split($/)
|
11
|
+
gem.require_paths = ["lib"]
|
12
|
+
gem.extensions = %w[ext/lockf.rb/extconf.rb]
|
13
|
+
gem.summary = "A Ruby interface for lockf(3)"
|
14
|
+
gem.description = gem.summary
|
15
|
+
gem.add_development_dependency "yard", "~> 0.9"
|
16
|
+
gem.add_development_dependency "redcarpet", "~> 3.5"
|
17
|
+
gem.add_development_dependency "standard", "= 1.12.1"
|
18
|
+
gem.add_development_dependency "rubocop", "= 1.29.1"
|
19
|
+
gem.add_development_dependency "test-unit", "~> 3.5.7"
|
20
|
+
gem.add_development_dependency "rake-compiler", "= 1.2.0"
|
21
|
+
gem.add_development_dependency "rack", "~> 3.0"
|
22
|
+
gem.add_development_dependency "rackup", "~> 2.1"
|
23
|
+
gem.add_development_dependency "test-cmd.rb", "~> 0.4"
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "lockf"
|
2
|
+
|
3
|
+
lockf = LockFile.from_temporary_file
|
4
|
+
lockf.lock
|
5
|
+
print "Lock acquired by parent process (#{Time.now.utc})", "\n"
|
6
|
+
pid = fork do
|
7
|
+
print "Child process waiting on lock (#{Time.now.utc})", "\n"
|
8
|
+
lockf.lock
|
9
|
+
print "Lock acquired by child process (#{Time.now.utc})", "\n"
|
10
|
+
end
|
11
|
+
sleep(3)
|
12
|
+
lockf.release
|
13
|
+
Process.wait(pid)
|
14
|
+
lockf.file.close
|
15
|
+
|
16
|
+
##
|
17
|
+
# Lock acquired by parent process (2023-02-11 16:43:15 UTC)
|
18
|
+
# Child process waiting on lock (2023-02-11 16:43:15 UTC)
|
19
|
+
# Lock acquired by child process (2023-02-11 16:43:18 UTC)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "lockf"
|
2
|
+
|
3
|
+
lockf = LockFile.from_temporary_file
|
4
|
+
lockf.lock_nonblock
|
5
|
+
print "Lock acquired by parent process (#{Time.now.utc})", "\n"
|
6
|
+
pid = fork do
|
7
|
+
lockf.lock_nonblock
|
8
|
+
print "Lock acquired by child process (#{Time.now.utc})", "\n"
|
9
|
+
rescue Errno::EWOULDBLOCK
|
10
|
+
sleep 1
|
11
|
+
print "Lock would block", "\n"
|
12
|
+
retry
|
13
|
+
end
|
14
|
+
sleep 3
|
15
|
+
lockf.release
|
16
|
+
Process.wait(pid)
|
17
|
+
lockf.file.close
|
18
|
+
|
19
|
+
##
|
20
|
+
# Lock acquired by parent process (2023-02-11 19:03:05 UTC)
|
21
|
+
# Lock would block
|
22
|
+
# Lock would block
|
23
|
+
# Lock would block
|
24
|
+
# Lock acquired by child process (2023-02-11 19:03:08 UTC)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "lockf"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
5
|
+
LockFile.lockf(file.fileno, LockFile::F_LOCK, 0)
|
6
|
+
print "Lock acquired", "\n"
|
7
|
+
LockFile.lockf(file.fileno, LockFile::F_ULOCK, 0)
|
8
|
+
print "Lock released", "\n"
|
9
|
+
file.close
|
10
|
+
|
11
|
+
##
|
12
|
+
# Lock acquired
|
13
|
+
# Lock released
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "lockf"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
file = Tempfile.new("lockf-ffi").tap(&:unlink)
|
5
|
+
LockFile.lockf(file.fileno, LockFile::F_TLOCK, 0)
|
6
|
+
print "Lock acquired", "\n"
|
7
|
+
LockFile.lockf(file.fileno, LockFile::F_ULOCK, 0)
|
8
|
+
print "Lock released", "\n"
|
9
|
+
file.close
|
10
|
+
|
11
|
+
##
|
12
|
+
# Lock acquired
|
13
|
+
# Lock released
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative "setup"
|
2
|
+
class LockFile::Test < Test::Unit::TestCase
|
3
|
+
include Timeout
|
4
|
+
include FileUtils
|
5
|
+
attr_reader :lockf
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@lockf = LockFile.new(file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
file.close
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Lock::File#lock tests
|
17
|
+
def test_lock
|
18
|
+
assert_equal 0, lockf.lock
|
19
|
+
ensure
|
20
|
+
lockf.release
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_lock_in_fork
|
24
|
+
Process.wait fork {
|
25
|
+
lockf.lock
|
26
|
+
Process.kill("SIGINT", Process.ppid)
|
27
|
+
sleep(1)
|
28
|
+
}
|
29
|
+
rescue Interrupt
|
30
|
+
assert_raises(Timeout::Error) { timeout(0.5) { lockf.lock } }
|
31
|
+
ensure
|
32
|
+
lockf.release
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Lock::File#lock_nonblock tests
|
37
|
+
def test_lock_nonblock
|
38
|
+
assert_equal 0, lockf.lock_nonblock
|
39
|
+
ensure
|
40
|
+
lockf.release
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_lock_nonblock_in_fork
|
44
|
+
Process.wait fork {
|
45
|
+
lockf.lock_nonblock
|
46
|
+
Process.kill("SIGINT", Process.ppid)
|
47
|
+
sleep(1)
|
48
|
+
}
|
49
|
+
rescue Interrupt
|
50
|
+
assert_raises(Errno::EAGAIN) { lockf.lock_nonblock }
|
51
|
+
ensure
|
52
|
+
lockf.release
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Lock::File#locked? tests
|
57
|
+
def test_locked?
|
58
|
+
lockf.lock
|
59
|
+
Process.wait fork { lockf.locked? ? exit(0) : exit(1) }
|
60
|
+
assert_equal 0, $?.exitstatus
|
61
|
+
ensure
|
62
|
+
lockf.release
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# LockFile#initialize
|
67
|
+
def test_initialize_with_str_path
|
68
|
+
path = File.join(Dir.getwd, "test", "tmp.txt")
|
69
|
+
touch(path)
|
70
|
+
lockf = LockFile.new(path)
|
71
|
+
assert_equal 0, lockf.lock
|
72
|
+
assert_equal 0, lockf.release
|
73
|
+
ensure
|
74
|
+
lockf.file.close
|
75
|
+
rm(path)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# LockFile.from_temporary_file
|
80
|
+
def test_from_temporary_file
|
81
|
+
lockf = LockFile.from_temporary_file
|
82
|
+
assert_equal 0, lockf.lock
|
83
|
+
assert_equal 0, lockf.release
|
84
|
+
lockf.file.close
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def file
|
90
|
+
@file ||= Tempfile.new("lockf-test").tap(&:unlink)
|
91
|
+
end
|
92
|
+
end
|
data/test/readme_test.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "setup"
|
4
|
+
require "test/cmd"
|
5
|
+
|
6
|
+
class LockFile::ReadmeTest < Test::Unit::TestCase
|
7
|
+
include Test::Cmd
|
8
|
+
|
9
|
+
def test_lockfile_blocking_variant
|
10
|
+
r = 'Lock acquired by parent process \(.+\)\s*' \
|
11
|
+
'Child process waiting on lock \(.+\)\s*' \
|
12
|
+
'Lock acquired by child process \(.+\)\s*'
|
13
|
+
assert_match Regexp.new(r),
|
14
|
+
readme_example("1_lockfile_blocking_variant.rb").stdout
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_lockfile_nonblocking_variant
|
18
|
+
r = 'Lock acquired by parent process \(.+\)\s*' \
|
19
|
+
'(Lock would block\s*){3,4}' \
|
20
|
+
'Lock acquired by child process \(.+\)\s*'
|
21
|
+
assert_match Regexp.new(r),
|
22
|
+
readme_example("2_lockfile_nonblocking_variant.rb").stdout
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_ffi_lockf_blocking_variant
|
26
|
+
assert_equal "Lock acquired\nLock released\n",
|
27
|
+
readme_example("3_ffilockf_blocking_variant.rb").stdout
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_ffi_lockf_nonblocking_variant
|
31
|
+
assert_equal "Lock acquired\nLock released\n",
|
32
|
+
readme_example("4_ffilockf_nonblocking_variant.rb").stdout
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def readme_example(path)
|
38
|
+
examples_dir = File.join(Dir.getwd, "share", "lockf.rb", "examples")
|
39
|
+
example = File.join(examples_dir, path)
|
40
|
+
cmd "bundle exec ruby #{example}"
|
41
|
+
end
|
42
|
+
end
|
data/test/setup.rb
ADDED
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lockf.rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.12.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- '0x1eef'
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redcarpet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: standard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.12.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.12.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.29.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.29.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: test-unit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.5.7
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.5.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake-compiler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.2.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.2.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rack
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rackup
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.1'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2.1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: test-cmd.rb
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.4'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.4'
|
139
|
+
description: A Ruby interface for lockf(3)
|
140
|
+
email:
|
141
|
+
- 0x1eef@protonmail.com
|
142
|
+
executables: []
|
143
|
+
extensions:
|
144
|
+
- ext/lockf.rb/extconf.rb
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".bundle/config"
|
148
|
+
- ".github/workflows/tests.yml"
|
149
|
+
- ".gitignore"
|
150
|
+
- ".projectile"
|
151
|
+
- ".rubocop.yml"
|
152
|
+
- ".uncrustify.cfg"
|
153
|
+
- ".yardopts"
|
154
|
+
- Gemfile
|
155
|
+
- LICENSE
|
156
|
+
- README.md
|
157
|
+
- Rakefile.rb
|
158
|
+
- ext/lockf.rb/extconf.rb
|
159
|
+
- ext/lockf.rb/lockf.c
|
160
|
+
- ext/lockf.rb/lockf.h
|
161
|
+
- lib/lockf.rb
|
162
|
+
- lib/lockf/version.rb
|
163
|
+
- lockf.rb.gemspec
|
164
|
+
- share/lockf.rb/examples/1_lockfile_blocking_variant.rb
|
165
|
+
- share/lockf.rb/examples/2_lockfile_nonblocking_variant.rb
|
166
|
+
- share/lockf.rb/examples/3_ffilockf_blocking_variant.rb
|
167
|
+
- share/lockf.rb/examples/4_ffilockf_nonblocking_variant.rb
|
168
|
+
- test/lock_file_test.rb
|
169
|
+
- test/readme_test.rb
|
170
|
+
- test/setup.rb
|
171
|
+
homepage: https://github.com/0x1eef/lockf.rb#readme
|
172
|
+
licenses:
|
173
|
+
- 0BSD
|
174
|
+
metadata: {}
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
requirements: []
|
190
|
+
rubygems_version: 3.5.3
|
191
|
+
signing_key:
|
192
|
+
specification_version: 4
|
193
|
+
summary: A Ruby interface for lockf(3)
|
194
|
+
test_files: []
|