kino 0.1.3 → 0.2.1

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.
@@ -50,7 +50,7 @@ pub fn sleep_chunk(ruby: &Ruby, seconds: f64) -> Result<f64, Error> {
50
50
  func: gvl::ubf_interrupt,
51
51
  data: &interrupted as *const _ as *mut c_void,
52
52
  }),
53
- );
53
+ )?;
54
54
 
55
55
  let remaining = requested.saturating_sub(chunk);
56
56
  Ok(remaining.as_secs_f64())
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
- @id, @port = Native.server_start(
77
- bind: @bind, port: @requested_port,
78
- queue_depth: @queue_depth, queue_timeout_ms: @queue_timeout_ms,
79
- request_timeout_ms: @request_timeout_ms,
80
- max_connections: @max_connections,
81
- max_body_size: @max_body_size,
82
- tokio_threads: @tokio_threads,
83
- tls_cert: @tls&.fetch(:cert), tls_key: @tls&.fetch(:key),
84
- lanes: @lanes, log_requests: @log_requests
85
- )
86
- File.write(@pidfile, "#{Process.pid}\n") if @pidfile
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
- File.delete(@pidfile) if @pidfile && File.exist?(@pidfile)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Kino
4
4
  # The gem version (single source of truth; ext/kino/Cargo.toml syncs).
5
- VERSION = "0.1.3"
5
+ VERSION = "0.2.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kino
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Markin
@@ -165,6 +165,7 @@ files:
165
165
  - ext/kino/src/gvl.rs
166
166
  - ext/kino/src/lib.rs
167
167
  - ext/kino/src/logsink.rs
168
+ - ext/kino/src/pin.rs
168
169
  - ext/kino/src/queue.rs
169
170
  - ext/kino/src/registry.rs
170
171
  - ext/kino/src/request.rs
@@ -213,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
214
  - !ruby/object:Gem::Version
214
215
  version: '0'
215
216
  requirements: []
216
- rubygems_version: 4.0.10
217
+ rubygems_version: 4.0.16
217
218
  specification_version: 4
218
219
  summary: High-performance Ractor web server for Ruby.
219
220
  test_files: []