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 +4 -4
- data/CHANGELOG.md +55 -0
- data/Cargo.lock +154 -25
- data/README.md +71 -14
- data/doc/architecture.md +7 -4
- data/exe/kino +0 -4
- data/ext/kino/Cargo.toml +4 -2
- data/ext/kino/src/access_log.rs +245 -0
- data/ext/kino/src/control.rs +279 -89
- data/ext/kino/src/cpus.rs +37 -0
- data/ext/kino/src/env_strings.rs +10 -2
- data/ext/kino/src/io_shards.rs +437 -0
- data/ext/kino/src/lib.rs +11 -1
- data/ext/kino/src/listen.rs +137 -0
- data/ext/kino/src/log.rs +152 -0
- data/ext/kino/src/queue.rs +9 -4
- data/ext/kino/src/registry.rs +102 -10
- data/ext/kino/src/request.rs +48 -2
- data/ext/kino/src/response.rs +1 -4
- data/ext/kino/src/server.rs +207 -95
- data/ext/kino/src/style.rs +42 -39
- data/ext/kino/src/tls.rs +11 -4
- data/lib/kino/cli.rb +20 -9
- data/lib/kino/configuration.rb +30 -5
- data/lib/kino/errors_stream.rb +4 -3
- data/lib/kino/hook_fire.rb +3 -4
- data/lib/kino/log.rb +104 -0
- data/lib/kino/quarantine_monitor.rb +7 -4
- data/lib/kino/ractor_supervisor.rb +8 -3
- data/lib/kino/server.rb +68 -15
- data/lib/kino/templates/kino.rb.tt +19 -7
- data/lib/kino/version.rb +1 -1
- data/lib/kino/worker.rb +12 -13
- data/lib/kino/worker_hooks.rb +3 -1
- data/lib/kino.rb +11 -0
- data/lib/rackup/handler/kino.rb +88 -0
- data/sig/kino.rbs +64 -1
- metadata +22 -1
data/ext/kino/src/log.rs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//! Server log lines: lifecycle notices, crashes and respawns, hook
|
|
2
|
+
//! failures, the failed-request report, and whatever apps write to
|
|
3
|
+
//! rack.errors, all in one shape:
|
|
4
|
+
//!
|
|
5
|
+
//! ```text
|
|
6
|
+
//! kino[4213] worker-3: after_worker_boot hook raised RuntimeError: boom
|
|
7
|
+
//! ```
|
|
8
|
+
//!
|
|
9
|
+
//! The label is syslog's `ident[pid]` tag plus the source that spoke (the
|
|
10
|
+
//! ractor and/or thread name, `main` for neither), styled by level; the
|
|
11
|
+
//! message stays plain. Ruby builds the source, since only Ruby knows its
|
|
12
|
+
//! ractor and thread names, and hands the rest over; this side decides
|
|
13
|
+
//! color per stream and writes, so worker ractors never touch $stdout or
|
|
14
|
+
//! $stderr themselves. A multi-line message is labelled on its first line.
|
|
15
|
+
|
|
16
|
+
use std::io::Write;
|
|
17
|
+
|
|
18
|
+
use magnus::{Error, Ruby};
|
|
19
|
+
|
|
20
|
+
use crate::style::{self, Stream};
|
|
21
|
+
|
|
22
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
23
|
+
pub enum Level {
|
|
24
|
+
Info,
|
|
25
|
+
Warn,
|
|
26
|
+
Error,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl Level {
|
|
30
|
+
/// The level named by Ruby ("info", "warn", "error").
|
|
31
|
+
pub fn parse(name: &str) -> Option<Level> {
|
|
32
|
+
match name {
|
|
33
|
+
"info" => Some(Level::Info),
|
|
34
|
+
"warn" => Some(Level::Warn),
|
|
35
|
+
"error" => Some(Level::Error),
|
|
36
|
+
_ => None,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Notes go to stdout; warnings and errors to stderr.
|
|
41
|
+
fn stream(self) -> Stream {
|
|
42
|
+
match self {
|
|
43
|
+
Level::Info => Stream::Stdout,
|
|
44
|
+
Level::Warn | Level::Error => Stream::Stderr,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn sgr(self) -> &'static str {
|
|
49
|
+
match self {
|
|
50
|
+
Level::Info => style::DIM,
|
|
51
|
+
Level::Warn => style::WARN,
|
|
52
|
+
Level::Error => style::ERROR,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Write one line from `source` at `level`.
|
|
58
|
+
pub fn emit(level: Level, source: &str, message: &str) {
|
|
59
|
+
let stream = level.stream();
|
|
60
|
+
let label = label(std::process::id(), source);
|
|
61
|
+
let line = format_line(level, &label, message, style::enabled(stream));
|
|
62
|
+
match stream {
|
|
63
|
+
Stream::Stdout => {
|
|
64
|
+
let _ = writeln!(std::io::stdout().lock(), "{line}");
|
|
65
|
+
}
|
|
66
|
+
Stream::Stderr => {
|
|
67
|
+
let _ = writeln!(std::io::stderr().lock(), "{line}");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// The `kino[<pid>] <source>:` tag.
|
|
73
|
+
pub fn label(pid: u32, source: &str) -> String {
|
|
74
|
+
format!("kino[{pid}] {source}:")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// The label styled by level, then the message as given.
|
|
78
|
+
pub fn format_line(level: Level, label: &str, message: &str, color: bool) -> String {
|
|
79
|
+
format!("{} {message}", style::sgr(level.sgr(), label, color))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The Ruby entry point (Kino::Log): Ruby knows its ractor and thread,
|
|
83
|
+
/// the native side knows the terminal.
|
|
84
|
+
pub fn log_line(ruby: &Ruby, level: String, source: String, message: String) -> Result<(), Error> {
|
|
85
|
+
let level = Level::parse(&level).ok_or_else(|| {
|
|
86
|
+
Error::new(
|
|
87
|
+
ruby.exception_arg_error(),
|
|
88
|
+
format!("unknown log level {level:?}"),
|
|
89
|
+
)
|
|
90
|
+
})?;
|
|
91
|
+
emit(level, &source, &message);
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[cfg(test)]
|
|
96
|
+
mod tests {
|
|
97
|
+
use super::{format_line, label, Level};
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
fn label_is_a_syslog_tag_plus_the_source() {
|
|
101
|
+
assert_eq!(label(4213, "main"), "kino[4213] main:");
|
|
102
|
+
assert_eq!(
|
|
103
|
+
label(4213, "worker-3/thread-2"),
|
|
104
|
+
"kino[4213] worker-3/thread-2:"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn plain_line_is_label_then_message() {
|
|
110
|
+
assert_eq!(
|
|
111
|
+
format_line(Level::Info, "kino[1] main:", "hello", false),
|
|
112
|
+
"kino[1] main: hello"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn color_styles_only_the_label_by_level() {
|
|
118
|
+
assert_eq!(
|
|
119
|
+
format_line(Level::Info, "kino[1] main:", "hello", true),
|
|
120
|
+
"\x1b[90mkino[1] main:\x1b[0m hello"
|
|
121
|
+
);
|
|
122
|
+
assert_eq!(
|
|
123
|
+
format_line(Level::Warn, "kino[1] main:", "careful", true),
|
|
124
|
+
"\x1b[33mkino[1] main:\x1b[0m careful"
|
|
125
|
+
);
|
|
126
|
+
assert_eq!(
|
|
127
|
+
format_line(Level::Error, "kino[1] main:", "broke", true),
|
|
128
|
+
"\x1b[91mkino[1] main:\x1b[0m broke"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#[test]
|
|
133
|
+
fn a_report_is_labelled_on_its_first_line_only() {
|
|
134
|
+
assert_eq!(
|
|
135
|
+
format_line(
|
|
136
|
+
Level::Error,
|
|
137
|
+
"kino[1] main:",
|
|
138
|
+
"500 GET / · X: y\n a.rb:1",
|
|
139
|
+
false
|
|
140
|
+
),
|
|
141
|
+
"kino[1] main: 500 GET / · X: y\n a.rb:1"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[test]
|
|
146
|
+
fn levels_parse_from_their_ruby_names() {
|
|
147
|
+
assert_eq!(Level::parse("info"), Some(Level::Info));
|
|
148
|
+
assert_eq!(Level::parse("warn"), Some(Level::Warn));
|
|
149
|
+
assert_eq!(Level::parse("error"), Some(Level::Error));
|
|
150
|
+
assert_eq!(Level::parse("debug"), None);
|
|
151
|
+
}
|
|
152
|
+
}
|
data/ext/kino/src/queue.rs
CHANGED
|
@@ -117,11 +117,16 @@ fn admit(
|
|
|
117
117
|
) -> Result<RHash, Error> {
|
|
118
118
|
server.served.fetch_add(1, Ordering::Relaxed);
|
|
119
119
|
slot.served.fetch_add(1, Ordering::Relaxed);
|
|
120
|
-
slot.last_started_ms
|
|
120
|
+
slot.last_started_ms
|
|
121
|
+
.store(crate::mono::mono_ms(), Ordering::Relaxed);
|
|
121
122
|
slot.in_flight.fetch_add(1, Ordering::Relaxed);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
// One clock read serves the histogram and, for the access log, the
|
|
124
|
+
// request's queue wait and the start of its time in Ruby.
|
|
125
|
+
let now = std::time::Instant::now();
|
|
126
|
+
let wait = now.duration_since(ctx.enqueued_at);
|
|
127
|
+
server.queue_histogram.record(wait.as_micros() as u64);
|
|
128
|
+
ctx.wait = wait;
|
|
129
|
+
ctx.admitted_at = now;
|
|
125
130
|
slot.current.lock().push(Arc::downgrade(&ctx.responder));
|
|
126
131
|
// Wire the slot into the request so blocked body reads/writes are
|
|
127
132
|
// interruptible the same way the queue pop is.
|
data/ext/kino/src/registry.rs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
|
7
7
|
use std::sync::{Arc, OnceLock, Weak};
|
|
8
|
+
use std::time::{Duration, Instant};
|
|
8
9
|
|
|
9
10
|
use parking_lot::{Mutex, RwLock};
|
|
10
11
|
|
|
@@ -32,6 +33,51 @@ pub type BoxedCtx = Box<RequestCtx>;
|
|
|
32
33
|
/// Probed on every take; keys are our own ids, so ahash over SipHash.
|
|
33
34
|
type HashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
|
|
34
35
|
|
|
36
|
+
/// What runs the server's I/O, taken out at shutdown. The default
|
|
37
|
+
/// multi-thread runtime and sharded I/O carry different teardown state,
|
|
38
|
+
/// so the shape stays explicit instead of parallel optional fields.
|
|
39
|
+
#[derive(Default)]
|
|
40
|
+
pub enum RuntimeHandle {
|
|
41
|
+
/// Not started yet, or already shut down.
|
|
42
|
+
#[default]
|
|
43
|
+
None,
|
|
44
|
+
MultiThread(tokio::runtime::Runtime),
|
|
45
|
+
/// The shards' final-teardown signal and every I/O thread (shards plus
|
|
46
|
+
/// the acceptor).
|
|
47
|
+
Shards {
|
|
48
|
+
shutdown_tx: tokio::sync::watch::Sender<bool>,
|
|
49
|
+
threads: Vec<std::thread::JoinHandle<()>>,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl RuntimeHandle {
|
|
54
|
+
/// Stop the I/O side, giving in-flight work `timeout` to finish. A
|
|
55
|
+
/// thread that misses the deadline is abandoned rather than joined:
|
|
56
|
+
/// a wedged shard must not hang `Server#shutdown`, which promises to
|
|
57
|
+
/// return by its deadline.
|
|
58
|
+
pub fn shutdown(self, timeout: Duration) {
|
|
59
|
+
match self {
|
|
60
|
+
RuntimeHandle::None => {}
|
|
61
|
+
RuntimeHandle::MultiThread(runtime) => runtime.shutdown_timeout(timeout),
|
|
62
|
+
RuntimeHandle::Shards {
|
|
63
|
+
shutdown_tx,
|
|
64
|
+
threads,
|
|
65
|
+
} => {
|
|
66
|
+
let _ = shutdown_tx.send(true);
|
|
67
|
+
let deadline = Instant::now() + timeout;
|
|
68
|
+
for thread in threads {
|
|
69
|
+
while !thread.is_finished() && Instant::now() < deadline {
|
|
70
|
+
std::thread::sleep(Duration::from_millis(1));
|
|
71
|
+
}
|
|
72
|
+
if thread.is_finished() {
|
|
73
|
+
let _ = thread.join();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
35
81
|
/// One per `Kino::Server`. Owns the tokio runtime, the request queue and the
|
|
36
82
|
/// worker slots.
|
|
37
83
|
pub struct ServerInner {
|
|
@@ -42,9 +88,9 @@ pub struct ServerInner {
|
|
|
42
88
|
pub req_rx: flume::Receiver<BoxedCtx>,
|
|
43
89
|
/// Signals the accept loop to stop. Watch channel: `true` = draining.
|
|
44
90
|
pub shutdown_tx: tokio::sync::watch::Sender<bool>,
|
|
45
|
-
/// Runtime is kept so we can shut it down explicitly;
|
|
46
|
-
///
|
|
47
|
-
pub runtime: Mutex<
|
|
91
|
+
/// Runtime is kept so we can shut it down explicitly; `shutdown_runtime`
|
|
92
|
+
/// takes ownership out of the Arc.
|
|
93
|
+
pub runtime: Mutex<RuntimeHandle>,
|
|
48
94
|
pub slots: RwLock<Vec<Arc<WorkerSlot>>>,
|
|
49
95
|
pub in_flight: AtomicUsize,
|
|
50
96
|
/// Requests handed to Ruby workers (admitted), and requests rejected
|
|
@@ -69,6 +115,8 @@ pub struct ServerInner {
|
|
|
69
115
|
pub quarantine_replacements: AtomicU64,
|
|
70
116
|
pub topology: Topology,
|
|
71
117
|
pub https: bool,
|
|
118
|
+
/// The socket file of a `unix://` bind, removed at shutdown.
|
|
119
|
+
pub unix_path: Option<std::path::PathBuf>,
|
|
72
120
|
/// Native access log sink (None unless log_requests is on).
|
|
73
121
|
pub access_log: Option<crate::logsink::Sink>,
|
|
74
122
|
/// Lane-dispatch mode: per-worker queues, awake-preferring dispatch.
|
|
@@ -116,8 +164,8 @@ pub const LANE_DEPTH: usize = 4;
|
|
|
116
164
|
/// Fixed queue-wait bucket boundaries in microseconds (0.5ms .. 10s),
|
|
117
165
|
/// ascending. Emitted in seconds. Not a knob (YAGNI).
|
|
118
166
|
pub const QUEUE_BOUNDS_US: [u64; 14] = [
|
|
119
|
-
500, 1_000, 2_500, 5_000, 10_000, 25_000, 50_000, 100_000,
|
|
120
|
-
|
|
167
|
+
500, 1_000, 2_500, 5_000, 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000,
|
|
168
|
+
2_500_000, 5_000_000, 10_000_000,
|
|
121
169
|
];
|
|
122
170
|
|
|
123
171
|
/// Queue-wait histogram: per-bucket counts plus an overflow (the implicit
|
|
@@ -287,7 +335,7 @@ pub fn test_server(lanes: bool, queue_depth: usize) -> Arc<ServerInner> {
|
|
|
287
335
|
req_tx: Mutex::new(Some(req_tx)),
|
|
288
336
|
req_rx,
|
|
289
337
|
shutdown_tx,
|
|
290
|
-
runtime: Mutex::new(None),
|
|
338
|
+
runtime: Mutex::new(RuntimeHandle::None),
|
|
291
339
|
slots: RwLock::new(Vec::new()),
|
|
292
340
|
in_flight: AtomicUsize::new(0),
|
|
293
341
|
served: AtomicU64::new(0),
|
|
@@ -299,8 +347,14 @@ pub fn test_server(lanes: bool, queue_depth: usize) -> Arc<ServerInner> {
|
|
|
299
347
|
state: std::sync::atomic::AtomicU8::new(STATE_BOOTING),
|
|
300
348
|
respawns: AtomicU64::new(0),
|
|
301
349
|
quarantine_replacements: AtomicU64::new(0),
|
|
302
|
-
topology: Topology {
|
|
350
|
+
topology: Topology {
|
|
351
|
+
mode: "threaded".to_string(),
|
|
352
|
+
workers: 0,
|
|
353
|
+
threads: 0,
|
|
354
|
+
batch: 1,
|
|
355
|
+
},
|
|
303
356
|
https: false,
|
|
357
|
+
unix_path: None,
|
|
304
358
|
access_log: None,
|
|
305
359
|
lanes,
|
|
306
360
|
lane_cursor: AtomicUsize::new(0),
|
|
@@ -314,6 +368,44 @@ mod tests {
|
|
|
314
368
|
use super::*;
|
|
315
369
|
use crate::request::test_ctx;
|
|
316
370
|
|
|
371
|
+
#[test]
|
|
372
|
+
fn shards_shutdown_joins_threads_that_observe_the_signal() {
|
|
373
|
+
let (shutdown_tx, rx) = tokio::sync::watch::channel(false);
|
|
374
|
+
let thread = std::thread::spawn(move || {
|
|
375
|
+
while !*rx.borrow() {
|
|
376
|
+
std::thread::sleep(Duration::from_millis(1));
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
let start = Instant::now();
|
|
381
|
+
RuntimeHandle::Shards {
|
|
382
|
+
shutdown_tx,
|
|
383
|
+
threads: vec![thread],
|
|
384
|
+
}
|
|
385
|
+
.shutdown(Duration::from_secs(5));
|
|
386
|
+
|
|
387
|
+
// The thread exits on the signal, so the join comes nowhere near
|
|
388
|
+
// the deadline.
|
|
389
|
+
assert!(start.elapsed() < Duration::from_secs(1));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#[test]
|
|
393
|
+
fn shards_shutdown_abandons_a_wedged_thread_at_the_deadline() {
|
|
394
|
+
let (shutdown_tx, _rx) = tokio::sync::watch::channel(false);
|
|
395
|
+
let wedged = std::thread::spawn(|| std::thread::sleep(Duration::from_secs(30)));
|
|
396
|
+
|
|
397
|
+
let start = Instant::now();
|
|
398
|
+
RuntimeHandle::Shards {
|
|
399
|
+
shutdown_tx,
|
|
400
|
+
threads: vec![wedged],
|
|
401
|
+
}
|
|
402
|
+
.shutdown(Duration::from_millis(50));
|
|
403
|
+
|
|
404
|
+
let elapsed = start.elapsed();
|
|
405
|
+
assert!(elapsed >= Duration::from_millis(50));
|
|
406
|
+
assert!(elapsed < Duration::from_secs(5));
|
|
407
|
+
}
|
|
408
|
+
|
|
317
409
|
#[test]
|
|
318
410
|
fn worker_registration_hands_out_sequential_slot_ids() {
|
|
319
411
|
let server = test_server(false, 4);
|
|
@@ -417,9 +509,9 @@ mod tests {
|
|
|
417
509
|
#[test]
|
|
418
510
|
fn queue_histogram_buckets_by_wait() {
|
|
419
511
|
let h = QueueHistogram::new();
|
|
420
|
-
h.record(400);
|
|
421
|
-
h.record(500);
|
|
422
|
-
h.record(600);
|
|
512
|
+
h.record(400); // <= 500 -> bucket 0
|
|
513
|
+
h.record(500); // == 500 -> bucket 0 (inclusive)
|
|
514
|
+
h.record(600); // (500, 1000] -> bucket 1
|
|
423
515
|
h.record(20_000_000); // > last bound -> overflow
|
|
424
516
|
let s = h.snapshot();
|
|
425
517
|
assert_eq!(s.buckets[0], 2);
|
data/ext/kino/src/request.rs
CHANGED
|
@@ -44,6 +44,41 @@ pub struct RequestCtx {
|
|
|
44
44
|
/// When this request entered the queue, for the queue-wait histogram.
|
|
45
45
|
/// Stamped at ctx creation; read once at admit (queue.rs).
|
|
46
46
|
pub enqueued_at: std::time::Instant,
|
|
47
|
+
/// Whether the access log wants timing: decided at intake, read on
|
|
48
|
+
/// the way out, so an idle log costs nothing per request.
|
|
49
|
+
pub timed: bool,
|
|
50
|
+
/// Queue wait, stamped at admit (queue.rs): the log's `wait`.
|
|
51
|
+
pub wait: std::time::Duration,
|
|
52
|
+
/// When a worker took the request; elapsed at the response head it is
|
|
53
|
+
/// the log's `ruby`.
|
|
54
|
+
pub admitted_at: std::time::Instant,
|
|
55
|
+
/// GC pause and objects allocated during the app call, when the
|
|
56
|
+
/// worker measured them (Request#timing).
|
|
57
|
+
pub gc: Option<(std::time::Duration, u64)>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl RequestCtx {
|
|
61
|
+
/// The timing this request carries to the access log.
|
|
62
|
+
fn timing(&self) -> crate::access_log::Timing {
|
|
63
|
+
crate::access_log::Timing {
|
|
64
|
+
wait: self.wait,
|
|
65
|
+
ruby: self.admitted_at.elapsed(),
|
|
66
|
+
gc: self.gc,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Attach the request's timing to a response head when the access log
|
|
72
|
+
/// wants it; the extension is the one allocation an idle log skips.
|
|
73
|
+
fn timed(
|
|
74
|
+
ctx: &RequestCtx,
|
|
75
|
+
builder: hyper::http::response::Builder,
|
|
76
|
+
) -> hyper::http::response::Builder {
|
|
77
|
+
if ctx.timed {
|
|
78
|
+
builder.extension(ctx.timing())
|
|
79
|
+
} else {
|
|
80
|
+
builder
|
|
81
|
+
}
|
|
47
82
|
}
|
|
48
83
|
|
|
49
84
|
impl Drop for RequestCtx {
|
|
@@ -233,7 +268,7 @@ pub fn respond_simple(
|
|
|
233
268
|
body: RString,
|
|
234
269
|
) -> Result<bool, Error> {
|
|
235
270
|
let ctx = request.0.borrow();
|
|
236
|
-
let builder = build_head(status, headers)
|
|
271
|
+
let builder = timed(&ctx, build_head(status, headers)?);
|
|
237
272
|
let bytes = body_bytes(&ctx, body);
|
|
238
273
|
let response = builder
|
|
239
274
|
.body(full_body(bytes))
|
|
@@ -252,6 +287,13 @@ fn body_bytes(ctx: &RequestCtx, body: RString) -> Bytes {
|
|
|
252
287
|
}
|
|
253
288
|
|
|
254
289
|
impl Request {
|
|
290
|
+
/// The worker's measurements around the app call, for the access log's
|
|
291
|
+
/// breakdown: the GC pause in nanoseconds and the objects allocated.
|
|
292
|
+
/// Called only when the access log is on.
|
|
293
|
+
pub fn set_timing(_ruby: &Ruby, rb_self: &Request, gc_nanos: u64, allocs: u64) {
|
|
294
|
+
rb_self.0.borrow_mut().gc = Some((std::time::Duration::from_nanos(gc_nanos), allocs));
|
|
295
|
+
}
|
|
296
|
+
|
|
255
297
|
/// Next chunk of the request body, at most `max_len` bytes; nil at EOF.
|
|
256
298
|
/// Blocks (GVL released) until the client sends more.
|
|
257
299
|
pub fn read_body(
|
|
@@ -306,7 +348,7 @@ impl Request {
|
|
|
306
348
|
headers: RHash,
|
|
307
349
|
) -> Result<bool, Error> {
|
|
308
350
|
let ctx = rb_self.0.borrow();
|
|
309
|
-
let builder = build_head(status, headers)
|
|
351
|
+
let builder = timed(&ctx, build_head(status, headers)?);
|
|
310
352
|
ctx.responder
|
|
311
353
|
.send_stream_head(builder)
|
|
312
354
|
.map_err(|e| invalid_response(ruby, e))
|
|
@@ -429,6 +471,10 @@ pub fn test_ctx() -> crate::registry::BoxedCtx {
|
|
|
429
471
|
pin_slab: Arc::new(crate::pin::PinSlab::new()),
|
|
430
472
|
responder: Arc::new(Responder::new(head_tx)),
|
|
431
473
|
enqueued_at: std::time::Instant::now(),
|
|
474
|
+
timed: false,
|
|
475
|
+
wait: std::time::Duration::ZERO,
|
|
476
|
+
admitted_at: std::time::Instant::now(),
|
|
477
|
+
gc: None,
|
|
432
478
|
})
|
|
433
479
|
}
|
|
434
480
|
|
data/ext/kino/src/response.rs
CHANGED
|
@@ -115,10 +115,7 @@ pub fn plain_response(status: u16, message: &'static str) -> HyperResponse {
|
|
|
115
115
|
mod tests {
|
|
116
116
|
use super::*;
|
|
117
117
|
|
|
118
|
-
fn pair() -> (
|
|
119
|
-
Responder,
|
|
120
|
-
tokio::sync::oneshot::Receiver<HyperResponse>,
|
|
121
|
-
) {
|
|
118
|
+
fn pair() -> (Responder, tokio::sync::oneshot::Receiver<HyperResponse>) {
|
|
122
119
|
let (head_tx, head_rx) = tokio::sync::oneshot::channel();
|
|
123
120
|
(Responder::new(head_tx), head_rx)
|
|
124
121
|
}
|