kino 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/Cargo.lock +60 -49
- data/README.md +75 -2
- data/ext/kino/Cargo.toml +1 -1
- data/ext/kino/src/control.rs +718 -0
- data/ext/kino/src/lib.rs +12 -0
- data/ext/kino/src/mono.rs +26 -0
- data/ext/kino/src/queue.rs +7 -0
- data/ext/kino/src/registry.rs +155 -0
- data/ext/kino/src/request.rs +4 -0
- data/ext/kino/src/server.rs +116 -6
- data/lib/kino/cli.rb +5 -2
- data/lib/kino/configuration.rb +57 -0
- data/lib/kino/hook_fire.rb +23 -0
- data/lib/kino/quarantine_monitor.rb +70 -0
- data/lib/kino/ractor_supervisor.rb +59 -14
- data/lib/kino/server.rb +143 -21
- data/lib/kino/templates/kino.rb.tt +51 -0
- data/lib/kino/version.rb +1 -1
- data/lib/kino/worker.rb +36 -19
- data/lib/kino/worker_hooks.rb +11 -0
- data/lib/kino.rb +3 -0
- data/sig/kino.rbs +9 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 129f2e9c0c20ba89c1f2f1e854d32f2b746858546b0c6fdb1a88a3fcb14e05cf
|
|
4
|
+
data.tar.gz: 66b465f2e5989e621487752d0b4dac48a9da0857b5290effaac9af803bd315e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac6656c0cd168c5c43dd5d1585b639b93f2489b5e017cbdd297c383c19d083c391cd7ac60034dbcd825bc525b42d7a966f884aa9d03eb0185e7bf8b5539a7781
|
|
7
|
+
data.tar.gz: 4392ca122bb8d865eec08393a6f339343bb2f5fac209b82e845ec6319d04f71a7a309a70d5d6518a70d70fc701c50dbd482646250b4d46f3e0d313e497bf4e3d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## [0.3.0] - 2026-08-13
|
|
2
|
+
|
|
3
|
+
- Queue-time histogram: `/metrics` exposes `kino_request_queue_seconds`, a
|
|
4
|
+
histogram of how long each request waited for a free worker (the
|
|
5
|
+
saturation signal), and `server.stats`/`/stats` gain `queue_time`
|
|
6
|
+
(count and summed seconds). Measured internally with a monotonic clock,
|
|
7
|
+
so it needs no proxy header and is immune to clock skew.
|
|
8
|
+
- Lifecycle hooks: `after_boot`, `after_worker_boot`,
|
|
9
|
+
`after_request_complete`, and `on_worker_exit` join `on_error`, so apps
|
|
10
|
+
can wire their own metrics, readiness, and error tracking. The
|
|
11
|
+
worker-context hooks must be Ractor-shareable in `:ractor` mode; a raising
|
|
12
|
+
hook is logged and never kills a worker.
|
|
13
|
+
- Stuck-worker quarantine: past `quarantine_timeout`, a wedged dispatch
|
|
14
|
+
slot is quarantined and a replacement worker is spawned to restore
|
|
15
|
+
capacity (capped by `quarantine_max`), surfaced via `/stats`, `/metrics`,
|
|
16
|
+
and `server.stats`. The wedged worker is never force-killed.
|
|
17
|
+
- Control plane: a read-only monitoring listener (`control_bind`,
|
|
18
|
+
optional `control_token`) serving live stats as JSON at `/stats`,
|
|
19
|
+
Prometheus metrics at `/metrics`, and `/ready`/`/live` probes, answered
|
|
20
|
+
from the native layer on a dedicated thread so it stays responsive
|
|
21
|
+
while workers are busy, stuck, or draining.
|
|
22
|
+
- Per-worker stats: `/stats`, `/metrics`, and `server.stats` now break the
|
|
23
|
+
counters down per dispatch slot (served, in-flight, and `busy_ms`, the
|
|
24
|
+
age of the slot's current request), so a stuck slot is visible
|
|
25
|
+
individually.
|
|
26
|
+
- Update Rust and Ruby dependencies.
|
|
27
|
+
|
|
1
28
|
## [0.2.1] - 2026-07-27
|
|
2
29
|
|
|
3
30
|
- Update Rust dependencies for Kino.
|
data/Cargo.lock
CHANGED
|
@@ -17,9 +17,9 @@ dependencies = [
|
|
|
17
17
|
|
|
18
18
|
[[package]]
|
|
19
19
|
name = "aho-corasick"
|
|
20
|
-
version = "1.1.
|
|
20
|
+
version = "1.1.5"
|
|
21
21
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
22
|
-
checksum = "
|
|
22
|
+
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
|
23
23
|
dependencies = [
|
|
24
24
|
"memchr",
|
|
25
25
|
]
|
|
@@ -51,7 +51,7 @@ dependencies = [
|
|
|
51
51
|
"regex",
|
|
52
52
|
"rustc-hash",
|
|
53
53
|
"shlex 1.3.0",
|
|
54
|
-
"syn",
|
|
54
|
+
"syn 2.0.119",
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
[[package]]
|
|
@@ -74,9 +74,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
|
74
74
|
|
|
75
75
|
[[package]]
|
|
76
76
|
name = "cc"
|
|
77
|
-
version = "1.4.
|
|
77
|
+
version = "1.4.2"
|
|
78
78
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
79
|
-
checksum = "
|
|
79
|
+
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
|
|
80
80
|
dependencies = [
|
|
81
81
|
"find-msvc-tools",
|
|
82
82
|
"shlex 2.0.1",
|
|
@@ -99,9 +99,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
99
99
|
|
|
100
100
|
[[package]]
|
|
101
101
|
name = "clang-sys"
|
|
102
|
-
version = "1.
|
|
102
|
+
version = "1.9.1"
|
|
103
103
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
104
|
-
checksum = "
|
|
104
|
+
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
|
105
105
|
dependencies = [
|
|
106
106
|
"glob",
|
|
107
107
|
"libc",
|
|
@@ -131,9 +131,9 @@ dependencies = [
|
|
|
131
131
|
|
|
132
132
|
[[package]]
|
|
133
133
|
name = "find-msvc-tools"
|
|
134
|
-
version = "0.1.
|
|
134
|
+
version = "0.1.10"
|
|
135
135
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
136
|
-
checksum = "
|
|
136
|
+
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
|
137
137
|
|
|
138
138
|
[[package]]
|
|
139
139
|
name = "flume"
|
|
@@ -155,24 +155,24 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
|
155
155
|
|
|
156
156
|
[[package]]
|
|
157
157
|
name = "futures-channel"
|
|
158
|
-
version = "0.3.
|
|
158
|
+
version = "0.3.34"
|
|
159
159
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
160
|
-
checksum = "
|
|
160
|
+
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
|
161
161
|
dependencies = [
|
|
162
162
|
"futures-core",
|
|
163
163
|
]
|
|
164
164
|
|
|
165
165
|
[[package]]
|
|
166
166
|
name = "futures-core"
|
|
167
|
-
version = "0.3.
|
|
167
|
+
version = "0.3.34"
|
|
168
168
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
169
|
-
checksum = "
|
|
169
|
+
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
|
170
170
|
|
|
171
171
|
[[package]]
|
|
172
172
|
name = "futures-sink"
|
|
173
|
-
version = "0.3.
|
|
173
|
+
version = "0.3.34"
|
|
174
174
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
175
|
-
checksum = "
|
|
175
|
+
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
|
|
176
176
|
|
|
177
177
|
[[package]]
|
|
178
178
|
name = "getrandom"
|
|
@@ -229,9 +229,9 @@ dependencies = [
|
|
|
229
229
|
|
|
230
230
|
[[package]]
|
|
231
231
|
name = "http"
|
|
232
|
-
version = "1.
|
|
232
|
+
version = "1.5.0"
|
|
233
233
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
234
|
-
checksum = "
|
|
234
|
+
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
|
235
235
|
dependencies = [
|
|
236
236
|
"bytes",
|
|
237
237
|
"itoa",
|
|
@@ -249,9 +249,9 @@ dependencies = [
|
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
251
251
|
name = "http-body-util"
|
|
252
|
-
version = "0.1.
|
|
252
|
+
version = "0.1.5"
|
|
253
253
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
254
|
-
checksum = "
|
|
254
|
+
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
|
255
255
|
dependencies = [
|
|
256
256
|
"bytes",
|
|
257
257
|
"futures-core",
|
|
@@ -323,9 +323,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
323
323
|
|
|
324
324
|
[[package]]
|
|
325
325
|
name = "js-sys"
|
|
326
|
-
version = "0.3.
|
|
326
|
+
version = "0.3.104"
|
|
327
327
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
328
|
-
checksum = "
|
|
328
|
+
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
|
329
329
|
dependencies = [
|
|
330
330
|
"cfg-if",
|
|
331
331
|
"wasm-bindgen",
|
|
@@ -333,7 +333,7 @@ dependencies = [
|
|
|
333
333
|
|
|
334
334
|
[[package]]
|
|
335
335
|
name = "kino"
|
|
336
|
-
version = "0.
|
|
336
|
+
version = "0.3.0"
|
|
337
337
|
dependencies = [
|
|
338
338
|
"ahash",
|
|
339
339
|
"bytes",
|
|
@@ -403,9 +403,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|
|
403
403
|
|
|
404
404
|
[[package]]
|
|
405
405
|
name = "lru"
|
|
406
|
-
version = "0.18.
|
|
406
|
+
version = "0.18.2"
|
|
407
407
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
408
|
-
checksum = "
|
|
408
|
+
checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
|
|
409
409
|
dependencies = [
|
|
410
410
|
"hashbrown",
|
|
411
411
|
]
|
|
@@ -430,7 +430,7 @@ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
|
430
430
|
dependencies = [
|
|
431
431
|
"proc-macro2",
|
|
432
432
|
"quote",
|
|
433
|
-
"syn",
|
|
433
|
+
"syn 2.0.119",
|
|
434
434
|
]
|
|
435
435
|
|
|
436
436
|
[[package]]
|
|
@@ -561,7 +561,7 @@ dependencies = [
|
|
|
561
561
|
"quote",
|
|
562
562
|
"regex",
|
|
563
563
|
"shell-words",
|
|
564
|
-
"syn",
|
|
564
|
+
"syn 2.0.119",
|
|
565
565
|
]
|
|
566
566
|
|
|
567
567
|
[[package]]
|
|
@@ -593,9 +593,9 @@ dependencies = [
|
|
|
593
593
|
|
|
594
594
|
[[package]]
|
|
595
595
|
name = "regex-automata"
|
|
596
|
-
version = "0.4.
|
|
596
|
+
version = "0.4.18"
|
|
597
597
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
598
|
-
checksum = "
|
|
598
|
+
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
|
599
599
|
dependencies = [
|
|
600
600
|
"aho-corasick",
|
|
601
601
|
"memchr",
|
|
@@ -630,9 +630,9 @@ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
|
630
630
|
|
|
631
631
|
[[package]]
|
|
632
632
|
name = "rustls"
|
|
633
|
-
version = "0.23.
|
|
633
|
+
version = "0.23.43"
|
|
634
634
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
635
|
-
checksum = "
|
|
635
|
+
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
|
636
636
|
dependencies = [
|
|
637
637
|
"log",
|
|
638
638
|
"once_cell",
|
|
@@ -663,9 +663,9 @@ dependencies = [
|
|
|
663
663
|
|
|
664
664
|
[[package]]
|
|
665
665
|
name = "rustls-webpki"
|
|
666
|
-
version = "0.103.
|
|
666
|
+
version = "0.103.14"
|
|
667
667
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
668
|
-
checksum = "
|
|
668
|
+
checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
|
|
669
669
|
dependencies = [
|
|
670
670
|
"ring",
|
|
671
671
|
"rustls-pki-types",
|
|
@@ -750,6 +750,17 @@ dependencies = [
|
|
|
750
750
|
"unicode-ident",
|
|
751
751
|
]
|
|
752
752
|
|
|
753
|
+
[[package]]
|
|
754
|
+
name = "syn"
|
|
755
|
+
version = "3.0.3"
|
|
756
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
757
|
+
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
|
758
|
+
dependencies = [
|
|
759
|
+
"proc-macro2",
|
|
760
|
+
"quote",
|
|
761
|
+
"unicode-ident",
|
|
762
|
+
]
|
|
763
|
+
|
|
753
764
|
[[package]]
|
|
754
765
|
name = "tokio"
|
|
755
766
|
version = "1.53.1"
|
|
@@ -767,13 +778,13 @@ dependencies = [
|
|
|
767
778
|
|
|
768
779
|
[[package]]
|
|
769
780
|
name = "tokio-macros"
|
|
770
|
-
version = "2.7.
|
|
781
|
+
version = "2.7.2"
|
|
771
782
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
772
|
-
checksum = "
|
|
783
|
+
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
|
773
784
|
dependencies = [
|
|
774
785
|
"proc-macro2",
|
|
775
786
|
"quote",
|
|
776
|
-
"syn",
|
|
787
|
+
"syn 3.0.3",
|
|
777
788
|
]
|
|
778
789
|
|
|
779
790
|
[[package]]
|
|
@@ -821,9 +832,9 @@ dependencies = [
|
|
|
821
832
|
|
|
822
833
|
[[package]]
|
|
823
834
|
name = "wasm-bindgen"
|
|
824
|
-
version = "0.2.
|
|
835
|
+
version = "0.2.127"
|
|
825
836
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
826
|
-
checksum = "
|
|
837
|
+
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
|
827
838
|
dependencies = [
|
|
828
839
|
"cfg-if",
|
|
829
840
|
"once_cell",
|
|
@@ -834,9 +845,9 @@ dependencies = [
|
|
|
834
845
|
|
|
835
846
|
[[package]]
|
|
836
847
|
name = "wasm-bindgen-macro"
|
|
837
|
-
version = "0.2.
|
|
848
|
+
version = "0.2.127"
|
|
838
849
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
839
|
-
checksum = "
|
|
850
|
+
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
|
840
851
|
dependencies = [
|
|
841
852
|
"quote",
|
|
842
853
|
"wasm-bindgen-macro-support",
|
|
@@ -844,22 +855,22 @@ dependencies = [
|
|
|
844
855
|
|
|
845
856
|
[[package]]
|
|
846
857
|
name = "wasm-bindgen-macro-support"
|
|
847
|
-
version = "0.2.
|
|
858
|
+
version = "0.2.127"
|
|
848
859
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
849
|
-
checksum = "
|
|
860
|
+
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
|
850
861
|
dependencies = [
|
|
851
862
|
"bumpalo",
|
|
852
863
|
"proc-macro2",
|
|
853
864
|
"quote",
|
|
854
|
-
"syn",
|
|
865
|
+
"syn 2.0.119",
|
|
855
866
|
"wasm-bindgen-shared",
|
|
856
867
|
]
|
|
857
868
|
|
|
858
869
|
[[package]]
|
|
859
870
|
name = "wasm-bindgen-shared"
|
|
860
|
-
version = "0.2.
|
|
871
|
+
version = "0.2.127"
|
|
861
872
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
862
|
-
checksum = "
|
|
873
|
+
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
|
863
874
|
dependencies = [
|
|
864
875
|
"unicode-ident",
|
|
865
876
|
]
|
|
@@ -960,22 +971,22 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
|
|
960
971
|
|
|
961
972
|
[[package]]
|
|
962
973
|
name = "zerocopy"
|
|
963
|
-
version = "0.8.
|
|
974
|
+
version = "0.8.56"
|
|
964
975
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
965
|
-
checksum = "
|
|
976
|
+
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
|
966
977
|
dependencies = [
|
|
967
978
|
"zerocopy-derive",
|
|
968
979
|
]
|
|
969
980
|
|
|
970
981
|
[[package]]
|
|
971
982
|
name = "zerocopy-derive"
|
|
972
|
-
version = "0.8.
|
|
983
|
+
version = "0.8.56"
|
|
973
984
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
974
|
-
checksum = "
|
|
985
|
+
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
|
975
986
|
dependencies = [
|
|
976
987
|
"proc-macro2",
|
|
977
988
|
"quote",
|
|
978
|
-
"syn",
|
|
989
|
+
"syn 2.0.119",
|
|
979
990
|
]
|
|
980
991
|
|
|
981
992
|
[[package]]
|
data/README.md
CHANGED
|
@@ -230,6 +230,8 @@ server = Kino::Server.new(app,
|
|
|
230
230
|
max_body_size: 50 * 1024 * 1024, # bytes before a 413; nil = let a proxy handle it
|
|
231
231
|
on_error: ->(e, env) { ErrorTracker.capture(e) }, # after the client got its 500
|
|
232
232
|
shutdown_timeout: 30, # drain deadline
|
|
233
|
+
control_bind: "127.0.0.1:9293", # monitoring: /stats /metrics /ready /live; port 0 reads back via server.control_port
|
|
234
|
+
control_token: ENV["KINO_CONTROL_TOKEN"], # optional Bearer auth for /stats + /metrics
|
|
233
235
|
tls: { cert: "cert.pem", key: "key.pem" }, # file paths or inline PEM
|
|
234
236
|
)
|
|
235
237
|
server.start
|
|
@@ -320,6 +322,44 @@ after the client got its 500—the only place a tracker sees errors
|
|
|
320
322
|
raised while the response was being written (in `:ractor` mode, build
|
|
321
323
|
the handler with `Ractor.shareable_proc`).
|
|
322
324
|
|
|
325
|
+
## Lifecycle hooks
|
|
326
|
+
|
|
327
|
+
Kino fires four lifecycle hooks alongside `on_error`, split by firing context.
|
|
328
|
+
|
|
329
|
+
**Worker-context hooks** run inside the worker and are available to all workers:
|
|
330
|
+
- `after_worker_boot { |worker_id| }`: runs once before the worker begins serving, with its slot id. In `:ractor` mode it runs inside the worker ractor and must be `Ractor.shareable_proc`.
|
|
331
|
+
- `after_request_complete { |env, status| }`: fires inside the worker after each successful response. This is the hot path—leave it unset for zero cost. In `:ractor` mode it must be `Ractor.shareable_proc`.
|
|
332
|
+
|
|
333
|
+
**Main-context hooks** run on the main thread, outside workers, and are plain procs:
|
|
334
|
+
- `after_boot { }`: fires once after the worker pool is up. Wire readiness here—sd_notify, a "server ready" metric, and so on.
|
|
335
|
+
- `on_worker_exit { |worker_index, error| }`: fires when a worker exits, with its index and the crash cause (or nil on a clean exit).
|
|
336
|
+
|
|
337
|
+
`after_worker_boot`'s argument is the worker's slot id, while in `:ractor` mode `on_worker_exit`'s argument identifies the exited ractor (`0`..`workers - 1`)—a different number space—so don't correlate boot and exit by that number in `:ractor` mode.
|
|
338
|
+
|
|
339
|
+
A raising hook is logged and never kills a worker.
|
|
340
|
+
|
|
341
|
+
## Stuck-worker quarantine
|
|
342
|
+
|
|
343
|
+
`quarantine_timeout: seconds` (or `quarantine_timeout 60` in `kino.rb`)
|
|
344
|
+
quarantines a dispatch slot whose request has run longer than the deadline
|
|
345
|
+
and spawns a replacement worker to restore capacity—distinct from
|
|
346
|
+
`request_timeout`, which gives the client a 504 but leaves the slot
|
|
347
|
+
occupied. `quarantine_max` (default: the worker count in `:ractor` mode,
|
|
348
|
+
workers × threads in `:threaded`) caps the total number of replacement
|
|
349
|
+
events over the process lifetime—past it the monitor stops replacing and
|
|
350
|
+
the server runs at reduced capacity.
|
|
351
|
+
|
|
352
|
+
The wedged worker is never interrupted or force-killed, and its slot stays
|
|
353
|
+
quarantined for good. In `:threaded` mode, if the blocked thread
|
|
354
|
+
eventually returns, it keeps serving requests on that same slot—but the
|
|
355
|
+
slot itself stays flagged quarantined (busy_ms reported as 0) for the rest
|
|
356
|
+
of the process; in `:ractor` mode the wedged ractor (and its supervisor
|
|
357
|
+
thread) leaks until the process exits, since a wedged ractor cannot be
|
|
358
|
+
safely interrupted. Monitor quarantine activity via `server.stats`
|
|
359
|
+
(top-level `quarantined` count and per-slot `worker_status[].quarantined`
|
|
360
|
+
flag), `GET /stats` (same), and `GET /metrics` (`kino_quarantined_workers`
|
|
361
|
+
gauge and `kino_quarantine_replacements_total` counter).
|
|
362
|
+
|
|
323
363
|
## Stats
|
|
324
364
|
|
|
325
365
|
`server.stats` returns a live snapshot: the configuration plus counters
|
|
@@ -330,7 +370,7 @@ cost):
|
|
|
330
370
|
server.stats
|
|
331
371
|
# => {mode: :ractor, lanes: false, workers: 8, threads: 1, batch: 1,
|
|
332
372
|
# respawns: 0, queued: 0, in_flight: 2, served: 1041, rejected: 0,
|
|
333
|
-
# timeouts: 0}
|
|
373
|
+
# timeouts: 0, worker_status: [...]}
|
|
334
374
|
# plus lane_depths: [...] when lane dispatch is on
|
|
335
375
|
```
|
|
336
376
|
|
|
@@ -341,6 +381,39 @@ From the outside, `kill -USR1 <pid>` prints the same snapshot as one line
|
|
|
341
381
|
Kino stats: mode=:ractor lanes=false workers=8 threads=1 batch=1 respawns=0 queued=0 in_flight=2 served=1041 rejected=0 timeouts=0
|
|
342
382
|
```
|
|
343
383
|
|
|
384
|
+
For pull-based monitoring, `control_bind "127.0.0.1:9293"` (or a
|
|
385
|
+
`unix://` path) serves a read-only **control plane** from the native
|
|
386
|
+
layer on its own thread—it keeps answering even while every Ruby worker
|
|
387
|
+
is busy or stuck, and reports `draining` through a graceful shutdown:
|
|
388
|
+
|
|
389
|
+
- `GET /stats`—the same snapshot as `server.stats`, as JSON (plus
|
|
390
|
+
`state` and `version`).
|
|
391
|
+
- `GET /metrics`—Prometheus text format (`kino_requests_served_total`,
|
|
392
|
+
`kino_queue_depth`, `kino_ready`, …).
|
|
393
|
+
|
|
394
|
+
Both `/stats` and `/metrics` also break the counters down per dispatch
|
|
395
|
+
slot: `/stats` carries a `worker_status` array (`index`, `served`,
|
|
396
|
+
`in_flight`, `busy_ms`) and `/metrics` emits `kino_worker_*{worker="N"}`
|
|
397
|
+
series, one entry per execution slot (`workers × threads`)—a crashed
|
|
398
|
+
worker's slot is never reused, so it stays in the list with its counters
|
|
399
|
+
frozen where they stopped, meaning the array (and its `worker="N"` metric
|
|
400
|
+
series) grows by one across every respawn. `busy_ms` is how long the
|
|
401
|
+
slot's current request has been running (0 when idle), so a single slot
|
|
402
|
+
climbing while the rest sit at 0 is your stuck worker.
|
|
403
|
+
|
|
404
|
+
The `/stats` response and `server.stats` carry `queue_time` (count and
|
|
405
|
+
summed seconds), and `/metrics` exposes `kino_request_queue_seconds`—a
|
|
406
|
+
Prometheus histogram of queue-wait time, the worker-saturation signal.
|
|
407
|
+
Counts admitted requests only; a 503 after queue wait goes to `rejected`,
|
|
408
|
+
not `queue_time`.
|
|
409
|
+
|
|
410
|
+
- `GET /ready`—`200` when serving, `503` while booting or draining:
|
|
411
|
+
wire it to your load balancer or Kubernetes readiness probe.
|
|
412
|
+
- `GET /live`—`200` whenever the process is alive: the liveness probe.
|
|
413
|
+
|
|
414
|
+
`control_token "..."` puts `/stats` and `/metrics` behind
|
|
415
|
+
`Authorization: Bearer`; the probes stay open.
|
|
416
|
+
|
|
344
417
|
## Logging
|
|
345
418
|
|
|
346
419
|
With one log line per request, `Kino::Logger` sustained **2.4× the
|
|
@@ -442,7 +515,7 @@ For the Rust network stack, thanks to [Sean McArthur](https://github.com/seanmon
|
|
|
442
515
|
|
|
443
516
|
## Assisted by
|
|
444
517
|
|
|
445
|
-
Claude Code (
|
|
518
|
+
Claude Code (Fable 5, Opus 4.8).
|
|
446
519
|
|
|
447
520
|
## Contributing
|
|
448
521
|
|