tipi 0.39 → 0.43
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 +4 -0
- data/.gitignore +5 -1
- data/CHANGELOG.md +30 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +62 -25
- data/Rakefile +7 -3
- data/benchmarks/bm_http1_parser.rb +85 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/df/server.rb +16 -87
- data/df/server_utils.rb +175 -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 +3 -1
- data/examples/http_server_routes.rb +29 -0
- data/examples/http_server_static.rb +26 -0
- data/examples/https_server.rb +3 -0
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +2 -8
- data/examples/ws_page.html +2 -2
- 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 +7 -5
- data/lib/tipi/digital_fabric/agent_proxy.rb +16 -8
- data/lib/tipi/digital_fabric/executive.rb +6 -2
- data/lib/tipi/digital_fabric/protocol.rb +18 -3
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +77 -49
- data/lib/tipi/http1_adapter.rb +91 -100
- data/lib/tipi/http2_adapter.rb +21 -6
- data/lib/tipi/http2_stream.rb +54 -44
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +17 -0
- data/lib/tipi/version.rb +1 -1
- data/test/helper.rb +60 -12
- data/test/test_http_server.rb +0 -27
- data/test/test_request.rb +2 -29
- data/tipi.gemspec +11 -7
- metadata +79 -26
- 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: c7a42c93f436a24b74aef2fc9de54d7f01b587ec42b2897d0b0c24a9090776d8
|
|
4
|
+
data.tar.gz: 1fa532504e9c79d620306b4ddfbd01bf1d1dc39b563ec2a2c3cfec9cb00a1b64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93282cde73eca46289cf212e76c90e7390dcd9e48e8594bd756d5f59ae3bc5fed41e61d216c85cca9c9d2c8fca1061ee0159f8cc0ca575fe0b7149a21b1edc24
|
|
7
|
+
data.tar.gz: 6fd1dc4de05351d0b7da91a7344798145f31659f4663f451355222ce63bb8504fb123c562199820b4f217bc14d064bb529fcc0c9ea495863856bd79f073a491b
|
data/.github/workflows/test.yml
CHANGED
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## 0.43 2021-08-20
|
|
2
|
+
|
|
3
|
+
- Extract HTTP/1 parser into a separate gem:
|
|
4
|
+
[H1P](https://github.com/digital-fabric/h1p)
|
|
5
|
+
|
|
6
|
+
## 0.42 2021-08-16
|
|
7
|
+
|
|
8
|
+
- HTTP/1 parser: disable UTF-8 parsing for all but header values
|
|
9
|
+
- Add support for parsing HTTP/1 from callable source
|
|
10
|
+
- Introduce full_service API for automatic HTTPS
|
|
11
|
+
- Introduce automatic SSL certificate provisioning
|
|
12
|
+
- Improve handling of exceptions
|
|
13
|
+
- Various fixes to DF service and agent pxoy
|
|
14
|
+
- Fix upgrading to HTTP2 with a request body
|
|
15
|
+
- Switch to new HTTP/1 parser
|
|
16
|
+
|
|
17
|
+
## 0.41 2021-07-26
|
|
18
|
+
|
|
19
|
+
- Fix Rack adapter (#11)
|
|
20
|
+
- Introduce experimental HTTP/1 parser
|
|
21
|
+
- More work on DF server
|
|
22
|
+
- Allow setting chunk size in `#respond_from_io`
|
|
23
|
+
|
|
24
|
+
## 0.40 2021-06-24
|
|
25
|
+
|
|
26
|
+
- Implement serving static files using splice_chunks (nice performance boost for
|
|
27
|
+
files bigger than 1M)
|
|
28
|
+
- Call shutdown before closing socket
|
|
29
|
+
- Fix examples (thanks @timhatch!)
|
|
30
|
+
|
|
1
31
|
## 0.39 2021-06-20
|
|
2
32
|
|
|
3
33
|
- More work on DF server
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,39 +1,73 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ../h1p
|
|
3
|
+
specs:
|
|
4
|
+
h1p (0.2)
|
|
5
|
+
|
|
6
|
+
PATH
|
|
7
|
+
remote: ../polyphony
|
|
8
|
+
specs:
|
|
9
|
+
polyphony (0.70)
|
|
10
|
+
|
|
11
|
+
PATH
|
|
12
|
+
remote: ../qeweney
|
|
13
|
+
specs:
|
|
14
|
+
qeweney (0.14)
|
|
15
|
+
escape_utils (~> 1.2.1)
|
|
16
|
+
|
|
1
17
|
PATH
|
|
2
18
|
remote: .
|
|
3
19
|
specs:
|
|
4
|
-
tipi (0.
|
|
5
|
-
|
|
6
|
-
|
|
20
|
+
tipi (0.43)
|
|
21
|
+
acme-client (~> 2.0.8)
|
|
22
|
+
extralite (~> 1.2)
|
|
23
|
+
h1p (~> 0.2)
|
|
24
|
+
http-2 (~> 0.11)
|
|
25
|
+
localhost (~> 1.1.4)
|
|
7
26
|
msgpack (~> 1.4.2)
|
|
8
|
-
polyphony (~> 0.
|
|
9
|
-
qeweney (~> 0.
|
|
27
|
+
polyphony (~> 0.69)
|
|
28
|
+
qeweney (~> 0.14)
|
|
10
29
|
rack (>= 2.0.8, < 2.3.0)
|
|
11
30
|
websocket (~> 1.2.8)
|
|
12
31
|
|
|
13
32
|
GEM
|
|
14
33
|
remote: https://rubygems.org/
|
|
15
34
|
specs:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
acme-client (2.0.8)
|
|
36
|
+
faraday (>= 0.17, < 2.0.0)
|
|
37
|
+
cuba (3.9.3)
|
|
38
|
+
rack (>= 1.6.0)
|
|
39
|
+
docile (1.4.0)
|
|
19
40
|
escape_utils (1.2.1)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
extralite (1.3)
|
|
42
|
+
faraday (1.7.0)
|
|
43
|
+
faraday-em_http (~> 1.0)
|
|
44
|
+
faraday-em_synchrony (~> 1.0)
|
|
45
|
+
faraday-excon (~> 1.1)
|
|
46
|
+
faraday-httpclient (~> 1.0.1)
|
|
47
|
+
faraday-net_http (~> 1.0)
|
|
48
|
+
faraday-net_http_persistent (~> 1.1)
|
|
49
|
+
faraday-patron (~> 1.0)
|
|
50
|
+
faraday-rack (~> 1.0)
|
|
51
|
+
multipart-post (>= 1.2, < 3)
|
|
52
|
+
ruby2_keywords (>= 0.0.4)
|
|
53
|
+
faraday-em_http (1.0.0)
|
|
54
|
+
faraday-em_synchrony (1.0.0)
|
|
55
|
+
faraday-excon (1.1.0)
|
|
56
|
+
faraday-httpclient (1.0.1)
|
|
57
|
+
faraday-net_http (1.0.1)
|
|
58
|
+
faraday-net_http_persistent (1.2.0)
|
|
59
|
+
faraday-patron (1.0.0)
|
|
60
|
+
faraday-rack (1.0.0)
|
|
61
|
+
http-2 (0.11.0)
|
|
62
|
+
json (2.5.1)
|
|
63
|
+
localhost (1.1.8)
|
|
64
|
+
memory_profiler (1.0.0)
|
|
24
65
|
minitest (5.11.3)
|
|
25
|
-
minitest-reporters (1.4.2)
|
|
26
|
-
ansi
|
|
27
|
-
builder
|
|
28
|
-
minitest (>= 5.0)
|
|
29
|
-
ruby-progressbar
|
|
30
66
|
msgpack (1.4.2)
|
|
31
|
-
|
|
32
|
-
qeweney (0.9.1)
|
|
33
|
-
escape_utils (~> 1.2.1)
|
|
67
|
+
multipart-post (2.1.1)
|
|
34
68
|
rack (2.2.3)
|
|
35
|
-
rake (
|
|
36
|
-
|
|
69
|
+
rake (13.0.6)
|
|
70
|
+
ruby2_keywords (0.0.5)
|
|
37
71
|
simplecov (0.17.1)
|
|
38
72
|
docile (~> 1.1)
|
|
39
73
|
json (>= 1.8, < 3)
|
|
@@ -45,10 +79,13 @@ PLATFORMS
|
|
|
45
79
|
ruby
|
|
46
80
|
|
|
47
81
|
DEPENDENCIES
|
|
48
|
-
|
|
82
|
+
cuba (~> 3.9.3)
|
|
83
|
+
h1p!
|
|
84
|
+
memory_profiler (~> 1.0.0)
|
|
49
85
|
minitest (~> 5.11.3)
|
|
50
|
-
|
|
51
|
-
|
|
86
|
+
polyphony!
|
|
87
|
+
qeweney!
|
|
88
|
+
rake (~> 13.0.6)
|
|
52
89
|
simplecov (~> 0.17.1)
|
|
53
90
|
tipi!
|
|
54
91
|
|
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
|
-
|
|
@@ -0,0 +1,85 @@
|
|
|
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 measure_time_and_allocs
|
|
8
|
+
4.times { GC.start }
|
|
9
|
+
GC.disable
|
|
10
|
+
|
|
11
|
+
t0 = Time.now
|
|
12
|
+
a0 = object_count
|
|
13
|
+
yield
|
|
14
|
+
t1 = Time.now
|
|
15
|
+
a1 = object_count
|
|
16
|
+
[t1 - t0, a1 - a0]
|
|
17
|
+
ensure
|
|
18
|
+
GC.enable
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def object_count
|
|
22
|
+
count = ObjectSpace.count_objects
|
|
23
|
+
count[:TOTAL] - count[:FREE]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def benchmark_other_http1_parser(iterations)
|
|
27
|
+
STDOUT << "http_parser.rb: "
|
|
28
|
+
require 'http_parser.rb'
|
|
29
|
+
|
|
30
|
+
i, o = IO.pipe
|
|
31
|
+
parser = Http::Parser.new
|
|
32
|
+
done = false
|
|
33
|
+
headers = nil
|
|
34
|
+
parser.on_headers_complete = proc do |h|
|
|
35
|
+
headers = h
|
|
36
|
+
headers[':method'] = parser.http_method
|
|
37
|
+
headers[':path'] = parser.request_url
|
|
38
|
+
end
|
|
39
|
+
parser.on_message_complete = proc { done = true }
|
|
40
|
+
|
|
41
|
+
elapsed, allocated = measure_time_and_allocs do
|
|
42
|
+
iterations.times do
|
|
43
|
+
o << HTTP_REQUEST
|
|
44
|
+
done = false
|
|
45
|
+
while !done
|
|
46
|
+
msg = i.readpartial(4096)
|
|
47
|
+
parser << msg
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def benchmark_tipi_http1_parser(iterations)
|
|
55
|
+
STDOUT << "tipi parser: "
|
|
56
|
+
require_relative '../lib/tipi_ext'
|
|
57
|
+
i, o = IO.pipe
|
|
58
|
+
reader = proc { |len| i.readpartial(len) }
|
|
59
|
+
parser = Tipi::HTTP1Parser.new(reader)
|
|
60
|
+
|
|
61
|
+
elapsed, allocated = measure_time_and_allocs do
|
|
62
|
+
iterations.times do
|
|
63
|
+
o << HTTP_REQUEST
|
|
64
|
+
headers = parser.parse_headers
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fork_benchmark(method, iterations)
|
|
71
|
+
pid = fork do
|
|
72
|
+
send(method, iterations)
|
|
73
|
+
rescue Exception => e
|
|
74
|
+
p e
|
|
75
|
+
p e.backtrace
|
|
76
|
+
exit!
|
|
77
|
+
end
|
|
78
|
+
Process.wait(pid)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
x = 500000
|
|
82
|
+
# fork_benchmark(:benchmark_other_http1_parser, x)
|
|
83
|
+
# fork_benchmark(:benchmark_tipi_http1_parser, x)
|
|
84
|
+
|
|
85
|
+
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/server.rb
CHANGED
|
@@ -1,96 +1,25 @@
|
|
|
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
|
-
require 'localhost/authority'
|
|
3
|
+
require_relative 'server_utils'
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
listeners = [
|
|
6
|
+
listen_http,
|
|
7
|
+
listen_https,
|
|
8
|
+
listen_unix
|
|
9
|
+
]
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
|
|
15
|
-
|
|
16
|
-
spin_loop(interval: 60) { GC.start }
|
|
17
|
-
|
|
18
|
-
class Polyphony::BaseException
|
|
19
|
-
attr_reader :caller_backtrace
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
puts "pid: #{Process.pid}"
|
|
23
|
-
|
|
24
|
-
http_listener = spin do
|
|
25
|
-
opts = {
|
|
26
|
-
reuse_addr: true,
|
|
27
|
-
dont_linger: true,
|
|
28
|
-
}
|
|
29
|
-
puts 'Listening for HTTP on localhost:10080'
|
|
30
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
|
|
31
|
-
server.accept_loop do |client|
|
|
32
|
-
spin do
|
|
33
|
-
service.incr_connection_count
|
|
34
|
-
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
|
35
|
-
ensure
|
|
36
|
-
service.decr_connection_count
|
|
37
|
-
end
|
|
38
|
-
rescue Exception => e
|
|
39
|
-
puts "HTTP accept_loop error: #{e.inspect}"
|
|
40
|
-
puts e.backtrace.join("\n")
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
|
|
45
|
-
|
|
46
|
-
https_listener = spin do
|
|
47
|
-
private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
|
|
48
|
-
c = IO.read('../../reality/ssl/cacert.pem')
|
|
49
|
-
certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
|
|
50
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
|
51
|
-
cert = certificates.shift
|
|
52
|
-
puts "Certificate expires: #{cert.not_after.inspect}"
|
|
53
|
-
ctx.add_certificate(cert, private_key, certificates)
|
|
54
|
-
# ctx = Localhost::Authority.fetch.server_context
|
|
55
|
-
opts = {
|
|
56
|
-
reuse_addr: true,
|
|
57
|
-
dont_linger: true,
|
|
58
|
-
secure_context: ctx,
|
|
59
|
-
alpn_protocols: Tipi::ALPN_PROTOCOLS
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
puts 'Listening for HTTPS on localhost:10443'
|
|
63
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
|
|
64
|
-
server.accept_loop do |client|
|
|
65
|
-
spin do
|
|
66
|
-
service.incr_connection_count
|
|
67
|
-
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
|
68
|
-
ensure
|
|
69
|
-
service.decr_connection_count
|
|
70
|
-
end
|
|
71
|
-
rescue Exception => e
|
|
72
|
-
puts "HTTPS accept_loop error: #{e.inspect}"
|
|
73
|
-
puts e.backtrace.join("\n")
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
UNIX_SOCKET_PATH = '/tmp/df.sock'
|
|
78
|
-
|
|
79
|
-
unix_listener = spin do
|
|
80
|
-
puts "Listening on #{UNIX_SOCKET_PATH}"
|
|
81
|
-
FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
|
|
82
|
-
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
|
83
|
-
Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
|
|
84
|
-
end
|
|
11
|
+
spin_loop(interval: 60) { GC.compact } if GC.respond_to?(:compact)
|
|
85
12
|
|
|
86
13
|
begin
|
|
87
|
-
|
|
14
|
+
log('Starting DF server')
|
|
15
|
+
Fiber.await(*listeners)
|
|
88
16
|
rescue Interrupt
|
|
89
|
-
|
|
90
|
-
service.graceful_shutdown
|
|
91
|
-
|
|
17
|
+
log('Got SIGINT, shutting down gracefully')
|
|
18
|
+
@service.graceful_shutdown
|
|
19
|
+
rescue SystemExit
|
|
20
|
+
# ignore
|
|
92
21
|
rescue Exception => e
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
22
|
+
log("Uncaught exception", error: e, source: e.source_fiber, raising: e.raising_fiber, backtrace: e.backtrace)
|
|
23
|
+
ensure
|
|
24
|
+
log('DF server stopped')
|
|
96
25
|
end
|