xchan.rb 0.16.4

Sign up to get free protection for your applications and to get access to all the features.
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: []