tiny-fast-gem 0.0.1
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 +7 -0
- data/puma-8.0.2/History.md +3334 -0
- data/puma-8.0.2/LICENSE +29 -0
- data/puma-8.0.2/README.md +484 -0
- data/puma-8.0.2/bin/puma +10 -0
- data/puma-8.0.2/bin/puma-wild +25 -0
- data/puma-8.0.2/bin/pumactl +12 -0
- data/puma-8.0.2/docs/5.0-Upgrade.md +98 -0
- data/puma-8.0.2/docs/6.0-Upgrade.md +56 -0
- data/puma-8.0.2/docs/7.0-Upgrade.md +52 -0
- data/puma-8.0.2/docs/8.0-Upgrade.md +100 -0
- data/puma-8.0.2/docs/architecture.md +74 -0
- data/puma-8.0.2/docs/compile_options.md +55 -0
- data/puma-8.0.2/docs/deployment.md +137 -0
- data/puma-8.0.2/docs/fork_worker.md +41 -0
- data/puma-8.0.2/docs/grpc.md +62 -0
- data/puma-8.0.2/docs/images/favicon.svg +1 -0
- data/puma-8.0.2/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/puma-8.0.2/docs/images/puma-connection-flow.png +0 -0
- data/puma-8.0.2/docs/images/puma-general-arch.png +0 -0
- data/puma-8.0.2/docs/images/running-puma.svg +1 -0
- data/puma-8.0.2/docs/images/standard-logo.svg +1 -0
- data/puma-8.0.2/docs/java_options.md +54 -0
- data/puma-8.0.2/docs/jungle/README.md +9 -0
- data/puma-8.0.2/docs/jungle/rc.d/README.md +74 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma +61 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma.conf +10 -0
- data/puma-8.0.2/docs/kubernetes.md +73 -0
- data/puma-8.0.2/docs/nginx.md +80 -0
- data/puma-8.0.2/docs/plugins.md +42 -0
- data/puma-8.0.2/docs/rails_dev_mode.md +28 -0
- data/puma-8.0.2/docs/restart.md +65 -0
- data/puma-8.0.2/docs/signals.md +98 -0
- data/puma-8.0.2/docs/stats.md +148 -0
- data/puma-8.0.2/docs/systemd.md +253 -0
- data/puma-8.0.2/docs/testing_benchmarks_local_files.md +150 -0
- data/puma-8.0.2/docs/testing_test_rackup_ci_files.md +36 -0
- data/puma-8.0.2/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/puma-8.0.2/ext/puma_http11/extconf.rb +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.c +1057 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.h +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.java.rl +131 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.rl +149 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser_common.rl +54 -0
- data/puma-8.0.2/ext/puma_http11/mini_ssl.c +852 -0
- data/puma-8.0.2/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11.java +321 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11Parser.java +441 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
- data/puma-8.0.2/ext/puma_http11/puma_http11.c +499 -0
- data/puma-8.0.2/lib/puma/app/status.rb +104 -0
- data/puma-8.0.2/lib/puma/binder.rb +511 -0
- data/puma-8.0.2/lib/puma/cli.rb +245 -0
- data/puma-8.0.2/lib/puma/client.rb +756 -0
- data/puma-8.0.2/lib/puma/client_env.rb +171 -0
- data/puma-8.0.2/lib/puma/cluster/worker.rb +183 -0
- data/puma-8.0.2/lib/puma/cluster/worker_handle.rb +127 -0
- data/puma-8.0.2/lib/puma/cluster.rb +634 -0
- data/puma-8.0.2/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/puma-8.0.2/lib/puma/commonlogger.rb +115 -0
- data/puma-8.0.2/lib/puma/configuration.rb +522 -0
- data/puma-8.0.2/lib/puma/const.rb +308 -0
- data/puma-8.0.2/lib/puma/control_cli.rb +320 -0
- data/puma-8.0.2/lib/puma/detect.rb +58 -0
- data/puma-8.0.2/lib/puma/dsl.rb +1562 -0
- data/puma-8.0.2/lib/puma/error_logger.rb +115 -0
- data/puma-8.0.2/lib/puma/events.rb +72 -0
- data/puma-8.0.2/lib/puma/io_buffer.rb +50 -0
- data/puma-8.0.2/lib/puma/jruby_restart.rb +11 -0
- data/puma-8.0.2/lib/puma/json_serialization.rb +96 -0
- data/puma-8.0.2/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/puma-8.0.2/lib/puma/launcher.rb +501 -0
- data/puma-8.0.2/lib/puma/log_writer.rb +153 -0
- data/puma-8.0.2/lib/puma/minissl/context_builder.rb +96 -0
- data/puma-8.0.2/lib/puma/minissl.rb +458 -0
- data/puma-8.0.2/lib/puma/null_io.rb +101 -0
- data/puma-8.0.2/lib/puma/plugin/systemd.rb +90 -0
- data/puma-8.0.2/lib/puma/plugin/tmp_restart.rb +36 -0
- data/puma-8.0.2/lib/puma/plugin.rb +111 -0
- data/puma-8.0.2/lib/puma/rack/builder.rb +297 -0
- data/puma-8.0.2/lib/puma/rack/urlmap.rb +93 -0
- data/puma-8.0.2/lib/puma/rack_default.rb +24 -0
- data/puma-8.0.2/lib/puma/reactor.rb +131 -0
- data/puma-8.0.2/lib/puma/response.rb +532 -0
- data/puma-8.0.2/lib/puma/runner.rb +211 -0
- data/puma-8.0.2/lib/puma/sd_notify.rb +146 -0
- data/puma-8.0.2/lib/puma/server.rb +773 -0
- data/puma-8.0.2/lib/puma/server_plugin_control.rb +32 -0
- data/puma-8.0.2/lib/puma/single.rb +72 -0
- data/puma-8.0.2/lib/puma/state_file.rb +69 -0
- data/puma-8.0.2/lib/puma/thread_pool.rb +517 -0
- data/puma-8.0.2/lib/puma/util.rb +134 -0
- data/puma-8.0.2/lib/puma.rb +88 -0
- data/puma-8.0.2/lib/rack/handler/puma.rb +144 -0
- data/puma-8.0.2/tools/Dockerfile +26 -0
- data/puma-8.0.2/tools/trickletest.rb +44 -0
- data/tiny-fast-gem.gemspec +12 -0
- metadata +138 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Welcome to Puma 6: Sunflower.
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Puma 6 brings performance improvements for most applications, experimental Rack 3 support, support for Sidekiq 7 Capsules, and more.
|
|
6
|
+
|
|
7
|
+
Here's what you should do:
|
|
8
|
+
|
|
9
|
+
1. Review the Upgrade section below to look for breaking changes that could affect you.
|
|
10
|
+
2. Upgrade to version 6.0 in your Gemfile and deploy.
|
|
11
|
+
3. Open up a new bug issue if you find any problems.
|
|
12
|
+
4. Join us in building Puma! We welcome first-timers. See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
13
|
+
|
|
14
|
+
For a complete list of changes, see [History.md](../History.md).
|
|
15
|
+
|
|
16
|
+
## What's New
|
|
17
|
+
|
|
18
|
+
Puma 6 is mostly about a few nice-to-have performance changes, and then a few breaking API changes we've been putting off for a while.
|
|
19
|
+
|
|
20
|
+
### Improved Performance
|
|
21
|
+
|
|
22
|
+
We've improved throughput and latency in Puma 6 in a few areas.
|
|
23
|
+
|
|
24
|
+
1. **Large chunked response body throughput 3-10x higher** Chunked response bodies >100kb should be 3 to 10 times faster than in Puma 5. String response bodies should be ~10% faster.
|
|
25
|
+
2. **File response throughput is 3x higher.** File responses (e.g. assets) should be about 3x faster.
|
|
26
|
+
3. **wait_for_less_busy_worker is now default, meaning lower latencies for high-utilization servers** `wait_for_less_busy_worker` was an experimental feature in Puma 5 and it's now the default in Puma 6. This feature makes each Puma child worker in cluster mode wait before listening on the socket, and that wait time is proportional to N * `number_of_threads_responding_to_requests`. This means that it's more likely that a request is picked up by the least-loaded Puma child worker listening on the socket. Many users reported back that this option was stable and decreased average latency, particularly in environments with high load and utilization.
|
|
27
|
+
|
|
28
|
+
### Experimental Rack 3 Support
|
|
29
|
+
|
|
30
|
+
[Rack 3 is now out](https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md) and we've started on Rack 3 support. Please open a bug if you find any incompatibilites.
|
|
31
|
+
|
|
32
|
+
### Sidekiq 7 Capsules
|
|
33
|
+
|
|
34
|
+
Sidekiq 7 (releasing soon) introduces Capsules, which allows you to run a Sidekiq server inside your Puma server (or any other Ruby process for that matter). We've added support by allowing you to pass data into `run_hooks`, see [issue #2915](https://github.com/puma/puma/issues/2915).
|
|
35
|
+
|
|
36
|
+
## Upgrade
|
|
37
|
+
|
|
38
|
+
Check the following list to see if you're depending on any of these behaviors:
|
|
39
|
+
|
|
40
|
+
1. Configuration constants like `DefaultRackup` removed, see [#2928](https://github.com/puma/puma/pull/2928/files#diff-2dc4e3e83be7fd97cebc482ae07d6a8216944003de82458783fb00b5ae9524c8) for the full list.
|
|
41
|
+
1. We have changed the names of the following environment variables: `DISABLE_SSL` is now `PUMA_DISABLE_SSL`, and `MAKE_WARNINGS_INTO_ERRORS` is now `PUMA_MAKE_WARNINGS_INTO_ERRORS`.
|
|
42
|
+
1. Nakayoshi GC (`nakayoshi_fork` option in config) has been removed without replacement.
|
|
43
|
+
1. `wait_for_less_busy_worker` is now on by default. If you don't want to use this feature, you must add `wait_for_less_busy_worker 0` in your config.
|
|
44
|
+
1. We've removed the following public methods on Puma::Server: `Puma::Server#min_threads`, `Puma::Server#max_threads`. Instead, you can pass in configuration as an option to Puma::Server#new. This might make certain gems break (`capybara` for example).
|
|
45
|
+
1. We've removed the following constants: `Puma::StateFile::FIELDS`, `Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE` and `Puma::Launcher::KEYS_NOT_TO_PERSIST_IN_STATE`, and `Puma::ControlCLI::COMMANDS`.
|
|
46
|
+
1. We no longer support Ruby 2.2, 2.3, or JRuby on Java 1.7 or below.
|
|
47
|
+
1. The behavior of `remote_addr` has changed. When using the set_remote_address header: "header_name" functionality, if the header is not passed, REMOTE_ADDR is now set to the physical peeraddr instead of always being set to 127.0.0.1. When an error occurs preventing the physical peeraddr from being fetched, REMOTE_ADDR is now set to the unspecified source address ('0.0.0.0') instead of to '127.0.0.1'
|
|
48
|
+
1. Previously, Puma supported anything as an HTTP method and passed it to the app. We now only accept the following 8 HTTP methods, based on [RFC 9110, section 9.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.1). The [IANA HTTP Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml) contains a full list of HTTP methods.
|
|
49
|
+
```
|
|
50
|
+
HEAD GET POST PUT DELETE OPTIONS TRACE PATCH
|
|
51
|
+
```
|
|
52
|
+
As of Puma 6.2, these can be overridden by `supported_http_methods` in your config file, see `Puma::DSL#supported_http_methods`.
|
|
53
|
+
|
|
54
|
+
Then, update your Gemfile:
|
|
55
|
+
|
|
56
|
+
`gem 'puma', '< 7'`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Welcome to Puma 7: Romantic Warrior.
|
|
2
|
+
|
|
3
|
+
Puma 7 brings better tail latency for keepalive-heavy traffic, support for fiber-per-request runtimes, and a handful of cleanup and compatibility changes across the server.
|
|
4
|
+
|
|
5
|
+
Here's what you should do:
|
|
6
|
+
|
|
7
|
+
1. Review the Upgrade section below to look for breaking changes that could affect you.
|
|
8
|
+
2. Upgrade to version 7.0 in your Gemfile and deploy.
|
|
9
|
+
3. Open up a new bug issue if you find any problems.
|
|
10
|
+
4. Join us in building Puma! We welcome first-timers. See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
11
|
+
|
|
12
|
+
For a complete list of changes, see [History.md](../History.md).
|
|
13
|
+
|
|
14
|
+
## What's New
|
|
15
|
+
|
|
16
|
+
Puma 7 is focused on request lifecycle improvements and long-request correctness.
|
|
17
|
+
|
|
18
|
+
1. **Better tail behavior for keepalive connections.** Puma 7 includes a high-effort fix for long tail response behavior with keepalive clients.
|
|
19
|
+
2. **Fiber-per-request support.** Puma now supports fiber-per-request execution.
|
|
20
|
+
3. **`rack.response_finished` support.** Puma now supports the Rack hook for response completion.
|
|
21
|
+
4. **Custom request logging support.** You can plug in a custom logger for request logs.
|
|
22
|
+
|
|
23
|
+
## Upgrade
|
|
24
|
+
|
|
25
|
+
Check the following list to see if you're depending on any of these behaviors:
|
|
26
|
+
|
|
27
|
+
1. The default `max_keep_alive` is now `999`.
|
|
28
|
+
1. The default `persistent_timeout` is now `65` seconds.
|
|
29
|
+
1. Hook methods now raise `ArgumentError` if called without a block.
|
|
30
|
+
1. For Rack > 3.1, Puma no longer sets `env['HTTP_VERSION']`.
|
|
31
|
+
1. `Puma::Runner#ruby_engine` has been removed.
|
|
32
|
+
1. `preload_app!` is now the default in cluster mode. If you need the old behavior, set `preload_app! false` explicitly.
|
|
33
|
+
1. `Puma::Configuration` must be `clamp`-ed before reading values.
|
|
34
|
+
1. Response headers are now normalized to lowercase.
|
|
35
|
+
1. The minimum supported Ruby version is now 3.0.
|
|
36
|
+
1. Callback hook names have been renamed:
|
|
37
|
+
|
|
38
|
+
| Old hook name | New hook name |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `on_worker_boot` | `before_worker_boot` |
|
|
41
|
+
| `on_worker_shutdown` | `before_worker_shutdown` |
|
|
42
|
+
| `on_restart` | `before_restart` |
|
|
43
|
+
| `on_booted` | `after_booted` |
|
|
44
|
+
| `on_stopped` | `after_stopped` |
|
|
45
|
+
| `on_refork` | `before_refork` |
|
|
46
|
+
| `on_thread_start` | `before_thread_start` |
|
|
47
|
+
| `on_thread_exit` | `before_thread_exit` |
|
|
48
|
+
| `on_worker_fork` | `before_worker_fork` |
|
|
49
|
+
|
|
50
|
+
Then, update your Gemfile:
|
|
51
|
+
|
|
52
|
+
`gem 'puma', '< 8'`
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Welcome to Puma 8.0: Into the Arena.
|
|
2
|
+
|
|
3
|
+
Puma 8 brings IPv6 by default, increased control over the threadpool, and more.
|
|
4
|
+
|
|
5
|
+
Here's what you should do:
|
|
6
|
+
|
|
7
|
+
1. Review the Upgrade section below to look for breaking changes that could affect you.
|
|
8
|
+
2. Upgrade to version 8.0 in your Gemfile and deploy.
|
|
9
|
+
3. Open up a new bug issue if you find any problems.
|
|
10
|
+
4. Join us in building Puma! We welcome first-timers. See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
11
|
+
|
|
12
|
+
For a complete list of changes, see [History.md](./History.md).
|
|
13
|
+
|
|
14
|
+
## What's New
|
|
15
|
+
|
|
16
|
+
### Smarter concurrency controls
|
|
17
|
+
|
|
18
|
+
**IO-bound requests can now go past your normal thread ceiling.** Puma 8 adds `max_io_threads` and injects `env["puma.mark_as_io_bound"]` into the Rack env so your app or middleware can tell Puma when a request has become mostly I/O wait. That helps mixed workloads a lot: slow API calls, report generation, and similar wait-heavy requests no longer need to crowd out CPU-bound work as aggressively. This landed in [#3816](https://github.com/puma/puma/pull/3816) and was refined in [#3894](https://github.com/puma/puma/pull/3894).
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# config/puma.rb
|
|
22
|
+
threads 0, 5
|
|
23
|
+
max_io_threads 5
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# config.ru
|
|
28
|
+
run lambda { |env|
|
|
29
|
+
env['puma.mark_as_io_bound'].call
|
|
30
|
+
report = SlowReportService.fetch
|
|
31
|
+
|
|
32
|
+
[200, { 'content-type' => 'application/json' }, [report]]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
We anticipate this will mainly by used by framework authors who have threads or types of requests they know are extremely IO-bound, and don't recommend it for use at the application level.
|
|
37
|
+
|
|
38
|
+
**Thread pool limits can be changed at runtime.** Puma now exposes `Puma::Server#update_thread_pool_min_max`, and hook/plugin code can do the same through `Puma::ServerPluginControl`.
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# from a plugin or other trusted in-process integration
|
|
42
|
+
server.update_thread_pool_min_max(min: 2, max: 12)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If you already use `before_thread_start`, note that hook behavior changed in this release; see the Upgrade section below.
|
|
46
|
+
|
|
47
|
+
### Cleaner config for single and cluster mode
|
|
48
|
+
|
|
49
|
+
**New `single` and `cluster` blocks let one config file express both modes cleanly.** These blocks run after config files are loaded, so you can keep the mode-specific settings in one obvious place instead of scattering `if` logic through `config/puma.rb`. This makes shared configs much easier to read and reuse across development, review apps, and production. See [#3621](https://github.com/puma/puma/pull/3621).
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
|
53
|
+
|
|
54
|
+
single do
|
|
55
|
+
silence_fork_callback_warning
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Only runs if workers > 0
|
|
59
|
+
cluster do
|
|
60
|
+
preload_app!
|
|
61
|
+
before_worker_boot do
|
|
62
|
+
# Do a thing
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Better debugging and operations
|
|
68
|
+
|
|
69
|
+
**`shutdown_debug` can now be limited to forced shutdowns.** If you want thread backtraces only when a graceful shutdown turns into a forced one, use `shutdown_debug on_force: true`. That keeps normal deploy logs quieter while still giving you the "what is hanging?" escape hatch when you need it. See [#3671](https://github.com/puma/puma/pull/3671).
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
shutdown_debug on_force: true
|
|
73
|
+
force_shutdown_after 30
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Thread backtrace via signal works on more platforms.** On systems without `SIGINFO`, Puma now uses `SIGPWR` for thread backtrace dumps. See [#3829](https://github.com/puma/puma/pull/3829).
|
|
77
|
+
|
|
78
|
+
**Phased restart is safer with `fork_worker`.** Puma no longer reforks from a stale worker 0 during phased restarts in `fork_worker` mode. There is nothing new to configure, but if you have tooling that watches worker order, check the Upgrade section because the rollout sequence changed. See [#3853](https://github.com/puma/puma/pull/3853).
|
|
79
|
+
|
|
80
|
+
### Networking and performance
|
|
81
|
+
|
|
82
|
+
**Puma now prefers IPv6 wildcard binds when the host supports them.** When Puma detects a non-loopback IPv6 interface, the default TCP host becomes `::` and the default bind becomes `tcp://[::]:PORT`. That lines Puma up better with modern dual-stack hosts, while still falling back to IPv4 when IPv6 is unavailable. See [#3847](https://github.com/puma/puma/pull/3847). If you need IPv4-only behavior, pin the bind explicitly.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
bind 'tcp://0.0.0.0:9292'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**There are also a couple of hot-path performance wins.** JRuby gets a faster HTTP parser with fewer copies and cheaper lookups, and Puma now avoids redundant header key downcasing when building responses. See [#3838](https://github.com/puma/puma/pull/3838) and [#3874](https://github.com/puma/puma/pull/3874).
|
|
89
|
+
|
|
90
|
+
## Upgrade
|
|
91
|
+
|
|
92
|
+
Check the following list to see if you're depending on any of these behaviors:
|
|
93
|
+
|
|
94
|
+
1. Puma will now listen on `::` (IPv6) by default. Previously, it listened to `0.0.0.0` (IPv4). Systems that support both IPv6 and IPv4 (Ubuntu and Mac commonly do) will still support receiving to `0.0.0.0`. See [[the support table in the PR](https://github.com/puma/puma/pull/3847)](https://github.com/puma/puma/pull/3847) for a binding compatibility. For systems that ONLY bind to IPv6 (without IPv4 support) this may be a breaking change. You can overwrite this default behavior by setting `bind 'tcp://0.0.0.0:9292'`, `port ENV.fetch('PORT', 9292), '0.0.0.0'`, or `set_default_host '0.0.0.0'` explicitly to remain IPv4 only. Review any firewall rules, health checks, deploy scripts, or host-string parsing code that assumed `0.0.0.0`, `127.0.0.1`, or unbracketed `HOST:PORT` formatting.
|
|
95
|
+
2. If you explicitly configure `bind 'tcp://[::]:...'`, `bind 'ssl://[::]:...'`, or equivalent `ssl_bind '::', ...`, Puma will now rewrite that unspecified IPv6 bind to `0.0.0.0` when the host has no non-loopback IPv6 interface, and it will warn on boot. If you were using `::` to force IPv6-only behavior or to detect IPv6 availability, switch to a concrete IPv6 address instead of `::`, or ensure the machine actually has a usable IPv6 interface before Puma starts.
|
|
96
|
+
3. `before_thread_start` hooks now receive a `Puma::ServerPluginControl` argument. Update any zero-arity lambdas, method objects, or other strict-arity hook code to accept one argument, for example `before_thread_start { |_control| ... }`, or Puma can raise `ArgumentError` when the hook runs.
|
|
97
|
+
4. On platforms without `SIGINFO`, Puma now traps `SIGPWR`, and `pumactl info` sends `SIGPWR` there. If your supervisor, init script, or container tooling already uses `SIGPWR` for something else, change that wiring before upgrading and update any runbooks that assumed Puma would ignore `SIGPWR`.
|
|
98
|
+
5. Requests rejected by `supported_http_methods` are now treated as parser/client errors earlier in request processing. If you use `supported_http_methods`, re-test unsupported-method requests and do not depend on the old 501 timing, connection handling, or `lowlevel_error_handler` behavior; if your error handler still sees these requests, make sure it tolerates a less-normalized Rack `env`.
|
|
99
|
+
6. `http_content_length_limit` enforcement is stricter now. A `413` on an HTTP/1.1 keep-alive request now forces `connection: close`, and chunked request bodies are rejected as soon as they cross the limit during parsing. Update clients, proxies, and tests not to reuse the socket after a `413`, and re-test any upload flows or custom error handling that depended on Puma's previous oversized-body behavior.
|
|
100
|
+
7. If you use `fork_worker`, phased restart order changed. During `USR1` or `pumactl phased-restart`, worker `0` is now reinserted and restarted/reforked first after replacement. Update deployment scripts, canary logic, or monitoring that assumed the old worker sequence or keyed rollout steps off worker index order.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Puma is a threaded Ruby HTTP application server processing requests across a TCP
|
|
8
|
+
and/or UNIX socket.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Puma processes (there can be one or many) accept connections from the socket via
|
|
12
|
+
a thread (in the [`Reactor`](../lib/puma/reactor.rb) class). The connection,
|
|
13
|
+
once fully buffered and read, moves into the `todo` list, where an available
|
|
14
|
+
thread will pick it up (in the [`ThreadPool`](../lib/puma/thread_pool.rb)
|
|
15
|
+
class).
|
|
16
|
+
|
|
17
|
+
Puma works in two main modes: cluster and single. In single mode, only one Puma
|
|
18
|
+
process boots. In cluster mode, a `master` process is booted, which prepares
|
|
19
|
+
(and may boot) the application and then uses the `fork()` system call to create
|
|
20
|
+
one or more `child` processes. These `child` processes all listen to the same
|
|
21
|
+
socket. The `master` process does not listen to the socket or process requests -
|
|
22
|
+
its purpose is primarily to manage and listen for UNIX signals and possibly kill
|
|
23
|
+
or boot `child` processes.
|
|
24
|
+
|
|
25
|
+
We sometimes call `child` processes (or Puma processes in `single` mode)
|
|
26
|
+
_workers_, and we sometimes call the threads created by Puma's
|
|
27
|
+
[`ThreadPool`](../lib/puma/thread_pool.rb) _worker threads_.
|
|
28
|
+
|
|
29
|
+
## How Requests Work
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
* Upon startup, Puma listens on a TCP or UNIX socket.
|
|
34
|
+
* The backlog of this socket is configured with a default of 1024, but the
|
|
35
|
+
actual backlog value is capped by the `net.core.somaxconn` sysctl value.
|
|
36
|
+
The backlog determines the size of the queue for unaccepted connections. If
|
|
37
|
+
the backlog is full, the operating system is not accepting new connections.
|
|
38
|
+
* This socket backlog is distinct from the `backlog` of work as reported by
|
|
39
|
+
`Puma.stats` or the control server. The backlog that `Puma.stats` refers to
|
|
40
|
+
represents the number of connections in the process' `todo` set waiting for
|
|
41
|
+
a thread from the [`ThreadPool`](../lib/puma/thread_pool.rb).
|
|
42
|
+
* By default, a single, separate thread (created by the
|
|
43
|
+
[`Reactor`](../lib/puma/reactor.rb) class) reads and buffers requests from the
|
|
44
|
+
socket.
|
|
45
|
+
* When at least one worker thread is available for work, the reactor thread
|
|
46
|
+
listens to the socket and accepts a request (if one is waiting).
|
|
47
|
+
* The reactor thread waits for the entire HTTP request to be received.
|
|
48
|
+
* Puma exposes the time spent waiting for the HTTP request body to be
|
|
49
|
+
received to the Rack app as `env['puma.request_body_wait']`
|
|
50
|
+
(milliseconds).
|
|
51
|
+
* Once fully buffered and received, the connection is pushed into the "todo"
|
|
52
|
+
set.
|
|
53
|
+
* Worker threads pop work off the "todo" set for processing.
|
|
54
|
+
* The worker thread processes the request via `call`ing the configured Rack
|
|
55
|
+
application. The Rack application generates the HTTP response.
|
|
56
|
+
* The worker thread writes the response to the connection. While Puma buffers
|
|
57
|
+
requests via a separate thread, it does not use a separate thread for
|
|
58
|
+
responses.
|
|
59
|
+
* Once done, the thread becomes available to process another connection in the
|
|
60
|
+
"todo" set.
|
|
61
|
+
|
|
62
|
+
### `queue_requests`
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
The `queue_requests` option is `true` by default, enabling the separate reactor
|
|
67
|
+
thread used to buffer requests as described above.
|
|
68
|
+
|
|
69
|
+
If set to `false`, this buffer will not be used for connections while waiting
|
|
70
|
+
for the request to arrive.
|
|
71
|
+
|
|
72
|
+
In this mode, when a connection is accepted, it is added to the "todo" queue
|
|
73
|
+
immediately, and a worker will synchronously do any waiting necessary to read
|
|
74
|
+
the HTTP request from the socket.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Compile Options
|
|
2
|
+
|
|
3
|
+
There are some `cflags` provided to change Puma's default configuration for its
|
|
4
|
+
C extension.
|
|
5
|
+
|
|
6
|
+
## Query String, `PUMA_QUERY_STRING_MAX_LENGTH`
|
|
7
|
+
|
|
8
|
+
By default, the max length of `QUERY_STRING` is `1024 * 10`. But you may want to
|
|
9
|
+
adjust it to accept longer queries in GET requests.
|
|
10
|
+
|
|
11
|
+
For manual install, pass the `PUMA_QUERY_STRING_MAX_LENGTH` option like this:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
gem install puma -- --with-cflags="-D PUMA_QUERY_STRING_MAX_LENGTH=64000"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For Bundler, use its configuration system:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
bundle config build.puma "--with-cflags='-D PUMA_QUERY_STRING_MAX_LENGTH=64000'"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Request Path, `PUMA_REQUEST_PATH_MAX_LENGTH`
|
|
24
|
+
|
|
25
|
+
By default, the max length of `REQUEST_PATH` is `8192`. But you may want to
|
|
26
|
+
adjust it to accept longer paths in requests.
|
|
27
|
+
|
|
28
|
+
For manual install, pass the `PUMA_REQUEST_PATH_MAX_LENGTH` option like this:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
gem install puma -- --with-cflags="-D PUMA_REQUEST_PATH_MAX_LENGTH=64000"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For Bundler, use its configuration system:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
bundle config build.puma "--with-cflags='-D PUMA_REQUEST_PATH_MAX_LENGTH=64000'"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Request URI, `PUMA_REQUEST_URI_MAX_LENGTH`
|
|
41
|
+
|
|
42
|
+
By default, the max length of `REQUEST_URI` is `1024 * 12`. But you may want to
|
|
43
|
+
adjust it to accept longer URIs in requests.
|
|
44
|
+
|
|
45
|
+
For manual install, pass the `PUMA_REQUEST_URI_MAX_LENGTH` option like this:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
gem install puma -- --with-cflags="-D PUMA_REQUEST_URI_MAX_LENGTH=64000"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For Bundler, use its configuration system:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
bundle config build.puma "--with-cflags='-D PUMA_REQUEST_URI_MAX_LENGTH=64000'"
|
|
55
|
+
```
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Deployment engineering for Puma
|
|
2
|
+
|
|
3
|
+
Puma expects to be run in a deployed environment eventually. You can use it as
|
|
4
|
+
your development server, but most people use it in their production deployments.
|
|
5
|
+
|
|
6
|
+
To that end, this document serves as a foundation of wisdom regarding deploying
|
|
7
|
+
Puma to production while increasing happiness and decreasing downtime.
|
|
8
|
+
|
|
9
|
+
## Specifying Puma
|
|
10
|
+
|
|
11
|
+
Most people will specify Puma by including `gem "puma"` in a Gemfile, so we'll
|
|
12
|
+
assume this is how you're using Puma.
|
|
13
|
+
|
|
14
|
+
## Single vs. Cluster mode
|
|
15
|
+
|
|
16
|
+
Initially, Puma was conceived as a thread-only web server, but support for
|
|
17
|
+
processes was added in version 2.
|
|
18
|
+
|
|
19
|
+
In general, use single mode only if:
|
|
20
|
+
|
|
21
|
+
* You are using JRuby, TruffleRuby or another fully-multithreaded implementation of Ruby
|
|
22
|
+
* You are using MRI but in an environment where only 1 CPU core is available.
|
|
23
|
+
|
|
24
|
+
Otherwise, you'll want to use cluster mode to utilize all available CPU resources.
|
|
25
|
+
|
|
26
|
+
To run `puma` in single mode (i.e., as a development environment), set the
|
|
27
|
+
number of workers to 0; anything higher will run in cluster mode.
|
|
28
|
+
|
|
29
|
+
## Cluster Mode Tips
|
|
30
|
+
|
|
31
|
+
For the purposes of Puma provisioning, "CPU cores" means:
|
|
32
|
+
|
|
33
|
+
1. On ARM, the number of physical cores.
|
|
34
|
+
2. On x86, the number of logical cores, hyperthreads, or vCPUs (these words all mean the same thing).
|
|
35
|
+
|
|
36
|
+
Set your config with the following process:
|
|
37
|
+
|
|
38
|
+
* Use cluster mode and set `workers :auto` (requires the `concurrent-ruby` gem) to match the number of CPU cores on the machine (minimum 2, otherwise use single mode!). If you can't add the gem, set the worker count manually to the available CPU cores.
|
|
39
|
+
* Set the number of threads to desired concurrent requests/number of workers.
|
|
40
|
+
Puma defaults to 5, and that's a decent number.
|
|
41
|
+
|
|
42
|
+
For most deployments, adding `concurrent-ruby` and using `workers :auto` is the right starting point.
|
|
43
|
+
|
|
44
|
+
See [`workers :auto` gotchas](../lib/puma/dsl.rb).
|
|
45
|
+
|
|
46
|
+
## Worker utilization
|
|
47
|
+
|
|
48
|
+
**How do you know if you've got enough (or too many workers)?**
|
|
49
|
+
|
|
50
|
+
A good question. Due to MRI's GIL, only one thread can be executing Ruby code at
|
|
51
|
+
a time. But since so many apps are waiting on IO from DBs, etc., they can
|
|
52
|
+
utilize threads to use the process more efficiently.
|
|
53
|
+
|
|
54
|
+
Generally, you never want processes that are pegged all the time. That can mean
|
|
55
|
+
there is more work to do than the process can get through, and requests will end up with additional latency. On the other hand, if
|
|
56
|
+
you have processes that sit around doing nothing, then you're wasting resources and money.
|
|
57
|
+
|
|
58
|
+
In general, you are making a tradeoff between:
|
|
59
|
+
|
|
60
|
+
1. CPU and memory utilization.
|
|
61
|
+
2. Time spent queueing for a Puma worker to `accept` requests and additional latency caused by CPU contention.
|
|
62
|
+
|
|
63
|
+
If latency is important to you, you will have to accept lower utilization, and vice versa.
|
|
64
|
+
|
|
65
|
+
## Container/VPS sizing
|
|
66
|
+
|
|
67
|
+
You will have to make a decision about how "big" to make each pod/VPS/server/dyno.
|
|
68
|
+
|
|
69
|
+
**TL:DR;**: 80% of Puma apps will end up deploying "pods" of 4 workers, 5 threads each, 4 vCPU and 8GB of RAM.
|
|
70
|
+
|
|
71
|
+
For the rest of this discussion, we'll adopt the Kubernetes term of "pods".
|
|
72
|
+
|
|
73
|
+
Should you run 2 pods with 50 workers each? 25 pods, each with 4 workers? 100 pods, with each Puma running in single mode? Each scenario represents the same total amount of capacity (100 Puma processes that can respond to requests), but there are tradeoffs to make:
|
|
74
|
+
|
|
75
|
+
* **Increasing worker counts decreases latency, but means you scale in bigger "chunks"**. Worker counts should be somewhere between 4 and 32 in most cases. You want more than 4 in order to minimize time spent in request queueing for a free Puma worker, but probably less than ~32 because otherwise autoscaling is working in too large of an increment or they probably won't fit very well into your nodes. In any queueing system, queue time is proportional to 1/n, where n is the number of things pulling from the queue. Each pod will have its own request queue (i.e., the socket backlog). If you have 4 pods with 1 worker each (4 request queues), wait times are, proportionally, about 4 times higher than if you had 1 pod with 4 workers (1 request queue).
|
|
76
|
+
* **Increasing thread counts will increase throughput, but also latency and memory use** Unless you have a very I/O-heavy application (50%+ time spent waiting on IO), use the default thread count (5 for MRI). Using higher numbers of threads with low I/O wait (<50% of wall clock time) will lead to additional request latency and additional memory usage.
|
|
77
|
+
* **Increasing worker counts decreases memory per worker on average**. More processes per pod reduces memory usage per process, because of copy-on-write memory and because the cost of the single master process is "amortized" over more child processes.
|
|
78
|
+
* **Low worker counts (<4) have exceptionally poor throughput**. Don't run less than 4 processes per pod if you can. Low numbers of processes per pod will lead to high request queueing (see discussion above), which means you will have to run more pods and resources.
|
|
79
|
+
* **CPU-core-to-worker ratios should be around 1**. If running Puma with `threads > 1`, allocate 1 CPU core (see definition above!) per worker. If single threaded, allocate ~0.75 cpus per worker. Most web applications spend about 25% of their time in I/O - but when you're running multi-threaded, your Puma process will have higher CPU usage and should be able to fully saturate a CPU core. Using `workers :auto` will size workers to this guidance on most platforms.
|
|
80
|
+
* **Don't set memory limits unless necessary**. Most Puma processes will use about ~512MB-1GB per worker, and about 1GB for the master process. However, you probably shouldn't bother with setting memory limits lower than around 2GB per process, because most places you are deploying will have 2GB of RAM per CPU. A sensible memory limit for a Puma configuration of 4 child workers might be something like 8 GB (1 GB for the master, 7GB for the 4 children).
|
|
81
|
+
|
|
82
|
+
**Measuring utilization and queue time**
|
|
83
|
+
|
|
84
|
+
Using a timestamp header from an upstream proxy server (e.g., `nginx` or
|
|
85
|
+
`haproxy`) makes it possible to indicate how long requests have been waiting for
|
|
86
|
+
a Puma thread to become available.
|
|
87
|
+
|
|
88
|
+
* Have your upstream proxy set a header with the time it received the request:
|
|
89
|
+
* nginx: `proxy_set_header X-Request-Start "${msec}";`
|
|
90
|
+
* haproxy >= 1.9: `http-request set-header X-Request-Start
|
|
91
|
+
t=%[date()]%[date_us()]`
|
|
92
|
+
* haproxy < 1.9: `http-request set-header X-Request-Start t=%[date()]`
|
|
93
|
+
* In your Rack middleware, determine the amount of time elapsed since
|
|
94
|
+
`X-Request-Start`.
|
|
95
|
+
* To improve accuracy, you will want to subtract time spent waiting for slow
|
|
96
|
+
clients:
|
|
97
|
+
* `env['puma.request_body_wait']` contains the number of milliseconds Puma
|
|
98
|
+
spent waiting for the client to send the request body.
|
|
99
|
+
* haproxy: `%Th` (TLS handshake time) and `%Ti` (idle time before request)
|
|
100
|
+
can also be added as headers.
|
|
101
|
+
|
|
102
|
+
## Should I daemonize?
|
|
103
|
+
|
|
104
|
+
The Puma 5.0 release removed daemonization. For older versions and alternatives,
|
|
105
|
+
continue reading.
|
|
106
|
+
|
|
107
|
+
I prefer not to daemonize my servers and use something like `runit` or `systemd`
|
|
108
|
+
to monitor them as child processes. This gives them fast response to crashes and
|
|
109
|
+
makes it easy to figure out what is going on. Additionally, unlike `unicorn`,
|
|
110
|
+
Puma does not require daemonization to do zero-downtime restarts.
|
|
111
|
+
|
|
112
|
+
I see people using daemonization because they start puma directly via Capistrano
|
|
113
|
+
task and thus want it to live on past the `cap deploy`. To these people, I say:
|
|
114
|
+
You need to be using a process monitor. Nothing is making sure Puma stays up in
|
|
115
|
+
this scenario! You're just waiting for something weird to happen, Puma to die,
|
|
116
|
+
and to get paged at 3 AM. Do yourself a favor, at least the process monitoring
|
|
117
|
+
your OS comes with, be it `sysvinit` or `systemd`. Or branch out and use `runit`
|
|
118
|
+
or hell, even `monit`.
|
|
119
|
+
|
|
120
|
+
## Restarting
|
|
121
|
+
|
|
122
|
+
You probably will want to deploy some new code at some point, and you'd like
|
|
123
|
+
Puma to start running that new code. There are a few options for restarting
|
|
124
|
+
Puma, described separately in our [restart documentation](restart.md).
|
|
125
|
+
|
|
126
|
+
## Migrating from Unicorn
|
|
127
|
+
|
|
128
|
+
* If you're migrating from unicorn though, here are some settings to start with:
|
|
129
|
+
* Set workers to half the number of unicorn workers you're using
|
|
130
|
+
* Set threads to 2
|
|
131
|
+
* Enjoy 50% memory savings
|
|
132
|
+
* As you grow more confident in the thread-safety of your app, you can tune the
|
|
133
|
+
workers down and the threads up.
|
|
134
|
+
|
|
135
|
+
## Ubuntu / Systemd (Systemctl) Installation
|
|
136
|
+
|
|
137
|
+
See [systemd.md](systemd.md)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Fork-Worker Cluster Mode [Experimental]
|
|
2
|
+
|
|
3
|
+
Puma 5 introduces an experimental new cluster-mode configuration option, `fork_worker` (`--fork-worker` from the CLI). This mode causes Puma to fork additional workers from worker 0, instead of directly from the master process:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
10000 \_ puma 4.3.3 (tcp://0.0.0.0:9292) [puma]
|
|
7
|
+
10001 \_ puma: cluster worker 0: 10000 [puma]
|
|
8
|
+
10002 \_ puma: cluster worker 1: 10000 [puma]
|
|
9
|
+
10003 \_ puma: cluster worker 2: 10000 [puma]
|
|
10
|
+
10004 \_ puma: cluster worker 3: 10000 [puma]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The `fork_worker` option allows your application to be initialized only once for copy-on-write memory savings, and it has two additional advantages:
|
|
14
|
+
|
|
15
|
+
1. **Compatible with phased restart.** Because the master process itself doesn't preload the application, this mode works with phased restart (`SIGUSR1` or `pumactl phased-restart`). When worker 0 reloads as part of a phased restart, it initializes a new copy of your application first, then the other workers reload by forking from this new worker already containing the new preloaded application.
|
|
16
|
+
|
|
17
|
+
This allows a phased restart to complete as quickly as a hot restart (`SIGUSR2` or `pumactl restart`), while still minimizing downtime by staggering the restart across cluster workers.
|
|
18
|
+
|
|
19
|
+
2. **'Refork' for additional copy-on-write improvements in running applications.** Fork-worker mode introduces a new `refork` command that re-loads all nonzero workers by re-forking them from worker 0.
|
|
20
|
+
|
|
21
|
+
This command can potentially improve memory utilization in large or complex applications that don't fully pre-initialize on startup, because the re-forked workers can share copy-on-write memory with a worker that has been running for a while and serving requests.
|
|
22
|
+
|
|
23
|
+
You can trigger a refork by sending the cluster the `SIGURG` signal or running the `pumactl refork` command at any time. A refork will also automatically trigger once, after a certain number of requests have been processed by worker 0 (default 1000). To configure the number of requests before the auto-refork, pass a positive integer argument to `fork_worker` (e.g., `fork_worker 1000`), or `0` to disable.
|
|
24
|
+
|
|
25
|
+
### Usage Considerations
|
|
26
|
+
|
|
27
|
+
- `fork_worker` introduces new `before_refork` and `after_refork` configuration hooks. Note the following:
|
|
28
|
+
- When initially forking the parent process to the worker 0 child, `before_fork` will trigger on the parent process and `before_worker_boot` will trigger on the worker 0 child as normal.
|
|
29
|
+
- When forking the worker 0 child to grandchild workers, `before_refork` and `after_refork` will trigger on the worker 0 child, and `before_worker_boot` will trigger on each grandchild worker.
|
|
30
|
+
- For clarity, `before_fork` does not trigger on worker 0, and `after_refork` does not trigger on the grandchild.
|
|
31
|
+
- As a general migration guide:
|
|
32
|
+
- Copy any logic within your existing `before_fork` hook to the `before_refork` hook.
|
|
33
|
+
- Consider to copy logic from your `before_worker_boot` hook to the `after_refork` hook, if it is needed to reset the state of worker 0 after it forks.
|
|
34
|
+
|
|
35
|
+
### Limitations
|
|
36
|
+
|
|
37
|
+
- This mode is still very experimental so there may be bugs or edge-cases, particularly around expected behavior of existing hooks. Please open a [bug report](https://github.com/puma/puma/issues/new?template=bug_report.md) if you encounter any issues.
|
|
38
|
+
|
|
39
|
+
- In order to fork new workers cleanly, worker 0 shuts down its server and stops serving requests so there are no open file descriptors or other kinds of shared global state between processes, and to maximize copy-on-write efficiency across the newly-forked workers. This may temporarily reduce total capacity of the cluster during a phased restart / refork.
|
|
40
|
+
|
|
41
|
+
- In a cluster with `n` workers, a normal phased restart stops and restarts workers one by one while the application is loaded in each process, so `n-1` workers are available serving requests during the restart. In a phased restart in fork-worker mode, the application is first loaded in worker 0 while `n-1` workers are available, then worker 0 remains stopped while the rest of the workers are reloaded one by one, leaving only `n-2` workers to be available for a brief period of time. Reloading the rest of the workers should be quick because the application is preloaded at that point, but there may be situations where it can take longer (slow clients, long-running application code, slow worker-fork hooks, etc).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Using gRPC with Puma in Clustered Mode
|
|
2
|
+
|
|
3
|
+
This guide shows how to set up gRPC with Puma in a clustered environment using Puma's hooks to manage gRPC's lifecycle methods during forking.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
In a clustered Puma setup, you might encounter the following error when using gRPC:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
grpc cannot be used between calls to GRPC.prefork and GRPC.postfork_child or GRPC.postfork_parent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
To work correctly, gRPC needs these methods called at specific points in the process lifecycle:
|
|
14
|
+
- `GRPC.prefork`: Called before forking.
|
|
15
|
+
- `GRPC.postfork_child`: Called in the child process after forking.
|
|
16
|
+
- `GRPC.postfork_parent`: Called in the parent process after forking.
|
|
17
|
+
|
|
18
|
+
Puma provides hooks such as `before_worker_fork`, `after_worker_fork`, and `before_worker_boot` to execute code during these lifecycle events. Understanding the behavior of these hooks is key to ensuring gRPC operates correctly in a clustered setup.
|
|
19
|
+
|
|
20
|
+
## The Solution
|
|
21
|
+
|
|
22
|
+
### Example Configuration
|
|
23
|
+
|
|
24
|
+
This configuration integrates gRPC's lifecycle methods in a clustered Puma setup and works whether preloading is enabled or not.
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# config/puma.rb
|
|
28
|
+
|
|
29
|
+
is_mac = RUBY_PLATFORM.include?("darwin")
|
|
30
|
+
|
|
31
|
+
before_worker_fork do |index|
|
|
32
|
+
GRPC.prefork unless is_mac
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
after_worker_fork do |index|
|
|
36
|
+
GRPC.postfork_parent unless is_mac
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
before_worker_boot do
|
|
40
|
+
GRPC.postfork_child unless is_mac
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Understanding the Lifecycle and Hooks
|
|
45
|
+
|
|
46
|
+
Puma's hooks determine when to call gRPC's lifecycle methods. Each hook plays a specific role in managing the lifecycle during forking:
|
|
47
|
+
|
|
48
|
+
- **`before_worker_fork`**:
|
|
49
|
+
- This hook runs before forking workers and is where you call `GRPC.prefork`.
|
|
50
|
+
- In preloading setups (default in Puma v7), it runs in the **master process** before workers are forked, as the application is preloaded in the master process.
|
|
51
|
+
- Without preloading, it still runs in the **master process** before forking workers, but the application is not preloaded.
|
|
52
|
+
- `GRPC.prefork` is called here to prepare GRPC for the forking process.
|
|
53
|
+
|
|
54
|
+
- **`after_worker_fork`**:
|
|
55
|
+
- This hook always runs in the **master process** after a worker is forked, regardless of whether preloading is enabled.
|
|
56
|
+
- Call `GRPC.postfork_parent` here to finalize the master process's state after forking.
|
|
57
|
+
|
|
58
|
+
- **`before_worker_boot`**:
|
|
59
|
+
- This hook always runs in the **worker process** after it is forked, regardless of whether preloading is enabled.
|
|
60
|
+
- Call `GRPC.postfork_child` here to finalize the worker's state.
|
|
61
|
+
|
|
62
|
+
**Note**: On macOS, these methods are skipped because gRPC does not require them due to differences in how forking works.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="152.027" height="150.013" viewBox="0 0 114.02 112.51"><defs><clipPath id="a"><path d="M0 .012h63V62H0Zm0 0"/></clipPath><clipPath id="b"><path d="M31.156.004c17.11 0 30.98 13.871 30.98 30.98s-13.87 30.977-30.98 30.977C14.051 61.96.18 48.094.18 30.984S14.05.004 31.156.004m0 0" clip-rule="evenodd"/></clipPath><clipPath id="d"><path d="M52 .012h62V62H52Zm0 0"/></clipPath><clipPath id="e"><path d="M83.043.008c17.105 0 30.977 13.867 30.977 30.976 0 17.11-13.872 30.977-30.977 30.977-17.11 0-30.98-13.867-30.98-30.977S65.933.008 83.043.008m0 0" clip-rule="evenodd"/></clipPath><clipPath id="g"><path d="M52 .012h11V62H52Zm0 0"/></clipPath><clipPath id="h"><path d="M31.156.004c17.11 0 30.98 13.871 30.98 30.98s-13.87 30.977-30.98 30.977C14.051 61.96.18 48.094.18 30.984S14.05.004 31.156.004m0 0" clip-rule="evenodd"/></clipPath><clipPath id="i"><path d="M83.043.008c17.105 0 30.977 13.867 30.977 30.976 0 17.11-13.872 30.977-30.977 30.977-17.11 0-30.98-13.867-30.98-30.977S65.933.008 83.043.008m0 0" clip-rule="evenodd"/></clipPath><clipPath id="k"><path d="M0 50h62v62.512H0Zm0 0"/></clipPath><clipPath id="l"><path d="M30.977 50.555c17.109 0 30.976 13.87 30.976 30.98s-13.867 30.973-30.976 30.973C13.867 112.508 0 98.645 0 81.535s13.867-30.98 30.977-30.98m0 0" clip-rule="evenodd"/></clipPath><clipPath id="n"><path d="M0 50h62v12H0Zm0 0"/></clipPath><clipPath id="o"><path d="M31.156.004c17.11 0 30.98 13.871 30.98 30.98s-13.87 30.977-30.98 30.977C14.051 61.96.18 48.094.18 30.984S14.05.004 31.156.004m0 0" clip-rule="evenodd"/></clipPath><clipPath id="p"><path d="M30.977 50.555c17.109 0 30.976 13.87 30.976 30.98s-13.867 30.973-30.976 30.973C13.867 112.508 0 98.645 0 81.535s13.867-30.98 30.977-30.98m0 0" clip-rule="evenodd"/></clipPath><clipPath id="r"><path d="M51 50h63v62.512H51Zm0 0"/></clipPath><clipPath id="s"><path d="M82.86 50.555c17.109 0 30.98 13.87 30.98 30.98s-13.871 30.977-30.98 30.977c-17.106 0-30.977-13.867-30.977-30.977s13.87-30.98 30.976-30.98m0 0" clip-rule="evenodd"/></clipPath><clipPath id="u"><path d="M52 50h62v12H52Zm0 0"/></clipPath><clipPath id="v"><path d="M83.043.008c17.105 0 30.977 13.867 30.977 30.976 0 17.11-13.872 30.977-30.977 30.977-17.11 0-30.98-13.867-30.98-30.977S65.933.008 83.043.008m0 0" clip-rule="evenodd"/></clipPath><clipPath id="w"><path d="M82.86 50.555c17.109 0 30.98 13.87 30.98 30.98s-13.871 30.977-30.98 30.977c-17.106 0-30.977-13.867-30.977-30.977s13.87-30.98 30.976-30.98m0 0" clip-rule="evenodd"/></clipPath><clipPath id="y"><path d="M51 50h11v62.512H51Zm0 0"/></clipPath><clipPath id="z"><path d="M30.977 50.555c17.109 0 30.976 13.87 30.976 30.98s-13.867 30.973-30.976 30.973C13.867 112.508 0 98.645 0 81.535s13.867-30.98 30.977-30.98m0 0" clip-rule="evenodd"/></clipPath><clipPath id="A"><path d="M82.86 50.555c17.109 0 30.98 13.87 30.98 30.98s-13.871 30.977-30.98 30.977c-17.106 0-30.977-13.867-30.977-30.977s13.87-30.98 30.976-30.98m0 0" clip-rule="evenodd"/></clipPath><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABK0lEQVR4nO3SwQmEQAAEQfUhPg4xFDM0AnM0lIuiWFi6IhiaWb/9WWZ0H+/oCcs2esDMigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaH1On+jN0yr50LFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKC/0BiakDO9qS1OUAAAAASUVORK5CYII=" id="c" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABKElEQVR4nO3dwQmAQBAEQc+3iLmYrCmZlUnYnEhVBEOz/x3Hvi1fcl737AmvWWcP+DNxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuaHztT8SfuNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUMPkcYCz6dHDDoAAAAASUVORK5CYII=" id="f" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABMElEQVR4nO3csQmEUBBAwfNiETO7sli7MrMEWzAZPuibApblsfFO6zL/Rli3nc6/zoPOf+I/eoE3Ky5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFplH/c7+gy4WKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXugG+KwQ7PgO2kwAAAABJRU5ErkJggg==" id="j" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABJ0lEQVR4nO3SsQmAQAAEQTUWEXPB/oszMTOyiuFBdio4lpv3bZ1iLKMH/FlxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouND/3MXoDcV7v6Ak9VyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRc6AM3AgQ7OdMblwAAAABJRU5ErkJggg==" id="m" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABM0lEQVR4nO3dsQnDQBBFQZ1waGSFasP9F+E2lKoDV+BIPA7MTAWfx+Y79u250FhnD/hn4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNzQ4/M6Zm+45X2dsyf85HJD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjc0PAnouNyQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNfQEBTgQ9BPCEIQAAAABJRU5ErkJggg==" id="q" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABJUlEQVR4nO3SsQmAQAAEQRVDEUvQzP4rNDd2eZCZCo7l5mPfJhrL6AF/Jm5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2towe8ndc9esJnPDckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3JC4IXFD4obEDYkbEjckbkjckLghcUPihsQNiRsSNyRuSNyQuCFxQ+KGxA2JGxI3JG5I3NADw1YBfSH2ahsAAAAASUVORK5CYII=" id="t" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABKElEQVR4nO3dMQqAQBAEQZULRQRz//9Mc2OLA+l6wdBsvut57EuMbfaAPysuVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhcbsAW/jumdP+EyXCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kJrfyKcLhcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXOgB/tcBcv0+qHgAAAAASUVORK5CYII=" id="x" width="116" height="116" x="0" y="0"/><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAIAAADb+IFwAAAABmJLR0QA/wD/AP+gvaeTAAABMklEQVR4nO3SwQmEUAxAwXXxpogdaP9VaRHet4W9DB/kTQFJeGTat/UT4zv6gDcrLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFheZRi89jofOv+6Hz/9HnQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi4UHGh4kLFhYoLFRcqLlRcqLhQcaHiQsWFigsVFyouVFyouFBxoeJCxYWKCxUXKi5UXKi40LRv6+gbXqvPhYoLFRf6AZBxBD0Rzoe7AAAAAElFTkSuQmCC" id="B" width="116" height="116" x="0" y="0"/></defs><g clip-path="url(#a)"><g clip-path="url(#b)"><use xlink:href="#c" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g><g clip-path="url(#d)"><g clip-path="url(#e)"><use xlink:href="#f" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g><g clip-path="url(#g)"><g clip-path="url(#h)"><g clip-path="url(#i)"><use xlink:href="#j" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g></g><g clip-path="url(#k)"><g clip-path="url(#l)"><use xlink:href="#m" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g><g clip-path="url(#n)"><g clip-path="url(#o)"><g clip-path="url(#p)"><use xlink:href="#q" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g></g><g clip-path="url(#r)"><g clip-path="url(#s)"><use xlink:href="#t" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g><g clip-path="url(#u)"><g clip-path="url(#v)"><g clip-path="url(#w)"><use xlink:href="#x" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g></g><g clip-path="url(#y)"><g clip-path="url(#z)"><g clip-path="url(#A)"><use xlink:href="#B" transform="translate(-.61 -1.1)scale(1.00103)"/></g></g></g></svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="596.587" height="219.92" viewBox="0 0 447.44 164.94"><defs><clipPath id="a"><path d="M0 0h437v164.941H0Zm0 0"/></clipPath><clipPath id="b"><path d="M10 0h437.441v164.941H10Zm0 0"/></clipPath><clipPath id="c"><path d="M5 0h438v164.941H5Zm0 0"/></clipPath></defs><g clip-path="url(#a)"><path fill="#e0067f" d="M238.277 102.027c-1.859-1.246-7.484-1.246-13.086-1.906-5.601-.598-8.093-1.844-3.734-4.328 4.363-2.496 12.465-16.813 12.465-16.813 0 3.727 0 9.317 2.496 11.22 2.477 1.835 6.848 1.835 6.848 4.956s-3.121 8.094-4.989 6.871m189.965-55.46c-3.742-3.735-9.965-11.82-13.707-15.555-3.742-3.723-8.71-4.375-18.07-6.23-4.98-7.485-13.703-4.989-16.82 0-3.735 0-18.692 5.609-24.922 10.593-6.22 4.98-32.371 4.98-42.336 0-9.969-4.984-19.93-22.422-36.121-31.145-16.2-8.73-39.88-1.25-39.88-1.25-21.8-4.37-59.144 32.395-74.71 45.454-15.578 13.086-29.899 18.687-43.606 23.062-13.687 4.379-62.902 12.45-81.593 14.293-18.668 1.906-33.63 16.852-36.118 20.582-2.5 3.758 8.715 13.7 13.715 10.606 4.965-3.114 21.164-11.836 29.875-15.602 8.742-3.727 42.367-8.078 55.446-9.34 13.066-1.25 52.32-13.055 54.808-14.922 2.484-1.883 7.473-6.87 8.73 0 1.231 6.832 9.344 16.184 17.415 14.332 8.109-1.89 6.226 1.852 3.742 7.453-2.473 5.598 1.254 15.555 9.976 15.555s28.649 3.121 32.38 3.121c3.745 0 2.48 2.496-4.36 4.989-6.863 2.5-9.332 5.624-1.246 8.714 8.086 3.121 21.187 3.121 26.144-.617 4.996-3.75 3.75-10.59 6.262-14.332 2.461-3.73 4.969 4.988 1.848 10.582-3.114 5.637-6.227 14.973-11.828 14.973-5.61 0-18.7 5.594-23.676 8.094-4.988 2.496-11.215 11.191-3.73 13.699 7.464 2.476 12.452 1.254 16.816-3.113 4.37-4.364 15.562-1.875 24.277-5.61 8.738-3.758 11.871-9.957 12.465-13.07.629-3.145 4.348-3.145 4.348-3.742.648 6.254 3.75 3.742 11.214 8.113 7.477 4.348 17.457 3.723 22.418-1.258 4.989-4.988 0-7.465-6.203-9.348-6.234-1.886-11.855-3.73-16.84-6.238-4.964-2.5 3.13-4.984 9.364-6.23 6.207-1.243 26.148-3.723 30.492-8.727 4.402-4.973 19.344-16.805 25.566-19.902 13.051-4.352 21.16-9.356 30.504-15.57 9.336-6.243 17.43-5.004 20.559-4.368 3.105.617 13.07 6.219 17.437 6.219 4.348 0 11.22-2.473 11.82-8.707.63-6.227 3.762-9.352 6.231-13.086 2.492-3.742-4.36-8.723-8.086-12.473"/></g><g clip-path="url(#b)"><path fill="#f4f014" d="M248.86 102.027c-1.86-1.246-7.485-1.246-13.087-1.906-5.605-.598-8.093-1.844-3.734-4.328C236.402 93.297 244.5 78.98 244.5 78.98c0 3.727 0 9.317 2.5 11.22 2.473 1.835 6.848 1.835 6.848 4.956s-3.121 8.094-4.989 6.871m189.964-55.46c-3.742-3.735-9.969-11.82-13.71-15.555-3.743-3.723-8.708-4.375-18.067-6.23-4.98-7.485-13.703-4.989-16.82 0-3.739 0-18.692 5.609-24.922 10.593-6.22 4.98-32.371 4.98-42.34 0C313 30.391 303.039 12.953 286.848 4.23c-16.2-8.73-39.88-1.25-39.88-1.25-21.8-4.37-59.144 32.395-74.714 45.454-15.574 13.086-29.895 18.687-43.602 23.062-13.687 4.379-62.902 12.45-81.593 14.293-18.668 1.906-33.63 16.852-36.118 20.582-2.5 3.758 8.715 13.7 13.715 10.606 4.965-3.114 21.164-11.836 29.875-15.602 8.742-3.727 42.364-8.078 55.446-9.34 13.062-1.25 52.32-13.055 54.808-14.922 2.485-1.883 7.469-6.87 8.73 0 1.231 6.832 9.344 16.184 17.415 14.332 8.11-1.89 6.226 1.852 3.742 7.453-2.477 5.598 1.254 15.555 9.976 15.555s28.649 3.121 32.375 3.121c3.75 0 2.485 2.496-4.355 4.989-6.863 2.5-9.332 5.624-1.246 8.714 8.086 3.121 21.187 3.121 26.144-.617 4.997-3.75 3.75-10.59 6.262-14.332 2.461-3.73 4.965 4.988 1.848 10.582-3.113 5.637-6.227 14.973-11.828 14.973-5.61 0-18.7 5.594-23.68 8.094-4.984 2.496-11.211 11.191-3.727 13.699 7.465 2.476 12.454 1.254 16.817-3.113 4.37-4.364 15.562-1.875 24.277-5.61 8.738-3.758 11.871-9.957 12.465-13.07.629-3.145 4.348-3.145 4.348-3.742.648 6.254 3.75 3.742 11.214 8.113 7.477 4.348 17.458 3.723 22.418-1.258 4.985-4.988 0-7.465-6.203-9.348-6.234-1.886-11.859-3.73-16.84-6.238-4.964-2.5 3.13-4.984 9.36-6.23 6.21-1.243 26.152-3.723 30.496-8.727 4.402-4.973 19.344-16.805 25.566-19.902 13.051-4.352 21.157-9.356 30.504-15.57 9.336-6.243 17.43-5.004 20.559-4.368 3.105.617 13.07 6.219 17.433 6.219 4.352 0 11.223-2.473 11.825-8.707.629-6.227 3.761-9.352 6.23-13.086 2.492-3.742-4.36-8.723-8.086-12.473"/></g><g clip-path="url(#c)"><path fill="#312f34" d="M244.07 102.027c-1.86-1.246-7.484-1.246-13.09-1.906-5.601-.598-8.09-1.844-3.734-4.328 4.367-2.496 12.465-16.813 12.465-16.813 0 3.727 0 9.317 2.5 11.22 2.473 1.835 6.844 1.835 6.844 4.956s-3.118 8.094-4.985 6.871m189.965-55.46c-3.742-3.735-9.969-11.82-13.71-15.555-3.743-3.723-8.708-4.375-18.067-6.23-4.98-7.485-13.703-4.989-16.824 0-3.735 0-18.688 5.609-24.922 10.593-6.215 4.98-32.367 4.98-42.336 0-9.969-4.984-19.926-22.422-36.121-31.145-16.2-8.73-39.875-1.25-39.875-1.25-21.801-4.37-59.145 32.395-74.715 45.454-15.578 13.086-29.895 18.687-43.606 23.062-13.687 4.379-62.902 12.45-81.59 14.293-18.667 1.906-33.628 16.852-36.12 20.582-2.497 3.758 8.714 13.7 13.718 10.606 4.965-3.114 21.164-11.836 29.871-15.602 8.746-3.727 42.367-8.078 55.45-9.34 13.062-1.25 52.316-13.055 54.808-14.922 2.48-1.883 7.469-6.87 8.727 0 1.234 6.832 9.347 16.184 17.418 14.332 8.105-1.89 6.222 1.852 3.742 7.453-2.477 5.598 1.254 15.555 9.972 15.555 8.723 0 28.653 3.121 32.38 3.121 3.75 0 2.484 2.496-4.36 4.989-6.86 2.5-9.328 5.624-1.242 8.714 8.086 3.121 21.183 3.121 26.144-.617 4.996-3.75 3.75-10.59 6.262-14.332 2.461-3.73 4.965 4.988 1.844 10.582-3.11 5.637-6.223 14.973-11.828 14.973-5.61 0-18.696 5.594-23.676 8.094-4.988 2.496-11.211 11.191-3.727 13.699 7.461 2.476 12.45 1.254 16.813-3.113 4.375-4.364 15.562-1.875 24.277-5.61 8.738-3.758 11.875-9.957 12.465-13.07.633-3.145 4.352-3.145 4.352-3.742.644 6.254 3.75 3.742 11.21 8.113 7.481 4.348 17.461 3.723 22.418-1.258 4.989-4.988 0-7.465-6.203-9.348-6.23-1.886-11.855-3.73-16.836-6.238-4.964-2.5 3.13-4.984 9.36-6.23 6.21-1.243 26.152-3.723 30.496-8.727 4.402-4.973 19.34-16.805 25.566-19.902 13.051-4.352 21.157-9.356 30.5-15.57 9.34-6.243 17.434-5.004 20.559-4.368 3.105.617 13.074 6.219 17.437 6.219 4.352 0 11.22-2.473 11.82-8.707.63-6.227 3.766-9.352 6.235-13.086 2.488-3.742-4.36-8.723-8.086-12.473"/></g></svg>
|