xchan.rb 0.19.0 → 0.21.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: a87a4e61f65ec4df37ecc2b09efd3d9abd4d39e57ac936db27e43ace4e99818d
4
- data.tar.gz: 1ac4be7c20abcdee66a441088e1a7bf67b92b96f63b16b3401227bb978afa6de
3
+ metadata.gz: e8936112cb188471884a6fa66ee0db2b61d5229ac75acb8953c78e5b76587bc8
4
+ data.tar.gz: e76eb21f0f7f2a11e465ee3d4834b3abd48b54269949ee3623e2b2c2bdc1a17c
5
5
  SHA512:
6
- metadata.gz: fc46a04ae48123fbf24371bd3438013cfcb070e353993811d10b4ea9d8b63628b577bed465d441fab1d964a0dab4ec9a2040e293c59272faa6c2081819788383
7
- data.tar.gz: 267dd70488de1b49c345c49a87c5f28a7bdf0713c20fdeeba2efbef13e2d114960dc021e3b1ec77df90ad3c1189772e3075dff3ce0f2ebfac27b727acc4a56a8
6
+ metadata.gz: d6ff8db4154e29a41a41b3878a93355ab3ce8092112f76f7fa4a61042a9747138f9a270217decf0e39cc5a9890f7ce5f34ebaa8a93e74aeb68d1cf5b0d470a09
7
+ data.tar.gz: bbe432d09de33e607b7940005a1d29ee0c1de6c14d3a2a56105c5fa26e32ea81a6327a87a6e520163f34836ffe820296674bf29a46c72cc45c603872500f090f
data/README.md CHANGED
@@ -1,9 +1,25 @@
1
1
  ## About
2
2
 
3
- xchan.rb is an easy to use library for InterProcess
4
- Communication (IPC). The library provides a channel
5
- that can help facilitate communication between Ruby
3
+ xchan.rb is an easy to use library for InterProcess Communication (IPC).
4
+
5
+ The library provides a channel that can help facilitate communication between Ruby
6
6
  processes who have a parent <=> child relationship.
7
+ A channel lock is provided by
8
+ [lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3) and a temporary, unlinked file to protect against race conditions
9
+ that can happen when multiple processes access the same channel
10
+ at the same time.
11
+
12
+ ## Features
13
+
14
+ * Minimalist Inter-Process Communication (IPC) for parent <=> child processes.
15
+ * Channel-based communication.
16
+ * Support for multiple serializers (`:marshal`, `:json`, `:yaml`) and raw string communication (`:pure`).
17
+ * Blocking (`#send`, `#recv`) and non-blocking (`#send_nonblock`, `#recv_nonblock`) operations.
18
+ * Built-in file-based locking ([lockf(3)](https://man.freebsd.org/cgi/man.cgi?query=lockf&sektion=3)) to prevent race conditions.
19
+ * Option to use a null lock for scenarios where locking is not needed.
20
+ * Access to underlying UNIX sockets for fine-grained control over socket options.
21
+ * Mac, BSD, and Linux support.
22
+ * Good docs.
7
23
 
8
24
  ## Examples
9
25
 
@@ -26,7 +42,7 @@ require "xchan"
26
42
  # Marshal as the serializer
27
43
  ch = xchan(:marshal)
28
44
  Process.wait fork { ch.send(5) }
29
- print "#{ch.recv} + 7 = 12", "\n"
45
+ puts "#{ch.recv} + 7 = 12"
30
46
  ch.close
31
47
 
32
48
  ##
@@ -40,7 +56,7 @@ ch.close
40
56
  The `ch.recv` method performs a blocking read. A read
41
57
  can block when a lock is held by another process, or
42
58
  when a read from
43
- [Chan::UNIXSocket#r](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
59
+ [Chan::UNIXSocket#r](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
44
60
  blocks. The example performs a read that blocks until
45
61
  the parent process writes to the channel:
46
62
 
@@ -53,7 +69,7 @@ fork do
53
69
  print "Received a random number (child process): ", ch.recv, "\n"
54
70
  end
55
71
  sleep(1)
56
- print "Send a random number (from parent process)", "\n"
72
+ puts "Send a random number (from parent process)"
57
73
  ch.send(rand(21))
58
74
  ch.close
59
75
  Process.wait
@@ -69,7 +85,7 @@ The non-blocking counterpart to `#recv` is `#recv_nonblock`.
69
85
  The `#recv_nonblock` method raises `Chan::WaitLockable` when
70
86
  a read blocks because of a lock held by another process, and
71
87
  the method raises `Chan::WaitReadable` when a read from
72
- [Chan::UNIXSocket#r](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
88
+ [Chan::UNIXSocket#r](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
73
89
  blocks:
74
90
 
75
91
  ```ruby
@@ -79,11 +95,12 @@ require "xchan"
79
95
  def read(ch)
80
96
  ch.recv_nonblock
81
97
  rescue Chan::WaitReadable
82
- print "Wait 1 second for channel to be readable", "\n"
98
+ puts "Wait 1 second for channel to be readable"
83
99
  ch.wait_readable(1)
84
100
  retry
85
101
  rescue Chan::WaitLockable
86
- sleep 0.01
102
+ puts "Wait 1 second for channel to be lockable"
103
+ ch.wait_lockable(1)
87
104
  retry
88
105
  end
89
106
  trap("SIGINT") { exit(1) }
@@ -102,7 +119,7 @@ read(xchan(:marshal))
102
119
  The `ch.send` method performs a blocking write.
103
120
  A write can block when a lock is held by another
104
121
  process, or when a write to
105
- [Chan::UNIXSocket#w](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
122
+ [Chan::UNIXSocket#w](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
106
123
  blocks. The example fills the send buffer:
107
124
 
108
125
  ```ruby
@@ -123,7 +140,7 @@ The non-blocking counterpart to `#send` is
123
140
  `Chan::WaitLockable` when a write blocks because of
124
141
  a lock held by another process, and the method raises
125
142
  `Chan::WaitWritable` when a write to
126
- [Chan::UNIXSocket#w](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
143
+ [Chan::UNIXSocket#w](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
127
144
  blocks. The example frees space on the send buffer:
128
145
 
129
146
  ```ruby
@@ -133,11 +150,11 @@ require "xchan"
133
150
  def send_nonblock(ch, buf)
134
151
  ch.send_nonblock(buf)
135
152
  rescue Chan::WaitWritable
136
- print "Blocked - free send buffer", "\n"
153
+ puts "Blocked - free send buffer"
137
154
  ch.recv
138
155
  retry
139
156
  rescue Chan::WaitLockable
140
- sleep 0.01
157
+ ch.wait_lockable
141
158
  retry
142
159
  end
143
160
 
@@ -166,6 +183,9 @@ processes:
166
183
  #!/usr/bin/env ruby
167
184
  require "xchan"
168
185
 
186
+ ##
187
+ # 'lock: :file' is added just for the example
188
+ # It is the default behavior, and not necessary
169
189
  ch = xchan(:marshal, lock: :file)
170
190
  5.times.map do
171
191
  fork do
@@ -179,7 +199,7 @@ end.each { Process.wait(_1) }
179
199
  The null lock is the same as using no lock at all. The null lock is
180
200
  implemented as a collection of no-op operations. The null lock is
181
201
  implemented in the
182
- [Chan::NullLock](https://0x1eef.github,io/x/xchan.rb/Chan/NullLock.html)
202
+ [Chan::NullLock](https://0x1eef.github.io/x/xchan.rb/Chan/NullLock.html)
183
203
  class, and in certain situations, it can be useful and preferable
184
204
  to using a file lock:
185
205
 
@@ -200,9 +220,9 @@ Process.wait
200
220
 
201
221
  A channel has one socket for read operations and another
202
222
  socket for write operations.
203
- [Chan::UNIXSocket#r](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
223
+ [Chan::UNIXSocket#r](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#r-instance_method)
204
224
  returns the socket used for read operations, and
205
- [Chan::UNIXSocket#w](https://0x1eef.github,io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
225
+ [Chan::UNIXSocket#w](https://0x1eef.github.io/x/xchan.rb/Chan/UNIXSocket.html#w-instance_method)
206
226
  returns the socket used for write operations:
207
227
 
208
228
  ```ruby
data/lib/xchan/bytes.rb CHANGED
@@ -7,7 +7,6 @@
7
7
  # increases in size, and when an object is read from
8
8
  # a channel, the collection decreases in size.
9
9
  class Chan::Bytes
10
- require "json"
11
10
  require_relative "counter"
12
11
 
13
12
  ##
@@ -15,7 +14,8 @@ class Chan::Bytes
15
14
  # Directory where temporary files are stored
16
15
  # @return [Chan::Bytes]
17
16
  def initialize(tmpdir)
18
- @io = Chan.temporary_file(%w[bytes .json], tmpdir:)
17
+ @io = Chan.temporary_file(%w[bytes .bin], tmpdir:)
18
+ @io.binmode
19
19
  @io.sync = true
20
20
  write(@io, [])
21
21
  end
@@ -79,15 +79,17 @@ class Chan::Bytes
79
79
  end
80
80
 
81
81
  def write(io, bytes)
82
+ io.rewind
82
83
  io.truncate(0)
83
- io.write(serialize(bytes)).tap { io.rewind }
84
+ io.write(serialize(bytes))
85
+ io.rewind
84
86
  end
85
87
 
86
88
  def serialize(bytes)
87
- JSON.dump(bytes)
89
+ bytes.pack("Q>*")
88
90
  end
89
91
 
90
92
  def deserialize(bytes)
91
- JSON.parse(bytes)
93
+ bytes.unpack("Q>*")
92
94
  end
93
95
  end
data/lib/xchan/counter.rb CHANGED
@@ -5,57 +5,72 @@
5
5
  # for the number of written and received bytes on a
6
6
  # given channel.
7
7
  class Chan::Counter
8
- require "json"
9
-
10
8
  ##
11
9
  # @param [String] tmpdir
12
10
  # Directory where temporary files are stored
13
11
  # @return [Chan::Counter]
14
12
  def initialize(tmpdir)
15
- @io = Chan.temporary_file(%w[counter .json], tmpdir:)
16
- write(@io, {"bytes_read" => 0, "bytes_written" => 0})
13
+ @io = Chan.temporary_file(%w[counter .bin], tmpdir:)
14
+ @io.binmode
15
+ @io.sync = true
16
+ write(@io, 0, 0)
17
17
  end
18
18
 
19
19
  ##
20
20
  # @return [Integer]
21
21
  # Returns the number of bytes written to a channel
22
22
  def bytes_written
23
- read(@io).fetch("bytes_written")
23
+ _, bytes = read(@io)
24
+ bytes
24
25
  end
25
26
 
26
27
  ##
27
28
  # @return [Integer]
28
29
  # Returns the number of bytes read from a channel
29
30
  def bytes_read
30
- read(@io).fetch("bytes_read")
31
+ bytes, _ = read(@io)
32
+ bytes
31
33
  end
32
34
 
33
35
  ##
34
- # @param [Hash] new_stat
36
+ # @param [Integer] bytes_read
37
+ # Number of bytes read to increment the counter by
38
+ # @param [Integer] bytes_written
39
+ # Number of bytes written to increment the counter by
35
40
  # @return [void]
36
41
  # @private
37
- def increment!(new_stat)
38
- stat = read(@io)
39
- new_stat.each { stat[_1.to_s] += _2 }
40
- write(@io, stat)
42
+ def increment!(bytes_read: 0, bytes_written: 0)
43
+ bytes_in, bytes_out = read(@io)
44
+ bytes_in += bytes_read
45
+ bytes_out += bytes_written
46
+ write(@io, bytes_in, bytes_out)
47
+ end
48
+
49
+ ##
50
+ # Close the counter
51
+ # @return [void]
52
+ def close
53
+ @io.close
41
54
  end
42
55
 
43
56
  private
44
57
 
45
- def write(io, o)
58
+ def write(io, bytes_read, bytes_written)
59
+ io.rewind
46
60
  io.truncate(0)
47
- io.write(serialize(o)).tap { io.rewind }
61
+ io.write(serialize(bytes_read, bytes_written))
62
+ io.rewind
48
63
  end
49
64
 
50
65
  def read(io)
51
66
  deserialize(io.read).tap { io.rewind }
52
67
  end
53
68
 
54
- def serialize(bytes)
55
- JSON.dump(bytes)
69
+ def serialize(bytes_read, bytes_written)
70
+ [bytes_read, bytes_written].pack("Q>Q>")
56
71
  end
57
72
 
58
- def deserialize(bytes)
59
- JSON.parse(bytes)
73
+ def deserialize(payload)
74
+ payload.unpack("Q>Q>")
60
75
  end
61
76
  end
@@ -36,9 +36,9 @@ class Chan::NullLock
36
36
  end
37
37
 
38
38
  ##
39
- # @return [void]
40
- # This method always returns false
41
- def self.locked?
42
- false
39
+ # @return [true]
40
+ # Always returns true
41
+ def self.lockable?
42
+ true
43
43
  end
44
44
  end
@@ -45,6 +45,7 @@ class Chan::UNIXSocket
45
45
  @bytes = Chan::Bytes.new(tmpdir)
46
46
  @counter = Chan::Counter.new(tmpdir)
47
47
  @lock = Chan.locks[lock]&.call(tmpdir) || lock
48
+ @mutex = Mutex.new
48
49
  end
49
50
 
50
51
  ##
@@ -61,8 +62,8 @@ class Chan::UNIXSocket
61
62
  # @return [void]
62
63
  def close
63
64
  @lock.lock
64
- raise IOError, "channel is closed" if closed?
65
- [@r, @w, @bytes, @lock].each(&:close)
65
+ raise IOError, "closed channel" if closed?
66
+ [@r, @w, @bytes, @counter, @lock].each(&:close)
66
67
  rescue IOError => ex
67
68
  @lock.release
68
69
  raise(ex)
@@ -81,7 +82,11 @@ class Chan::UNIXSocket
81
82
  # Returns the number of bytes written to the channel
82
83
  def send(object)
83
84
  send_nonblock(object)
84
- rescue Chan::WaitWritable, Chan::WaitLockable
85
+ rescue Chan::WaitWritable
86
+ wait_writable
87
+ retry
88
+ rescue Chan::WaitLockable
89
+ wait_lockable
85
90
  retry
86
91
  end
87
92
  alias_method :write, :send
@@ -99,17 +104,19 @@ class Chan::UNIXSocket
99
104
  # @return [Integer, nil]
100
105
  # Returns the number of bytes written to the channel
101
106
  def send_nonblock(object)
102
- @lock.lock_nonblock
103
- raise IOError, "channel closed" if closed?
104
- len = @w.write_nonblock(serialize(object))
105
- @bytes.push(len)
106
- @counter.increment!(bytes_written: len)
107
- len.tap { @lock.release }
108
- rescue IOError, IO::WaitWritable, Errno::ENOBUFS => ex
109
- @lock.release
110
- raise Chan::WaitWritable, ex.message
111
- rescue Errno::EWOULDBLOCK => ex
112
- raise Chan::WaitLockable, ex.message
107
+ @mutex.synchronize do
108
+ @lock.lock_nonblock
109
+ raise IOError, "channel closed" if closed?
110
+ len = @w.write_nonblock(serialize(object))
111
+ @bytes.push(len)
112
+ @counter.increment!(bytes_written: len)
113
+ len.tap { @lock.release }
114
+ rescue IOError, IO::WaitWritable, Errno::ENOBUFS => ex
115
+ @lock.release
116
+ raise Chan::WaitWritable, ex.message
117
+ rescue Errno::EWOULDBLOCK => ex
118
+ raise Chan::WaitLockable, ex.message
119
+ end
113
120
  end
114
121
  alias_method :write_nonblock, :send_nonblock
115
122
 
@@ -131,6 +138,7 @@ class Chan::UNIXSocket
131
138
  wait_readable
132
139
  retry
133
140
  rescue Chan::WaitLockable
141
+ wait_lockable
134
142
  retry
135
143
  end
136
144
  alias_method :read, :recv
@@ -146,21 +154,23 @@ class Chan::UNIXSocket
146
154
  # @return [Object]
147
155
  # Returns an object from the channel
148
156
  def recv_nonblock
149
- @lock.lock_nonblock
150
- raise IOError, "closed channel" if closed?
151
- len = @bytes.shift
152
- obj = deserialize(@r.read_nonblock(len.zero? ? 1 : len))
153
- @counter.increment!(bytes_read: len)
154
- obj.tap { @lock.release }
155
- rescue IOError => ex
156
- @lock.release
157
- raise(ex)
158
- rescue IO::WaitReadable => ex
159
- @bytes.unshift(len)
160
- @lock.release
161
- raise Chan::WaitReadable, ex.message
162
- rescue Errno::EAGAIN => ex
163
- raise Chan::WaitLockable, ex.message
157
+ @mutex.synchronize do
158
+ @lock.lock_nonblock
159
+ raise IOError, "closed channel" if closed?
160
+ len = @bytes.shift
161
+ obj = deserialize(@r.read_nonblock(len.zero? ? 1 : len))
162
+ @counter.increment!(bytes_read: len)
163
+ obj.tap { @lock.release }
164
+ rescue IOError => ex
165
+ @lock.release
166
+ raise(ex)
167
+ rescue IO::WaitReadable => ex
168
+ @bytes.unshift(len)
169
+ @lock.release
170
+ raise Chan::WaitReadable, ex.message
171
+ rescue Errno::EAGAIN => ex
172
+ raise Chan::WaitLockable, ex.message
173
+ end
164
174
  end
165
175
  alias_method :read_nonblock, :recv_nonblock
166
176
 
@@ -222,22 +232,40 @@ class Chan::UNIXSocket
222
232
 
223
233
  ##
224
234
  # Waits for the channel to become readable
225
- # @param [Float, Integer, nil] s
226
- # The number of seconds to wait. Waits indefinitely with no arguments.
235
+ # @param [Float, Integer, nil] timeout
236
+ # The number of seconds to wait before timeout.
237
+ # Waits indefinitely with no arguments
227
238
  # @return [Chan::UNIXSocket, nil]
228
239
  # Returns self when the channel is readable, otherwise returns nil
229
- def wait_readable(s = nil)
230
- @r.wait_readable(s) and self
240
+ def wait_readable(timeout = nil)
241
+ @r.wait_readable(timeout) and self
231
242
  end
232
243
 
233
244
  ##
234
245
  # Waits for the channel to become writable
235
- # @param [Float, Integer, nil] s
236
- # The number of seconds to wait. Waits indefinitely with no arguments.
246
+ # @param [Float, Integer, nil] timeout
247
+ # The number of seconds to wait before timeout.
248
+ # Waits indefinitely with no arguments
237
249
  # @return [Chan::UNIXSocket, nil]
238
250
  # Returns self when the channel is writable, otherwise returns nil
239
- def wait_writable(s = nil)
240
- @w.wait_writable(s) and self
251
+ def wait_writable(timeout = nil)
252
+ @w.wait_writable(timeout) and self
253
+ end
254
+
255
+ ##
256
+ # Waits for the channel to become lockable
257
+ # @param [Float, Integer, nil] timeout
258
+ # The number of seconds to wait before timeout.
259
+ # Waits indefinitely with no arguments
260
+ # @return [Chan::UNIXSocket, nil]
261
+ # Returns self when the channel is lockable, otherwise returns nil
262
+ def wait_lockable(timeout = nil)
263
+ start = (timeout ? gettime : nil)
264
+ loop do
265
+ break(nil) if start && (gettime - start) >= timeout
266
+ break(self) if @lock.lockable?
267
+ sleep 0.01
268
+ end
241
269
  end
242
270
 
243
271
  ##
@@ -259,4 +287,8 @@ class Chan::UNIXSocket
259
287
  def deserialize(str)
260
288
  @s.load(str)
261
289
  end
290
+
291
+ def gettime
292
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
293
+ end
262
294
  end
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.19.0"
4
+ VERSION = "0.21.0"
5
5
  end
data/lib/xchan.rb CHANGED
@@ -65,7 +65,7 @@ module Chan
65
65
  def self.locks
66
66
  {
67
67
  null: lambda { |_tmpdir| Chan::NullLock },
68
- file: lambda { |tmpdir| Lock::File.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
68
+ file: lambda { |tmpdir| Lockf.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
69
69
  }
70
70
  end
71
71
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xchan"
4
+ require "test/unit"
5
+
6
+ class ThreadSafetyTest < Test::Unit::TestCase
7
+ ##
8
+ # Without a Mutex wrapping the critical sections in send_nonblock and
9
+ # recv_nonblock, lockf(3) record locks do not provide thread safety
10
+ # because they are per-process (PID-based). Two threads sharing the
11
+ # same channel can both acquire the lock simultaneously, causing the
12
+ # @bytes and @counter tempfiles to be read and written concurrently.
13
+ #
14
+ # This test reproduces the race by having one thread write many
15
+ # messages while another reads them. A mismatch between the byte
16
+ # length recorded by @bytes.push and the actual data read from the
17
+ # socket triggers Marshal.load errors.
18
+ def test_concurrent_send_and_recv
19
+ ch = xchan(:marshal)
20
+ n = 1000
21
+ writer = Thread.new do
22
+ n.times { ch.send("hello") }
23
+ end
24
+ reader = Thread.new do
25
+ count = 0
26
+ n.times do
27
+ ch.recv
28
+ count += 1
29
+ end
30
+ count
31
+ end
32
+ count = [writer, reader].map(&:value).last
33
+ assert_equal n, count
34
+ ensure
35
+ ch.close unless ch.closed?
36
+ end
37
+
38
+ def test_concurrent_send_nonblock_and_recv_nonblock
39
+ ch = xchan(:marshal)
40
+ n = 1000
41
+ writer = Thread.new do
42
+ n.times { ch.send("world") }
43
+ end
44
+ reader = Thread.new do
45
+ count = 0
46
+ n.times do
47
+ ch.recv
48
+ count += 1
49
+ end
50
+ count
51
+ end
52
+ count = [writer, reader].map(&:value).last
53
+ assert_equal n, count
54
+ ensure
55
+ ch.close unless ch.closed?
56
+ end
57
+ end
data/test/xchan_test.rb CHANGED
@@ -223,3 +223,42 @@ class Chan::TemporaryFileTest < Chan::Test
223
223
  @file ||= Chan.temporary_file %w[foobar .txt]
224
224
  end
225
225
  end
226
+
227
+ ##
228
+ # Chan::UNIXSocket#wait_lockable
229
+ class Chan::WaitLockableTest < Chan::Test
230
+ def test_wait_lockable_on_lockable_channel
231
+ assert_instance_of Chan::UNIXSocket, ch.wait_lockable
232
+ end
233
+
234
+ def test_wait_lockable_on_locked_channel
235
+ aux = xchan(:pure)
236
+ lock! do
237
+ Process.wait fork { aux.send ch.wait_lockable(0.1).class.to_s }
238
+ end
239
+ assert_equal "NilClass", aux.recv
240
+ ensure
241
+ aux.close
242
+ end
243
+
244
+ def test_wait_lockable_on_null_lock
245
+ ch = xchan(:pure, lock: :null)
246
+ assert_instance_of Chan::UNIXSocket, ch.wait_lockable
247
+ ensure
248
+ ch.close
249
+ end
250
+
251
+ private
252
+
253
+ def lock!
254
+ ch.instance_variable_get(:@lock).lock
255
+ yield
256
+ ensure
257
+ release!
258
+ end
259
+
260
+ def release!
261
+ ch.instance_variable_get(:@lock).release
262
+ end
263
+ end
264
+
data/xchan.rb.gemspec CHANGED
@@ -18,7 +18,7 @@ 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", "~> 2.1"
21
+ gem.add_runtime_dependency "lockf.rb", "~> 3.0"
22
22
  gem.add_development_dependency "test-unit", "~> 3.5.7"
23
23
  gem.add_development_dependency "yard", "~> 0.9"
24
24
  gem.add_development_dependency "kramdown", "~> 2.5"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xchan.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - '0x1eef'
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-09 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: lockf.rb
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '2.1'
18
+ version: '3.0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '2.1'
25
+ version: '3.0'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: test-unit
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -147,13 +146,13 @@ files:
147
146
  - share/xchan.rb/examples/write_operations/2_nonblocking_write.rb
148
147
  - test/readme_test.rb
149
148
  - test/setup.rb
149
+ - test/thread_safety_test.rb
150
150
  - test/xchan_test.rb
151
151
  - xchan.rb.gemspec
152
152
  homepage: https://github.com/0x1eef/xchan.rb#readme
153
153
  licenses:
154
154
  - 0BSD
155
155
  metadata: {}
156
- post_install_message:
157
156
  rdoc_options: []
158
157
  require_paths:
159
158
  - lib
@@ -168,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
167
  - !ruby/object:Gem::Version
169
168
  version: '0'
170
169
  requirements: []
171
- rubygems_version: 3.5.23
172
- signing_key:
170
+ rubygems_version: 3.6.9
173
171
  specification_version: 4
174
172
  summary: An easy to use InterProcess Communication (IPC) library
175
173
  test_files: []