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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec37a5aaacb048ffa4533a89545295c9816a6fa7e8a968e6ffa2e154ab753926
4
- data.tar.gz: 1405b92f68fc9dc61a82a099be2471027d341e649d2c874158fe6f03e4c9a471
3
+ metadata.gz: 129f2e9c0c20ba89c1f2f1e854d32f2b746858546b0c6fdb1a88a3fcb14e05cf
4
+ data.tar.gz: 66b465f2e5989e621487752d0b4dac48a9da0857b5290effaac9af803bd315e2
5
5
  SHA512:
6
- metadata.gz: 2ded253aa471a55909aeacde1646650abdd29e240b7d5d304ce494c17c223d2869a3872210f7303ca30a307b4a8421c2772487156257a3b10e2d5e9bbda60ece
7
- data.tar.gz: fc589bb25c4274d21c708dead07a3e8324eb99975f46d07b9fa721140c8483347fe5f84c082a59c936ff95aa3699d714b341f82f6911041c484a59454d7f8d82
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.4"
20
+ version = "1.1.5"
21
21
  source = "registry+https://github.com/rust-lang/crates.io-index"
22
- checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
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.0"
77
+ version = "1.4.2"
78
78
  source = "registry+https://github.com/rust-lang/crates.io-index"
79
- checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
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.8.1"
102
+ version = "1.9.1"
103
103
  source = "registry+https://github.com/rust-lang/crates.io-index"
104
- checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
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.9"
134
+ version = "0.1.10"
135
135
  source = "registry+https://github.com/rust-lang/crates.io-index"
136
- checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
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.33"
158
+ version = "0.3.34"
159
159
  source = "registry+https://github.com/rust-lang/crates.io-index"
160
- checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
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.33"
167
+ version = "0.3.34"
168
168
  source = "registry+https://github.com/rust-lang/crates.io-index"
169
- checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
169
+ checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
170
170
 
171
171
  [[package]]
172
172
  name = "futures-sink"
173
- version = "0.3.33"
173
+ version = "0.3.34"
174
174
  source = "registry+https://github.com/rust-lang/crates.io-index"
175
- checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
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.4.2"
232
+ version = "1.5.0"
233
233
  source = "registry+https://github.com/rust-lang/crates.io-index"
234
- checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
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.4"
252
+ version = "0.1.5"
253
253
  source = "registry+https://github.com/rust-lang/crates.io-index"
254
- checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
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.103"
326
+ version = "0.3.104"
327
327
  source = "registry+https://github.com/rust-lang/crates.io-index"
328
- checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
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.2.1"
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.1"
406
+ version = "0.18.2"
407
407
  source = "registry+https://github.com/rust-lang/crates.io-index"
408
- checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
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.16"
596
+ version = "0.4.18"
597
597
  source = "registry+https://github.com/rust-lang/crates.io-index"
598
- checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
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.42"
633
+ version = "0.23.43"
634
634
  source = "registry+https://github.com/rust-lang/crates.io-index"
635
- checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
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.13"
666
+ version = "0.103.14"
667
667
  source = "registry+https://github.com/rust-lang/crates.io-index"
668
- checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
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.1"
781
+ version = "2.7.2"
771
782
  source = "registry+https://github.com/rust-lang/crates.io-index"
772
- checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
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.126"
835
+ version = "0.2.127"
825
836
  source = "registry+https://github.com/rust-lang/crates.io-index"
826
- checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
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.126"
848
+ version = "0.2.127"
838
849
  source = "registry+https://github.com/rust-lang/crates.io-index"
839
- checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
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.126"
858
+ version = "0.2.127"
848
859
  source = "registry+https://github.com/rust-lang/crates.io-index"
849
- checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
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.126"
871
+ version = "0.2.127"
861
872
  source = "registry+https://github.com/rust-lang/crates.io-index"
862
- checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
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.55"
974
+ version = "0.8.56"
964
975
  source = "registry+https://github.com/rust-lang/crates.io-index"
965
- checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
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.55"
983
+ version = "0.8.56"
973
984
  source = "registry+https://github.com/rust-lang/crates.io-index"
974
- checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
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 (Mythos, Opus).
518
+ Claude Code (Fable 5, Opus 4.8).
446
519
 
447
520
  ## Contributing
448
521
 
data/ext/kino/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "kino"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  authors = ["Yaroslav Markin <yaroslav@markin.net>"]
6
6
  license = "MIT"