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.
data/lib/xchan.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chan
4
+ require_relative "xchan/version"
5
+ require_relative "xchan/unix_socket"
6
+ require_relative "xchan/tempfile"
7
+ require_relative "xchan/mixin"
8
+
9
+ WaitReadable = Class.new(IO::EAGAINWaitReadable)
10
+ WaitWritable = Class.new(IO::EAGAINWaitWritable)
11
+ WaitLockable = Class.new(Errno::EWOULDBLOCK)
12
+ Plain = Class.new do
13
+ def self.dump(str) = str.to_s
14
+ def self.load(str) = str.to_s
15
+ end
16
+
17
+ ##
18
+ # Returns an unlinked {Chan::Tempfile Chan::Tempfile} object
19
+ # that can be read from, and written to by the process that
20
+ # created it, inclusive of its child processes, but not of
21
+ # processes other than that.
22
+ #
23
+ # @param [String] basename
24
+ # Basename of the temporary file.
25
+ #
26
+ # @param [String] tmpdir
27
+ # Parent directory of the temporary file.
28
+ #
29
+ # @return [Chan::Tempfile]
30
+ # Returns an instance of {Chan::Tempfile Chan::Tempfile}.
31
+ def self.temporary_file(basename, tmpdir: Dir.tmpdir)
32
+ Chan::Tempfile.new(basename, tmpdir, perm: 0).tap(&:unlink)
33
+ end
34
+
35
+ def self.serializers
36
+ {
37
+ plain: lambda { Plain },
38
+ marshal: lambda { Marshal },
39
+ json: lambda {
40
+ require "json" unless defined?(JSON)
41
+ JSON
42
+ },
43
+ yaml: lambda {
44
+ require "yaml" unless defined?(YAML)
45
+ YAML
46
+ }
47
+ }
48
+ end
49
+ end
50
+
51
+ class Object
52
+ include Chan::Mixin
53
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ $stdout.sync = true
7
+ ch = xchan
8
+ pid = fork do
9
+ print "Received random number (child process): ", ch.recv, "\n"
10
+ end
11
+ # Delay for a second to let a process fork, and call "ch.recv"
12
+ sleep(1)
13
+ print "Send a random number (from parent process)", "\n"
14
+ ch.send(rand(21))
15
+ Process.wait(pid)
16
+ ch.close
17
+
18
+ ##
19
+ # Send a random number (from parent process)
20
+ # Received random number (child process): XX
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ def read(ch)
7
+ ch.recv_nonblock
8
+ rescue Chan::WaitReadable
9
+ print "Wait 1 second for channel to be readable", "\n"
10
+ ch.wait_readable(1)
11
+ retry
12
+ rescue Chan::WaitLockable
13
+ sleep 0.01
14
+ retry
15
+ end
16
+ trap("SIGINT") { exit(1) }
17
+ read(xchan)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ ##
7
+ # This channel uses Marshal to serialize objects.
8
+ ch = xchan
9
+ pid = fork { print "Received message: ", ch.recv[:msg], "\n" }
10
+ ch.send(msg: "serialized by Marshal")
11
+ ch.close
12
+ Process.wait(pid)
13
+
14
+ ##
15
+ # This channel also uses Marshal to serialize objects.
16
+ ch = xchan(:marshal)
17
+ pid = fork { print "Received message: ", ch.recv[:msg], "\n" }
18
+ ch.send(msg: "serialized by Marshal")
19
+ ch.close
20
+ Process.wait(pid)
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ libdir = File.expand_path File.join(__dir__, "..", "lib")
4
+ $LOAD_PATH.unshift(libdir)
@@ -0,0 +1,12 @@
1
+ require "xchan"
2
+ ch = xchan(:marshal)
3
+
4
+ ##
5
+ # Print the value of SO_RCVBUF
6
+ rcvbuf = ch.getsockopt(:reader, Socket::SOL_SOCKET, Socket::SO_RCVBUF)
7
+ print "The read buffer can contain a maximum of: ", rcvbuf.int, " bytes.\n"
8
+
9
+ ##
10
+ # Print the value of SO_SNDBUF
11
+ sndbuf = ch.getsockopt(:writer, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
12
+ print "The maximum size of a single message is: ", sndbuf.int, " bytes.\n"
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ pids = []
7
+ ch = xchan
8
+ pids.concat 50.times.map { fork { ch.send(["a" * rand(200)]) } }
9
+ pids.concat 50.times.map { fork { print "PID: ", Process.pid, ", buf size:", ch.recv[0].size, "\n" } }
10
+ pids.each { Process.wait(_1) }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ ch = xchan(:marshal, socket: Socket::SOCK_STREAM)
7
+ sndbuf = ch.getsockopt(:reader, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
8
+ while ch.bytes_sent <= sndbuf.int
9
+ ch.send(1)
10
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../setup"
4
+ require "xchan"
5
+
6
+ def send_nonblock(ch, buf)
7
+ ch.send_nonblock(buf)
8
+ rescue Chan::WaitWritable
9
+ print "Blocked - free send buffer", "\n"
10
+ ch.recv
11
+ retry
12
+ rescue Chan::WaitLockable
13
+ sleep 0.01
14
+ retry
15
+ end
16
+
17
+ ch = xchan(:marshal, socket: Socket::SOCK_STREAM)
18
+ sndbuf = ch.getsockopt(:writer, Socket::SOL_SOCKET, Socket::SO_SNDBUF)
19
+ while ch.bytes_sent <= sndbuf.int
20
+ send_nonblock(ch, 1)
21
+ end
22
+
23
+ ##
24
+ # Blocked - free send buffer
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "test/cmd"
5
+
6
+ class Chan::ReadmeTest < Test::Unit::TestCase
7
+ include Test::Cmd
8
+
9
+ def test_serialization_1_serializers
10
+ assert_equal "Received message: serialized by Marshal\n" * 2,
11
+ readme_example("serialization/1_serializers.rb").stdout
12
+ end
13
+
14
+ def test_read_operations_1_blocking_read
15
+ r = 'Send a random number \(from parent process\)\s*' \
16
+ 'Received random number \(child process\): \d+'
17
+ assert_match Regexp.new(r),
18
+ readme_example("read_operations/1_blocking_read.rb").stdout
19
+ .tr("\n", " ")
20
+ end
21
+
22
+ def test_write_operations_2_non_blocking_write
23
+ assert_equal ["Blocked - free send buffer\n"],
24
+ readme_example("write_operations/2_nonblocking_write.rb").stdout
25
+ .each_line
26
+ .uniq
27
+ end
28
+
29
+ def test_socket_2_options
30
+ r = 'The read buffer can contain a maximum of: \d{1,6} bytes.\s*' \
31
+ 'The maximum size of a single message is: \d{1,6} bytes.\s*'
32
+ assert_match Regexp.new(r),
33
+ readme_example("socket/2_options.rb").stdout
34
+ .tr("\n", " ")
35
+ end
36
+
37
+ private
38
+
39
+ def readme_example(path)
40
+ examples_dir = File.join(Dir.getwd, "share", "xchan.rb", "examples")
41
+ example = File.join(examples_dir, path)
42
+ cmd "bundle exec ruby #{example}"
43
+ end
44
+ end
data/test/setup.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require "timeout"
6
+ require "xchan"
7
+ require "test-unit"
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ class Chan::Test < Test::Unit::TestCase
6
+ def setup
7
+ @ch = xchan ENV.fetch("SERIALIZER", "marshal").to_sym
8
+ end
9
+
10
+ def teardown
11
+ ch.close unless ch.closed?
12
+ end
13
+
14
+ private
15
+
16
+ def ch
17
+ @ch
18
+ end
19
+
20
+ def object
21
+ case ENV["SERIALIZER"]
22
+ when "plain" then "xchan"
23
+ else %w[xchan]
24
+ end
25
+ end
26
+
27
+ def object_size
28
+ ch.serializer.dump(object).bytesize
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Chan::UNIXSocket#send
34
+ class Chan::SendTest < Chan::Test
35
+ def test_send_return_value
36
+ assert_equal object_size, ch.send(object)
37
+ end
38
+
39
+ def test_send_with_multiple_objects
40
+ 3.times { |i| Process.wait fork { ch.send(object) } }
41
+ assert_equal [object, object, object], 3.times.map { ch.recv }
42
+ end
43
+
44
+ def test_send_race_condition
45
+ pids = 4.times.map { fork { exit(ch.recv.to_i) } }
46
+ sleep(0.1 * 4)
47
+ pids.each.with_index(1) { ch.send(object) }
48
+ assert_equal pids.map { 42 }, pids.map { Process.wait2(_1).last.exitstatus }
49
+ end
50
+
51
+ def object
52
+ 42.to_s
53
+ end
54
+ end
55
+
56
+ ##
57
+ # Chan::UNIXSocket#recv
58
+ class Chan::RecvTest < Chan::Test
59
+ include Timeout
60
+
61
+ def test_recv_with_null_byte
62
+ ch.send(object.dup << "\x00")
63
+ assert_equal object.dup << "\x00", ch.recv
64
+ end
65
+
66
+ def test_that_recv_blocks
67
+ assert_raises(Timeout::Error) do
68
+ timeout(0.3) { ch.recv }
69
+ end
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Chan::UNIXSocket#recv_nonblock
75
+ class Chan::RecvNonBlockTest < Chan::Test
76
+ include Timeout
77
+
78
+ def test_recv_nonblock_with_empty_channel
79
+ assert_raise(Chan::WaitReadable) { ch.recv_nonblock }
80
+ end
81
+
82
+ def test_recv_nonblock_with_a_lock
83
+ ch.instance_variable_get(:@lock).lock
84
+ pid = fork do
85
+ ch.recv_nonblock
86
+ exit(1)
87
+ rescue Chan::WaitLockable
88
+ exit(0)
89
+ end
90
+ Process.wait(pid)
91
+ assert_equal 0, $?.exitstatus
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Chan::UNIXSocket#empty?
97
+ class Chan::EmptyTest < Chan::Test
98
+ def test_empty_with_empty_channel
99
+ assert_equal true, ch.empty?
100
+ end
101
+
102
+ def test_empty_with_one_object
103
+ ch.send(object)
104
+ assert_equal false, ch.empty?
105
+ end
106
+
107
+ def test_empty_after_recv
108
+ ch.send(object)
109
+ ch.recv
110
+ assert_equal true, ch.empty?
111
+ end
112
+
113
+ def test_empty_on_closed_channel
114
+ ch.send(object)
115
+ ch.close
116
+ assert_equal true, ch.empty?
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Chan::UNIXSocket#size
122
+ class Chan::SizeTest < Chan::Test
123
+ def test_size_with_one_object
124
+ ch.send(object)
125
+ assert_equal 1, ch.size
126
+ end
127
+
128
+ def test_size_with_two_objects
129
+ 2.times { ch.send(object) }
130
+ assert_equal 2, ch.size
131
+ end
132
+
133
+ def test_size_after_recv
134
+ ch.send(object)
135
+ ch.recv
136
+ assert_equal 0, ch.size
137
+ end
138
+ end
139
+
140
+ ##
141
+ # Chan::UNIXSocket#to_a
142
+ class Chan::ToArrayTest < Chan::Test
143
+ def test_to_a_with_splat
144
+ 3.times { ch.send(object) }
145
+ assert_equal [object, object, object], splat(*ch)
146
+ end
147
+
148
+ def test_to_a_with_last
149
+ 3.times { ch.send(object) }
150
+ assert_equal object, ch.to_a.last
151
+ end
152
+
153
+ def test_to_a_with_empty_channel
154
+ assert_equal [], ch.to_a
155
+ end
156
+
157
+ private
158
+
159
+ def splat(*args)
160
+ args
161
+ end
162
+ end
163
+
164
+ ##
165
+ # Chan::UNIXSocket#bytes_written
166
+ class Chan::BytesWrittenTest < Chan::Test
167
+ def test_bytes_written_with_one_object
168
+ Process.wait fork { ch.send(object) }
169
+ assert_equal object_size, ch.bytes_written
170
+ end
171
+
172
+ def test_bytes_written_with_two_objects
173
+ 2.times { Process.wait fork { ch.send(object) } }
174
+ assert_equal object_size * 2, ch.bytes_written
175
+ end
176
+ end
177
+
178
+ ##
179
+ # Chan::UNIXSocket#bytes_read
180
+ class Chan::BytesReadTest < Chan::Test
181
+ def test_bytes_read_with_one_object
182
+ ch.send(object)
183
+ Process.wait fork { ch.recv }
184
+ assert_equal object_size, ch.bytes_read
185
+ end
186
+
187
+ def test_bytes_read_with_two_objects
188
+ 2.times { ch.send(object) }
189
+ 2.times { Process.wait fork { ch.recv } }
190
+ assert_equal object_size * 2, ch.bytes_read
191
+ end
192
+ end
data/xchan.rb.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "./lib/xchan/version"
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "xchan.rb"
6
+ gem.authors = ["0x1eef"]
7
+ gem.email = ["0x1eef@protonmail.com"]
8
+ gem.homepage = "https://github.com/0x1eef/xchan.rb#readme"
9
+ gem.version = Chan::VERSION
10
+ gem.licenses = ["0BSD"]
11
+ gem.files = `git ls-files`.split($/)
12
+ gem.require_paths = ["lib"]
13
+ gem.summary = "An easy to use InterProcess Communication (IPC) library."
14
+ gem.description = gem.summary
15
+ gem.add_runtime_dependency "lockf.rb", "~> 0.10.6"
16
+ gem.add_development_dependency "test-unit", "~> 3.5.7"
17
+ gem.add_development_dependency "yard", "~> 0.9"
18
+ gem.add_development_dependency "redcarpet", "~> 3.5"
19
+ gem.add_development_dependency "standard", "~> 1.13"
20
+ gem.add_development_dependency "test-cmd.rb", "~> 0.2"
21
+ gem.add_development_dependency "rake", "~> 13.1"
22
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xchan.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.16.4
5
+ platform: ruby
6
+ authors:
7
+ - '0x1eef'
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lockf.rb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.5.7
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.5.7
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.13'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: test-cmd.rb
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.1'
111
+ description: An easy to use InterProcess Communication (IPC) library.
112
+ email:
113
+ - 0x1eef@protonmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".github/workflows/tests.yml"
119
+ - ".gitignore"
120
+ - ".gitlab-ci.yml"
121
+ - ".projectile"
122
+ - ".rubocop.yml"
123
+ - ".yardopts"
124
+ - Gemfile
125
+ - LICENSE
126
+ - README.md
127
+ - Rakefile.rb
128
+ - lib/xchan.rb
129
+ - lib/xchan/bytes.rb
130
+ - lib/xchan/mixin.rb
131
+ - lib/xchan/stat.rb
132
+ - lib/xchan/tempfile.rb
133
+ - lib/xchan/unix_socket.rb
134
+ - lib/xchan/version.rb
135
+ - share/xchan.rb/examples/read_operations/1_blocking_read.rb
136
+ - share/xchan.rb/examples/read_operations/2_nonblocking_read.rb
137
+ - share/xchan.rb/examples/serialization/1_serializers.rb
138
+ - share/xchan.rb/examples/setup.rb
139
+ - share/xchan.rb/examples/socket/2_options.rb
140
+ - share/xchan.rb/examples/stress_tests/1_parallel_access_stress_test.rb
141
+ - share/xchan.rb/examples/write_operations/1_blocking_write.rb
142
+ - share/xchan.rb/examples/write_operations/2_nonblocking_write.rb
143
+ - test/readme_test.rb
144
+ - test/setup.rb
145
+ - test/xchan_test.rb
146
+ - xchan.rb.gemspec
147
+ homepage: https://github.com/0x1eef/xchan.rb#readme
148
+ licenses:
149
+ - 0BSD
150
+ metadata: {}
151
+ post_install_message:
152
+ rdoc_options: []
153
+ require_paths:
154
+ - lib
155
+ required_ruby_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ requirements: []
166
+ rubygems_version: 3.5.3
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: An easy to use InterProcess Communication (IPC) library.
170
+ test_files: []