lockf.rb 2.0.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: 2a49042a4d79251c0a85dbe626fefaa323862ea30a45426012a1d8bab08f63fb
4
- data.tar.gz: 57f2c700f963a299ce4502e3a7856cc75283ceb80d1c27864ea08438950047d5
3
+ metadata.gz: 6002e2f48aae8711b6171d35f4458aab8144d287395c29c7072a71d0d7f5e5ca
4
+ data.tar.gz: f2d13d124949cb8bb12e2d3116fc24a9dc6b15df4d48212d51fc9adb52bf5878
5
5
  SHA512:
6
- metadata.gz: 7d5c94dbaa6ad0b2eff1fe114e902d7c49014f2bb0ce69b2c5512ae0d107274d7c3c8b6a2d688672faeb85c7b48510689952efd9c64dc4cc78616e3e566f2e8e
7
- data.tar.gz: 27c55d59bbab2f8e76b5370b010b3541d65a346b88279361e7f64110b3e6a1fe3f1ddaf4677fbefcec7f2e6409d70483937298f47cda1e3d729c209122d821df
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,15 +1,28 @@
1
1
  require "bundler/setup"
2
+ require "bundler/gem_tasks"
2
3
 
3
4
  namespace :format do
4
- desc "Run rubocop"
5
- task :ruby do
5
+ desc "Apply rubocop fixes"
6
+ task :apply do
6
7
  sh "bundle exec rubocop -A"
7
8
  end
9
+
10
+ desc "Run rubocop"
11
+ task :check do
12
+ sh "bundle exec rubocop"
13
+ end
8
14
  end
9
- task format: %w[format:ruby]
15
+ task format: %w[format:apply]
16
+
17
+ desc "Run CI tasks"
18
+ task ci: %i[format:check test]
10
19
 
11
20
  desc "Run tests"
12
21
  task :test do
13
- 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
14
27
  end
15
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.0.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-28 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
@@ -44,14 +57,14 @@ dependencies:
44
57
  requirements:
45
58
  - - "~>"
46
59
  - !ruby/object:Gem::Version
47
- version: '1.12'
60
+ version: '1.39'
48
61
  type: :development
49
62
  prerelease: false
50
63
  version_requirements: !ruby/object:Gem::Requirement
51
64
  requirements:
52
65
  - - "~>"
53
66
  - !ruby/object:Gem::Version
54
- version: '1.12'
67
+ version: '1.39'
55
68
  - !ruby/object:Gem::Dependency
56
69
  name: rubocop
57
70
  requirement: !ruby/object:Gem::Requirement
@@ -101,38 +114,21 @@ executables: []
101
114
  extensions: []
102
115
  extra_rdoc_files: []
103
116
  files:
104
- - ".bundle/config"
105
- - ".clang-format"
106
- - ".github/workflows/tests.yml"
107
- - ".gitignore"
108
- - ".projectile"
109
- - ".rubocop.yml"
110
- - ".uncrustify.cfg"
111
- - ".yardopts"
112
- - Gemfile
113
117
  - LICENSE
114
118
  - README.md
115
119
  - Rakefile.rb
116
- - bin/test-runner
117
- - lib/lock/file.rb
118
- - lib/lock/file/constants.rb
119
- - lib/lock/file/ffi.rb
120
- - lib/lock/file/version.rb
121
120
  - lib/lockf.rb
122
- - lib/lockfile.rb
123
- - lockf.rb.gemspec
124
- - share/lockf.rb/examples/1_lockfile_blocking_variant.rb
125
- - share/lockf.rb/examples/2_lockfile_nonblocking_variant.rb
126
- - share/lockf.rb/examples/3_ffilockf_blocking_variant.rb
127
- - share/lockf.rb/examples/4_ffilockf_nonblocking_variant.rb
128
- - test/lock_file_test.rb
129
- - test/readme_test.rb
130
- - test/setup.rb
121
+ - lib/lockf/constants.rb
122
+ - lib/lockf/ffi.rb
123
+ - lib/lockf/version.rb
124
+ - share/lockf.rb/examples/1_lock_file_blocking_variant.rb
125
+ - share/lockf.rb/examples/2_lock_file_nonblocking_variant.rb
126
+ - share/lockf.rb/examples/3_ffi_lockf_blocking_variant.rb
127
+ - share/lockf.rb/examples/4_ffi_lockf_nonblocking_variant.rb
131
128
  homepage: https://github.com/0x1eef/lockf.rb#readme
132
129
  licenses:
133
130
  - 0BSD
134
131
  metadata: {}
135
- post_install_message:
136
132
  rdoc_options: []
137
133
  require_paths:
138
134
  - lib
@@ -147,8 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
143
  - !ruby/object:Gem::Version
148
144
  version: '0'
149
145
  requirements: []
150
- rubygems_version: 3.5.11
151
- signing_key:
146
+ rubygems_version: 3.6.9
152
147
  specification_version: 4
153
148
  summary: Ruby bindings for lockf(3)
154
149
  test_files: []
data/.bundle/config DELETED
@@ -1,2 +0,0 @@
1
- ---
2
- BUNDLE_PATH: ".gems"
data/.clang-format DELETED
@@ -1,11 +0,0 @@
1
- BasedOnStyle: LLVM
2
- IndentWidth: 2
3
- SortIncludes: false
4
- UseTab: Never
5
- BreakBeforeBraces: Allman
6
- AllowShortFunctionsOnASingleLine: Inline
7
- AlwaysBreakAfterDefinitionReturnType: TopLevel
8
- BreakBeforeBinaryOperators: All
9
- BinPackArguments: false
10
- AlignConsecutiveAssignments: true
11
- AlwaysBreakAfterReturnType: None
@@ -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]
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 DELETED
@@ -1,10 +0,0 @@
1
- *.so
2
- *.o
3
- *.lock
4
- Makefile
5
- tmp/
6
- pkg/
7
- *~
8
- .yardoc/
9
- .gems/
10
- doc/
data/.projectile DELETED
@@ -1,3 +0,0 @@
1
- +./
2
- +.github/
3
- -.gems/
data/.rubocop.yml DELETED
@@ -1,25 +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
-
18
- ##
19
- # Options for all cops.
20
- AllCops:
21
- TargetRubyVersion: 3.2
22
- Include:
23
- - lib/*.rb
24
- - lib/**/*.rb
25
- - test/*.rb
data/.uncrustify.cfg DELETED
@@ -1,2 +0,0 @@
1
- indent_with_tabs = 0
2
- indent_columns = 4
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,36 +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
- private
31
-
32
- def libc
33
- @libc ||= Fiddle.dlopen Dir["/lib/libc.*"].first
34
- end
35
- end
36
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lock::File
4
- VERSION = "2.0.0"
5
- end
data/lib/lock/file.rb DELETED
@@ -1,114 +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 "tmpdir"
10
- require_relative "file/version"
11
- require_relative "file/ffi"
12
- require_relative "file/constants"
13
-
14
- include FFI
15
- include Constants
16
-
17
- ##
18
- # @example
19
- # lockf = Lock::File.temporary_file
20
- # lockf.lock
21
- # # ...
22
- #
23
- # @param [String] basename
24
- # @param [String] tmpdir
25
- # @return [Lock::File]
26
- # Returns a {Lock::File Lock::File} for a random,
27
- # unlinked temporary file
28
- def self.from_temporary_file(basename: "lockf", tmpdir: Dir.tmpdir)
29
- require "tempfile" unless defined?(Tempfile)
30
- file = Tempfile.new(basename, tmpdir:).tap(&:unlink)
31
- Lock::File.new(file)
32
- end
33
- class << self
34
- alias_method :temporary_file, :from_temporary_file
35
- end
36
-
37
- ##
38
- # @return [<#fileno>]
39
- # Returns a file handle
40
- attr_reader :file
41
-
42
- ##
43
- # @param [<#fileno>] file
44
- # @param [Integer] size
45
- # @return [Lock::File]
46
- # Returns an instance of {Lock::File Lock::File}
47
- def initialize(file, size = 0)
48
- @file = file
49
- @size = size
50
- end
51
-
52
- ##
53
- # Acquire lock (blocking)
54
- #
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
61
- tries ||= 0
62
- lockf(@file, F_LOCK, @size)
63
- rescue Errno::EINTR => ex
64
- tries += 1
65
- tries == 3 ? raise(ex) : retry
66
- end
67
-
68
- ##
69
- # Acquire lock (non-blocking)
70
- #
71
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
72
- # @raise [SystemCallError]
73
- # Might raise a subclass of SystemCallError
74
- # @return [Boolean]
75
- # Returns true when successful
76
- def lock_nonblock
77
- lockf(@file, F_TLOCK, @size)
78
- end
79
-
80
- ##
81
- # Release lock
82
- #
83
- # @see https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3 lockf(3)
84
- # @raise [SystemCallError]
85
- # Might raise a subclass of SystemCallError
86
- # @return [Boolean]
87
- # Returns true when successful
88
- def release
89
- lockf(@file, F_ULOCK, @size)
90
- end
91
-
92
- ##
93
- # @return [Boolean]
94
- # Returns true when lock is held by another process
95
- def locked?
96
- lockf(@file, F_TEST, @size)
97
- false
98
- rescue Errno::EACCES, Errno::EAGAIN
99
- true
100
- end
101
-
102
- ##
103
- # Closes {Lock::File#file Lock::File#file}
104
- #
105
- # @example
106
- # # Equivalent to:
107
- # lockf = Lock::File.temporary_file
108
- # lockf.file.close
109
- # @return [void]
110
- def close
111
- return unless @file.respond_to?(:close)
112
- @file.close
113
- end
114
- 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.12"
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,80 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "setup"
3
-
4
- class Lock::File::Test < Test::Unit::TestCase
5
- attr_reader :file
6
- attr_reader :lockf
7
-
8
- def setup
9
- @file = Tempfile.new("lockf-test").tap(&:unlink)
10
- @lockf = Lock::File.new(file)
11
- end
12
-
13
- def teardown
14
- file.close
15
- end
16
-
17
- ##
18
- # LockFile#lock
19
- def test_lock
20
- assert_equal true, lockf.lock
21
- ensure
22
- lockf.release
23
- end
24
-
25
- def test_lock_in_fork
26
- pid = fork_sleep { lockf.lock }
27
- sleep(0.1)
28
- assert_raises(Errno::EWOULDBLOCK) { lockf.lock_nonblock }
29
- ensure
30
- Process.kill("KILL", pid)
31
- lockf.release
32
- end
33
-
34
- ##
35
- # LockFile#lock_nonblock
36
- def test_lock_nonblock
37
- assert_equal true, lockf.lock_nonblock
38
- ensure
39
- lockf.release
40
- end
41
-
42
- def test_lock_nonblock_in_fork
43
- pid = fork_sleep { lockf.lock_nonblock }
44
- sleep(0.1)
45
- assert_raises(Errno::EWOULDBLOCK) { lockf.lock_nonblock }
46
- ensure
47
- Process.kill("KILL", pid)
48
- lockf.release
49
- end
50
-
51
- ##
52
- # LockFile#locked?
53
- def test_locked?
54
- pid = fork_sleep { lockf.lock }
55
- sleep(0.1)
56
- assert_equal true, lockf.locked?
57
- ensure
58
- Process.kill("KILL", pid)
59
- lockf.release
60
- end
61
-
62
- ##
63
- # LockFile.temporary_file
64
- def test_temporary_file
65
- lockf = Lock::File.temporary_file
66
- assert_equal true, lockf.lock
67
- assert_equal true, lockf.release
68
- ensure
69
- lockf.file.close
70
- end
71
-
72
- private
73
-
74
- def fork_sleep
75
- fork do
76
- yield
77
- sleep
78
- end
79
- end
80
- 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_lockfile_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_lockfile_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_ffilockf_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_ffilockf_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"