kino 0.3.0 → 0.5.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: 129f2e9c0c20ba89c1f2f1e854d32f2b746858546b0c6fdb1a88a3fcb14e05cf
4
- data.tar.gz: 66b465f2e5989e621487752d0b4dac48a9da0857b5290effaac9af803bd315e2
3
+ metadata.gz: 19a396c4f03ec64ac1f598ca8563c69122a0db0a543372c629a56e3ed88d8d06
4
+ data.tar.gz: 57c185c402a0f89d0b177c0c017d377abc5fbe8effff999401ae3196426bd6c2
5
5
  SHA512:
6
- metadata.gz: ac6656c0cd168c5c43dd5d1585b639b93f2489b5e017cbdd297c383c19d083c391cd7ac60034dbcd825bc525b42d7a966f884aa9d03eb0185e7bf8b5539a7781
7
- data.tar.gz: 4392ca122bb8d865eec08393a6f339343bb2f5fac209b82e845ec6319d04f71a7a309a70d5d6518a70d70fc701c50dbd482646250b4d46f3e0d313e497bf4e3d
6
+ metadata.gz: c208de3731186a58be3c4f0ca4f6795fae98405133f0d954ff105a3353892769842cc10bc11fe72dabcaa33c275218e2154eac839cef2fdf729d7959e886429f
7
+ data.tar.gz: 549bd8c04aa00b3e2c799d717a8d5bbeb702530a81ea688948b04535b848f1eb03c8e0f52fc1dca362189a00b01269c06ff1d5993e6f2de8e6d0f1e322132f4b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,58 @@
1
+ ## [0.5.0] - 2026-08-29
2
+
3
+ - Add opt-in `io_shards true`: accepted HTTP connections can run on
4
+ current-thread Tokio I/O shards instead of Tokio's shared multi-thread
5
+ runtime, reducing scheduler contention on very fast handlers. `io_threads`
6
+ sets the shard count; otherwise Kino uses half the available CPUs
7
+ ([Patrik Wenger](https://github.com/paddor)).
8
+ - Update Rust dependencies: hyper 1.11.1, rb-sys 0.9.130,
9
+ rustls-webpki 0.103.15, among others.
10
+
11
+ ## [0.4.0] - 2026-08-22
12
+
13
+ - Rack handler: `rails server -u kino` and `rackup -s kino` boot Kino
14
+ through `Rackup::Handler::Kino`, reading the same config file as the
15
+ `kino` CLI with the host's flags on top; `rackup -s kino --help` lists
16
+ the `-O` options (Workers, Threads, Mode, Config).
17
+ - The config file is also looked up at `config/kino.rb` (the Rails
18
+ layout) when there is no `kino.rb`, by the CLI and the handler alike.
19
+ - `Kino::Server#run` serves an already built server the way
20
+ `Kino::Server.run` does (banner, signal traps, block until shutdown),
21
+ and syncs stdout there so the banner is never held back by block
22
+ buffering under a pipe, whichever entry point booted the server.
23
+ - `workers` now defaults to `Kino.available_parallelism`, the CPUs the
24
+ process may actually use: the affinity mask and, in a container, the
25
+ cgroup CPU quota (a pod limited to 2 CPUs on a 64-core node gets 2
26
+ workers, not 64). `Etc.nprocessors` only ever saw the mask.
27
+ - `bind "unix:///path/to.sock"` listens on a unix domain socket, the
28
+ usual shape behind nginx: a stale socket file is reclaimed, a live one
29
+ is refused, and the file is removed on shutdown. `port` is unused on
30
+ it and TLS is rejected (terminate TLS at the proxy). Requests arriving
31
+ over the socket report `REMOTE_ADDR` 127.0.0.1.
32
+ - `Kino::Server#url`, `#control_url`, and `#unix?` report where a started
33
+ server and its control plane listen.
34
+ - The access log is two records per request: an arrival line queued
35
+ before the app runs (a hang shows as an arrow with no answer) and a
36
+ status-tinted completion line with a timing breakdown of `ruby`,
37
+ `kino`, and `wait`, the `ruby` part carrying the GC pause and objects
38
+ allocated where one request at a time can own the VM's counters
39
+ (`:threaded`, or `:ractor` with `workers 1`). Local timestamps with
40
+ their UTC offset; a blank line between requests. The former one-line
41
+ format is gone.
42
+ - A failed request is reported as `500 GET /path · Class: message (site)`
43
+ followed by its backtrace relative to the working directory, the app's
44
+ own frames first, the rest folded into `… N more`.
45
+ - Every line Kino logs about itself (draining, a crash and its respawn,
46
+ hook failures, quarantine, the USR1 stats line, `rack.errors`) reads
47
+ `kino[<pid>] <source>: message`, the source naming the worker that
48
+ spoke (`worker-3`, `worker-3/thread-2`) or `main`; worker ractors and
49
+ threads now carry those names. The label is dim, yellow, or red by
50
+ level on color terminals. `Kino::Log.info`, `.warn`, and `.error` are
51
+ public, for hooks.
52
+ - The startup banner lists the Ruby build with its JIT and parser flags,
53
+ the environment, the topology, the pid, and the control-plane address
54
+ when one is bound.
55
+
1
56
  ## [0.3.0] - 2026-08-13
2
57
 
3
58
  - Queue-time histogram: `/metrics` exposes `kino_request_queue_seconds`, a
data/Cargo.lock CHANGED
@@ -42,7 +42,7 @@ version = "0.72.1"
42
42
  source = "registry+https://github.com/rust-lang/crates.io-index"
43
43
  checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
44
44
  dependencies = [
45
- "bitflags",
45
+ "bitflags 2.13.1",
46
46
  "cexpr",
47
47
  "clang-sys",
48
48
  "itertools",
@@ -54,6 +54,12 @@ dependencies = [
54
54
  "syn 2.0.119",
55
55
  ]
56
56
 
57
+ [[package]]
58
+ name = "bitflags"
59
+ version = "1.3.2"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
62
+
57
63
  [[package]]
58
64
  name = "bitflags"
59
65
  version = "2.13.1"
@@ -74,9 +80,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
74
80
 
75
81
  [[package]]
76
82
  name = "cc"
77
- version = "1.4.2"
83
+ version = "1.4.4"
78
84
  source = "registry+https://github.com/rust-lang/crates.io-index"
79
- checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
85
+ checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
80
86
  dependencies = [
81
87
  "find-msvc-tools",
82
88
  "shlex 2.0.1",
@@ -108,11 +114,42 @@ dependencies = [
108
114
  "libloading",
109
115
  ]
110
116
 
117
+ [[package]]
118
+ name = "defmt"
119
+ version = "1.1.1"
120
+ source = "registry+https://github.com/rust-lang/crates.io-index"
121
+ checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
122
+ dependencies = [
123
+ "bitflags 1.3.2",
124
+ "defmt-macros",
125
+ ]
126
+
127
+ [[package]]
128
+ name = "defmt-macros"
129
+ version = "1.1.1"
130
+ source = "registry+https://github.com/rust-lang/crates.io-index"
131
+ checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
132
+ dependencies = [
133
+ "defmt-parser",
134
+ "proc-macro2",
135
+ "quote",
136
+ "syn 2.0.119",
137
+ ]
138
+
139
+ [[package]]
140
+ name = "defmt-parser"
141
+ version = "1.0.0"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
144
+ dependencies = [
145
+ "thiserror",
146
+ ]
147
+
111
148
  [[package]]
112
149
  name = "either"
113
- version = "1.17.0"
150
+ version = "1.18.0"
114
151
  source = "registry+https://github.com/rust-lang/crates.io-index"
115
- checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
152
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
116
153
 
117
154
  [[package]]
118
155
  name = "equivalent"
@@ -131,9 +168,9 @@ dependencies = [
131
168
 
132
169
  [[package]]
133
170
  name = "find-msvc-tools"
134
- version = "0.1.10"
171
+ version = "0.1.11"
135
172
  source = "registry+https://github.com/rust-lang/crates.io-index"
136
- checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
173
+ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
137
174
 
138
175
  [[package]]
139
176
  name = "flume"
@@ -274,9 +311,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
274
311
 
275
312
  [[package]]
276
313
  name = "hyper"
277
- version = "1.11.0"
314
+ version = "1.11.1"
278
315
  source = "registry+https://github.com/rust-lang/crates.io-index"
279
- checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
316
+ checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
280
317
  dependencies = [
281
318
  "atomic-waker",
282
319
  "bytes",
@@ -321,6 +358,43 @@ version = "1.0.18"
321
358
  source = "registry+https://github.com/rust-lang/crates.io-index"
322
359
  checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
323
360
 
361
+ [[package]]
362
+ name = "jiff"
363
+ version = "0.2.35"
364
+ source = "registry+https://github.com/rust-lang/crates.io-index"
365
+ checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
366
+ dependencies = [
367
+ "defmt",
368
+ "jiff-core",
369
+ "jiff-static",
370
+ "log",
371
+ "portable-atomic",
372
+ "portable-atomic-util",
373
+ "serde_core",
374
+ "windows-link",
375
+ ]
376
+
377
+ [[package]]
378
+ name = "jiff-core"
379
+ version = "0.1.0"
380
+ source = "registry+https://github.com/rust-lang/crates.io-index"
381
+ checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
382
+ dependencies = [
383
+ "defmt",
384
+ ]
385
+
386
+ [[package]]
387
+ name = "jiff-static"
388
+ version = "0.2.35"
389
+ source = "registry+https://github.com/rust-lang/crates.io-index"
390
+ checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
391
+ dependencies = [
392
+ "jiff-core",
393
+ "proc-macro2",
394
+ "quote",
395
+ "syn 2.0.119",
396
+ ]
397
+
324
398
  [[package]]
325
399
  name = "js-sys"
326
400
  version = "0.3.104"
@@ -333,16 +407,16 @@ dependencies = [
333
407
 
334
408
  [[package]]
335
409
  name = "kino"
336
- version = "0.3.0"
410
+ version = "0.5.0"
337
411
  dependencies = [
338
412
  "ahash",
339
413
  "bytes",
340
414
  "flume",
341
415
  "http",
342
416
  "http-body-util",
343
- "httpdate",
344
417
  "hyper",
345
418
  "hyper-util",
419
+ "jiff",
346
420
  "lru",
347
421
  "magnus",
348
422
  "mimalloc",
@@ -397,15 +471,15 @@ dependencies = [
397
471
 
398
472
  [[package]]
399
473
  name = "log"
400
- version = "0.4.33"
474
+ version = "0.4.34"
401
475
  source = "registry+https://github.com/rust-lang/crates.io-index"
402
- checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
476
+ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
403
477
 
404
478
  [[package]]
405
479
  name = "lru"
406
- version = "0.18.2"
480
+ version = "0.18.3"
407
481
  source = "registry+https://github.com/rust-lang/crates.io-index"
408
- checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
482
+ checksum = "0d317b4b9eb398e6acce275758ec6125535505e7a146fb1a9b8bda2451b0ff4c"
409
483
  dependencies = [
410
484
  "hashbrown",
411
485
  ]
@@ -510,6 +584,21 @@ version = "0.2.17"
510
584
  source = "registry+https://github.com/rust-lang/crates.io-index"
511
585
  checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
512
586
 
587
+ [[package]]
588
+ name = "portable-atomic"
589
+ version = "1.15.0"
590
+ source = "registry+https://github.com/rust-lang/crates.io-index"
591
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
592
+
593
+ [[package]]
594
+ name = "portable-atomic-util"
595
+ version = "0.2.7"
596
+ source = "registry+https://github.com/rust-lang/crates.io-index"
597
+ checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
598
+ dependencies = [
599
+ "portable-atomic",
600
+ ]
601
+
513
602
  [[package]]
514
603
  name = "proc-macro2"
515
604
  version = "1.0.107"
@@ -542,18 +631,18 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
542
631
 
543
632
  [[package]]
544
633
  name = "rb-sys"
545
- version = "0.9.128"
634
+ version = "0.9.130"
546
635
  source = "registry+https://github.com/rust-lang/crates.io-index"
547
- checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
636
+ checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
548
637
  dependencies = [
549
638
  "rb-sys-build",
550
639
  ]
551
640
 
552
641
  [[package]]
553
642
  name = "rb-sys-build"
554
- version = "0.9.128"
643
+ version = "0.9.130"
555
644
  source = "registry+https://github.com/rust-lang/crates.io-index"
556
- checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
645
+ checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
557
646
  dependencies = [
558
647
  "bindgen",
559
648
  "lazy_static",
@@ -576,7 +665,7 @@ version = "0.5.18"
576
665
  source = "registry+https://github.com/rust-lang/crates.io-index"
577
666
  checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
578
667
  dependencies = [
579
- "bitflags",
668
+ "bitflags 2.13.1",
580
669
  ]
581
670
 
582
671
  [[package]]
@@ -663,9 +752,9 @@ dependencies = [
663
752
 
664
753
  [[package]]
665
754
  name = "rustls-webpki"
666
- version = "0.103.14"
755
+ version = "0.103.15"
667
756
  source = "registry+https://github.com/rust-lang/crates.io-index"
668
- checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
757
+ checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
669
758
  dependencies = [
670
759
  "ring",
671
760
  "rustls-pki-types",
@@ -690,6 +779,26 @@ version = "0.3.6"
690
779
  source = "registry+https://github.com/rust-lang/crates.io-index"
691
780
  checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
692
781
 
782
+ [[package]]
783
+ name = "serde_core"
784
+ version = "1.0.229"
785
+ source = "registry+https://github.com/rust-lang/crates.io-index"
786
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
787
+ dependencies = [
788
+ "serde_derive",
789
+ ]
790
+
791
+ [[package]]
792
+ name = "serde_derive"
793
+ version = "1.0.229"
794
+ source = "registry+https://github.com/rust-lang/crates.io-index"
795
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
796
+ dependencies = [
797
+ "proc-macro2",
798
+ "quote",
799
+ "syn 3.0.4",
800
+ ]
801
+
693
802
  [[package]]
694
803
  name = "shell-words"
695
804
  version = "1.1.1"
@@ -752,15 +861,35 @@ dependencies = [
752
861
 
753
862
  [[package]]
754
863
  name = "syn"
755
- version = "3.0.3"
864
+ version = "3.0.4"
756
865
  source = "registry+https://github.com/rust-lang/crates.io-index"
757
- checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
866
+ checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
758
867
  dependencies = [
759
868
  "proc-macro2",
760
869
  "quote",
761
870
  "unicode-ident",
762
871
  ]
763
872
 
873
+ [[package]]
874
+ name = "thiserror"
875
+ version = "2.0.20"
876
+ source = "registry+https://github.com/rust-lang/crates.io-index"
877
+ checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
878
+ dependencies = [
879
+ "thiserror-impl",
880
+ ]
881
+
882
+ [[package]]
883
+ name = "thiserror-impl"
884
+ version = "2.0.20"
885
+ source = "registry+https://github.com/rust-lang/crates.io-index"
886
+ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
887
+ dependencies = [
888
+ "proc-macro2",
889
+ "quote",
890
+ "syn 3.0.4",
891
+ ]
892
+
764
893
  [[package]]
765
894
  name = "tokio"
766
895
  version = "1.53.1"
@@ -784,7 +913,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
784
913
  dependencies = [
785
914
  "proc-macro2",
786
915
  "quote",
787
- "syn 3.0.3",
916
+ "syn 3.0.4",
788
917
  ]
789
918
 
790
919
  [[package]]
data/README.md CHANGED
@@ -196,6 +196,12 @@ bundle exec kino # picks up config.ru + kino.rb, serves on :9292
196
196
  (After a standalone `gem install`, the `kino` command works without
197
197
  `bundle exec`.)
198
198
 
199
+ Prefer your framework's own command? Kino ships a Rack handler, so
200
+ `rails server -u kino` and `rackup -s kino` boot it too. They read the
201
+ same `kino.rb` (or `config/kino.rb`), the host's `-p`/`-b` flags win over
202
+ the file, and `rackup -s kino -O Workers=4 -O Mode=threaded` reaches the
203
+ rest (`rackup -s kino --help` lists them).
204
+
199
205
  No Rust compiler needed: released versions ship precompiled native gems
200
206
  for Linux (x86_64/aarch64, glibc and musl) and macOS (arm64). On other
201
207
  platforms the gem compiles at install time; that needs a Rust toolchain,
@@ -218,9 +224,9 @@ Or embedded, with everything spelled out:
218
224
 
219
225
  ```ruby
220
226
  server = Kino::Server.new(app,
221
- bind: "127.0.0.1",
227
+ bind: "127.0.0.1", # or "unix:///run/kino.sock" behind a proxy
222
228
  port: 9292, # 0 = ephemeral; read back via server.port
223
- workers: Etc.nprocessors, # ractors (parallelism)
229
+ workers: Kino.available_parallelism, # ractors (parallelism); the default
224
230
  threads: 1, # per worker; ractor default 1, threaded default 3
225
231
  mode: :auto, # :auto | :ractor | :threaded
226
232
  queue_depth: 1024, # bounded queue; overflow → 503
@@ -253,10 +259,27 @@ server.shutdown # graceful: drain → deadline → abort straggler
253
259
  always counts as "shareable" (classes are), even if calling it touches
254
260
  unshareable state. Force `:threaded` for those.
255
261
 
262
+ ### Sharded I/O
263
+
264
+ `io_shards true` (off by default) moves HTTP I/O from Tokio's shared
265
+ multi-thread runtime onto current-thread shards: one thread accepts and
266
+ hands each connection to the least-loaded shard, which then owns it for
267
+ its lifetime—no work-stealing, no cross-thread wakeups on the hot path.
268
+ Fast handlers gain double-digit throughput; Ruby-bound endpoints are
269
+ unchanged. Orthogonal to `mode`: it reshapes the Rust side only.
270
+
271
+ ```ruby
272
+ # kino.rb
273
+ io_shards true
274
+ io_threads 4 # optional; default: half the available CPUs
275
+ ```
276
+
256
277
  ## Config file and CLI
257
278
 
258
- Settings can live in a Puma-style Ruby DSL file. Precedence: explicit
259
- kwargs and CLI flags > config file > defaults.
279
+ Settings can live in a Puma-style Ruby DSL file: `kino.rb` in the
280
+ working directory, or `config/kino.rb` (the Rails layout), is picked up
281
+ automatically; `-C PATH` names any other. Precedence: explicit kwargs
282
+ and CLI flags > config file > defaults.
260
283
 
261
284
  ```ruby
262
285
  # kino.rb
@@ -374,11 +397,11 @@ server.stats
374
397
  # plus lane_depths: [...] when lane dispatch is on
375
398
  ```
376
399
 
377
- From the outside, `kill -USR1 <pid>` prints the same snapshot as one line
400
+ From the outside, `kill -USR1 <pid>` logs the same snapshot as one line
378
401
  (pair it with `pidfile` to find the pid):
379
402
 
380
403
  ```
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
404
+ kino[4213] main: stats mode=:ractor lanes=false workers=8 threads=1 batch=1 respawns=0 queued=0 in_flight=2 served=1041 rejected=0 timeouts=0
382
405
  ```
383
406
 
384
407
  For pull-based monitoring, `control_bind "127.0.0.1:9293"` (or a
@@ -422,16 +445,28 @@ box). There are two native pieces. Both write through a lock-free
422
445
  channel to a Rust flusher thread, so request threads never take a log
423
446
  mutex and never make a write syscall:
424
447
 
425
- - **Access log** (`log_requests true`): one line per request to stdout,
426
- including the 503s that never reach your app. Recommended in
427
- development; cheap enough for production. On color terminals the
428
- lines are tinted by status class: 2xx green, 3xx yellow, 4xx maroon,
429
- 5xx bright red:
448
+ - **Access log** (`log_requests true`): two records per request to
449
+ stdout, including the 503s that never reach your app. The arrival line
450
+ is queued before the app runs, so a request that hangs shows as an
451
+ arrow with no answer; the completion line carries the status, the
452
+ total, and a timing breakdown: `ruby` is the time the request spent in
453
+ Ruby (with the GC pause and the objects allocated during it), `kino`
454
+ the server's own overhead, `wait` the queue time before a worker took
455
+ it. Recommended in development; cheap enough for production. On color
456
+ terminals the completion line is tinted by status class: 2xx green,
457
+ 3xx yellow, 4xx maroon, 5xx bright red:
430
458
 
431
459
  ```
432
- 127.0.0.1 [Tue, 10 Jun 2026 13:39:56 GMT] "GET / HTTP/1.1" 200 0.1ms
460
+ 2026-08-22 14:03:11 +0300 GET /users?q=1 from 127.0.0.1
461
+ 2026-08-22 14:03:11 +0300 ← 200 GET /users?q=1 12.4ms (ruby 9.1ms [gc 0.8ms; 1.5k obj]; kino 3.2ms; wait 0.1ms)
433
462
  ```
434
463
 
464
+ The GC and allocation figures come from the VM's process-wide
465
+ counters, so they appear only where one request at a time can own
466
+ them: in `:threaded` mode, or in `:ractor` mode with `workers 1`.
467
+ Parallel ractors would bill each other's work, so there the breakdown
468
+ is `(ruby; kino; wait)` alone.
469
+
435
470
  - **`Kino::Logger`**: a `::Logger` over the same async sink, for your
436
471
  app's own logging (`Kino::Logger.new("log/production.log")`, or no
437
472
  argument for stdout). The raw IO-like device is `Kino::Logger::Device`,
@@ -473,6 +508,27 @@ lines/s), the sink drops lines instead of blocking request threads.
473
508
  These trade-offs are measured in
474
509
  [doc/benchmarks.md](doc/benchmarks.md#logging-costs).
475
510
 
511
+ **Server lines.** Everything Kino says about itself (draining, a crash
512
+ and its respawn, a hook that raised, quarantine, the stats line,
513
+ `rack.errors`) reads `kino[<pid>] <source>: message`, the source being
514
+ the worker that spoke, `worker-3` (or `worker-3/thread-2` in a
515
+ multi-threaded ractor), or `main`. On color terminals the label is dim
516
+ for notes, yellow for warnings, red for errors; the message stays plain.
517
+ A failed request gets a report instead of a bare backtrace: the request
518
+ line, the error, and where it raised in your code, then the trace with
519
+ your frames first, relative to the working directory, and the rest
520
+ folded:
521
+
522
+ ```
523
+ kino[4213] worker-2: 500 GET /boom · RuntimeError: kaboom (app.rb:12:in 'explode')
524
+ app.rb:12:in 'explode'
525
+ /usr/lib/ruby/gems/4.0.0/gems/rack-3.2.7/lib/rack/builder.rb:...
526
+ … 38 more
527
+ ```
528
+
529
+ Hooks can log through the same channel with `Kino::Log.info`, `.warn`,
530
+ and `.error`; it is safe inside worker ractors.
531
+
476
532
  ## Timer waits
477
533
 
478
534
  `Kino.sleep(seconds)` is a high-resolution sleep on the OS clock with
@@ -491,8 +547,9 @@ optional in Rack 3.
491
547
 
492
548
  ## Rails
493
549
 
494
- Rails (edge) runs on Kino today in `:threaded` mode; see
495
- `examples/rails-hello`. Ractor-mode Rails is blocked upstream. The exact
550
+ Rails (edge) runs on Kino today in `:threaded` mode (`rails server -u
551
+ kino`, or the `kino` CLI); see `examples/rails-hello`. Ractor-mode Rails
552
+ is blocked upstream. The exact
496
553
  blockers, the `Ruby::Box` findings, and what would unlock it are written
497
554
  up in [doc/rails-on-ractors.md](doc/rails-on-ractors.md). The example
498
555
  ships a probe script that re-tests against whatever Rails you bundle.
data/doc/architecture.md CHANGED
@@ -11,10 +11,13 @@ tokio (Rust threads) Ruby
11
11
  └──────────────────────────┘ └────────────────────────────┘
12
12
  ```
13
13
 
14
- All network I/O lives in Rust on a tokio multi-threaded runtime; hyper
15
- parses HTTP/1.1 and handles keep-alive; rustls terminates TLS. Ruby never
16
- touches a socket. Each request becomes a Rust-side `RequestCtx` pushed to a
17
- bounded flume MPMC queue; Ruby workers pull from it.
14
+ All network I/O lives in Rust on tokio runtimes; hyper parses HTTP/1.1 and
15
+ handles keep-alive; rustls terminates TLS. The default is Tokio's
16
+ multi-thread runtime. With `io_shards true`, one current-thread runtime
17
+ accepts connections and assigns them to current-thread I/O shards, where
18
+ each connection stays for its lifetime. Ruby never touches a socket. Each
19
+ request becomes a Rust-side `RequestCtx` pushed to a bounded flume MPMC
20
+ queue; Ruby workers pull from it.
18
21
 
19
22
  ## Topology
20
23
 
data/exe/kino CHANGED
@@ -9,10 +9,6 @@
9
9
  # process-global bits an executable owns.
10
10
 
11
11
  Warning[:experimental] = false
12
- # Startup output must land immediately even when stdout is a pipe or file
13
- # (process supervisors, `kino > server.log`); block buffering would hold
14
- # the banner back until exit.
15
- $stdout.sync = true
16
12
 
17
13
  # Running from a git checkout (no installed gem, no bundler context):
18
14
  # prefer the checkout's own lib so `require "kino"` resolves.
data/ext/kino/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "kino"
3
- version = "0.3.0"
3
+ version = "0.5.0"
4
4
  edition = "2021"
5
5
  authors = ["Yaroslav Markin <yaroslav@markin.net>"]
6
6
  license = "MIT"
@@ -26,7 +26,9 @@ hyper-util = { version = "0.1", features = ["server", "tokio", "http1"] }
26
26
  http = "1"
27
27
  http-body-util = "0.1"
28
28
  bytes = "1"
29
- httpdate = "1"
29
+ # Local-time stamps for the access log: the system zone only (no bundled
30
+ # tzdb, no serde), which keeps the dependency tree to jiff itself.
31
+ jiff = { version = "0.2", default-features = false, features = ["std", "tz-system", "tzdb-zoneinfo"] }
30
32
  # ring (not the default aws-lc-rs) as the rustls crypto provider: it
31
33
  # cross-compiles cleanly in rb-sys-dock containers, while aws-lc-sys wants
32
34
  # cmake and a per-target C toolchain.