xchan.rb 0.16.4
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 +7 -0
- data/.github/workflows/tests.yml +26 -0
- data/.gitignore +9 -0
- data/.gitlab-ci.yml +12 -0
- data/.projectile +1 -0
- data/.rubocop.yml +34 -0
- data/.yardopts +4 -0
- data/Gemfile +6 -0
- data/LICENSE +15 -0
- data/README.md +242 -0
- data/Rakefile.rb +9 -0
- data/lib/xchan/bytes.rb +109 -0
- data/lib/xchan/mixin.rb +20 -0
- data/lib/xchan/stat.rb +63 -0
- data/lib/xchan/tempfile.rb +347 -0
- data/lib/xchan/unix_socket.rb +329 -0
- data/lib/xchan/version.rb +5 -0
- data/lib/xchan.rb +53 -0
- data/share/xchan.rb/examples/read_operations/1_blocking_read.rb +20 -0
- data/share/xchan.rb/examples/read_operations/2_nonblocking_read.rb +17 -0
- data/share/xchan.rb/examples/serialization/1_serializers.rb +20 -0
- data/share/xchan.rb/examples/setup.rb +4 -0
- data/share/xchan.rb/examples/socket/2_options.rb +12 -0
- data/share/xchan.rb/examples/stress_tests/1_parallel_access_stress_test.rb +10 -0
- data/share/xchan.rb/examples/write_operations/1_blocking_write.rb +10 -0
- data/share/xchan.rb/examples/write_operations/2_nonblocking_write.rb +24 -0
- data/test/readme_test.rb +44 -0
- data/test/setup.rb +7 -0
- data/test/xchan_test.rb +192 -0
- data/xchan.rb.gemspec +22 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f17c01d5c34d8de2176fa5241ca08bc985798d6a3d7d6b4fa355cd0f2cc6e739
|
4
|
+
data.tar.gz: 0f1908263e8a272e32d2ec6e8026c21e836c1da92ee2ed12a0a9949e69ed1d46
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac3779d634423270951d4c2d4818c021a856f066837f3b2015365d50af390cb5ccc2453475773f4ee0ed13ce1c19a30787e873ee43d84dc7d1ebd7f744e7a3f9
|
7
|
+
data.tar.gz: d7fccca83af9d0d73302b17b50d7b03843ce1c7d599f711e1a7f5b9252d1a8e9f40f7919134ffb25fb88007ee2578b0a9e7049a51b29c3cb0440df66a5ea0d09
|
@@ -0,0 +1,26 @@
|
|
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]
|
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=plain bundle exec rake
|
data/.gitignore
ADDED
data/.gitlab-ci.yml
ADDED
data/.projectile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
+.
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,34 @@
|
|
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
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
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/README.md
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
## About
|
2
|
+
|
3
|
+
xchan.rb is an easy to use library that provides a channel for
|
4
|
+
InterProcess Communication (IPC) between a parent Ruby process,
|
5
|
+
and its child processes. The channel is implemented through both
|
6
|
+
serialization, and an unbound unix socket.
|
7
|
+
|
8
|
+
## Examples
|
9
|
+
|
10
|
+
### Serialization
|
11
|
+
|
12
|
+
#### Options
|
13
|
+
|
14
|
+
When a channel is written to or read from, a Ruby object is serialized
|
15
|
+
(on write) or deserialized (on read). The default serializers are available as
|
16
|
+
`xchan(:marshal)`, `xchan(:json)`, and `xchan(:yaml)`. For scenarios where it
|
17
|
+
is preferred to send and receive plain strings, the "plain" serializer is
|
18
|
+
available as `xchan(:plain)`. This example uses
|
19
|
+
[`Marshal`](https://www.rubydoc.info/stdlib/core/Marshal):
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "xchan"
|
23
|
+
|
24
|
+
##
|
25
|
+
# This channel uses Marshal to serialize objects.
|
26
|
+
ch = xchan
|
27
|
+
pid = fork { print "Received message: ", ch.recv[:msg], "\n" }
|
28
|
+
ch.send(msg: "serialized by Marshal")
|
29
|
+
ch.close
|
30
|
+
Process.wait(pid)
|
31
|
+
|
32
|
+
##
|
33
|
+
# This channel also uses Marshal to serialize objects.
|
34
|
+
ch = xchan(:marshal)
|
35
|
+
pid = fork { print "Received message: ", ch.recv[:msg], "\n"
|
36
|
+
ch.send(msg: "serialized by Marshal")
|
37
|
+
ch.close
|
38
|
+
Process.wait(pid)
|
39
|
+
|
40
|
+
##
|
41
|
+
# Received message: serialized by Marshal
|
42
|
+
# Received message: serialized by Marshal
|
43
|
+
```
|
44
|
+
|
45
|
+
### Read operations
|
46
|
+
|
47
|
+
#### `#recv`
|
48
|
+
|
49
|
+
The `ch.recv` method performs a blocking read. A read can block when
|
50
|
+
a lock is held by another process, or when a read from the underlying
|
51
|
+
socket blocks. This example performs a read that blocks in a child
|
52
|
+
process until the parent process writes to the channel:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require "xchan"
|
56
|
+
|
57
|
+
ch = xchan
|
58
|
+
pid = fork do
|
59
|
+
print "Received a random number (child process): ", ch.recv, "\n"
|
60
|
+
end
|
61
|
+
# Delay for a second to let a process fork, and call "ch.recv"
|
62
|
+
sleep(1)
|
63
|
+
print "Send a random number (from parent process)", "\n"
|
64
|
+
ch.send(rand(21))
|
65
|
+
Process.wait(pid)
|
66
|
+
ch.close
|
67
|
+
|
68
|
+
##
|
69
|
+
# Send a random number (from parent process)
|
70
|
+
# Received random number (child process): XX
|
71
|
+
```
|
72
|
+
|
73
|
+
#### `#recv_nonblock`
|
74
|
+
|
75
|
+
The non-blocking counterpart to `#recv` is `#recv_nonblock`. The `#recv_nonblock`
|
76
|
+
method raises `Chan::WaitLockable` when a read blocks because of a lock held by
|
77
|
+
another process, and the method raises `Chan::WaitReadable` when a read on the
|
78
|
+
underlying socket blocks. This example performs a read that will
|
79
|
+
raise `Chan::WaitReadable`:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
require "xchan"
|
83
|
+
|
84
|
+
def read(ch)
|
85
|
+
ch.recv_nonblock
|
86
|
+
rescue Chan::WaitReadable
|
87
|
+
print "Wait 1 second for channel to be readable", "\n"
|
88
|
+
ch.wait_readable(1)
|
89
|
+
retry
|
90
|
+
rescue Chan::WaitLockable
|
91
|
+
sleep 0.01
|
92
|
+
retry
|
93
|
+
end
|
94
|
+
trap("SIGINT") { exit(1) }
|
95
|
+
read(xchan)
|
96
|
+
|
97
|
+
##
|
98
|
+
# Wait 1 second for channel to be readable
|
99
|
+
# Wait 1 second for channel to be readable
|
100
|
+
# ^C
|
101
|
+
```
|
102
|
+
|
103
|
+
### Write operations
|
104
|
+
|
105
|
+
#### `#send`
|
106
|
+
|
107
|
+
The `ch.send` method performs a blocking write. A write can block when a lock
|
108
|
+
is held by another process, or when a write to the underlying socket blocks.
|
109
|
+
This example performs a write that will block when the send buffer becomes full:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
require "xchan"
|
113
|
+
|
114
|
+
ch = xchan(:marshal, socket: Socket::SOCK_STREAM)
|
115
|
+
sndbuf = ch.getsockopt(:reader, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
116
|
+
while ch.bytes_sent <= sndbuf.int
|
117
|
+
ch.send(1)
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
#### `#send_nonblock`
|
122
|
+
|
123
|
+
The non-blocking counterpart to `#send` is `#send_nonblock`. The `#send_nonblock`
|
124
|
+
method raises `Chan::WaitLockable` when a write blocks because of a lock held
|
125
|
+
by another process, and the method raises `Chan::WaitReadable` when a write to
|
126
|
+
the underlying socket blocks. This example builds on the previous example by
|
127
|
+
freeing space on the send buffer when a write is found to block:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
require "xchan"
|
131
|
+
|
132
|
+
def send_nonblock(ch, buf)
|
133
|
+
ch.send_nonblock(buf)
|
134
|
+
rescue Chan::WaitWritable
|
135
|
+
print "Blocked - free send buffer", "\n"
|
136
|
+
ch.recv
|
137
|
+
retry
|
138
|
+
rescue Chan::WaitLockable
|
139
|
+
sleep 0.01
|
140
|
+
retry
|
141
|
+
end
|
142
|
+
|
143
|
+
ch = xchan(:marshal, socket: Socket::SOCK_STREAM)
|
144
|
+
sndbuf = ch.getsockopt(:writer, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
145
|
+
while ch.bytes_sent <= sndbuf.int
|
146
|
+
send_nonblock(ch, 1)
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Blocked - free send buffer
|
151
|
+
```
|
152
|
+
|
153
|
+
### Socket
|
154
|
+
|
155
|
+
#### Types
|
156
|
+
|
157
|
+
A channel can be created with one of three sockets types:
|
158
|
+
|
159
|
+
* `Socket::SOCK_DGRAM`
|
160
|
+
* `Socket::SOCK_STREAM`
|
161
|
+
* `Socket::SOCK_SEQPACKET`
|
162
|
+
|
163
|
+
The default is `Socket::SOCK_DGRAM` because its default settings
|
164
|
+
provide the most buffer space. The socket type can be specified as
|
165
|
+
a keyword argument:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
require "xchan"
|
169
|
+
ch = xchan(:marshal, socket: Socket::SOCK_STREAM)
|
170
|
+
```
|
171
|
+
|
172
|
+
#### Options
|
173
|
+
|
174
|
+
A channel is composed of two sockets, one for reading and the other for writing.
|
175
|
+
Socket options can be read and set on either of the two sockets with the
|
176
|
+
`Chan::UNIXSocket#getsockopt`, and `Chan::UNIXSocket#setsockopt` methods.
|
177
|
+
Besides the first argument (`:reader`, or `:writer`), the rest of the arguments
|
178
|
+
are identical to `Socket#{getsockopt,setsockopt}`. This example's results can
|
179
|
+
vary depending on the operating system it is run on:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
require "xchan"
|
183
|
+
ch = xchan(:marshal)
|
184
|
+
|
185
|
+
##
|
186
|
+
# Print the value of SO_RCVBUF
|
187
|
+
rcvbuf = ch.getsockopt(:reader, Socket::SOL_SOCKET, Socket::SO_RCVBUF)
|
188
|
+
print "The read buffer can contain a maximum of: ", rcvbuf.int, " bytes.\n"
|
189
|
+
|
190
|
+
##
|
191
|
+
# Print the value of SO_SNDBUF
|
192
|
+
sndbuf = ch.getsockopt(:writer, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
193
|
+
print "The maximum size of a single message is: ", sndbuf.int, " bytes.\n"
|
194
|
+
|
195
|
+
##
|
196
|
+
# The read buffer can contain a maximum of: 16384 bytes.
|
197
|
+
# The maximum size of a single message is: 2048 bytes.
|
198
|
+
```
|
199
|
+
|
200
|
+
### Temporary files
|
201
|
+
|
202
|
+
#### tmpdir
|
203
|
+
|
204
|
+
A single channel creates three temporary files that are removed
|
205
|
+
from the filesystem as soon as they are created. By default the
|
206
|
+
files are stored - for a short time - in `Dir.tmpdir`. Read and
|
207
|
+
write permissions are reserved for the process that created
|
208
|
+
them, inclusive of its child processes.
|
209
|
+
|
210
|
+
The parent directory of the temporary files can be changed with the
|
211
|
+
`tmpdir` option:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
require "xchan"
|
215
|
+
ch = xchan(:marshal, tmpdir: Dir.home)
|
216
|
+
```
|
217
|
+
|
218
|
+
## Sources
|
219
|
+
|
220
|
+
* [Source code (GitHub)](https://github.com/0x1eef/xchan.rb#readme)
|
221
|
+
* [Source code (GitLab)](https://gitlab.com/0x1eef/xchan.rb#about)
|
222
|
+
|
223
|
+
## Install
|
224
|
+
|
225
|
+
xchan.rb is distributed as a RubyGem through its git repositories. <br>
|
226
|
+
[GitHub](https://github.com/0x1eef/xchan.rb),
|
227
|
+
and
|
228
|
+
[GitLab](https://gitlab.com/0x1eef/xchan.rb)
|
229
|
+
are available as sources.
|
230
|
+
|
231
|
+
**Gemfile**
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
gem "xchan.rb", github: "0x1eef/xchan.rb", tag: "v0.16.3"
|
235
|
+
gem "lockf.rb", github: "0x1eef/lockf.rb", tag: "v0.10.6"
|
236
|
+
```
|
237
|
+
|
238
|
+
## <a id="license"> License </a>
|
239
|
+
|
240
|
+
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/).
|
241
|
+
<br>
|
242
|
+
See [LICENSE](./LICENSE).
|
data/Rakefile.rb
ADDED
data/lib/xchan/bytes.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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.
|
11
|
+
class Chan::Bytes
|
12
|
+
require "json"
|
13
|
+
require_relative "stat"
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return [Chan::Stat]
|
17
|
+
attr_reader :stat
|
18
|
+
|
19
|
+
##
|
20
|
+
# @param [String] tmpdir
|
21
|
+
# Path to a directory where temporary files will be stored.
|
22
|
+
#
|
23
|
+
# @return [Chan::Bytes]
|
24
|
+
def initialize(tmpdir)
|
25
|
+
@serializer = JSON
|
26
|
+
@io = Chan.temporary_file("xchan.bytes", tmpdir:)
|
27
|
+
@io.sync = true
|
28
|
+
@stat = Chan::Stat.new(tmpdir)
|
29
|
+
write(@io, [])
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Insert a byte count at the head of the array
|
34
|
+
#
|
35
|
+
# @param [Integer] len
|
36
|
+
# Number of bytes
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def unshift(len)
|
40
|
+
return 0 if len.nil? || len.zero?
|
41
|
+
bytes = read(@io)
|
42
|
+
bytes.unshift(len)
|
43
|
+
write(@io, bytes)
|
44
|
+
@stat.store(bytes_written: len)
|
45
|
+
len
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Insert a byte count at the tail of the array
|
50
|
+
#
|
51
|
+
# @param [Integer] len
|
52
|
+
# Number of bytes
|
53
|
+
#
|
54
|
+
# @return [void]
|
55
|
+
def push(len)
|
56
|
+
return 0 if len.nil? || len.zero?
|
57
|
+
bytes = read(@io)
|
58
|
+
bytes.push(len)
|
59
|
+
write(@io, bytes)
|
60
|
+
@stat.store(bytes_written: len)
|
61
|
+
len
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# @return [Integer]
|
66
|
+
# Returns (and removes) a byte count from the head of the array
|
67
|
+
def shift
|
68
|
+
bytes = read(@io)
|
69
|
+
return 0 if bytes.size.zero?
|
70
|
+
len = bytes.shift
|
71
|
+
write(@io, bytes)
|
72
|
+
@stat.store(bytes_read: len)
|
73
|
+
len
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# @return [Integer]
|
78
|
+
# Returns the size of the array
|
79
|
+
def size
|
80
|
+
read(@io).size
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Close the underlying IO
|
85
|
+
#
|
86
|
+
# @return [void]
|
87
|
+
def close
|
88
|
+
@io.close
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def read(io)
|
94
|
+
deserialize(io.read).tap { io.rewind }
|
95
|
+
end
|
96
|
+
|
97
|
+
def write(io, bytes)
|
98
|
+
io.truncate(0)
|
99
|
+
io.write(serialize(bytes)).tap { io.rewind }
|
100
|
+
end
|
101
|
+
|
102
|
+
def serialize(bytes)
|
103
|
+
@serializer.dump(bytes)
|
104
|
+
end
|
105
|
+
|
106
|
+
def deserialize(bytes)
|
107
|
+
@serializer.load(bytes)
|
108
|
+
end
|
109
|
+
end
|
data/lib/xchan/mixin.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# A module that is included into Ruby's {Object} class.
|
5
|
+
module Chan::Mixin
|
6
|
+
##
|
7
|
+
# @example
|
8
|
+
# ch = xchan
|
9
|
+
# ch.send([1,2,3])
|
10
|
+
# ch.recv.pop # => 3
|
11
|
+
# ch.close
|
12
|
+
#
|
13
|
+
# @param serializer (see Chan::UNIXSocket#initialize)
|
14
|
+
# @param socket (see Chan::UNIXSocket#initialize)
|
15
|
+
# @param tmpdir (see Chan::UNIXSocket#initialize)
|
16
|
+
# @return (see Chan::UNIXSocket#initialize)
|
17
|
+
def xchan(serializer = :marshal, **kw_args)
|
18
|
+
Chan::UNIXSocket.new(serializer, **kw_args)
|
19
|
+
end
|
20
|
+
end
|
data/lib/xchan/stat.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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
|
8
|
+
require "json"
|
9
|
+
|
10
|
+
##
|
11
|
+
# @param [String] tmpdir
|
12
|
+
# Path to a directory where temporary files will be stored.
|
13
|
+
#
|
14
|
+
# @return [Chan::Stat]
|
15
|
+
def initialize(tmpdir)
|
16
|
+
@serializer = JSON
|
17
|
+
@io = Chan.temporary_file("xchan.stat", tmpdir:)
|
18
|
+
write(@io, {"bytes_read" => 0, "bytes_written" => 0})
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @return [Integer]
|
23
|
+
# Returns the number of bytes written to a channel.
|
24
|
+
def bytes_written
|
25
|
+
read(@io).fetch("bytes_written")
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @return [Integer]
|
30
|
+
# Returns the number of bytes read from a channel.
|
31
|
+
def bytes_read
|
32
|
+
read(@io).fetch("bytes_read")
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# @param [Hash] new_stat
|
37
|
+
# @return [void]
|
38
|
+
# @private
|
39
|
+
def store(new_stat)
|
40
|
+
stat = read(@io)
|
41
|
+
new_stat.each { stat[_1.to_s] += _2 }
|
42
|
+
write(@io, stat)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def write(io, o)
|
48
|
+
io.truncate(0)
|
49
|
+
io.write(serialize(o)).tap { io.rewind }
|
50
|
+
end
|
51
|
+
|
52
|
+
def read(io)
|
53
|
+
deserialize(io.read).tap { io.rewind }
|
54
|
+
end
|
55
|
+
|
56
|
+
def serialize(bytes)
|
57
|
+
@serializer.dump(bytes)
|
58
|
+
end
|
59
|
+
|
60
|
+
def deserialize(bytes)
|
61
|
+
@serializer.load(bytes)
|
62
|
+
end
|
63
|
+
end
|