xchan.rb 0.17.0 → 0.17.2

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: bf2db46e92eb29ed0182377c209a8ff7511460d22a73225616c719cd05372884
4
- data.tar.gz: 05b70d50483d354f79e29f9219628c44cb6f4b25e63cbd5aee7e0c0a14da8865
3
+ metadata.gz: 9ce716f918994e2481e8fbd333dbdb7e0b258341a09e32d464a54a2331edbd09
4
+ data.tar.gz: f17d86bf4004ca1ebef87950ff9d20c7aaad29940e8c031ff84d817ade3adeb9
5
5
  SHA512:
6
- metadata.gz: f8fb7b0f73b09c4593dfa7892fd3e0061dce37242c0e9bebab0edd204cda399dd16c8fdbadce2ee6a5fac1fc6c1d583ad60eadd85c7ece4a145fdb18358aff0a
7
- data.tar.gz: ef454d2422ae0a719a19b7fad3bf670a3cc4b46154ea6e87786a4c6404bbe9f7af5437e81b3ed8651fa9e1f438ee7f43c38d10497ca9b50facd3aea3ededc7f0
6
+ metadata.gz: c77e364ea0f6365d4fddae810cd14e999cf584dd301e5a651a5565d78fcce157909f0b96a155f67f8f22bb50f1c7d5c2535653e4871f2e5dab862c1e38f207f3
7
+ data.tar.gz: a4f2d57cf1843407d7056c4528d7e41372ed84378e33f6ca2777b6b2dde93b80fe5e57d33d2b01337a089d808b11e3e6d92c91b4f57f86aeefc4a3e4782c6afa
@@ -12,7 +12,7 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  os: [ubuntu-latest, macos-latest]
15
- ruby: [3.1, 3.2]
15
+ ruby: [3.1, 3.2, 3.3]
16
16
  runs-on: ${{ matrix.os }}
17
17
  steps:
18
18
  - uses: actions/checkout@v2
@@ -23,4 +23,4 @@ jobs:
23
23
  - run: SERIALIZER=marshal bundle exec rake
24
24
  - run: SERIALIZER=json bundle exec rake
25
25
  - run: SERIALIZER=yaml bundle exec rake
26
- - run: SERIALIZER=plain bundle exec rake
26
+ - run: SERIALIZER=pure bundle exec rake
data/.gitlab-ci.yml CHANGED
@@ -9,4 +9,4 @@ test-ruby32:
9
9
  - SERIALIZER=marshal bundle exec rake
10
10
  - SERIALIZER=json bundle exec rake
11
11
  - SERIALIZER=yaml bundle exec rake
12
- - SERIALIZER=plain bundle exec rake
12
+ - SERIALIZER=pure bundle exec rake
data/README.md CHANGED
@@ -52,13 +52,14 @@ the parent process writes to the channel:
52
52
  require "xchan"
53
53
 
54
54
  ch = xchan(:marshal)
55
- Process.detach fork {
55
+ fork do
56
56
  print "Received a random number (child process): ", ch.recv, "\n"
57
- }
57
+ end
58
58
  sleep(1)
59
59
  print "Send a random number (from parent process)", "\n"
60
60
  ch.send(rand(21))
61
61
  ch.close
62
+ Process.wait
62
63
 
63
64
  ##
64
65
  # Send a random number (from parent process)
data/lib/xchan/bytes.rb CHANGED
@@ -1,39 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ##
4
- # The {Chan::Bytes Chan::Bytes} class is similar
5
- # to an array, where each element represents the
6
- # number of bytes used to store an object on a
7
- # channel. When an object is written to a channel,
8
- # the array increases in size, and when an object
9
- # is read from a channel, the array decreases in
10
- # size.
4
+ # {Chan::Bytes Chan::Bytes} represents a collection
5
+ # of byte counts for each object stored on a channel.
6
+ # When an object is written to a channel, the collection
7
+ # increases in size, and when an object is read from
8
+ # a channel, the collection decreases in size.
11
9
  class Chan::Bytes
12
10
  require "json"
13
- require_relative "stat"
14
-
15
- ##
16
- # @return [Chan::Stat]
17
- attr_reader :stat
11
+ require_relative "counter"
18
12
 
19
13
  ##
20
14
  # @param [String] tmpdir
21
- # Path to a directory where temporary files will be stored.
15
+ # Directory where temporary files are stored
22
16
  #
23
17
  # @return [Chan::Bytes]
24
18
  def initialize(tmpdir)
25
- @serializer = JSON
26
- @io = Chan.temporary_file("xchan.bytes", tmpdir:)
19
+ @io = Chan.temporary_file(%w[bytes .json], tmpdir:)
27
20
  @io.sync = true
28
- @stat = Chan::Stat.new(tmpdir)
29
21
  write(@io, [])
30
22
  end
31
23
 
32
24
  ##
33
- # Insert a byte count at the head of the array
25
+ # Adds a count to the start of the collection
34
26
  #
35
27
  # @param [Integer] len
36
- # Number of bytes
28
+ # The bytesize of an object
37
29
  #
38
30
  # @return [void]
39
31
  def unshift(len)
@@ -41,15 +33,14 @@ class Chan::Bytes
41
33
  bytes = read(@io)
42
34
  bytes.unshift(len)
43
35
  write(@io, bytes)
44
- @stat.store(bytes_written: len)
45
36
  len
46
37
  end
47
38
 
48
39
  ##
49
- # Insert a byte count at the tail of the array
40
+ # Adds a count to the end of the collection
50
41
  #
51
42
  # @param [Integer] len
52
- # Number of bytes
43
+ # The bytesize of an object
53
44
  #
54
45
  # @return [void]
55
46
  def push(len)
@@ -57,25 +48,25 @@ class Chan::Bytes
57
48
  bytes = read(@io)
58
49
  bytes.push(len)
59
50
  write(@io, bytes)
60
- @stat.store(bytes_written: len)
61
51
  len
62
52
  end
63
53
 
64
54
  ##
55
+ # Removes a count from the start of the collection
56
+ #
65
57
  # @return [Integer]
66
- # Returns (and removes) a byte count from the head of the array
58
+ # Returns the removed byte count
67
59
  def shift
68
60
  bytes = read(@io)
69
61
  return 0 if bytes.size.zero?
70
62
  len = bytes.shift
71
63
  write(@io, bytes)
72
- @stat.store(bytes_read: len)
73
64
  len
74
65
  end
75
66
 
76
67
  ##
77
68
  # @return [Integer]
78
- # Returns the size of the array
69
+ # Returns the number of objects in the collection
79
70
  def size
80
71
  read(@io).size
81
72
  end
@@ -100,10 +91,10 @@ class Chan::Bytes
100
91
  end
101
92
 
102
93
  def serialize(bytes)
103
- @serializer.dump(bytes)
94
+ JSON.dump(bytes)
104
95
  end
105
96
 
106
97
  def deserialize(bytes)
107
- @serializer.load(bytes)
98
+ JSON.load(bytes)
108
99
  end
109
100
  end
@@ -1,33 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ##
4
- # The {Chan::Stat Chan::Stat} class provides statistics
5
- # (eg number of bytes read, number of bytes written) for
6
- # a given channel.
7
- class Chan::Stat
4
+ # {Chan::Counter Chan::Counter} provides a counter
5
+ # for the number of written and received bytes on a
6
+ # given channel.
7
+ class Chan::Counter
8
8
  require "json"
9
9
 
10
10
  ##
11
11
  # @param [String] tmpdir
12
- # Path to a directory where temporary files will be stored.
12
+ # Directory where temporary files are stored
13
13
  #
14
- # @return [Chan::Stat]
14
+ # @return [Chan::Counter]
15
15
  def initialize(tmpdir)
16
- @serializer = JSON
17
- @io = Chan.temporary_file("xchan.stat", tmpdir:)
16
+ @io = Chan.temporary_file(%w[counter .json], tmpdir:)
18
17
  write(@io, {"bytes_read" => 0, "bytes_written" => 0})
19
18
  end
20
19
 
21
20
  ##
22
21
  # @return [Integer]
23
- # Returns the number of bytes written to a channel.
22
+ # Returns the number of bytes written to a channel
24
23
  def bytes_written
25
24
  read(@io).fetch("bytes_written")
26
25
  end
27
26
 
28
27
  ##
29
28
  # @return [Integer]
30
- # Returns the number of bytes read from a channel.
29
+ # Returns the number of bytes read from a channel
31
30
  def bytes_read
32
31
  read(@io).fetch("bytes_read")
33
32
  end
@@ -36,7 +35,7 @@ class Chan::Stat
36
35
  # @param [Hash] new_stat
37
36
  # @return [void]
38
37
  # @private
39
- def store(new_stat)
38
+ def increment!(new_stat)
40
39
  stat = read(@io)
41
40
  new_stat.each { stat[_1.to_s] += _2 }
42
41
  write(@io, stat)
@@ -54,10 +53,10 @@ class Chan::Stat
54
53
  end
55
54
 
56
55
  def serialize(bytes)
57
- @serializer.dump(bytes)
56
+ JSON.dump(bytes)
58
57
  end
59
58
 
60
59
  def deserialize(bytes)
61
- @serializer.load(bytes)
60
+ JSON.load(bytes)
62
61
  end
63
62
  end
@@ -17,6 +17,12 @@ class Chan::UNIXSocket
17
17
  # Returns a socket used for write operations
18
18
  attr_reader :w
19
19
 
20
+ ##
21
+ # @return [<#dump, #load>]
22
+ # Returns the serializer used by the channel
23
+ attr_reader :s
24
+ alias_method :serializer, :s
25
+
20
26
  ##
21
27
  # @example
22
28
  # ch = Chan::UNIXSocket.new(:marshal)
@@ -24,7 +30,7 @@ class Chan::UNIXSocket
24
30
  # ch.recv.pop # => 3
25
31
  # ch.close
26
32
  #
27
- # @param [Symbol, <#dump, #load>] serializer
33
+ # @param [Symbol, <#dump, #load>] s
28
34
  # The name of a serializer
29
35
  #
30
36
  # @param [Integer] sock_type
@@ -35,32 +41,26 @@ class Chan::UNIXSocket
35
41
  #
36
42
  # @return [Chan::UNIXSocket]
37
43
  # Returns an instance of {Chan::UNIXSocket Chan::UNIXSocket}
38
- def initialize(serializer, sock_type: Socket::SOCK_DGRAM, tmpdir: Dir.tmpdir)
39
- @serializer = Chan.serializers[serializer]&.call || serializer
44
+ def initialize(s, sock_type: Socket::SOCK_DGRAM, tmpdir: Dir.tmpdir)
45
+ @s = Chan.shortcuts[s]&.call || s
40
46
  @r, @w = ::UNIXSocket.pair(sock_type)
41
47
  @bytes = Chan::Bytes.new(tmpdir)
42
- @lock = LockFile.new Chan.temporary_file("xchan.lock", tmpdir:)
43
- end
44
-
45
- ##
46
- # @return [<#dump, #load>]
47
- # Returns the serializer used by the channel.
48
- def serializer
49
- @serializer
48
+ @counter = Chan::Counter.new(tmpdir)
49
+ @lock = LockFile.new Chan.temporary_file(%w[xchan .lock], tmpdir:)
50
50
  end
51
51
 
52
52
  ##
53
53
  # @return [Boolean]
54
- # Returns true when the channel is closed.
54
+ # Returns true when the channel is closed
55
55
  def closed?
56
56
  @r.closed? and @w.closed?
57
57
  end
58
58
 
59
59
  ##
60
- # Closes the channel.
60
+ # Closes the channel
61
61
  #
62
62
  # @raise [IOError]
63
- # When the channel is closed.
63
+ # When the channel is closed
64
64
  #
65
65
  # @return [void]
66
66
  def close
@@ -79,13 +79,13 @@ class Chan::UNIXSocket
79
79
  # Performs a blocking write
80
80
  #
81
81
  # @param [Object] object
82
- # An object to write to the channel.
82
+ # An object
83
83
  #
84
84
  # @raise [IOError]
85
- # When the channel is closed.
85
+ # When the channel is closed
86
86
  #
87
87
  # @return [Object]
88
- # Returns the number of bytes written to the channel.
88
+ # Returns the number of bytes written to the channel
89
89
  def send(object)
90
90
  send_nonblock(object)
91
91
  rescue Chan::WaitWritable, Chan::WaitLockable
@@ -97,24 +97,25 @@ class Chan::UNIXSocket
97
97
  # Performs a non-blocking write
98
98
  #
99
99
  # @param [Object] object
100
- # An object to write to the channel.
100
+ # An object
101
101
  #
102
102
  # @raise [IOError]
103
- # When the channel is closed.
103
+ # When the channel is closed
104
104
  #
105
105
  # @raise [Chan::WaitWritable]
106
- # When a write to the underlying IO blocks.
106
+ # When a write to {#w} blocks
107
107
  #
108
108
  # @raise [Chan::WaitLockable]
109
- # When a write blocks because of a lock held by another process.
109
+ # When a write blocks because of a lock held by another process
110
110
  #
111
111
  # @return [Integer, nil]
112
- # Returns the number of bytes written to the channel.
112
+ # Returns the number of bytes written to the channel
113
113
  def send_nonblock(object)
114
114
  @lock.lock_nonblock
115
115
  raise IOError, "channel closed" if closed?
116
116
  len = @w.write_nonblock(serialize(object))
117
117
  @bytes.push(len)
118
+ @counter.increment!(bytes_written: len)
118
119
  len.tap { @lock.release }
119
120
  rescue IOError, IO::WaitWritable, Errno::ENOBUFS => ex
120
121
  @lock.release
@@ -134,10 +135,10 @@ class Chan::UNIXSocket
134
135
  # Performs a blocking read
135
136
  #
136
137
  # @raise [IOError]
137
- # When the channel is closed.
138
+ # When the channel is closed
138
139
  #
139
140
  # @return [Object]
140
- # Returns an object from the channel.
141
+ # Returns an object from the channel
141
142
  def recv
142
143
  recv_nonblock
143
144
  rescue Chan::WaitReadable
@@ -152,21 +153,22 @@ class Chan::UNIXSocket
152
153
  # Performs a non-blocking read
153
154
  #
154
155
  # @raise [IOError]
155
- # When the channel is closed.
156
+ # When the channel is closed
156
157
  #
157
158
  # @raise [Chan::WaitReadable]
158
- # When a read from the underlying IO blocks.
159
+ # When a read from {#r} blocks
159
160
  #
160
161
  # @raise [Chan::WaitLockable]
161
- # When a read blocks because of a lock held by another process.
162
+ # When a read blocks because of a lock held by another process
162
163
  #
163
164
  # @return [Object]
164
- # Returns an object from the channel.
165
+ # Returns an object from the channel
165
166
  def recv_nonblock
166
167
  @lock.lock_nonblock
167
168
  raise IOError, "closed channel" if closed?
168
169
  len = @bytes.shift
169
170
  obj = deserialize(@r.read_nonblock(len.zero? ? 1 : len))
171
+ @counter.increment!(bytes_read: len)
170
172
  obj.tap { @lock.release }
171
173
  rescue IOError => ex
172
174
  @lock.release
@@ -190,7 +192,7 @@ class Chan::UNIXSocket
190
192
  # ch.to_a.last # => 4
191
193
  #
192
194
  # @return [Array<Object>]
193
- # Returns the consumed contents of the channel.
195
+ # Returns the contents of the channel
194
196
  def to_a
195
197
  lock do
196
198
  [].tap { _1.push(recv) until empty? }
@@ -199,7 +201,7 @@ class Chan::UNIXSocket
199
201
 
200
202
  ##
201
203
  # @return [Boolean]
202
- # Returns true when the channel is empty.
204
+ # Returns true when the channel is empty
203
205
  def empty?
204
206
  return true if closed?
205
207
  lock { size.zero? }
@@ -210,23 +212,23 @@ class Chan::UNIXSocket
210
212
 
211
213
  ##
212
214
  # @return [Integer]
213
- # Returns the total number of bytes written to the channel.
215
+ # Returns the total number of bytes written to the channel
214
216
  def bytes_sent
215
- lock { @bytes.stat.bytes_written }
217
+ lock { @counter.bytes_written }
216
218
  end
217
219
  alias_method :bytes_written, :bytes_sent
218
220
 
219
221
  ##
220
222
  # @return [Integer]
221
- # Returns the total number of bytes read from the channel.
223
+ # Returns the total number of bytes read from the channel
222
224
  def bytes_received
223
- lock { @bytes.stat.bytes_read }
225
+ lock { @counter.bytes_read }
224
226
  end
225
227
  alias_method :bytes_read, :bytes_received
226
228
 
227
229
  ##
228
230
  # @return [Integer]
229
- # Returns the number of objects waiting to be read.
231
+ # Returns the number of objects waiting to be read
230
232
  def size
231
233
  lock { @bytes.size }
232
234
  end
@@ -238,25 +240,25 @@ class Chan::UNIXSocket
238
240
  # @group Wait methods
239
241
 
240
242
  ##
241
- # Waits for the channel to become readable.
243
+ # Waits for the channel to become readable
242
244
  #
243
245
  # @param [Float, Integer, nil] s
244
- # The number of seconds to wait. Waits indefinitely when "nil".
246
+ # The number of seconds to wait. Waits indefinitely with no arguments.
245
247
  #
246
248
  # @return [Chan::UNIXSocket, nil]
247
- # Returns self when the channel is readable, otherwise returns nil.
249
+ # Returns self when the channel is readable, otherwise returns nil
248
250
  def wait_readable(s = nil)
249
251
  @r.wait_readable(s) and self
250
252
  end
251
253
 
252
254
  ##
253
- # Waits for the channel to become writable.
255
+ # Waits for the channel to become writable
254
256
  #
255
257
  # @param [Float, Integer, nil] s
256
- # The number of seconds to wait. Waits indefinitely when "nil".
258
+ # The number of seconds to wait. Waits indefinitely with no arguments.
257
259
  #
258
260
  # @return [Chan::UNIXSocket, nil]
259
- # Returns self when the channel is writable, otherwise returns nil.
261
+ # Returns self when the channel is writable, otherwise returns nil
260
262
  def wait_writable(s = nil)
261
263
  @w.wait_writable(s) and self
262
264
  end
@@ -274,10 +276,10 @@ class Chan::UNIXSocket
274
276
  end
275
277
 
276
278
  def serialize(obj)
277
- @serializer.dump(obj)
279
+ @s.dump(obj)
278
280
  end
279
281
 
280
282
  def deserialize(str)
281
- @serializer.load(str)
283
+ @s.load(str)
282
284
  end
283
285
  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.17.0"
4
+ VERSION = "0.17.2"
5
5
  end
data/lib/xchan.rb CHANGED
@@ -47,8 +47,8 @@ module Chan
47
47
 
48
48
  ##
49
49
  # @return [Hash<Symbol, Proc>]
50
- # A mapping of serializers
51
- def self.serializers
50
+ # Maps a short name to a serializer
51
+ def self.shortcuts
52
52
  {
53
53
  pure: lambda { Pure },
54
54
  marshal: lambda { Marshal },
@@ -72,11 +72,11 @@ module Kernel
72
72
  # ch.recv.pop # => 3
73
73
  # ch.close
74
74
  #
75
- # @param serializer (see Chan::UNIXSocket#initialize)
75
+ # @param s (see Chan::UNIXSocket#initialize)
76
76
  # @param sock_type (see Chan::UNIXSocket#initialize)
77
77
  # @param tmpdir (see Chan::UNIXSocket#initialize)
78
78
  # @return (see Chan::UNIXSocket#initialize)
79
- def xchan(serializer, **kw_args)
80
- Chan::UNIXSocket.new(serializer, **kw_args)
79
+ def xchan(s, **kw_args)
80
+ Chan::UNIXSocket.new(s, **kw_args)
81
81
  end
82
82
  end
@@ -5,13 +5,14 @@ require "xchan"
5
5
 
6
6
  $stdout.sync = true
7
7
  ch = xchan(:marshal)
8
- Process.detach fork {
8
+ fork do
9
9
  print "Received random number (child process): ", ch.recv, "\n"
10
- }
10
+ end
11
11
  sleep(1)
12
12
  print "Send a random number (from parent process)", "\n"
13
13
  ch.send(rand(21))
14
14
  ch.close
15
+ Process.wait
15
16
 
16
17
  ##
17
18
  # Send a random number (from parent process)
data/test/xchan_test.rb CHANGED
@@ -4,7 +4,7 @@ require_relative "setup"
4
4
 
5
5
  class Chan::Test < Test::Unit::TestCase
6
6
  def setup
7
- @ch = xchan ENV.fetch("SERIALIZER", "marshal").to_sym
7
+ @ch = xchan(serializer)
8
8
  end
9
9
 
10
10
  def teardown
@@ -17,9 +17,13 @@ class Chan::Test < Test::Unit::TestCase
17
17
  @ch
18
18
  end
19
19
 
20
+ def serializer
21
+ ENV.fetch("SERIALIZER", "pure").to_sym
22
+ end
23
+
20
24
  def object
21
- case ENV["SERIALIZER"]
22
- when "plain" then "xchan"
25
+ case serializer
26
+ when :pure then "xchan"
23
27
  else %w[xchan]
24
28
  end
25
29
  end
@@ -190,3 +194,32 @@ class Chan::BytesReadTest < Chan::Test
190
194
  assert_equal object_size * 2, ch.bytes_read
191
195
  end
192
196
  end
197
+
198
+ ##
199
+ # Chan.temporary_file
200
+ class Chan::TemporaryFileTest < Chan::Test
201
+ def test_temporary_file_mode
202
+ assert_equal 0, file.stat.mode & 0o777
203
+ ensure
204
+ file.close
205
+ end
206
+
207
+ def test_temporary_file_path
208
+ assert_match %r|#{Regexp.escape(Dir.tmpdir)}/foobar[a-zA-Z0-9-]+\.txt|,
209
+ file.to_path
210
+ ensure
211
+ file.close
212
+ end
213
+
214
+ def test_temporary_file_unlinked
215
+ refute File.exist?(file.to_path)
216
+ ensure
217
+ file.close
218
+ end
219
+
220
+ private
221
+
222
+ def file
223
+ @file ||= Chan.temporary_file %w[foobar .txt]
224
+ end
225
+ 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.0
4
+ version: 0.17.2
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-10 00:00:00.000000000 Z
11
+ date: 2024-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lockf.rb
@@ -127,7 +127,7 @@ files:
127
127
  - Rakefile.rb
128
128
  - lib/xchan.rb
129
129
  - lib/xchan/bytes.rb
130
- - lib/xchan/stat.rb
130
+ - lib/xchan/counter.rb
131
131
  - lib/xchan/tempfile.rb
132
132
  - lib/xchan/unix_socket.rb
133
133
  - lib/xchan/version.rb