syntropy 0.28.2 → 0.29.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,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'syntropy/connection'
4
+ require 'syntropy/request_extensions'
5
+
6
+ module Syntropy
7
+ class Server
8
+ PENDING_REQUESTS_GRACE_PERIOD = 0.1
9
+ PENDING_REQUESTS_TIMEOUT_PERIOD = 5
10
+
11
+ def self.syntropy_app(_machine, env)
12
+ if env[:app_location]
13
+ env[:logger]&.info(message: 'Loading web app', location: env[:app_location])
14
+ require env[:app_location]
15
+
16
+ env.merge!(Syntropy.config)
17
+ end
18
+ env[:app]
19
+ end
20
+
21
+ def self.static_app(env); end
22
+
23
+ def initialize(machine, env, &app)
24
+ @machine = machine
25
+ @env = env
26
+ @app = app || app_from_env
27
+ @server_fds = []
28
+ @accept_fibers = []
29
+ end
30
+
31
+ def app_from_env
32
+ case @env[:app_type]
33
+ when nil, :syntropy
34
+ Server.syntropy_app(@machine, @env)
35
+ when :static
36
+ Server.static_app(@env)
37
+ else
38
+ raise "Invalid app type #{@env[:app_type].inspect}"
39
+ end
40
+ end
41
+
42
+ def run
43
+ setup
44
+ @machine.await(@accept_fibers)
45
+ rescue UM::Terminate
46
+ graceful_shutdown
47
+ end
48
+
49
+ def stop!
50
+ graceful_shutdown
51
+ end
52
+
53
+ private
54
+
55
+ def setup
56
+ bind_info = get_bind_entries
57
+ bind_info.each do |(host, port)|
58
+ fd = setup_server_socket(host, port)
59
+ @server_fds << fd
60
+ @accept_fibers << @machine.spin { accept_incoming(fd) }
61
+ end
62
+ bind_string = bind_info.map { it.join(':') }.join(', ')
63
+ @env[:logger]&.info(message: "Listening on #{bind_string}")
64
+ setup_server_extensions
65
+
66
+ # map fibers
67
+ @connection_fibers = Set.new
68
+ end
69
+
70
+ def get_bind_entries
71
+ bind = @env[:bind]
72
+ case bind
73
+ when Array
74
+ bind.map { bind_info(it) }
75
+ when String
76
+ [bind_info(bind)]
77
+ else
78
+ # default
79
+ [['0.0.0.0', 1234]]
80
+ end
81
+ end
82
+
83
+ def bind_info(bind_string)
84
+ parts = bind_string.split(':')
85
+ [parts[0], parts[1].to_i]
86
+ end
87
+
88
+ def setup_server_socket(host, port)
89
+ fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
90
+ @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
91
+ @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEPORT, true)
92
+ @machine.bind(fd, host, port)
93
+ @machine.listen(fd, UM::SOMAXCONN)
94
+ fd
95
+ end
96
+
97
+ def setup_server_extensions
98
+ extensions = @env[:server_extensions]
99
+ return if !extensions
100
+
101
+ server_name = extensions[:name]
102
+ if extensions[:date]
103
+ @date_header_fiber = @machine.spin {
104
+ @machine.periodically(1) { update_server_headers(server_name) }
105
+ }
106
+ update_server_headers(server_name)
107
+ elsif server_name
108
+ @env[:server_headers] = "Server: #{server_name}\r\n"
109
+ end
110
+ end
111
+
112
+ def update_server_headers(server_name)
113
+ @env[:server_date] = Time.now
114
+ if server_name
115
+ @env[:server_headers] = "Server: #{server_name}\r\nDate: #{@env[:server_date].httpdate}\r\n"
116
+ else
117
+ @env[:server_headers] = "Date: #{Time.now.httpdate}\r\n"
118
+ end
119
+ end
120
+
121
+ def accept_incoming(listen_fd)
122
+ @machine.accept_each(listen_fd) { start_client_connection(it) }
123
+ rescue UM::Terminate
124
+ # terminated
125
+ end
126
+
127
+ def start_client_connection(fd)
128
+ conn = Connection.new(self, @machine, fd, @env, &@app)
129
+ f = @machine.spin(conn) do
130
+ it.run
131
+ ensure
132
+ @connection_fibers.delete(f)
133
+ end
134
+ @connection_fibers << f
135
+ end
136
+
137
+ def close_all_server_fds
138
+ @server_fds.each { @machine.close_async(it) }
139
+ end
140
+
141
+ STOP = UM::Terminate.new
142
+
143
+ def stop_accept_fibers
144
+ @accept_fibers.each { @machine.schedule(it, STOP) if !it.done? }
145
+ @machine.await(@accept_fibers)
146
+ end
147
+
148
+ def graceful_shutdown
149
+ @env[:logger]&.info(message: 'Shutting down gracefully...')
150
+
151
+ # stop listening
152
+ close_all_server_fds
153
+ stop_accept_fibers
154
+ @machine.snooze
155
+
156
+ return if @connection_fibers.empty?
157
+
158
+ # sleep for a bit, let requests finish
159
+ @machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
160
+ return if @connection_fibers.empty?
161
+
162
+ # terminate pending fibers
163
+ pending = @connection_fibers.to_a
164
+ pending.each { @machine.schedule(it, STOP) }
165
+
166
+ @machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
167
+ @machine.await(@connection_fibers)
168
+ rescue UM::Terminate
169
+ # timeout on waiting for adapters to finish running, do nothing
170
+ end
171
+ end
172
+ end
173
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.28.2'
4
+ VERSION = '0.29.0'
5
5
  end
data/lib/syntropy.rb CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  require 'qeweney'
4
4
  require 'uringmachine'
5
- require 'tp2'
6
5
  require 'papercraft'
7
6
 
7
+ require 'syntropy/logger'
8
+ require 'syntropy/connection'
9
+ require 'syntropy/server'
8
10
  require 'syntropy/app'
9
11
  require 'syntropy/connection_pool'
10
12
  require 'syntropy/errors'
@@ -16,6 +18,7 @@ require 'syntropy/routing_tree'
16
18
  require 'syntropy/json_api'
17
19
  require 'syntropy/side_run'
18
20
  require 'syntropy/utils'
21
+ require 'syntropy/version'
19
22
 
20
23
  module Syntropy
21
24
  Status = Qeweney::Status
@@ -50,4 +53,52 @@ module Syntropy
50
53
  " #{GREEN} #{YELLOW}|#{GREEN} vvv o #{CLEAR}https://github.com/digital-fabric/syntropy\n"\
51
54
  " #{GREEN} :#{YELLOW}|#{GREEN}:::#{YELLOW}|#{GREEN}::#{YELLOW}|#{GREEN}:\n"\
52
55
  "#{YELLOW}+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\e[0m\n\n"
56
+
57
+ class << self
58
+ def run(env = {}, &app)
59
+ if @in_run
60
+ @env = env
61
+ @env[:app] = app if app
62
+ return
63
+ end
64
+
65
+ env ||= @env || {}
66
+ begin
67
+ @in_run = true
68
+ machine = env[:machine] || UM.new
69
+ machine.puts(env[:banner]) if env[:banner]
70
+
71
+ env[:logger]&.info(message: "Running Syntropy #{Syntropy::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
72
+
73
+ server = Server.new(machine, env, &app)
74
+
75
+ setup_signal_handling(machine, Fiber.current)
76
+ server.run
77
+ ensure
78
+ @in_run = false
79
+ end
80
+ end
81
+
82
+ def env(env = nil, &app)
83
+ return @env if !env && !app
84
+
85
+ @env = env || {}
86
+ @env[:app] = app if app
87
+ end
88
+
89
+ private
90
+
91
+ def setup_signal_handling(machine, fiber)
92
+ queue = UM::Queue.new
93
+ trap('SIGINT') { machine.push(queue, :SIGINT) }
94
+ machine.spin { watch_for_int_signal(machine, queue, fiber) }
95
+ end
96
+
97
+ # waits for signal from queue, then terminates given fiber
98
+ # to be done
99
+ def watch_for_int_signal(machine, queue, fiber)
100
+ machine.shift(queue)
101
+ machine.schedule(fiber, UM::Terminate.new)
102
+ end
103
+ end
53
104
  end
data/syntropy.gemspec CHANGED
@@ -13,9 +13,9 @@ Gem::Specification.new do |s|
13
13
  s.metadata = {
14
14
  'homepage_uri' => 'https://github.com/digital-fabric/syntropy',
15
15
  'documentation_uri' => 'https://www.rubydoc.info/gems/syntropy',
16
- 'changelog_uri' => 'https://github.com/digital-fabric/syntropy/blob/master/CHANGELOG.md'
16
+ 'changelog_uri' => 'https://github.com/digital-fabric/syntropy/blob/main/CHANGELOG.md'
17
17
  }
18
- s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
18
+ s.rdoc_options = ['--title', 'Syntropy', '--main', 'README.md']
19
19
  s.extra_rdoc_files = ['README.md']
20
20
  s.require_paths = ['lib']
21
21
  s.required_ruby_version = '>= 3.4'
@@ -24,8 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency 'extralite', '~>2.14'
25
25
  s.add_dependency 'papercraft', '~>3.2.0'
26
26
  s.add_dependency 'qeweney', '~>0.24'
27
- s.add_dependency 'tp2', '~>0.20.2'
28
- s.add_dependency 'uringmachine', '~>0.24.0'
27
+ s.add_dependency 'uringmachine', '~>1.0.0'
29
28
 
30
29
  s.add_dependency 'listen', '~>3.9.0'
31
30
 
data/test/helper.rb CHANGED
@@ -11,6 +11,12 @@ require 'fileutils'
11
11
  STDOUT.sync = true
12
12
  STDERR.sync = true
13
13
 
14
+ class ::String
15
+ def crlf_lines
16
+ chomp.gsub("\n", "\r\n").chomp
17
+ end
18
+ end
19
+
14
20
  module ::Kernel
15
21
  def mock_req(headers, body = nil)
16
22
  Qeweney::MockAdapter.mock(headers, body).tap { it.setup_mock_request }
@@ -113,5 +119,3 @@ class Qeweney::Request
113
119
  response_headers['Content-Type']
114
120
  end
115
121
  end
116
-
117
- # puts "Polyphony backend: #{Thread.current.backend.kind}"
data/test/test_caching.rb CHANGED
@@ -40,7 +40,7 @@ class CachingTest < Minitest::Test
40
40
  @app = Syntropy::App.new(**@env)
41
41
 
42
42
  @c_fd, @s_fd = make_socket_pair
43
- @adapter = TP2::Connection.new(nil, @machine, @s_fd, @env) { @app.(it) }
43
+ @adapter = Syntropy::Connection.new(nil, @machine, @s_fd, @env) { @app.(it) }
44
44
  end
45
45
 
46
46
  def teardown