xchan.rb 0.17.2 → 0.19.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: 9ce716f918994e2481e8fbd333dbdb7e0b258341a09e32d464a54a2331edbd09
4
- data.tar.gz: f17d86bf4004ca1ebef87950ff9d20c7aaad29940e8c031ff84d817ade3adeb9
3
+ metadata.gz: a87a4e61f65ec4df37ecc2b09efd3d9abd4d39e57ac936db27e43ace4e99818d
4
+ data.tar.gz: 1ac4be7c20abcdee66a441088e1a7bf67b92b96f63b16b3401227bb978afa6de
5
5
  SHA512:
6
- metadata.gz: c77e364ea0f6365d4fddae810cd14e999cf584dd301e5a651a5565d78fcce157909f0b96a155f67f8f22bb50f1c7d5c2535653e4871f2e5dab862c1e38f207f3
7
- data.tar.gz: a4f2d57cf1843407d7056c4528d7e41372ed84378e33f6ca2777b6b2dde93b80fe5e57d33d2b01337a089d808b11e3e6d92c91b4f57f86aeefc4a3e4782c6afa
6
+ metadata.gz: fc46a04ae48123fbf24371bd3438013cfcb070e353993811d10b4ea9d8b63628b577bed465d441fab1d964a0dab4ec9a2040e293c59272faa6c2081819788383
7
+ data.tar.gz: 267dd70488de1b49c345c49a87c5f28a7bdf0713c20fdeeba2efbef13e2d114960dc021e3b1ec77df90ad3c1189772e3075dff3ce0f2ebfac27b727acc4a56a8
data/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  xchan.rb is an easy to use library for InterProcess
4
4
  Communication (IPC). The library provides a channel
5
- that can transfer Ruby objects between Ruby processes
6
- with a parent <-> child relationship.
5
+ that can help facilitate communication between Ruby
6
+ processes who have a parent &lt;=&gt; child relationship.
7
7
 
8
8
  ## Examples
9
9
 
@@ -11,30 +11,26 @@ with a parent <-> child relationship.
11
11
 
12
12
  #### Options
13
13
 
14
- The first argument given to xchan is the serializer
15
- that it should use. A channel that will communicate
16
- in pure strings (ie with no serialization) is
17
- available as `xchan(:pure)`.
18
-
19
- Otherwise, when a channel is written to or read from,
20
- a Ruby object is serialized (on write) or deserialized
21
- (on read). The serializers available to choose from
22
- are `xchan(:marshal)`, `xchan(:json)`, and `xchan(:yaml)`.
23
- The example uses
24
- [`Marshal`](https://www.rubydoc.info/stdlib/core/Marshal):
14
+ The first argument provided to xchan is the serializer
15
+ that should be used. A channel that will communicate
16
+ purely in strings (in other words: without serialization)
17
+ is available as `xchan(:pure)` - otherwise a wide range of
18
+ serializers are available by default: `xchan(:marshal)`,
19
+ `xchan(:json)`, and `xchan(:yaml)`.
25
20
 
26
21
  ```ruby
22
+ #!/usr/bin/env ruby
27
23
  require "xchan"
28
24
 
29
25
  ##
30
- # This channel uses Marshal to serialize objects
26
+ # Marshal as the serializer
31
27
  ch = xchan(:marshal)
32
28
  Process.wait fork { ch.send(5) }
33
- print "There are ", ch.recv + 7, " disciples and the same number of tribes", "\n"
29
+ print "#{ch.recv} + 7 = 12", "\n"
34
30
  ch.close
35
31
 
36
32
  ##
37
- # There are 12 disciples and the same number of tribes
33
+ # 5 + 7 = 12
38
34
  ```
39
35
 
40
36
  ### Read operations
@@ -49,6 +45,7 @@ blocks. The example performs a read that blocks until
49
45
  the parent process writes to the channel:
50
46
 
51
47
  ```ruby
48
+ #!/usr/bin/env ruby
52
49
  require "xchan"
53
50
 
54
51
  ch = xchan(:marshal)
@@ -76,6 +73,7 @@ the method raises `Chan::WaitReadable` when a read from
76
73
  blocks:
77
74
 
78
75
  ```ruby
76
+ #!/usr/bin/env ruby
79
77
  require "xchan"
80
78
 
81
79
  def read(ch)
@@ -108,9 +106,10 @@ process, or when a write to
108
106
  blocks. The example fills the send buffer:
109
107
 
110
108
  ```ruby
109
+ #!/usr/bin/env ruby
111
110
  require "xchan"
112
111
 
113
- ch = xchan(:marshal, sock_type: Socket::SOCK_STREAM)
112
+ ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
114
113
  sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
115
114
  while ch.bytes_sent <= sndbuf.int
116
115
  ch.send(1)
@@ -128,6 +127,7 @@ a lock held by another process, and the method raises
128
127
  blocks. The example frees space on the send buffer:
129
128
 
130
129
  ```ruby
130
+ #!/usr/bin/env ruby
131
131
  require "xchan"
132
132
 
133
133
  def send_nonblock(ch, buf)
@@ -141,7 +141,7 @@ rescue Chan::WaitLockable
141
141
  retry
142
142
  end
143
143
 
144
- ch = xchan(:marshal, sock_type: Socket::SOCK_STREAM)
144
+ ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
145
145
  sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
146
146
  while ch.bytes_sent <= sndbuf.int
147
147
  send_nonblock(ch, 1)
@@ -151,6 +151,49 @@ end
151
151
  # Blocked - free send buffer
152
152
  ```
153
153
 
154
+ ### Lock
155
+
156
+ #### File
157
+
158
+ The default lock for a channel is a file lock. The locking mechanism is
159
+ implemented with the
160
+ [lockf](https://man.freebsd.org/cgi/man.cgi?query=lockf&apropos=0&sektion=3&manpath=FreeBSD+14.2-RELEASE+and+Ports&arch=default&format=html)
161
+ function from the C standard library. Nothing special has to be done to
162
+ use it, and it allows a channel to be safely accessed across multiple
163
+ processes:
164
+
165
+ ```ruby
166
+ #!/usr/bin/env ruby
167
+ require "xchan"
168
+
169
+ ch = xchan(:marshal, lock: :file)
170
+ 5.times.map do
171
+ fork do
172
+ ch.send(5)
173
+ end
174
+ end.each { Process.wait(_1) }
175
+ ```
176
+
177
+ #### Null
178
+
179
+ The null lock is the same as using no lock at all. The null lock is
180
+ implemented as a collection of no-op operations. The null lock is
181
+ implemented in the
182
+ [Chan::NullLock](https://0x1eef.github,io/x/xchan.rb/Chan/NullLock.html)
183
+ class, and in certain situations, it can be useful and preferable
184
+ to using a file lock:
185
+
186
+ ```ruby
187
+ #!/usr/bin/env ruby
188
+ require "xchan"
189
+
190
+ ch = xchan(:marshal, lock: :null)
191
+ fork do
192
+ ch.send(5)
193
+ end
194
+ Process.wait
195
+ ```
196
+
154
197
  ### Socket
155
198
 
156
199
  #### Options
@@ -163,6 +206,7 @@ returns the socket used for read operations, and
163
206
  returns the socket used for write operations:
164
207
 
165
208
  ```ruby
209
+ #!/usr/bin/env ruby
166
210
  require "xchan"
167
211
  ch = xchan(:marshal)
168
212
 
@@ -184,7 +228,7 @@ print "The maximum size of a single message is: ", sndbuf.int, " bytes.\n"
184
228
  ## Documentation
185
229
 
186
230
  A complete API reference is available at
187
- [0x1eef.github.io/x/xchan.rb](https://0x1eef.github.io/x/xchan.rb/).
231
+ [0x1eef.github.io/x/xchan.rb](https://0x1eef.github.io/x/xchan.rb/)
188
232
 
189
233
  ## Install
190
234
 
@@ -194,11 +238,11 @@ xchan.rb can be installed via rubygems.org:
194
238
 
195
239
  ## Sources
196
240
 
197
- * [Source code (GitHub)](https://github.com/0x1eef/xchan.rb#readme)
198
- * [Source code (GitLab)](https://gitlab.com/0x1eef/xchan.rb#about)
241
+ * [github.com/@0x1eef](https://github.com/0x1eef/xchan.rb#readme)
242
+ * [gitlab.com/@0x1eef](https://gitlab.com/0x1eef/xchan.rb#about)
199
243
 
200
- ## <a id="license"> License </a>
244
+ ## License
201
245
 
202
- [BSD Zero Clause](https://choosealicense.com/licenses/0bsd/).
246
+ [BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
203
247
  <br>
204
- See [LICENSE](./LICENSE).
248
+ See [share/xchan.rb/LICENSE](./share/xchan.rb/LICENSE)
data/lib/xchan/bytes.rb CHANGED
@@ -13,7 +13,6 @@ class Chan::Bytes
13
13
  ##
14
14
  # @param [String] tmpdir
15
15
  # Directory where temporary files are stored
16
- #
17
16
  # @return [Chan::Bytes]
18
17
  def initialize(tmpdir)
19
18
  @io = Chan.temporary_file(%w[bytes .json], tmpdir:)
@@ -23,10 +22,8 @@ class Chan::Bytes
23
22
 
24
23
  ##
25
24
  # Adds a count to the start of the collection
26
- #
27
25
  # @param [Integer] len
28
26
  # The bytesize of an object
29
- #
30
27
  # @return [void]
31
28
  def unshift(len)
32
29
  return 0 if len.nil? || len.zero?
@@ -38,10 +35,8 @@ class Chan::Bytes
38
35
 
39
36
  ##
40
37
  # Adds a count to the end of the collection
41
- #
42
38
  # @param [Integer] len
43
39
  # The bytesize of an object
44
- #
45
40
  # @return [void]
46
41
  def push(len)
47
42
  return 0 if len.nil? || len.zero?
@@ -53,7 +48,6 @@ class Chan::Bytes
53
48
 
54
49
  ##
55
50
  # Removes a count from the start of the collection
56
- #
57
51
  # @return [Integer]
58
52
  # Returns the removed byte count
59
53
  def shift
@@ -73,7 +67,6 @@ class Chan::Bytes
73
67
 
74
68
  ##
75
69
  # Close the underlying IO
76
- #
77
70
  # @return [void]
78
71
  def close
79
72
  @io.close
@@ -95,6 +88,6 @@ class Chan::Bytes
95
88
  end
96
89
 
97
90
  def deserialize(bytes)
98
- JSON.load(bytes)
91
+ JSON.parse(bytes)
99
92
  end
100
93
  end
data/lib/xchan/counter.rb CHANGED
@@ -10,7 +10,6 @@ class Chan::Counter
10
10
  ##
11
11
  # @param [String] tmpdir
12
12
  # Directory where temporary files are stored
13
- #
14
13
  # @return [Chan::Counter]
15
14
  def initialize(tmpdir)
16
15
  @io = Chan.temporary_file(%w[counter .json], tmpdir:)
@@ -57,6 +56,6 @@ class Chan::Counter
57
56
  end
58
57
 
59
58
  def deserialize(bytes)
60
- JSON.load(bytes)
59
+ JSON.parse(bytes)
61
60
  end
62
61
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # {Chan::NullLock Chan::NullLock} is a no-op lock that can be used
5
+ # instead of the standard file lock when a lock is not needed
6
+ #
7
+ # @example
8
+ # ch = xchan(:marshal, lock: Chan::NullLock)
9
+ # ch.send([1,2,3])
10
+ # # ditto
11
+ # ch = xchan(:marshal, lock: :null)
12
+ # ch.send([1,2,3])
13
+ class Chan::NullLock
14
+ ##
15
+ # @return [void]
16
+ # This method is a no-op
17
+ def self.lock
18
+ end
19
+
20
+ ##
21
+ # @return [void]
22
+ # This method is a no-op
23
+ def self.lock_nonblock
24
+ end
25
+
26
+ ##
27
+ # @return [void]
28
+ # This method is a no-op
29
+ def self.release
30
+ end
31
+
32
+ ##
33
+ # @return [void]
34
+ # This method is a no-op
35
+ def self.close
36
+ end
37
+
38
+ ##
39
+ # @return [void]
40
+ # This method always returns false
41
+ def self.locked?
42
+ false
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
- require 'delegate'
2
- require 'tmpdir'
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "tmpdir"
3
5
 
4
6
  ##
5
7
  # @private
@@ -63,12 +65,12 @@ class Chan::Tempfile < DelegateClass(File)
63
65
  #
64
66
  # Related: Tempfile.create.
65
67
  #
66
- def initialize(basename="", tmpdir=nil, mode: 0, perm: 0600, **options)
68
+ def initialize(basename = "", tmpdir = nil, mode: 0, perm: 0o600, **kwargs)
67
69
  warn "Tempfile.new doesn't call the given block.", uplevel: 1 if block_given?
68
70
 
69
71
  @unlinked = false
70
- @mode = mode|File::RDWR|File::CREAT|File::EXCL
71
- ::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
72
+ @mode = mode | File::RDWR | File::CREAT | File::EXCL
73
+ ::Dir::Tmpname.create(basename, tmpdir, **kwargs) do |tmpname, n, opts|
72
74
  @tmpfile = File.open(tmpname, @mode, perm, **opts)
73
75
  @perm = perm
74
76
  @opts = opts.freeze
@@ -81,7 +83,7 @@ class Chan::Tempfile < DelegateClass(File)
81
83
  # Opens or reopens the file with mode "r+".
82
84
  def open
83
85
  _close
84
- mode = @mode & ~(File::CREAT|File::EXCL)
86
+ mode = @mode & ~(File::CREAT | File::EXCL)
85
87
  @tmpfile = File.open(@tmpfile.path, mode, **@opts)
86
88
  __setobj__(@tmpfile)
87
89
  end
@@ -97,7 +99,7 @@ class Chan::Tempfile < DelegateClass(File)
97
99
  #
98
100
  # If you don't explicitly unlink the temporary file, the removal
99
101
  # will be delayed until the object is finalized.
100
- def close(unlink_now=false)
102
+ def close(unlink_now = false)
101
103
  _close
102
104
  unlink if unlink_now
103
105
  end
@@ -153,7 +155,7 @@ class Chan::Tempfile < DelegateClass(File)
153
155
  ObjectSpace.undefine_finalizer(self)
154
156
  @unlinked = true
155
157
  end
156
- alias delete unlink
158
+ alias_method :delete, :unlink
157
159
 
158
160
  # Returns the full path name of the temporary file.
159
161
  # This will be nil if #unlink has been called.
@@ -170,7 +172,7 @@ class Chan::Tempfile < DelegateClass(File)
170
172
  File.size(@tmpfile.path)
171
173
  end
172
174
  end
173
- alias length size
175
+ alias_method :length, :size
174
176
 
175
177
  # :stopdoc:
176
178
  def inspect
@@ -190,7 +192,7 @@ class Chan::Tempfile < DelegateClass(File)
190
192
  def call(*args)
191
193
  return if @pid != Process.pid
192
194
 
193
- $stderr.puts "removing #{@tmpfile.path}..." if $DEBUG
195
+ warn "removing #{@tmpfile.path}..." if $DEBUG
194
196
 
195
197
  @tmpfile.close
196
198
  begin
@@ -198,7 +200,7 @@ class Chan::Tempfile < DelegateClass(File)
198
200
  rescue Errno::ENOENT
199
201
  end
200
202
 
201
- $stderr.puts "done" if $DEBUG
203
+ warn "done" if $DEBUG
202
204
  end
203
205
  end
204
206
 
@@ -240,8 +242,8 @@ class Chan::Tempfile < DelegateClass(File)
240
242
  # ensure
241
243
  # f.close
242
244
  # end
243
- def self.open(*args, **kw)
244
- tempfile = new(*args, **kw)
245
+ def self.open(*, **)
246
+ tempfile = new(*, **)
245
247
 
246
248
  if block_given?
247
249
  begin
@@ -313,10 +315,10 @@ module Chan
313
315
  #
314
316
  # Related: Tempfile.new.
315
317
  #
316
- def Tempfile.create(basename="", tmpdir=nil, mode: 0, perm: 0600, **options)
318
+ def Tempfile.create(basename = "", tmpdir = nil, mode: 0, perm: 0o600, **kwargs)
317
319
  tmpfile = nil
318
- Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
319
- mode |= File::RDWR|File::CREAT|File::EXCL
320
+ Dir::Tmpname.create(basename, tmpdir, **kwargs) do |tmpname, n, opts|
321
+ mode |= File::RDWR | File::CREAT | File::EXCL
320
322
  tmpfile = File.open(tmpname, mode, perm, **opts)
321
323
  end
322
324
  if block_given?
@@ -325,7 +327,11 @@ module Chan
325
327
  ensure
326
328
  unless tmpfile.closed?
327
329
  if File.identical?(tmpfile, tmpfile.path)
328
- unlinked = File.unlink tmpfile.path rescue nil
330
+ unlinked = begin
331
+ File.unlink tmpfile.path
332
+ rescue
333
+ nil
334
+ end
329
335
  end
330
336
  tmpfile.close
331
337
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ##
4
- # An easy-to-use InterProcess Communication (IPC) library.
4
+ # An easy-to-use InterProcess Communication (IPC) library
5
5
  class Chan::UNIXSocket
6
6
  require "socket"
7
7
  require "lockf"
@@ -29,24 +29,22 @@ class Chan::UNIXSocket
29
29
  # ch.send([1,2,3])
30
30
  # ch.recv.pop # => 3
31
31
  # ch.close
32
- #
33
- # @param [Symbol, <#dump, #load>] s
32
+ # @param [Symbol, <#dump, #load>] serializer
34
33
  # The name of a serializer
35
- #
36
- # @param [Integer] sock_type
34
+ # @param [Integer] sock
37
35
  # Type of socket (eg `Socket::SOCK_STREAM`)
38
- #
39
36
  # @param [String] tmpdir
40
37
  # Directory where temporary files can be stored
41
- #
38
+ # @param [Symbol, <Lock::File, Chan::NullLock>] lock
39
+ # The name of a lock, or an instance of `Lock::File`, or {Chan::NullLock Chan::NullLock}
42
40
  # @return [Chan::UNIXSocket]
43
41
  # Returns an instance of {Chan::UNIXSocket Chan::UNIXSocket}
44
- def initialize(s, sock_type: Socket::SOCK_DGRAM, tmpdir: Dir.tmpdir)
45
- @s = Chan.shortcuts[s]&.call || s
46
- @r, @w = ::UNIXSocket.pair(sock_type)
42
+ def initialize(serializer, sock: Socket::SOCK_DGRAM, tmpdir: Dir.tmpdir, lock: :file)
43
+ @s = Chan.serializers[serializer]&.call || serializer
44
+ @r, @w = ::UNIXSocket.pair(sock)
47
45
  @bytes = Chan::Bytes.new(tmpdir)
48
46
  @counter = Chan::Counter.new(tmpdir)
49
- @lock = LockFile.new Chan.temporary_file(%w[xchan .lock], tmpdir:)
47
+ @lock = Chan.locks[lock]&.call(tmpdir) || lock
50
48
  end
51
49
 
52
50
  ##
@@ -58,15 +56,13 @@ class Chan::UNIXSocket
58
56
 
59
57
  ##
60
58
  # Closes the channel
61
- #
62
59
  # @raise [IOError]
63
60
  # When the channel is closed
64
- #
65
61
  # @return [void]
66
62
  def close
67
63
  @lock.lock
68
64
  raise IOError, "channel is closed" if closed?
69
- [@r, @w, @bytes, @lock.file].each(&:close)
65
+ [@r, @w, @bytes, @lock].each(&:close)
70
66
  rescue IOError => ex
71
67
  @lock.release
72
68
  raise(ex)
@@ -77,13 +73,10 @@ class Chan::UNIXSocket
77
73
 
78
74
  ##
79
75
  # Performs a blocking write
80
- #
81
76
  # @param [Object] object
82
77
  # An object
83
- #
84
78
  # @raise [IOError]
85
79
  # When the channel is closed
86
- #
87
80
  # @return [Object]
88
81
  # Returns the number of bytes written to the channel
89
82
  def send(object)
@@ -95,19 +88,14 @@ class Chan::UNIXSocket
95
88
 
96
89
  ##
97
90
  # Performs a non-blocking write
98
- #
99
91
  # @param [Object] object
100
92
  # An object
101
- #
102
93
  # @raise [IOError]
103
94
  # When the channel is closed
104
- #
105
95
  # @raise [Chan::WaitWritable]
106
96
  # When a write to {#w} blocks
107
- #
108
97
  # @raise [Chan::WaitLockable]
109
98
  # When a write blocks because of a lock held by another process
110
- #
111
99
  # @return [Integer, nil]
112
100
  # Returns the number of bytes written to the channel
113
101
  def send_nonblock(object)
@@ -133,10 +121,8 @@ class Chan::UNIXSocket
133
121
 
134
122
  ##
135
123
  # Performs a blocking read
136
- #
137
124
  # @raise [IOError]
138
125
  # When the channel is closed
139
- #
140
126
  # @return [Object]
141
127
  # Returns an object from the channel
142
128
  def recv
@@ -151,16 +137,12 @@ class Chan::UNIXSocket
151
137
 
152
138
  ##
153
139
  # Performs a non-blocking read
154
- #
155
140
  # @raise [IOError]
156
141
  # When the channel is closed
157
- #
158
142
  # @raise [Chan::WaitReadable]
159
143
  # When a read from {#r} blocks
160
- #
161
144
  # @raise [Chan::WaitLockable]
162
145
  # When a read blocks because of a lock held by another process
163
- #
164
146
  # @return [Object]
165
147
  # Returns an object from the channel
166
148
  def recv_nonblock
@@ -187,10 +169,9 @@ class Chan::UNIXSocket
187
169
 
188
170
  ##
189
171
  # @example
190
- # ch = xchan
172
+ # ch = xchan(:pure)
191
173
  # 1.upto(4) { ch.send(_1) }
192
- # ch.to_a.last # => 4
193
- #
174
+ # ch.to_a.last # => "4"
194
175
  # @return [Array<Object>]
195
176
  # Returns the contents of the channel
196
177
  def to_a
@@ -241,10 +222,8 @@ class Chan::UNIXSocket
241
222
 
242
223
  ##
243
224
  # Waits for the channel to become readable
244
- #
245
225
  # @param [Float, Integer, nil] s
246
226
  # The number of seconds to wait. Waits indefinitely with no arguments.
247
- #
248
227
  # @return [Chan::UNIXSocket, nil]
249
228
  # Returns self when the channel is readable, otherwise returns nil
250
229
  def wait_readable(s = nil)
@@ -253,10 +232,8 @@ class Chan::UNIXSocket
253
232
 
254
233
  ##
255
234
  # Waits for the channel to become writable
256
- #
257
235
  # @param [Float, Integer, nil] s
258
236
  # The number of seconds to wait. Waits indefinitely with no arguments.
259
- #
260
237
  # @return [Chan::UNIXSocket, nil]
261
238
  # Returns self when the channel is writable, otherwise returns nil
262
239
  def wait_writable(s = nil)
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.17.2"
4
+ VERSION = "0.19.0"
5
5
  end
data/lib/xchan.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  module Chan
4
4
  require_relative "xchan/version"
5
5
  require_relative "xchan/unix_socket"
6
+ require_relative "xchan/null_lock"
6
7
  require_relative "xchan/tempfile"
7
8
 
8
9
  WaitReadable = Class.new(IO::EAGAINWaitReadable)
@@ -10,17 +11,14 @@ module Chan
10
11
  WaitLockable = Class.new(Errno::EWOULDBLOCK)
11
12
 
12
13
  ##
13
- # The Pure serializer won't perform
14
- # serialization that goes beyond calling
15
- # `.to_s` on the object it is given. It
16
- # can be useful when you want to communicate
17
- # purely in strings.
14
+ # Coerces an object to a string for a
15
+ # channel communicating in raw strings
16
+ # (in other words: without serialization)
18
17
  #
19
18
  # @example
20
19
  # ch = xchan(:pure)
21
- # Process.wait fork {
22
- # ch.send "Hello world"
23
- # }
20
+ # fork { ch.send "Hello world" }
21
+ # Process.wait
24
22
  # puts ch.recv
25
23
  Pure = Class.new do
26
24
  def self.dump(str) = str.to_s
@@ -35,10 +33,8 @@ module Chan
35
33
  #
36
34
  # @param [String] basename
37
35
  # Basename of the temporary file
38
- #
39
36
  # @param [String] tmpdir
40
37
  # Parent directory of the temporary file
41
- #
42
38
  # @return [Chan::Tempfile]
43
39
  # Returns an instance of {Chan::Tempfile Chan::Tempfile}
44
40
  def self.temporary_file(basename, tmpdir: Dir.tmpdir)
@@ -47,8 +43,8 @@ module Chan
47
43
 
48
44
  ##
49
45
  # @return [Hash<Symbol, Proc>]
50
- # Maps a short name to a serializer
51
- def self.shortcuts
46
+ # Returns the default serializers
47
+ def self.serializers
52
48
  {
53
49
  pure: lambda { Pure },
54
50
  marshal: lambda { Marshal },
@@ -62,6 +58,16 @@ module Chan
62
58
  }
63
59
  }
64
60
  end
61
+
62
+ ##
63
+ # @return [Hash<Symbol, Proc>]
64
+ # Returns the default locks
65
+ def self.locks
66
+ {
67
+ null: lambda { |_tmpdir| Chan::NullLock },
68
+ file: lambda { |tmpdir| Lock::File.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
69
+ }
70
+ end
65
71
  end
66
72
 
67
73
  module Kernel
@@ -71,12 +77,12 @@ module Kernel
71
77
  # ch.send([1,2,3])
72
78
  # ch.recv.pop # => 3
73
79
  # ch.close
74
- #
75
- # @param s (see Chan::UNIXSocket#initialize)
76
- # @param sock_type (see Chan::UNIXSocket#initialize)
80
+ # @param serializer (see Chan::UNIXSocket#initialize)
81
+ # @param sock (see Chan::UNIXSocket#initialize)
77
82
  # @param tmpdir (see Chan::UNIXSocket#initialize)
83
+ # @param lock (see Chan::UNIXSocket#initialize)
78
84
  # @return (see Chan::UNIXSocket#initialize)
79
- def xchan(s, **kw_args)
80
- Chan::UNIXSocket.new(s, **kw_args)
85
+ def xchan(s, ...)
86
+ Chan::UNIXSocket.new(s, ...)
81
87
  end
82
88
  end
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative "../setup"
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative "../setup"
@@ -1,14 +1,15 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative "../setup"
4
5
  require "xchan"
5
6
 
6
7
  ##
7
- # This channel uses Marshal to serialize objects
8
+ # Marshal as the serializer
8
9
  ch = xchan(:marshal)
9
10
  Process.wait fork { ch.send(5) }
10
- print "There are ", ch.recv + 7, " disciples and the same number of tribes", "\n"
11
+ print "#{ch.recv} + 7 = 12", "\n"
11
12
  ch.close
12
13
 
13
14
  ##
14
- # There are 12 disciples and the same number of tribes
15
+ # 5 + 7 = 12
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
1
4
  require "xchan"
2
5
  ch = xchan(:marshal)
3
6
 
@@ -1,9 +1,10 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative "../setup"
4
5
  require "xchan"
5
6
 
6
- ch = xchan(:marshal, sock_type: Socket::SOCK_STREAM)
7
+ ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
7
8
  sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
8
9
  while ch.bytes_sent <= sndbuf.int
9
10
  ch.send(1)
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative "../setup"
@@ -14,7 +15,7 @@ rescue Chan::WaitLockable
14
15
  retry
15
16
  end
16
17
 
17
- ch = xchan(:marshal, sock_type: Socket::SOCK_STREAM)
18
+ ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
18
19
  sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
19
20
  while ch.bytes_sent <= sndbuf.int
20
21
  send_nonblock(ch, 1)
data/test/readme_test.rb CHANGED
@@ -1,42 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "setup"
4
- require "test/cmd"
4
+ require "test-cmd"
5
5
 
6
6
  class Chan::ReadmeTest < Test::Unit::TestCase
7
7
  def test_serialization_1_serializers
8
- assert_equal "There are 12 disciples and the same number of tribes\n",
9
- readme_example("serialization/1_serializers.rb").stdout
8
+ assert_equal "5 + 7 = 12\n",
9
+ cmd("ruby", readme_example("serialization/1_serializers.rb")).stdout
10
10
  end
11
11
 
12
12
  def test_read_operations_1_blocking_read
13
13
  r = 'Send a random number \(from parent process\)\s*' \
14
14
  'Received random number \(child process\): \d+'
15
15
  assert_match Regexp.new(r),
16
- readme_example("read_operations/1_blocking_read.rb").stdout
17
- .tr("\n", " ")
16
+ cmd("ruby", readme_example("read_operations/1_blocking_read.rb"))
17
+ .stdout
18
+ .tr("\n", " ")
18
19
  end
19
20
 
20
21
  def test_write_operations_2_non_blocking_write
21
22
  assert_equal ["Blocked - free send buffer\n"],
22
- readme_example("write_operations/2_nonblocking_write.rb").stdout
23
- .each_line
24
- .uniq
23
+ cmd("ruby", readme_example("write_operations/2_nonblocking_write.rb"))
24
+ .stdout
25
+ .each_line
26
+ .uniq
25
27
  end
26
28
 
27
29
  def test_socket_2_options
28
- r = 'The read buffer can contain a maximum of: \d{1,6} bytes.\s*' \
29
- 'The maximum size of a single message is: \d{1,6} bytes.\s*'
30
+ r = 'The read buffer can contain a maximum of: \d{1,7} bytes.\s*' \
31
+ 'The maximum size of a single message is: \d{1,7} bytes.\s*'
30
32
  assert_match Regexp.new(r),
31
- readme_example("socket/2_options.rb").stdout
32
- .tr("\n", " ")
33
+ cmd("ruby", readme_example("socket/1_options.rb"))
34
+ .stdout
35
+ .tr("\n", " ")
33
36
  end
34
37
 
35
38
  private
36
39
 
37
40
  def readme_example(path)
38
- examples_dir = File.join(Dir.getwd, "share", "xchan.rb", "examples")
39
- example = File.join(examples_dir, path)
40
- cmd "bundle exec ruby #{example}"
41
+ dir = File.join(Dir.getwd, "share", "xchan.rb", "examples")
42
+ File.join(dir, path)
41
43
  end
42
44
  end
data/test/setup.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler/setup"
3
4
  require "json"
4
5
  require "yaml"
5
6
  require "timeout"
data/test/xchan_test.rb CHANGED
@@ -205,7 +205,7 @@ class Chan::TemporaryFileTest < Chan::Test
205
205
  end
206
206
 
207
207
  def test_temporary_file_path
208
- assert_match %r|#{Regexp.escape(Dir.tmpdir)}/foobar[a-zA-Z0-9-]+\.txt|,
208
+ assert_match %r{#{Regexp.escape(Dir.tmpdir)}/foobar[a-zA-Z0-9-]+\.txt},
209
209
  file.to_path
210
210
  ensure
211
211
  file.close
data/xchan.rb.gemspec CHANGED
@@ -8,15 +8,22 @@ Gem::Specification.new do |gem|
8
8
  gem.homepage = "https://github.com/0x1eef/xchan.rb#readme"
9
9
  gem.version = Chan::VERSION
10
10
  gem.licenses = ["0BSD"]
11
- gem.files = `git ls-files`.split($/)
11
+ gem.files = Dir[
12
+ "README.md", "LICENSE",
13
+ "share/xchan.rb/**/*.rb",
14
+ "lib/*.rb", "lib/**/*.rb",
15
+ "test/*.rb", "test/**/*.rb",
16
+ "xchan.rb.gemspec"
17
+ ]
12
18
  gem.require_paths = ["lib"]
13
19
  gem.summary = "An easy to use InterProcess Communication (IPC) library"
14
20
  gem.description = gem.summary
15
- gem.add_runtime_dependency "lockf.rb", "~> 1.0"
21
+ gem.add_runtime_dependency "lockf.rb", "~> 2.1"
16
22
  gem.add_development_dependency "test-unit", "~> 3.5.7"
17
23
  gem.add_development_dependency "yard", "~> 0.9"
18
- gem.add_development_dependency "redcarpet", "~> 3.5"
24
+ gem.add_development_dependency "kramdown", "~> 2.5"
19
25
  gem.add_development_dependency "standard", "~> 1.13"
20
- gem.add_development_dependency "test-cmd.rb", "~> 0.8"
26
+ gem.add_development_dependency "test-cmd.rb", "~> 0.12.4"
21
27
  gem.add_development_dependency "rake", "~> 13.1"
28
+ gem.add_development_dependency "irb", "~> 1.14"
22
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xchan.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - '0x1eef'
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-11 00:00:00.000000000 Z
11
+ date: 2025-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lockf.rb
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: '2.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: test-unit
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -53,19 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.9'
55
55
  - !ruby/object:Gem::Dependency
56
- name: redcarpet
56
+ name: kramdown
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.5'
61
+ version: '2.5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.5'
68
+ version: '2.5'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: standard
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.8'
89
+ version: 0.12.4
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.8'
96
+ version: 0.12.4
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '13.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: irb
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.14'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.14'
111
125
  description: An easy to use InterProcess Communication (IPC) library
112
126
  email:
113
127
  - 0x1eef@protonmail.com
@@ -115,19 +129,11 @@ executables: []
115
129
  extensions: []
116
130
  extra_rdoc_files: []
117
131
  files:
118
- - ".github/workflows/tests.yml"
119
- - ".gitignore"
120
- - ".gitlab-ci.yml"
121
- - ".projectile"
122
- - ".rubocop.yml"
123
- - ".yardopts"
124
- - Gemfile
125
- - LICENSE
126
132
  - README.md
127
- - Rakefile.rb
128
133
  - lib/xchan.rb
129
134
  - lib/xchan/bytes.rb
130
135
  - lib/xchan/counter.rb
136
+ - lib/xchan/null_lock.rb
131
137
  - lib/xchan/tempfile.rb
132
138
  - lib/xchan/unix_socket.rb
133
139
  - lib/xchan/version.rb
@@ -135,7 +141,7 @@ files:
135
141
  - share/xchan.rb/examples/read_operations/2_nonblocking_read.rb
136
142
  - share/xchan.rb/examples/serialization/1_serializers.rb
137
143
  - share/xchan.rb/examples/setup.rb
138
- - share/xchan.rb/examples/socket/2_options.rb
144
+ - share/xchan.rb/examples/socket/1_options.rb
139
145
  - share/xchan.rb/examples/stress_tests/1_parallel_access_stress_test.rb
140
146
  - share/xchan.rb/examples/write_operations/1_blocking_write.rb
141
147
  - share/xchan.rb/examples/write_operations/2_nonblocking_write.rb
@@ -162,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
168
  - !ruby/object:Gem::Version
163
169
  version: '0'
164
170
  requirements: []
165
- rubygems_version: 3.5.9
171
+ rubygems_version: 3.5.23
166
172
  signing_key:
167
173
  specification_version: 4
168
174
  summary: An easy to use InterProcess Communication (IPC) library
@@ -1,26 +0,0 @@
1
- name: xchan.rb
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- specs:
11
- strategy:
12
- fail-fast: false
13
- matrix:
14
- os: [ubuntu-latest, macos-latest]
15
- ruby: [3.1, 3.2, 3.3]
16
- runs-on: ${{ matrix.os }}
17
- steps:
18
- - uses: actions/checkout@v2
19
- - uses: ruby/setup-ruby@v1
20
- with:
21
- ruby-version: ${{ matrix.ruby }}
22
- - run: bundle install
23
- - run: SERIALIZER=marshal bundle exec rake
24
- - run: SERIALIZER=json bundle exec rake
25
- - run: SERIALIZER=yaml bundle exec rake
26
- - run: SERIALIZER=pure bundle exec rake
data/.gitignore DELETED
@@ -1,8 +0,0 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- .yardoc/
5
- .dev/
6
- doc/
7
- pkg/
8
- .gems/
data/.gitlab-ci.yml DELETED
@@ -1,12 +0,0 @@
1
- stages:
2
- - test
3
-
4
- test-ruby32:
5
- stage: test
6
- image: ruby:3.2.0
7
- script:
8
- - bundle install
9
- - SERIALIZER=marshal bundle exec rake
10
- - SERIALIZER=json bundle exec rake
11
- - SERIALIZER=yaml bundle exec rake
12
- - SERIALIZER=pure bundle exec rake
data/.projectile DELETED
@@ -1,4 +0,0 @@
1
- +.
2
- -/doc/
3
- -/.yardoc/
4
- -/.gems/
data/.rubocop.yml DELETED
@@ -1,34 +0,0 @@
1
- ##
2
- # Plugins
3
- require:
4
- - standard
5
-
6
- ##
7
- # Defaults: standard-rb
8
- inherit_gem:
9
- standard: config/base.yml
10
-
11
- ##
12
- # All cops
13
- AllCops:
14
- TargetRubyVersion: 3.2
15
- Include:
16
- - lib/*.rb
17
- - lib/**/*.rb
18
- - test/*_test.rb
19
-
20
- ##
21
- # Enabled
22
- Style/FrozenStringLiteralComment:
23
- Enabled: true
24
-
25
- ##
26
- # Disabled
27
- Layout/ArgumentAlignment:
28
- Enabled: false
29
- Layout/MultilineMethodCallIndentation:
30
- Enabled: false
31
- Layout/EmptyLineBetweenDefs:
32
- Enabled: false
33
- Style/TrivialAccessors:
34
- Enabled: false
data/.yardopts DELETED
@@ -1,4 +0,0 @@
1
- -m markdown -M redcarpet --no-private
2
- -
3
- README.md
4
- LICENSE
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
- gemspec
data/LICENSE DELETED
@@ -1,15 +0,0 @@
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/Rakefile.rb DELETED
@@ -1,9 +0,0 @@
1
- require "rake/testtask"
2
- require "bundler/setup"
3
-
4
- Rake::TestTask.new do |t|
5
- t.test_files = FileList['test/*_test.rb']
6
- t.verbose = true
7
- t.warning = false
8
- end
9
- task default: :test