tipi 0.38 → 0.42
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 +4 -4
- data/.github/workflows/test.yml +5 -1
- data/.gitignore +5 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +58 -16
- data/Rakefile +7 -3
- data/TODO.md +77 -1
- data/benchmarks/bm_http1_parser.rb +61 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/df/sample_agent.rb +1 -1
- data/df/server.rb +16 -47
- data/df/server_utils.rb +178 -0
- data/examples/full_service.rb +13 -0
- data/examples/http1_parser.rb +55 -0
- data/examples/http_server.rb +15 -3
- data/examples/http_server_forked.rb +5 -1
- data/examples/http_server_routes.rb +29 -0
- data/examples/http_server_static.rb +26 -0
- data/examples/http_server_throttled.rb +3 -2
- data/examples/https_server.rb +6 -4
- data/examples/https_wss_server.rb +2 -1
- data/examples/rack_server.rb +5 -0
- data/examples/rack_server_https.rb +1 -1
- data/examples/rack_server_https_forked.rb +4 -3
- data/examples/routing_server.rb +5 -4
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +2 -8
- data/examples/ws_page.html +2 -2
- data/ext/tipi/extconf.rb +13 -0
- data/ext/tipi/http1_parser.c +823 -0
- data/ext/tipi/http1_parser.h +18 -0
- data/ext/tipi/tipi_ext.c +5 -0
- data/lib/tipi.rb +89 -1
- data/lib/tipi/acme.rb +308 -0
- data/lib/tipi/cli.rb +30 -0
- data/lib/tipi/digital_fabric/agent.rb +22 -17
- data/lib/tipi/digital_fabric/agent_proxy.rb +95 -40
- data/lib/tipi/digital_fabric/executive.rb +6 -2
- data/lib/tipi/digital_fabric/protocol.rb +87 -15
- data/lib/tipi/digital_fabric/request_adapter.rb +6 -10
- data/lib/tipi/digital_fabric/service.rb +77 -51
- data/lib/tipi/http1_adapter.rb +116 -117
- data/lib/tipi/http2_adapter.rb +56 -10
- data/lib/tipi/http2_stream.rb +106 -53
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +17 -0
- data/lib/tipi/version.rb +1 -1
- data/security/http1.rb +12 -0
- data/test/helper.rb +60 -11
- data/test/test_http1_parser.rb +586 -0
- data/test/test_http_server.rb +0 -27
- data/test/test_request.rb +1 -28
- data/tipi.gemspec +11 -5
- metadata +96 -22
- data/e +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82155f4b86223d3fb32dc8c314abfeb75fda951747e63832ed5c15bc3b1f43cc
|
4
|
+
data.tar.gz: 721b98fb8d330d1bc38f8f236f82ddd909235d31f5875c1b4f89895e4e3926da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10389779975234d90c968626b4f9d6b168491f9a32ecf1be71683c37cf11f58f1bac52a36478691e72328236a16d309a8462104bda556772e25cebe52b2b0d53
|
7
|
+
data.tar.gz: 99db2c15278cdfc286ffad05f266f5d62eec6a3bb1363f8dfa4c302549a308a691496ee60ec085ede59e3b594533e8e196bbca144878e5a6dccf2ecfc962151c
|
data/.github/workflows/test.yml
CHANGED
@@ -22,6 +22,10 @@ jobs:
|
|
22
22
|
- name: Install dependencies
|
23
23
|
run: |
|
24
24
|
gem install bundler
|
25
|
-
bundle install
|
25
|
+
POLYPHONY_USE_LIBEV=1 bundle install
|
26
|
+
- name: Show Linux kernel version
|
27
|
+
run: uname -r
|
28
|
+
- name: Compile C-extension
|
29
|
+
run: bundle exec rake compile
|
26
30
|
- name: Run tests
|
27
31
|
run: bundle exec rake test
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
## 0.42 2021-08-16
|
2
|
+
|
3
|
+
- HTTP/1 parser: disable UTF-8 parsing for all but header values
|
4
|
+
- Add support for parsing HTTP/1 from callable source
|
5
|
+
- Introduce full_service API for automatic HTTPS
|
6
|
+
- Introduce automatic SSL certificate provisioning
|
7
|
+
- Improve handling of exceptions
|
8
|
+
- Various fixes to DF service and agent pxoy
|
9
|
+
- Fix upgrading to HTTP2 with a request body
|
10
|
+
- Switch to new HTTP/1 parser
|
11
|
+
|
12
|
+
## 0.41 2021-07-26
|
13
|
+
|
14
|
+
- Fix Rack adapter (#11)
|
15
|
+
- Introduce experimental HTTP/1 parser
|
16
|
+
- More work on DF server
|
17
|
+
- Allow setting chunk size in `#respond_from_io`
|
18
|
+
|
19
|
+
## 0.40 2021-06-24
|
20
|
+
|
21
|
+
- Implement serving static files using splice_chunks (nice performance boost for
|
22
|
+
files bigger than 1M)
|
23
|
+
- Call shutdown before closing socket
|
24
|
+
- Fix examples (thanks @timhatch!)
|
25
|
+
|
26
|
+
## 0.39 2021-06-20
|
27
|
+
|
28
|
+
- More work on DF server
|
29
|
+
- Fix HTTP2StreamHandler#send_headers
|
30
|
+
- Various fixes to HTTP/2 adapter
|
31
|
+
- Fix host detection for HTTP/2 connections
|
32
|
+
- Fix HTTP/1 adapter #respond with nil body
|
33
|
+
- Fix HTTP1Adapter#send_headers
|
34
|
+
|
1
35
|
## 0.38 2021-03-09
|
2
36
|
|
3
37
|
- Don't use chunked transfer encoding for non-streaming responses
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,39 +1,77 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../polyphony
|
3
|
+
specs:
|
4
|
+
polyphony (0.69)
|
5
|
+
|
6
|
+
PATH
|
7
|
+
remote: ../qeweney
|
8
|
+
specs:
|
9
|
+
qeweney (0.14)
|
10
|
+
escape_utils (~> 1.2.1)
|
11
|
+
|
1
12
|
PATH
|
2
13
|
remote: .
|
3
14
|
specs:
|
4
|
-
tipi (0.
|
5
|
-
|
6
|
-
|
15
|
+
tipi (0.42)
|
16
|
+
acme-client (~> 2.0.8)
|
17
|
+
extralite (~> 1.2)
|
18
|
+
http-2 (~> 0.11)
|
19
|
+
localhost (~> 1.1.4)
|
7
20
|
msgpack (~> 1.4.2)
|
8
|
-
polyphony (~> 0.
|
9
|
-
qeweney (~> 0.
|
21
|
+
polyphony (~> 0.69)
|
22
|
+
qeweney (~> 0.14)
|
10
23
|
rack (>= 2.0.8, < 2.3.0)
|
11
24
|
websocket (~> 1.2.8)
|
12
25
|
|
13
26
|
GEM
|
14
27
|
remote: https://rubygems.org/
|
15
28
|
specs:
|
29
|
+
acme-client (2.0.8)
|
30
|
+
faraday (>= 0.17, < 2.0.0)
|
16
31
|
ansi (1.5.0)
|
17
32
|
builder (3.2.4)
|
18
|
-
|
33
|
+
cuba (3.9.3)
|
34
|
+
rack (>= 1.6.0)
|
35
|
+
docile (1.4.0)
|
19
36
|
escape_utils (1.2.1)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
37
|
+
extralite (1.2)
|
38
|
+
faraday (1.7.0)
|
39
|
+
faraday-em_http (~> 1.0)
|
40
|
+
faraday-em_synchrony (~> 1.0)
|
41
|
+
faraday-excon (~> 1.1)
|
42
|
+
faraday-httpclient (~> 1.0.1)
|
43
|
+
faraday-net_http (~> 1.0)
|
44
|
+
faraday-net_http_persistent (~> 1.1)
|
45
|
+
faraday-patron (~> 1.0)
|
46
|
+
faraday-rack (~> 1.0)
|
47
|
+
multipart-post (>= 1.2, < 3)
|
48
|
+
ruby2_keywords (>= 0.0.4)
|
49
|
+
faraday-em_http (1.0.0)
|
50
|
+
faraday-em_synchrony (1.0.0)
|
51
|
+
faraday-excon (1.1.0)
|
52
|
+
faraday-httpclient (1.0.1)
|
53
|
+
faraday-net_http (1.0.1)
|
54
|
+
faraday-net_http_persistent (1.2.0)
|
55
|
+
faraday-patron (1.0.0)
|
56
|
+
faraday-rack (1.0.0)
|
57
|
+
http-2 (0.11.0)
|
58
|
+
http_parser.rb (0.7.0)
|
59
|
+
json (2.5.1)
|
60
|
+
localhost (1.1.8)
|
24
61
|
minitest (5.11.3)
|
25
|
-
minitest-reporters (1.4.
|
62
|
+
minitest-reporters (1.4.3)
|
26
63
|
ansi
|
27
64
|
builder
|
28
65
|
minitest (>= 5.0)
|
29
66
|
ruby-progressbar
|
30
67
|
msgpack (1.4.2)
|
31
|
-
|
32
|
-
qeweney (0.7.5)
|
33
|
-
escape_utils (~> 1.2.1)
|
68
|
+
multipart-post (2.1.1)
|
34
69
|
rack (2.2.3)
|
35
70
|
rake (12.3.3)
|
36
|
-
|
71
|
+
rake-compiler (1.1.1)
|
72
|
+
rake
|
73
|
+
ruby-progressbar (1.11.0)
|
74
|
+
ruby2_keywords (0.0.5)
|
37
75
|
simplecov (0.17.1)
|
38
76
|
docile (~> 1.1)
|
39
77
|
json (>= 1.8, < 3)
|
@@ -45,10 +83,14 @@ PLATFORMS
|
|
45
83
|
ruby
|
46
84
|
|
47
85
|
DEPENDENCIES
|
48
|
-
|
86
|
+
cuba (~> 3.9.3)
|
87
|
+
http_parser.rb (= 0.7.0)
|
49
88
|
minitest (~> 5.11.3)
|
50
89
|
minitest-reporters (~> 1.4.2)
|
90
|
+
polyphony!
|
91
|
+
qeweney!
|
51
92
|
rake (~> 12.3.3)
|
93
|
+
rake-compiler (= 1.1.1)
|
52
94
|
simplecov (~> 0.17.1)
|
53
95
|
tipi!
|
54
96
|
|
data/Rakefile
CHANGED
@@ -3,10 +3,14 @@
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require "rake/clean"
|
5
5
|
|
6
|
-
|
6
|
+
require "rake/extensiontask"
|
7
|
+
Rake::ExtensionTask.new("tipi_ext") do |ext|
|
8
|
+
ext.ext_dir = "ext/tipi"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :recompile => [:clean, :compile]
|
12
|
+
task :default => [:compile, :test]
|
7
13
|
|
8
|
-
task :default => [:test]
|
9
14
|
task :test do
|
10
15
|
exec 'ruby test/run.rb'
|
11
16
|
end
|
12
|
-
|
data/TODO.md
CHANGED
@@ -1,4 +1,80 @@
|
|
1
|
-
|
1
|
+
## Add an API for reading a request body chunk into an IO (pipe)
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
# currently
|
5
|
+
chunk = req.next_chunk
|
6
|
+
# or
|
7
|
+
req.each_chunk { |c| do_something(c) }
|
8
|
+
|
9
|
+
# what we'd like to do
|
10
|
+
r, w = IO.pipe
|
11
|
+
len = req.splice_chunk(w)
|
12
|
+
sock << "Here comes a chunk of #{len} bytes\n"
|
13
|
+
sock.splice(r, len)
|
14
|
+
|
15
|
+
# or:
|
16
|
+
r, w = IO.pipe
|
17
|
+
req.splice_each_chunk(w) do |len|
|
18
|
+
sock << "Here comes a chunk of #{len} bytes\n"
|
19
|
+
sock.splice(r, len)
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
# HTTP/1.1 parser
|
24
|
+
|
25
|
+
- httparser.rb is not actively updated
|
26
|
+
- the httparser.rb C parser code comes originally from https://github.com/nodejs/llhttp
|
27
|
+
- there's a Ruby gem https://github.com/metabahn/llhttp, but its API is too low-level
|
28
|
+
(lots of callbacks, headers need to be retained across callbacks)
|
29
|
+
- the basic idea is to import the C-code, then build a parser object with the following
|
30
|
+
callbacks:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
on_headers_complete(headers)
|
34
|
+
on_body_chunk(chunk)
|
35
|
+
on_message_complete
|
36
|
+
```
|
37
|
+
|
38
|
+
- The llhttp gem's C-code is here: https://github.com/metabahn/llhttp/tree/main/mri
|
39
|
+
|
40
|
+
- Actually, if you do a C extension, instead of a callback-based API, we can
|
41
|
+
design a blocking API:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
parser = Tipi::HTTP1::Parser.new
|
45
|
+
parser.each_request(socket) do |headers|
|
46
|
+
request = Request.new(normalize_headers(headers))
|
47
|
+
handle_request(request)
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
# What about HTTP/2?
|
52
|
+
|
53
|
+
It would be a nice exercise in converting a callback-based API to a blocking
|
54
|
+
one:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
parser = Tipi::HTTP2::Parser.new(socket)
|
58
|
+
parser.each_stream(socket) do |stream|
|
59
|
+
spin { handle_stream(stream) }
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
# DF
|
66
|
+
|
67
|
+
- Add attack protection for IP-address HTTP host:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
IPV4_REGEXP = /^\d+\.\d+\.\d+\.\d+$/.freeze
|
71
|
+
|
72
|
+
def is_attack_request?(req)
|
73
|
+
return true if req.host =~ IPV4_REGEXP && req.query[:q] != 'ping'
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
- Add attack route to Qeweney routing API
|
2
78
|
|
3
79
|
|
4
80
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
|
6
|
+
|
7
|
+
def benchmark_other_http1_parser(iterations)
|
8
|
+
STDOUT << "http_parser.rb: "
|
9
|
+
require 'http_parser.rb'
|
10
|
+
|
11
|
+
i, o = IO.pipe
|
12
|
+
parser = Http::Parser.new
|
13
|
+
done = false
|
14
|
+
headers = nil
|
15
|
+
parser.on_headers_complete = proc do |h|
|
16
|
+
headers = h
|
17
|
+
headers[':method'] = parser.http_method
|
18
|
+
headers[':path'] = parser.request_url
|
19
|
+
headers[':protocol'] = parser.http_version
|
20
|
+
end
|
21
|
+
parser.on_message_complete = proc { done = true }
|
22
|
+
|
23
|
+
t0 = Time.now
|
24
|
+
iterations.times do
|
25
|
+
o << HTTP_REQUEST
|
26
|
+
done = false
|
27
|
+
while !done
|
28
|
+
msg = i.readpartial(4096)
|
29
|
+
parser << msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
t1 = Time.now
|
33
|
+
puts "#{iterations / (t1 - t0)} ips"
|
34
|
+
end
|
35
|
+
|
36
|
+
def benchmark_tipi_http1_parser(iterations)
|
37
|
+
STDOUT << "tipi parser: "
|
38
|
+
require_relative '../lib/tipi_ext'
|
39
|
+
i, o = IO.pipe
|
40
|
+
reader = proc { |len| i.readpartial(len) }
|
41
|
+
parser = Tipi::HTTP1Parser.new(reader)
|
42
|
+
|
43
|
+
t0 = Time.now
|
44
|
+
iterations.times do
|
45
|
+
o << HTTP_REQUEST
|
46
|
+
headers = parser.parse_headers
|
47
|
+
end
|
48
|
+
t1 = Time.now
|
49
|
+
puts "#{iterations / (t1 - t0)} ips"
|
50
|
+
end
|
51
|
+
|
52
|
+
def fork_benchmark(method, iterations)
|
53
|
+
pid = fork { send(method, iterations) }
|
54
|
+
Process.wait(pid)
|
55
|
+
end
|
56
|
+
|
57
|
+
x = 500000
|
58
|
+
fork_benchmark(:benchmark_other_http1_parser, x)
|
59
|
+
fork_benchmark(:benchmark_tipi_http1_parser, x)
|
60
|
+
|
61
|
+
# benchmark_tipi_http1_parser(x)
|
data/bin/benchmark
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
def parse_latency(latency)
|
7
|
+
m = latency.match(/^([\d\.]+)(us|ms|s)$/)
|
8
|
+
return nil unless m
|
9
|
+
|
10
|
+
value = m[1].to_f
|
11
|
+
case m[2]
|
12
|
+
when 's' then value
|
13
|
+
when 'ms' then value / 1000
|
14
|
+
when 'us' then value / 1000000
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_wrk_results(results)
|
19
|
+
lines = results.lines
|
20
|
+
latencies = lines[3].strip.split(/\s+/)
|
21
|
+
throughput = lines[6].strip.split(/\s+/)
|
22
|
+
|
23
|
+
{
|
24
|
+
latency_avg: parse_latency(latencies[1]),
|
25
|
+
latency_max: parse_latency(latencies[3]),
|
26
|
+
rate: throughput[1].to_f
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_wrk(duration: 10, threads: 2, connections: 10, url: )
|
31
|
+
`wrk -d#{duration} -t#{threads} -c#{connections} #{url}`
|
32
|
+
end
|
33
|
+
|
34
|
+
[8, 64, 256, 512].each do |c|
|
35
|
+
puts "connections: #{c}"
|
36
|
+
p parse_wrk_results(run_wrk(duration: 10, threads: 4, connections: c, url: "http://localhost:10080/"))
|
37
|
+
end
|
data/bin/h1pd
ADDED
data/bin/tipi
CHANGED
@@ -1,26 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
|
-
require '
|
5
|
-
require File.expand_path('../lib/tipi/configuration', __dir__)
|
4
|
+
require 'tipi/cli'
|
6
5
|
|
7
|
-
|
8
|
-
#config[:forked] = 4
|
6
|
+
trap('SIGINT') { exit }
|
9
7
|
|
10
|
-
|
11
|
-
puts
|
12
|
-
|
13
|
-
configuration_manager = spin { Tipi::Configuration.supervise_config }
|
14
|
-
|
15
|
-
configuration_manager << config
|
16
|
-
configuration_manager.await
|
17
|
-
|
18
|
-
__END__
|
19
|
-
|
20
|
-
ooo
|
21
|
-
oo
|
22
|
-
o
|
23
|
-
\|/
|
24
|
-
/ \ Tipi - A better web server for a better world
|
25
|
-
/___\
|
26
|
-
|
8
|
+
Tipi::CLI.start
|
data/df/sample_agent.rb
CHANGED
@@ -13,7 +13,7 @@ class SampleAgent < DigitalFabric::Agent
|
|
13
13
|
HTML_SSE = IO.read(File.join(__dir__, 'sse_page.html'))
|
14
14
|
|
15
15
|
def http_request(req)
|
16
|
-
path = req
|
16
|
+
path = req.headers[':path']
|
17
17
|
case path
|
18
18
|
when '/agent'
|
19
19
|
send_df_message(Protocol.http_response(
|
data/df/server.rb
CHANGED
@@ -1,54 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'tipi'
|
5
|
-
require 'tipi/digital_fabric'
|
6
|
-
require 'tipi/digital_fabric/executive'
|
7
|
-
require 'json'
|
8
|
-
require 'fileutils'
|
9
|
-
FileUtils.cd(__dir__)
|
3
|
+
require_relative 'server_utils'
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
class Polyphony::BaseException
|
17
|
-
attr_reader :caller_backtrace
|
18
|
-
end
|
19
|
-
|
20
|
-
puts "pid: #{Process.pid}"
|
21
|
-
|
22
|
-
tcp_listener = spin do
|
23
|
-
opts = {
|
24
|
-
reuse_addr: true,
|
25
|
-
dont_linger: true,
|
26
|
-
}
|
27
|
-
puts 'Listening on localhost:4411'
|
28
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0', 4411, opts)
|
29
|
-
server.accept_loop do |client|
|
30
|
-
spin do
|
31
|
-
service.incr_connection_count
|
32
|
-
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
33
|
-
ensure
|
34
|
-
service.decr_connection_count
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
UNIX_SOCKET_PATH = '/tmp/df.sock'
|
40
|
-
|
41
|
-
unix_listener = spin do
|
42
|
-
puts "Listening on #{UNIX_SOCKET_PATH}"
|
43
|
-
FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
|
44
|
-
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
45
|
-
Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
|
46
|
-
end
|
5
|
+
listeners = [
|
6
|
+
listen_http,
|
7
|
+
listen_https,
|
8
|
+
listen_unix
|
9
|
+
]
|
47
10
|
|
48
11
|
begin
|
49
|
-
|
12
|
+
log('Starting DF server')
|
13
|
+
Fiber.await(*listeners)
|
50
14
|
rescue Interrupt
|
51
|
-
|
52
|
-
service.graceful_shutdown
|
53
|
-
|
15
|
+
log('Got SIGINT, shutting down gracefully')
|
16
|
+
@service.graceful_shutdown
|
17
|
+
rescue SystemExit
|
18
|
+
# ignore
|
19
|
+
rescue Exception => e
|
20
|
+
log("Uncaught exception", error: e, source: e.source_fiber, raising: e.raising_fiber, backtrace: e.backtrace)
|
21
|
+
ensure
|
22
|
+
log('DF server stopped')
|
54
23
|
end
|