uma 0.1.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.
@@ -0,0 +1,344 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'uma/server'
5
+
6
+ class ServerControlTest < UMBaseTest
7
+ ServerControl = Uma::ServerControl
8
+
9
+ def setup
10
+ super
11
+ @env = { foo: 42 }
12
+ end
13
+
14
+ def test_server_config
15
+ config = Uma::ServerControl.server_config({})
16
+ assert_equal 2, config[:thread_count]
17
+
18
+ port1 = random_port
19
+ port2 = random_port
20
+
21
+ port = random_port
22
+ config = Uma::ServerControl.server_config({
23
+ bind: ["127.0.0.1:#{port1}", "127.0.0.1:#{port2}"]
24
+ })
25
+ assert_equal 2, config[:thread_count]
26
+ assert_equal [
27
+ ['127.0.0.1', port1], ['127.0.0.1', port2]
28
+ ], config[:bind_entries]
29
+
30
+
31
+ config = Uma::ServerControl.server_config({
32
+ bind: "127.0.0.1:#{port1}"
33
+ })
34
+ assert_equal 2, config[:thread_count]
35
+ assert_equal [['127.0.0.1', port1]], config[:bind_entries]
36
+
37
+ conn_proc = ->(machine, fd) { }
38
+ config = Uma::ServerControl.server_config({
39
+ connection_proc: conn_proc,
40
+ })
41
+ assert_equal conn_proc, config[:connection_proc]
42
+ end
43
+
44
+ def test_await_process_termination_term
45
+ r, w = UM.pipe
46
+ pid = fork do
47
+ m = UM.new
48
+ m.close(r)
49
+ m.spin { sleep(0.05); m.write(w, 'ready') }
50
+ ServerControl.await_process_termination(m)
51
+ m.write(w, 'done')
52
+ m.close(w)
53
+ end
54
+ machine.close(w)
55
+ buf = +''
56
+
57
+ machine.read(r, buf, 128)
58
+ assert_equal 'ready', buf
59
+
60
+ Process.kill('SIGTERM', pid)
61
+ machine.read(r, buf, 128)
62
+ assert_equal 'done', buf
63
+ ensure
64
+ machine.close(r) rescue nil
65
+ machine.close(w) rescue nil
66
+ if pid
67
+ Process.kill('SIGKILL', pid)
68
+ Process.wait(pid)
69
+ end
70
+ end
71
+
72
+ def test_await_process_termination_int
73
+ r, w = UM.pipe
74
+ pid = fork do
75
+ m = UM.new
76
+ m.close(r)
77
+ m.spin { sleep(0.05); m.write(w, 'ready') }
78
+ ServerControl.await_process_termination(m)
79
+ m.write(w, 'done')
80
+ m.close(w)
81
+ end
82
+ machine.close(w)
83
+ buf = +''
84
+
85
+ machine.read(r, buf, 128)
86
+ assert_equal 'ready', buf
87
+
88
+ Process.kill('SIGINT', pid)
89
+ machine.read(r, buf, 128)
90
+ assert_equal 'done', buf
91
+ ensure
92
+ machine.close(r) rescue nil
93
+ machine.close(w) rescue nil
94
+ if pid
95
+ Process.kill('SIGKILL', pid)
96
+ Process.wait(pid)
97
+ end
98
+ end
99
+
100
+ def test_worker_thread_graceful_stop
101
+ buf = []
102
+
103
+ accept_fibers = 3.times.map { |i|
104
+ machine.spin do
105
+ machine.sleep(0.05)
106
+ buf << "accept_#{i}_done"
107
+ rescue UM::Terminate
108
+ buf << "accept_#{i}_terminated"
109
+ end
110
+ }
111
+
112
+ connection_fibers = 3.times.map { |i|
113
+ machine.spin do
114
+ machine.sleep(0.05)
115
+ buf << "connection_#{i}_done"
116
+ rescue UM::Terminate
117
+ buf << "connection_#{i}_terminated"
118
+ end
119
+ }
120
+
121
+ ServerControl.worker_thread_graceful_stop(machine, accept_fibers, connection_fibers)
122
+
123
+ assert_equal %w{
124
+ accept_0_terminated accept_1_terminated accept_2_terminated
125
+ connection_0_terminated connection_1_terminated connection_2_terminated
126
+ }, buf.sort
127
+ end
128
+
129
+ def test_start_connection
130
+ s1, s2 = UM.socketpair(UM::AF_UNIX, UM::SOCK_STREAM, 0)
131
+ config = {
132
+ connection_proc: ->(machine, fd) {
133
+ buf = +''
134
+ machine.recv(fd, buf, 128, 0)
135
+ machine.send(fd, buf, buf.bytesize, 0)
136
+ }
137
+ }
138
+ ff = Set.new
139
+
140
+ f = ServerControl.start_connection(machine, config, ff, s2)
141
+ assert_kind_of Fiber, f
142
+ assert_equal 0, ff.size
143
+
144
+ machine.snooze
145
+ assert_equal 1, ff.size
146
+ assert_equal [f], ff.to_a
147
+
148
+ machine.write(s1, 'foobar')
149
+ buf = +''
150
+ machine.read(s1, buf, 128)
151
+ assert_equal 'foobar', buf
152
+ 3.times { machine.snooze }
153
+ assert_equal 0, ff.size
154
+ ensure
155
+ machine.close(s1)
156
+ machine.close(s2) rescue nil
157
+ end
158
+
159
+ def test_prepare_listening_socket
160
+ port = random_port
161
+ listen_fd = ServerControl.prepare_listening_socket(machine, '127.0.0.1', port)
162
+ assert_kind_of Integer, listen_fd
163
+
164
+ f = machine.spin {
165
+ sock = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
166
+ res = machine.connect(sock, '127.0.0.1', port)
167
+ machine.close(sock)
168
+ res
169
+ }
170
+
171
+ fd = machine.accept(listen_fd)
172
+ assert_equal 0, machine.join(f)
173
+ assert_kind_of Integer, fd
174
+ machine.close(fd)
175
+ end
176
+
177
+ def test_start_acceptors
178
+ port1 = random_port
179
+ port2 = random_port
180
+ config = {
181
+ bind_entries: [
182
+ ['127.0.0.1', port1],
183
+ ['127.0.0.1', port2]
184
+ ]
185
+ }
186
+ connection_fibers = []
187
+ accept_fibers = ServerControl.start_acceptors(machine, config, connection_fibers)
188
+
189
+ assert_kind_of Set, accept_fibers
190
+ assert_equal 2, accept_fibers.size
191
+ assert_kind_of Fiber, accept_fibers.to_a.first
192
+
193
+ machine.sleep(0.05)
194
+
195
+ sock1 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
196
+ res = machine.connect(sock1, '127.0.0.1', port1)
197
+ assert_equal 0, res
198
+ machine.snooze
199
+ assert_equal 1, connection_fibers.size
200
+
201
+ sock2 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
202
+ res = machine.connect(sock2, '127.0.0.1', port2)
203
+ assert_equal 0, res
204
+ machine.snooze
205
+ assert_equal 1, connection_fibers.size # first connection will have been closed already
206
+
207
+ 3.times { machine.snooze }
208
+
209
+ assert_equal 0, connection_fibers.size
210
+ ensure
211
+ machine.close(sock1) rescue nil
212
+ machine.close(sock2) rescue nil
213
+ if !accept_fibers.empty?
214
+ machine.sleep(0.05)
215
+ accept_fibers.each { machine.schedule(it, UM::Terminate.new) }
216
+ machine.await_fibers(accept_fibers)
217
+ end
218
+
219
+ if !connection_fibers.empty?
220
+ connection_fibers.each { machine.schedule(it, UM::Terminate.new) }
221
+ machine.await_fibers(connection_fibers)
222
+ end
223
+ end
224
+
225
+ def test_start_worker_thread
226
+ port1 = random_port
227
+ port2 = random_port
228
+ config = {
229
+ bind_entries: [
230
+ ['127.0.0.1', port1],
231
+ ['127.0.0.1', port2]
232
+ ]
233
+ }
234
+
235
+ stop_queue = UM::Queue.new
236
+ th = ServerControl.start_worker_thread(config, stop_queue)
237
+
238
+ machine.sleep(0.05)
239
+
240
+ sock1 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
241
+ res = machine.connect(sock1, '127.0.0.1', port1)
242
+ assert_equal 0, res
243
+
244
+ sock2 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
245
+ res = machine.connect(sock2, '127.0.0.1', port2)
246
+ assert_equal 0, res
247
+
248
+ machine.close(sock1)
249
+ machine.close(sock2)
250
+
251
+ machine.push(stop_queue, :stop)
252
+
253
+ th.join
254
+ th = nil
255
+
256
+ sock1 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
257
+ assert_raises(Errno::ECONNREFUSED) { machine.connect(sock1, '127.0.0.1', port1) }
258
+ ensure
259
+ machine.close(sock1) rescue nil
260
+ machine.close(sock2) rescue nil
261
+ th&.kill
262
+ end
263
+
264
+ def test_start_acceptors_with_connection_proc
265
+ port1 = random_port
266
+ config = {
267
+ bind_entries: [
268
+ ['127.0.0.1', port1]
269
+ ],
270
+ connection_proc: ->(machine, fd) {
271
+ machine.send(fd, 'hi', 2, 0)
272
+ }
273
+ }
274
+ connection_fibers = []
275
+ accept_fibers = ServerControl.start_acceptors(machine, config, connection_fibers)
276
+
277
+ assert_kind_of Set, accept_fibers
278
+ assert_equal 1, accept_fibers.size
279
+ assert_kind_of Fiber, accept_fibers.to_a.first
280
+
281
+ machine.sleep(0.05)
282
+
283
+ sock1 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
284
+ res = machine.connect(sock1, '127.0.0.1', port1)
285
+ assert_equal 0, res
286
+ buf = +''
287
+ machine.recv(sock1, buf, 128, 0)
288
+ assert_equal 'hi', buf
289
+ ensure
290
+ machine.close(sock1) rescue nil
291
+ if !accept_fibers.empty?
292
+ machine.sleep(0.05)
293
+ accept_fibers.each { machine.schedule(it, UM::Terminate.new) }
294
+ machine.await_fibers(accept_fibers)
295
+ end
296
+
297
+ if !connection_fibers.empty?
298
+ connection_fibers.each { machine.schedule(it, UM::Terminate.new) }
299
+ machine.await_fibers(connection_fibers)
300
+ end
301
+ end
302
+
303
+ def test_start_acceptors_with_connection_proc_with_stream
304
+ port1 = random_port
305
+ config = {
306
+ bind_entries: [
307
+ ['127.0.0.1', port1]
308
+ ],
309
+ connection_proc: ->(machine, fd) {
310
+ stream = UM::Stream.new(machine, fd)
311
+ msg = stream.get_line(nil, 0)
312
+ machine.send(fd, msg, msg.bytesize, 0)
313
+ }
314
+ }
315
+ connection_fibers = []
316
+ accept_fibers = ServerControl.start_acceptors(machine, config, connection_fibers)
317
+
318
+ assert_kind_of Set, accept_fibers
319
+ assert_equal 1, accept_fibers.size
320
+ assert_kind_of Fiber, accept_fibers.to_a.first
321
+
322
+ machine.sleep(0.05)
323
+
324
+ sock1 = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
325
+ res = machine.connect(sock1, '127.0.0.1', port1)
326
+ assert_equal 0, res
327
+ machine.send(sock1, "bar\n", 4, 0)
328
+ buf = +''
329
+ machine.recv(sock1, buf, 128, 0)
330
+ assert_equal 'bar', buf
331
+ ensure
332
+ machine.close(sock1) rescue nil
333
+ if !accept_fibers.empty?
334
+ machine.sleep(0.05)
335
+ accept_fibers.each { machine.schedule(it, UM::Terminate.new) }
336
+ machine.await_fibers(accept_fibers)
337
+ end
338
+
339
+ if !connection_fibers.empty?
340
+ connection_fibers.each { machine.schedule(it, UM::Terminate.new) }
341
+ machine.await_fibers(connection_fibers)
342
+ end
343
+ end
344
+ end
data/uma.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require_relative './lib/uma/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'uma'
5
+ s.summary = 'Uma is a Ruby app server'
6
+ s.version = Uma::VERSION
7
+ s.licenses = ['MIT']
8
+ s.author = 'Sharon Rosner'
9
+ s.email = 'sharon@noteflakes.com'
10
+ s.files = `git ls-files`.split
11
+
12
+ s.homepage = 'https://github.com/digital-fabric/uma'
13
+ s.metadata = {
14
+ 'homepage_uri' => 'https://github.com/digital-fabric/uma',
15
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/uma',
16
+ 'changelog_uri' => 'https://github.com/digital-fabric/uma/blob/main/CHANGELOG.md'
17
+ }
18
+ s.rdoc_options = ['--title', 'Uma', '--main', 'README.md']
19
+ s.extra_rdoc_files = ['README.md']
20
+ s.require_paths = ['lib']
21
+ s.required_ruby_version = '>= 4.0'
22
+ s.executables = ['uma']
23
+
24
+ s.add_dependency 'uringmachine', '>=0.28.3'
25
+ s.add_dependency 'rack', '~>3.2'
26
+ s.add_dependency 'logger'
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uma
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sharon Rosner
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: uringmachine
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.28.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.28.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ email: sharon@noteflakes.com
55
+ executables:
56
+ - uma
57
+ extensions: []
58
+ extra_rdoc_files:
59
+ - README.md
60
+ files:
61
+ - ".gitignore"
62
+ - CHANGELOG.md
63
+ - Gemfile
64
+ - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - TODO.md
68
+ - bin/uma
69
+ - lib/uma.rb
70
+ - lib/uma/app.rb
71
+ - lib/uma/cli.rb
72
+ - lib/uma/cli/error.rb
73
+ - lib/uma/error.rb
74
+ - lib/uma/http.rb
75
+ - lib/uma/server.rb
76
+ - lib/uma/version.rb
77
+ - test/apps/bad_syntax.ru
78
+ - test/apps/config.ru
79
+ - test/apps/roda1.ru
80
+ - test/apps/simple.ru
81
+ - test/helper.rb
82
+ - test/run.rb
83
+ - test/test_app.rb
84
+ - test/test_cli.rb
85
+ - test/test_http.rb
86
+ - test/test_server.rb
87
+ - uma.gemspec
88
+ homepage: https://github.com/digital-fabric/uma
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ homepage_uri: https://github.com/digital-fabric/uma
93
+ documentation_uri: https://www.rubydoc.info/gems/uma
94
+ changelog_uri: https://github.com/digital-fabric/uma/blob/main/CHANGELOG.md
95
+ rdoc_options:
96
+ - "--title"
97
+ - Uma
98
+ - "--main"
99
+ - README.md
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '4.0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 4.0.3
114
+ specification_version: 4
115
+ summary: Uma is a Ruby app server
116
+ test_files: []