xchan.rb 0.22.0 → 0.24.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: 2d385aa7803c7861610c50348dec4108760bf2e0a21d36bd6a9402788ec56f62
4
- data.tar.gz: 5292469bfb8a3cc8fb581d12ecde445cef46551000604b6e8d1c3b9ca96b2d93
3
+ metadata.gz: 181f4b9e1add150781f56e06f8d6d011a7474e55a501a6dce1713c975d52280d
4
+ data.tar.gz: 96b2056582afaa5f67f774c626a21cb9f8feadb09aea346150bcf685660e4dce
5
5
  SHA512:
6
- metadata.gz: 10b83c2ad3e4861715b4348f0e4aa54e9f84377fa08acf3fb59caaccbb164909326ae1d527afaac5bed6a7401a4981942f75b767a370cb276553784ab0bce2b0
7
- data.tar.gz: 990ed1ae55dba125d7f95a4e2d2477353b7bddeaa275149713fc2769fd36cea564ebe179567fe93924b1a37bc18f9e4c51ea7da1310587c34c94190975154fb8
6
+ metadata.gz: ab568cfe894198659cb45e72759dfa7daa95b290d647a1d633225e83093eb86f28b1a2c13e47b38d8626d2a275cbd80364416b15dbff050c6e2ba4ac36aff76c
7
+ data.tar.gz: 2a45fd7441fb2312d1fcfce43ad6f53fe60ad7c304a833667a22e7024c06a74ad21b6b235b1950835e71ddd49f43b03c958662ed92b746bc52829cead23db9da
data/README.md CHANGED
@@ -1,4 +1,16 @@
1
- ## About
1
+ <p align="center">
2
+ <a href="https://r.uby.dev">
3
+ <img
4
+ src="rubydev.svg"
5
+ width="400"
6
+ height="200"
7
+ border="0"
8
+ alt="a r.uby.dev project"
9
+ >
10
+ </a>
11
+ </p>
12
+
13
+ > [r.uby.dev](https://r.uby.dev/llm) project.
2
14
 
3
15
  xchan.rb is an easy to use library for InterProcess Communication (IPC).
4
16
 
@@ -245,22 +257,12 @@ print "The maximum size of a single message is: ", sndbuf.int, " bytes.\n"
245
257
  # The maximum size of a single message is: 2048 bytes.
246
258
  ```
247
259
 
248
- ## Documentation
249
-
250
- A complete API reference is available at
251
- [0x1eef.github.io/x/xchan.rb](https://0x1eef.github.io/x/xchan.rb/)
252
-
253
260
  ## Install
254
261
 
255
262
  xchan.rb can be installed via rubygems.org:
256
263
 
257
264
  gem install xchan.rb
258
265
 
259
- ## Sources
260
-
261
- * [github.com/@0x1eef](https://github.com/0x1eef/xchan.rb#readme)
262
- * [gitlab.com/@0x1eef](https://gitlab.com/0x1eef/xchan.rb#about)
263
-
264
266
  ## License
265
267
 
266
268
  [BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle/import"
4
+ require "tempfile"
5
+
6
+ ##
7
+ # @private
8
+ # {Chan::Lockf Chan::Lockf} provides an object-oriented
9
+ # [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)
10
+ # interface backed by Fiddle from the standard library.
11
+ # It supersedes the runtime dependency on the lockf.rb gem.
12
+ class Chan::Lockf
13
+ module LibC
14
+ extend Fiddle::Importer
15
+ dlload Fiddle.dlopen(nil)
16
+ extern "int lockf(int fd, int cmd, int size)"
17
+ end
18
+ private_constant :LibC
19
+
20
+ F_ULOCK = 0x0
21
+ F_LOCK = 0x1
22
+ F_TLOCK = 0x2
23
+ F_TEST = 0x3
24
+
25
+ ##
26
+ # @return [<#fileno>]
27
+ # Returns a file handle
28
+ attr_reader :file
29
+
30
+ ##
31
+ # @param [<#fileno>] file
32
+ # @param [Integer] size
33
+ # @return [Chan::Lockf]
34
+ # Returns an instance of {Chan::Lockf Chan::Lockf}
35
+ def initialize(file, size = 0)
36
+ @file = file
37
+ @size = size
38
+ end
39
+
40
+ ##
41
+ # Acquire lock (blocking)
42
+ # @raise [SystemCallError]
43
+ # Might raise a subclass of SystemCallError
44
+ # @return [Boolean]
45
+ # Returns true when successful
46
+ def lock = try(F_LOCK)
47
+
48
+ ##
49
+ # Acquire lock (non-blocking)
50
+ # @raise [SystemCallError]
51
+ # Might raise a subclass of SystemCallError
52
+ # @return [Boolean]
53
+ # Returns true when successful
54
+ def lock_nonblock = try(F_TLOCK)
55
+
56
+ ##
57
+ # Release lock
58
+ # @raise [SystemCallError]
59
+ # Might raise a subclass of SystemCallError
60
+ # @return [Boolean]
61
+ # Returns true when successful
62
+ def release = try(F_ULOCK)
63
+
64
+ ##
65
+ # @return [Boolean]
66
+ # Returns true when lock can be acquired
67
+ def lockable?
68
+ try(F_TEST)
69
+ true
70
+ rescue Errno::EACCES, Errno::EAGAIN, Errno::EWOULDBLOCK
71
+ false
72
+ end
73
+
74
+ ##
75
+ # Closes {Chan::Lockf#file Chan::Lockf#file}
76
+ # @return [void]
77
+ def close
78
+ @file.respond_to?(:close) ? @file.close : nil
79
+ end
80
+
81
+ private
82
+
83
+ def try(function, attempts: 3)
84
+ fileno = @file.respond_to?(:fileno) ? @file.fileno : @file
85
+ LibC.lockf(fileno, function, @size).zero? ||
86
+ raise(SystemCallError.new("lockf", Fiddle.last_error))
87
+ rescue Errno::EINTR => ex
88
+ attempts -= 1
89
+ (attempts.zero? ? raise(ex) : retry)
90
+ end
91
+ end
@@ -4,7 +4,6 @@
4
4
  # An easy-to-use InterProcess Communication (IPC) library
5
5
  class Chan::UNIXSocket
6
6
  require "socket"
7
- require "lockf"
8
7
  require_relative "bytes"
9
8
 
10
9
  ##
@@ -286,13 +285,35 @@ class Chan::UNIXSocket
286
285
  end
287
286
 
288
287
  ##
289
- # Waits for the channel to become lockable
288
+ # Waits for the channel to become lockable, and takes the lock.
289
+ #
290
+ # Without a timeout the wait is `lockf(F_LOCK)`, which blocks in the
291
+ # kernel: the releaser wakes the waiter, so there is no interval to poll
292
+ # on - a lock held for microseconds is released with a wakeup rather than
293
+ # up to a tick of the loop below.
294
+ #
295
+ # **The caller comes back holding the lock**, which is what the retry in
296
+ # `send` and `recv` expects, and why the release paths are theirs. With a
297
+ # timeout the channel comes back as it was, because that loop gives up
298
+ # rather than acquires.
299
+ #
300
+ # The acquisition is no longer under `@mutex`, where it used to be - only
301
+ # the release is. With threads the two ends can therefore belong to
302
+ # different threads, which is a redundant retry at worst: `lockf` locks
303
+ # are held per process and give no intra-process exclusion at all, and
304
+ # every socket operation still happens under a successfully taken
305
+ # `F_TLOCK`.
306
+ #
290
307
  # @param [Float, Integer, nil] timeout
291
308
  # The number of seconds to wait before timeout.
292
309
  # Waits indefinitely with no arguments
293
310
  # @return [Chan::UNIXSocket, nil]
294
311
  # Returns self when the channel is lockable, otherwise returns nil
295
312
  def wait_lockable(timeout = nil)
313
+ if timeout.nil?
314
+ @lock.lock
315
+ return self
316
+ end
296
317
  start = (timeout ? gettime : nil)
297
318
  loop do
298
319
  break(nil) if start && (gettime - start) >= timeout
data/lib/xchan/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chan
4
- VERSION = "0.22.0"
4
+ VERSION = "0.24.0"
5
5
  end
data/lib/xchan.rb CHANGED
@@ -5,6 +5,7 @@ module Chan
5
5
  require_relative "xchan/unix_socket"
6
6
  require_relative "xchan/null_lock"
7
7
  require_relative "xchan/tempfile"
8
+ require_relative "xchan/lockf"
8
9
 
9
10
  WaitReadable = Class.new(IO::EAGAINWaitReadable)
10
11
  WaitWritable = Class.new(IO::EAGAINWaitWritable)
@@ -65,7 +66,7 @@ module Chan
65
66
  def self.locks
66
67
  {
67
68
  null: lambda { |_tmpdir| Chan::NullLock },
68
- file: lambda { |tmpdir| Lockf.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
69
+ file: lambda { |tmpdir| Chan::Lockf.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
69
70
  }
70
71
  end
71
72
  end
data/test/xchan_test.rb CHANGED
@@ -227,6 +227,8 @@ end
227
227
  ##
228
228
  # Chan::UNIXSocket#wait_lockable
229
229
  class Chan::WaitLockableTest < Chan::Test
230
+ include Timeout
231
+
230
232
  def test_wait_lockable_on_lockable_channel
231
233
  assert_instance_of Chan::UNIXSocket, ch.wait_lockable
232
234
  end
@@ -241,6 +243,27 @@ class Chan::WaitLockableTest < Chan::Test
241
243
  aux.close
242
244
  end
243
245
 
246
+ ##
247
+ # Without a timeout the wait is a blocking lock rather than a loop, so
248
+ # the answer can only arrive because the release woke the child - a lock
249
+ # that was never contended would answer either way, and a timeout would
250
+ # answer before the release at all.
251
+ def test_wait_lockable_without_a_timeout_on_a_locked_channel
252
+ aux = xchan(:pure)
253
+ lock! do
254
+ pid = fork { aux.send ch.wait_lockable.class.to_s }
255
+ ##
256
+ # Long enough that the child is inside the lock rather than between
257
+ # two calls, and then the release.
258
+ sleep 0.1
259
+ release!
260
+ Process.wait(pid)
261
+ end
262
+ assert_equal "Chan::UNIXSocket", timeout(5) { aux.recv }
263
+ ensure
264
+ aux.close
265
+ end
266
+
244
267
  def test_wait_lockable_on_null_lock
245
268
  ch = xchan(:pure, lock: :null)
246
269
  assert_instance_of Chan::UNIXSocket, ch.wait_lockable
@@ -261,4 +284,3 @@ class Chan::WaitLockableTest < Chan::Test
261
284
  ch.instance_variable_get(:@lock).release
262
285
  end
263
286
  end
264
-
data/xchan.rb.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
  gem.summary = "An easy to use InterProcess Communication (IPC) library"
20
20
  gem.description = gem.summary
21
- gem.add_runtime_dependency "lockf.rb", "~> 3.0"
21
+
22
+ gem.add_runtime_dependency "fiddle", "~> 1.1"
22
23
  gem.add_development_dependency "test-unit", "~> 3.5.7"
23
24
  gem.add_development_dependency "yard", "~> 0.9"
24
25
  gem.add_development_dependency "kramdown", "~> 2.5"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xchan.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - '0x1eef'
@@ -10,19 +10,19 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: lockf.rb
13
+ name: fiddle
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '3.0'
18
+ version: '1.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '3.0'
25
+ version: '1.1'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: test-unit
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,7 @@ files:
132
132
  - lib/xchan.rb
133
133
  - lib/xchan/bytes.rb
134
134
  - lib/xchan/counter.rb
135
+ - lib/xchan/lockf.rb
135
136
  - lib/xchan/null_lock.rb
136
137
  - lib/xchan/tempfile.rb
137
138
  - lib/xchan/unix_socket.rb
@@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
168
  - !ruby/object:Gem::Version
168
169
  version: '0'
169
170
  requirements: []
170
- rubygems_version: 4.0.6
171
+ rubygems_version: 4.0.16
171
172
  specification_version: 4
172
173
  summary: An easy to use InterProcess Communication (IPC) library
173
174
  test_files: []