kino 0.1.3-aarch64-linux → 0.2.0-aarch64-linux
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 +14 -0
- data/README.md +17 -0
- data/lib/kino/kino.so +0 -0
- data/lib/kino/server.rb +87 -12
- data/lib/kino/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '04184f3cc709476d0cf35ceb23fbe9cbdd46f13d56f55f9e5f6a3fd261c9a8c9'
|
|
4
|
+
data.tar.gz: 261bf88fb0bd9ca45633c2141d189591e2c183a4002cd300e124b3a16e74ae8c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52cb0b30513607501b56c01ed343a119ad0fc1277ad17655fa1453b82bfdaa8a345cb52ce4937058a491e67db485560481d4523ca85255d7ff57546e4d1146f5
|
|
7
|
+
data.tar.gz: f1fb8a9e78b4c28c2ed3f4e589e10c40775e9605863148cac6edcd1e3f47dc2ab16b7b1e168f481e7e6ff15fc21ad545c680c732a33e5e4a4ccaadf534b0e114
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [0.2.0] - 2026-07-13
|
|
2
|
+
|
|
3
|
+
- Strip debug info from release builds.
|
|
4
|
+
- A panic in the native layer now raises a RuntimeError on the affected
|
|
5
|
+
worker (visible to `on_error` and the error log) instead of killing the
|
|
6
|
+
server process.
|
|
7
|
+
- The pidfile is claimed exclusively: starting refuses (instead of silently
|
|
8
|
+
overwriting) while the pidfile's owner is alive, a leftover file from a
|
|
9
|
+
dead process is replaced, symlinks are never followed, and shutdown
|
|
10
|
+
removes the file only while it still holds our pid.
|
|
11
|
+
- Zero-copy response bodies: bodies of 4 KB and up ride to the network
|
|
12
|
+
layer by reference instead of being copied at the FFI boundary, in both
|
|
13
|
+
dispatch modes. A 10 KB-body endpoint now serves at plaintext speed.
|
|
14
|
+
|
|
1
15
|
## [0.1.3] - 2026-07-04
|
|
2
16
|
|
|
3
17
|
- Non-String response header names and values (booleans, numbers, symbols)
|
data/README.md
CHANGED
|
@@ -21,6 +21,8 @@ and a threaded fallback mode runs everything else, Rails included.
|
|
|
21
21
|
small process.
|
|
22
22
|
* **Production plumbing included.** Graceful drain, crash supervision
|
|
23
23
|
and respawn, bounded queues with 503 backpressure, request timeouts,
|
|
24
|
+
hardened intake (slowloris and TLS-handshake deadlines, connection
|
|
25
|
+
and body-size caps), an `on_error` hook for your error tracker,
|
|
24
26
|
TLS (rustls), live stats, async access and app logging.
|
|
25
27
|
* **Tells you why.** `kino --check` lists exactly what blocks your app
|
|
26
28
|
from ractor mode, finding by finding, so you do not have to decode
|
|
@@ -224,6 +226,9 @@ server = Kino::Server.new(app,
|
|
|
224
226
|
queue_depth: 1024, # bounded queue; overflow → 503
|
|
225
227
|
queue_timeout: 5.0, # seconds before 503 on a full queue
|
|
226
228
|
request_timeout: nil, # seconds before a slow response becomes a 504 (nil = off)
|
|
229
|
+
max_connections: 8192, # cap concurrent connections; default: most of ulimit -n
|
|
230
|
+
max_body_size: 50 * 1024 * 1024, # bytes before a 413; nil = let a proxy handle it
|
|
231
|
+
on_error: ->(e, env) { ErrorTracker.capture(e) }, # after the client got its 500
|
|
227
232
|
shutdown_timeout: 30, # drain deadline
|
|
228
233
|
tls: { cert: "cert.pem", key: "key.pem" }, # file paths or inline PEM
|
|
229
234
|
)
|
|
@@ -303,6 +308,18 @@ is unsafe. A stuck handler still occupies its worker slot until it
|
|
|
303
308
|
returns, so set the deadline above your slowest legitimate endpoint and
|
|
304
309
|
watch `stats[:timeouts]`.
|
|
305
310
|
|
|
311
|
+
Timeouts guard your app; the network intake guards itself. New
|
|
312
|
+
connections past `max_connections` (default: most of `ulimit -n`) wait
|
|
313
|
+
in the kernel backlog; request bodies past `max_body_size` (default
|
|
314
|
+
50 MB, `nil` delegates to a fronting proxy) get a **413**; and fixed
|
|
315
|
+
deadlines drop slow-header clients (15 s), stalled TLS handshakes
|
|
316
|
+
(10 s), and uploads stalled mid-body (30 s). When a worker catches an
|
|
317
|
+
app or delivery error,
|
|
318
|
+
`on_error ->(error, env) { ErrorTracker.capture(error) }` is called
|
|
319
|
+
after the client got its 500—the only place a tracker sees errors
|
|
320
|
+
raised while the response was being written (in `:ractor` mode, build
|
|
321
|
+
the handler with `Ractor.shareable_proc`).
|
|
322
|
+
|
|
306
323
|
## Stats
|
|
307
324
|
|
|
308
325
|
`server.stats` returns a live snapshot: the configuration plus counters
|
data/lib/kino/kino.so
CHANGED
|
Binary file
|
data/lib/kino/server.rb
CHANGED
|
@@ -73,17 +73,28 @@ module Kino
|
|
|
73
73
|
def start
|
|
74
74
|
raise Error, "server already started" if @started
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
# Claim the pidfile before binding: refusing to start (another
|
|
77
|
+
# instance is alive) must not leave a booted native runtime behind.
|
|
78
|
+
write_pidfile if @pidfile
|
|
79
|
+
booted = false
|
|
80
|
+
begin
|
|
81
|
+
@id, @port = Native.server_start(
|
|
82
|
+
bind: @bind, port: @requested_port,
|
|
83
|
+
queue_depth: @queue_depth, queue_timeout_ms: @queue_timeout_ms,
|
|
84
|
+
request_timeout_ms: @request_timeout_ms,
|
|
85
|
+
max_connections: @max_connections,
|
|
86
|
+
max_body_size: @max_body_size,
|
|
87
|
+
tokio_threads: @tokio_threads,
|
|
88
|
+
tls_cert: @tls&.fetch(:cert), tls_key: @tls&.fetch(:key),
|
|
89
|
+
lanes: @lanes, log_requests: @log_requests
|
|
90
|
+
)
|
|
91
|
+
booted = true
|
|
92
|
+
ensure
|
|
93
|
+
remove_pidfile if @pidfile && !booted
|
|
94
|
+
end
|
|
95
|
+
# GC anchor for zero-copy response buffers: held for the server's
|
|
96
|
+
# lifetime so in-flight buffers survive even a worker ractor crash.
|
|
97
|
+
@pin_keeper = Native.pin_keeper(@id)
|
|
87
98
|
if @mode == :ractor
|
|
88
99
|
@supervisor = RactorSupervisor.new(@id, @app, workers: @workers, threads: @threads,
|
|
89
100
|
batch: @batch, on_error: @on_error).start
|
|
@@ -133,9 +144,12 @@ module Kino
|
|
|
133
144
|
end
|
|
134
145
|
|
|
135
146
|
Native.shutdown_runtime(@id, 1_000)
|
|
147
|
+
# The runtime is gone, so hyper has dropped every pinned buffer;
|
|
148
|
+
# the keeper (and the strings it marked) may now be collected.
|
|
149
|
+
@pin_keeper = nil
|
|
136
150
|
@worker_threads.clear
|
|
137
151
|
@started = false
|
|
138
|
-
|
|
152
|
+
remove_pidfile if @pidfile
|
|
139
153
|
nil
|
|
140
154
|
end
|
|
141
155
|
|
|
@@ -241,6 +255,67 @@ module Kino
|
|
|
241
255
|
[soft * 8 / 10, 64].max
|
|
242
256
|
end
|
|
243
257
|
|
|
258
|
+
# Claim the pidfile for this process. O_EXCL creation fails on ANY
|
|
259
|
+
# existing directory entry (regular file, symlink, even a dangling
|
|
260
|
+
# one), so a live instance's pidfile is never overwritten and a
|
|
261
|
+
# symlink is never followed. A leftover entry whose owner is gone is
|
|
262
|
+
# replaced; one that does not hold a pid is refused, not clobbered.
|
|
263
|
+
def write_pidfile
|
|
264
|
+
claim_pidfile
|
|
265
|
+
rescue Errno::EEXIST
|
|
266
|
+
refuse_unless_stale
|
|
267
|
+
begin
|
|
268
|
+
# Unlink removes the entry itself; a symlink's target is untouched.
|
|
269
|
+
File.unlink(@pidfile)
|
|
270
|
+
rescue Errno::ENOENT
|
|
271
|
+
# Vanished on its own; the claim below settles any remaining race.
|
|
272
|
+
end
|
|
273
|
+
begin
|
|
274
|
+
claim_pidfile
|
|
275
|
+
rescue Errno::EEXIST
|
|
276
|
+
raise Error, "lost the race for #{@pidfile}: another instance is starting"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def claim_pidfile
|
|
281
|
+
File.open(@pidfile, File::WRONLY | File::CREAT | File::EXCL, 0o644) do |file|
|
|
282
|
+
file.write("#{Process.pid}\n")
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @raise [Kino::Error] when the pidfile's owner is still alive, or the
|
|
287
|
+
# file does not look like a pidfile at all
|
|
288
|
+
def refuse_unless_stale
|
|
289
|
+
content = begin
|
|
290
|
+
File.read(@pidfile)
|
|
291
|
+
rescue Errno::ENOENT
|
|
292
|
+
return # already gone; nothing to refuse
|
|
293
|
+
end
|
|
294
|
+
pid = Integer(content.strip, exception: false)
|
|
295
|
+
unless pid&.positive?
|
|
296
|
+
raise Error, "refusing to overwrite #{@pidfile}: does not hold a pid"
|
|
297
|
+
end
|
|
298
|
+
raise Error, "already running (pid #{pid}, per #{@pidfile})" if process_alive?(pid)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def process_alive?(pid)
|
|
302
|
+
Process.kill(0, pid)
|
|
303
|
+
true
|
|
304
|
+
rescue Errno::ESRCH
|
|
305
|
+
false
|
|
306
|
+
rescue Errno::EPERM
|
|
307
|
+
true # exists, just not ours to signal
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Delete only a pidfile that is still ours: by shutdown time the path
|
|
311
|
+
# may belong to a replacement instance, or an operator may have
|
|
312
|
+
# repointed it at something that is not a pidfile at all.
|
|
313
|
+
def remove_pidfile
|
|
314
|
+
File.unlink(@pidfile) if File.read(@pidfile) == "#{Process.pid}\n"
|
|
315
|
+
rescue Errno::ENOENT
|
|
316
|
+
nil
|
|
317
|
+
end
|
|
318
|
+
|
|
244
319
|
def join_workers(deadline)
|
|
245
320
|
if @supervisor
|
|
246
321
|
@supervisor.shutdown([deadline - monotonic_now, 0].max)
|
data/lib/kino/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kino
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Markin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|