uringmachine 0.5 → 0.6

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: 2067cd0f88d6dbc341410ca6911916f510132ee23ce3f6d5fb5978259eeaef11
4
- data.tar.gz: f816a9478f714a3a9770740bc14fc03ae2687f2fe7d0e29cbc0b8632994bad1f
3
+ metadata.gz: dc849d9467e24fa296a104e5b1caf71907495a3ed9cc68370bc6eb134a474cbf
4
+ data.tar.gz: 4fcdf5cd6055321f37375094aa11b56696c894d6a2c47ed9f756fb9a40d79753
5
5
  SHA512:
6
- metadata.gz: 6e0a9ed6e6f131bf6d67a56622786893532a525f4427e134670499997c0461a4df6de3ca1cb9e70851769c1135575986468a83edfd9b5d75c7d592e41bb0ca8c
7
- data.tar.gz: 2c73ed9a3268ea96b3a167064251d16279c531ee50e5f9c6de9aa2c56f6a70973ef04ce5d6c44f46e39309f7bf8cb3f3ff5e61bf1512ca07cc5f35b91f7303dc
6
+ metadata.gz: 3d83d7419000bd434bac101d72c6850274a71dd0324bd1732043cbdb08ef659b683e1010dc31093fe7a77e37368698db56592685bf7fe8a2f76ee4cd70cee52f
7
+ data.tar.gz: dcb79682bbdc20b99bd312aad22145642c4479009250f01f2e0c83f506026218d973b5c6e8b0aecff4e993acbe3998822bccbdf1de7c77a025b7560b5860ca77
@@ -13,7 +13,7 @@ jobs:
13
13
  matrix:
14
14
  # macos-latest uses arm64, macos-13 uses x86
15
15
  os: [ubuntu-latest]
16
- ruby: ['3.3', 'head']
16
+ ruby: ['3.3', '3.4', 'head']
17
17
 
18
18
  name: ${{matrix.os}}, ${{matrix.ruby}}
19
19
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 2025-04-23 Version 0.6
2
+
3
+ - Add `#periodically` for multishot timeout
4
+ - Add `UM::Actor` class
5
+ - Add `#prep_timeout` and `AsyncOp`
6
+
1
7
  # 2024-11-14 Version 0.5
2
8
 
3
9
  - Add `#waitpid`
data/TODO.md CHANGED
@@ -1,3 +1,7 @@
1
+ - [ ] multishot timeout
2
+ - [v] machine.periodically(interval) { ... }
3
+ - [ ] machine.prep_timeout_multishot(interval)
4
+
1
5
  - splice / - tee
2
6
  - sendto
3
7
  - recvfrom
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark-ips'
9
+ gem 'http_parser.rb'
10
+ end
11
+
12
+ require 'benchmark/ips'
13
+ require 'uringmachine'
14
+ require 'http/parser'
15
+
16
+ $machine = UM.new
17
+
18
+ HTTP_MSG = "GET /foo/bar HTTP/1.1\r\nServer: foobar.com\r\nFoo: bar\r\n\r\n"
19
+
20
+ $count = 0
21
+
22
+ def parse_http_parser
23
+ current_fiber = Fiber.current
24
+ $count += 1
25
+ r, w = IO.pipe
26
+ parser = Http::Parser.new
27
+ $machine.spin do
28
+ buffer = +''
29
+ loop do
30
+ res = $machine.read(r.fileno, buffer, 512)
31
+ break if res == 0
32
+ parser << buffer
33
+ end
34
+ rescue Exception => e
35
+ $machine.schedule(current_fiber, e)
36
+ # puts e.backtrace.join("\n")
37
+ # exit!
38
+ end
39
+ parser.on_message_complete = -> do
40
+ headers = parser.headers
41
+ headers['method'] = parser.http_method.downcase
42
+ headers['path'] = parser.request_url
43
+ headers['protocol'] = parser.http_version
44
+ $machine.schedule(current_fiber, headers)
45
+ end
46
+
47
+ $machine.write(w.fileno, HTTP_MSG)
48
+ $machine.yield
49
+ ensure
50
+ $machine.close(r.fileno)
51
+ $machine.close(w.fileno)
52
+ end
53
+
54
+ require 'stringio'
55
+
56
+ RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
57
+ RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
58
+
59
+ def get_line(fd, sio, buffer)
60
+ while true
61
+ line = sio.gets(chomp: true)
62
+ return line if line
63
+
64
+ res = $machine.read(fd, buffer, 1024, -1)
65
+ return nil if res == 0
66
+ end
67
+ end
68
+
69
+ def get_request_line(fd, sio, buffer)
70
+ line = get_line(fd, sio, buffer)
71
+
72
+ m = line.match(RE_REQUEST_LINE)
73
+ return nil if !m
74
+
75
+ {
76
+ 'method' => m[1].downcase,
77
+ 'path' => m[2],
78
+ 'protocol' => m[3].downcase
79
+ }
80
+ end
81
+
82
+ def parse_headers(fd)
83
+ buffer = String.new('', capacity: 4096)
84
+ sio = StringIO.new(buffer)
85
+
86
+ headers = get_request_line(fd, sio, buffer)
87
+ return nil if !headers
88
+
89
+ while true
90
+ line = get_line(fd, sio, buffer)
91
+ break if line.empty?
92
+
93
+ m = line.match(RE_HEADER_LINE)
94
+ raise "Invalid header" if !m
95
+
96
+ headers[m[1]] = m[2]
97
+ end
98
+
99
+ headers
100
+ end
101
+
102
+ def parse_http_stringio
103
+ current_fiber = Fiber.current
104
+ r, w = IO.pipe
105
+
106
+ $machine.spin do
107
+ headers = parse_headers(r.fileno)
108
+ $machine.schedule(current_fiber, headers)
109
+ rescue Exception => e
110
+ p e
111
+ puts e.backtrace.join("\n")
112
+ exit!
113
+ end
114
+
115
+ $machine.write(w.fileno, HTTP_MSG)
116
+ $machine.yield
117
+ ensure
118
+ $machine.close(r.fileno)
119
+ $machine.close(w.fileno)
120
+ end
121
+
122
+ # p parse_http_parser
123
+ # p parse_http_stringio
124
+ # exit
125
+
126
+ GC.disable
127
+
128
+ def alloc_count
129
+ count0 = ObjectSpace.count_objects[:TOTAL]
130
+ yield
131
+ count1 = ObjectSpace.count_objects[:TOTAL]
132
+ count1 - count0
133
+ end
134
+
135
+ X = 100
136
+ p(
137
+ alloc_http_parser: alloc_count { X.times { parse_http_parser } },
138
+ alloc_stringio: alloc_count { X.times { parse_http_stringio } }
139
+ )
140
+ exit
141
+
142
+ Benchmark.ips do |x|
143
+ x.config(:time => 5, :warmup => 3)
144
+
145
+ x.report("http_parser") { parse_http_parser }
146
+ x.report("homegrown") { parse_http_stringio }
147
+
148
+ x.compare!
149
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark-ips'
9
+ end
10
+
11
+ require 'benchmark/ips'
12
+ require 'uringmachine'
13
+
14
+ COUNT = 1000
15
+ NUM_PRODUCERS = 2
16
+ NUM_CONSUMERS = 10
17
+
18
+ def run_threads
19
+ queue = Queue.new
20
+ done = Queue.new
21
+
22
+ NUM_PRODUCERS.times do
23
+ Thread.new do
24
+ COUNT.times { queue << rand(1000) }
25
+ done << true
26
+ end
27
+ end
28
+
29
+ total = 0
30
+ NUM_CONSUMERS.times do
31
+ Thread.new do
32
+ loop do
33
+ item = queue.shift
34
+ break if item.nil?
35
+
36
+ total += item
37
+ end
38
+ done << true
39
+ end
40
+ end
41
+
42
+ # wait for producers
43
+ NUM_PRODUCERS.times { done.shift }
44
+
45
+ # stop and wait for consumers
46
+ NUM_CONSUMERS.times do
47
+ queue << nil
48
+ done.shift
49
+ end
50
+
51
+ total
52
+ end
53
+
54
+ def run_um
55
+ machine = UM.new
56
+ queue = UM::Queue.new
57
+ done = UM::Queue.new
58
+
59
+ NUM_PRODUCERS.times do
60
+ machine.spin do
61
+ COUNT.times { machine.push(queue, rand(1000)) }
62
+ machine.push(done, true)
63
+ end
64
+ end
65
+
66
+ total = 0
67
+ NUM_CONSUMERS.times do
68
+ machine.spin do
69
+ loop do
70
+ item = machine.shift(queue)
71
+ break if item.nil?
72
+
73
+ total += item
74
+ end
75
+ machine.push(done, true)
76
+ end
77
+ end
78
+
79
+ # wait for producers
80
+ NUM_PRODUCERS.times { machine.shift(done) }
81
+
82
+ # stop and wait for consumers
83
+ NUM_CONSUMERS.times do
84
+ machine.push(queue, nil)
85
+ machine.shift(done)
86
+ end
87
+
88
+ total
89
+ end
90
+
91
+
92
+ # puts "running"
93
+ # res = run_threads
94
+ # p threads: res
95
+
96
+ # 100.times {
97
+ # res = run_um
98
+ # p fibers: res
99
+ # }
100
+
101
+
102
+ # __END__
103
+
104
+ Benchmark.ips do |x|
105
+ x.config(:time => 5, :warmup => 2)
106
+
107
+ x.report("threads") { run_threads }
108
+ x.report("UM") { run_um }
109
+
110
+ x.compare!
111
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'extralite'
9
+ gem 'benchmark-ips'
10
+ end
11
+
12
+ require 'uringmachine'
13
+ require 'extralite'
14
+
15
+ class UM::Actor < Fiber
16
+ def initialize(machine, target)
17
+ @machine = machine
18
+ @target = target
19
+ @mailbox = UM::Queue.new
20
+ super { act }
21
+ end
22
+
23
+ def act
24
+ while (sym, a, k, peer = @machine.shift(@mailbox))
25
+
26
+ begin
27
+ ret = @target.send(sym, *a, **k)
28
+ @machine.schedule(peer, ret)
29
+ rescue => e
30
+ @machine.schedule(peer, e)
31
+ end
32
+ end
33
+ rescue Exception => e
34
+ # handle unhandled exceptions
35
+ ensure
36
+ @machine.fiber_map.delete(self)
37
+ @machine.yield
38
+ end
39
+
40
+ def method_missing(sym, *a, **k)
41
+ @machine.push(@mailbox, [sym, a, k, Fiber.current])
42
+ ret = @machine.yield
43
+ raise(ret) if ret.is_a?(Exception)
44
+ ret
45
+ end
46
+ end
47
+
48
+ class UM
49
+ def spin_actor(target)
50
+ f = UM::Actor.new(self, target)
51
+ schedule(f, nil)
52
+ @@fiber_map[f] = true
53
+ f
54
+ end
55
+ end
56
+
57
+ class Locker
58
+ def initialize(machine, target)
59
+ @machine = machine
60
+ @target = target
61
+ @mutex = UM::Mutex.new
62
+ end
63
+
64
+ def method_missing(sym, *a, **k)
65
+ @machine.synchronize(@mutex) { @target.send(sym, *a, **k) }
66
+ end
67
+ end
68
+
69
+
70
+ PATH = '/tmp/foo'
71
+
72
+ $machine = UM.new
73
+ $raw_db = Extralite::Database.new(PATH)
74
+ $actor_db = $machine.spin_actor(Extralite::Database.new(PATH))
75
+ $locker_db = Locker.new($machine, Extralite::Database.new(PATH))
76
+
77
+ [$raw_db, $actor_db, $locker_db].each do |db|
78
+ p db.query('select 1')
79
+ end
80
+
81
+ bm = Benchmark.ips do |x|
82
+ x.config(:time => 5, :warmup => 2)
83
+
84
+ x.report("raw") { $raw_db.query('select 1') }
85
+ x.report("actor") { $actor_db.query('select 1') }
86
+ x.report("locker") { $locker_db.query('select 1') }
87
+
88
+ x.compare!
89
+ end
@@ -35,7 +35,7 @@ end
35
35
 
36
36
  server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
37
37
  @machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
38
- @machine.bind(server_fd, '127.0.0.1', 1234)
38
+ @machine.bind(server_fd, '0.0.0.0', 1234)
39
39
  @machine.listen(server_fd, UM::SOMAXCONN)
40
40
  puts 'Listening on port 1234'
41
41
 
data/examples/pg.rb ADDED
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+
5
+ @machine = UM.new
6
+
7
+ class UM::Stream
8
+ def initialize(machine, fd)
9
+ @machine, @fd, @bgid = machine, fd
10
+ @buffer = +''
11
+ @ofs_head = 0
12
+ @ofs_tail = 0
13
+ end
14
+
15
+ def feed
16
+ if (@ofs_head == @ofs_tail) && (@ofs_head >= 4096)
17
+ @buffer = +''
18
+ @ofs_head = @ofs_tail = 0
19
+ end
20
+ ret = @machine.read(@fd, @buffer, 65536, @ofs_tail)
21
+ if ret == 0
22
+ @eof = true
23
+ return false
24
+ end
25
+
26
+ @ofs_tail += ret
27
+ true
28
+ end
29
+
30
+ def read(len)
31
+ if @ofs_head + len > @ofs_tail
32
+ feed
33
+ end
34
+
35
+ str = @buffer[@ofs_head, len]
36
+ @ofs_head += str.bytesize
37
+ str
38
+ end
39
+
40
+ def gets(sep = $/, _limit = nil, _chomp: nil)
41
+ if sep.is_a?(Integer)
42
+ sep = $/
43
+ _limit = sep
44
+ end
45
+ sep_size = sep.bytesize
46
+
47
+ while true
48
+ idx = @buffer.index(sep, @ofs_head)
49
+ if idx
50
+ str = @buffer[@ofs_head, idx + sep_size]
51
+ @ofs_head += str.bytesize
52
+ return str
53
+ end
54
+
55
+ return nil if !feed
56
+ end
57
+ end
58
+ end
59
+
60
+ $machine = UringMachine.new
61
+
62
+ server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
63
+ $machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
64
+ $machine.bind(server_fd, '127.0.0.1', 1234)
65
+ $machine.listen(server_fd, UM::SOMAXCONN)
66
+ puts 'Listening on port 1234'
67
+
68
+ def handle_connection(fd)
69
+ stream = UM::Stream.new($machine, fd)
70
+
71
+ while (l = stream.gets)
72
+ $machine.write(fd, "You said: #{l}")
73
+ end
74
+ rescue Exception => e
75
+ puts "Got error #{e.inspect}, closing connection"
76
+ $machine.close(fd) rescue nil
77
+ end
78
+
79
+ main = Fiber.current
80
+ trap('SIGINT') { $machine.spin { $machine.schedule(main, SystemExit.new) } }
81
+
82
+ $machine.accept_each(server_fd) do |fd|
83
+ puts "Connection accepted fd #{fd}"
84
+ $machine.spin(fd) { handle_connection(_1) }
85
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+
5
+ @machine = UM.new
6
+
7
+ class UM::Stream
8
+ def initialize(machine, fd)
9
+ @machine, @fd, @bgid = machine, fd
10
+ @buffer = +''
11
+ @ofs_head = 0
12
+ @ofs_tail = 0
13
+ end
14
+
15
+ def feed
16
+ if (@ofs_head == @ofs_tail) && (@ofs_head >= 4096)
17
+ @buffer = +''
18
+ @ofs_head = @ofs_tail = 0
19
+ end
20
+ ret = @machine.read(@fd, @buffer, 65536, @ofs_tail)
21
+ if ret == 0
22
+ @eof = true
23
+ return false
24
+ end
25
+
26
+ @ofs_tail += ret
27
+ true
28
+ end
29
+
30
+ def read(len)
31
+ if @ofs_head + len > @ofs_tail
32
+ feed
33
+ end
34
+
35
+ str = @buffer[@ofs_head, len]
36
+ @ofs_head += str.bytesize
37
+ str
38
+ end
39
+
40
+ def gets(sep = $/, _limit = nil, _chomp: nil)
41
+ if sep.is_a?(Integer)
42
+ sep = $/
43
+ _limit = sep
44
+ end
45
+ sep_size = sep.bytesize
46
+
47
+ while true
48
+ idx = @buffer.index(sep, @ofs_head)
49
+ if idx
50
+ str = @buffer[@ofs_head, idx + sep_size]
51
+ @ofs_head += str.bytesize
52
+ return str
53
+ end
54
+
55
+ return nil if !feed
56
+ end
57
+ end
58
+ end
59
+
60
+ $machine = UringMachine.new
61
+
62
+ server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
63
+ $machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
64
+ $machine.bind(server_fd, '127.0.0.1', 1234)
65
+ $machine.listen(server_fd, UM::SOMAXCONN)
66
+ puts 'Listening on port 1234'
67
+
68
+ def handle_connection(fd)
69
+ stream = UM::Stream.new($machine, fd)
70
+
71
+ while (l = stream.gets)
72
+ $machine.write(fd, "You said: #{l}")
73
+ end
74
+ rescue Exception => e
75
+ puts "Got error #{e.inspect}, closing connection"
76
+ $machine.close(fd) rescue nil
77
+ end
78
+
79
+ main = Fiber.current
80
+ trap('SIGINT') { $machine.spin { $machine.schedule(main, SystemExit.new) } }
81
+
82
+ $machine.accept_each(server_fd) do |fd|
83
+ puts "Connection accepted fd #{fd}"
84
+ $machine.spin(fd) { handle_connection(_1) }
85
+ end
data/ext/um/extconf.rb CHANGED
@@ -6,6 +6,61 @@ require 'rbconfig'
6
6
 
7
7
  dir_config 'um_ext'
8
8
 
9
+ def config_ssl
10
+ # don't use pkg_config('openssl') if '--with-openssl-dir' is used
11
+ has_openssl_dir = dir_config('openssl').any? ||
12
+ RbConfig::CONFIG['configure_args']&.include?('openssl')
13
+
14
+ found_pkg_config = !has_openssl_dir && pkg_config('openssl')
15
+
16
+ found_ssl = if !$mingw && found_pkg_config
17
+ puts '──── Using OpenSSL pkgconfig (openssl.pc) ────'
18
+ true
19
+ elsif have_library('libcrypto', 'BIO_read') && have_library('libssl', 'SSL_CTX_new')
20
+ true
21
+ elsif %w'crypto libeay32'.find {|crypto| have_library(crypto, 'BIO_read')} &&
22
+ %w'ssl ssleay32'.find {|ssl| have_library(ssl, 'SSL_CTX_new')}
23
+ true
24
+ else
25
+ puts '** Puma will be compiled without SSL support'
26
+ false
27
+ end
28
+
29
+ if found_ssl
30
+ have_header "openssl/bio.h"
31
+
32
+ ssl_h = "openssl/ssl.h".freeze
33
+
34
+ puts "\n──── Below are yes for 1.0.2 & later ────"
35
+ have_func "DTLS_method" , ssl_h
36
+ have_func "SSL_CTX_set_session_cache_mode(NULL, 0)", ssl_h
37
+
38
+ puts "\n──── Below are yes for 1.1.0 & later ────"
39
+ have_func "TLS_server_method" , ssl_h
40
+ have_func "SSL_CTX_set_min_proto_version(NULL, 0)" , ssl_h
41
+
42
+ puts "\n──── Below is yes for 1.1.0 and later, but isn't documented until 3.0.0 ────"
43
+ # https://github.com/openssl/openssl/blob/OpenSSL_1_1_0/include/openssl/ssl.h#L1159
44
+ have_func "SSL_CTX_set_dh_auto(NULL, 0)" , ssl_h
45
+
46
+ puts "\n──── Below is yes for 1.1.1 & later ────"
47
+ have_func "SSL_CTX_set_ciphersuites(NULL, \"\")" , ssl_h
48
+
49
+ puts "\n──── Below is yes for 3.0.0 & later ────"
50
+ have_func "SSL_get1_peer_certificate" , ssl_h
51
+
52
+ puts ''
53
+
54
+ # Random.bytes available in Ruby 2.5 and later, Random::DEFAULT deprecated in 3.0
55
+ if Random.respond_to?(:bytes)
56
+ $defs.push "-DHAVE_RANDOM_BYTES"
57
+ puts "checking for Random.bytes... yes"
58
+ else
59
+ puts "checking for Random.bytes... no"
60
+ end
61
+ end
62
+ end
63
+
9
64
  KERNEL_INFO_RE = /Linux (\d)\.(\d+)(?:\.)?((?:\d+\.?)*)(?:\-)?([\w\-]+)?/
10
65
  def get_config
11
66
  if RUBY_PLATFORM !~ /linux/
@@ -35,6 +90,8 @@ def get_config
35
90
  }
36
91
  end
37
92
 
93
+ # config_ssl
94
+
38
95
  config = get_config
39
96
  puts "Building UringMachine (\n#{config.map { |(k, v)| " #{k}: #{v}\n"}.join})"
40
97