bidi2pdf 0.1.18 → 0.1.20
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 +21 -2
- data/README.md +8 -5
- data/lib/bidi2pdf/chrome_sweeper/settings.rb +3 -2
- data/lib/bidi2pdf/chrome_sweeper.rb +1 -1
- data/lib/bidi2pdf/session_registry/heartbeat.rb +5 -2
- data/lib/bidi2pdf/session_registry.rb +25 -16
- data/lib/bidi2pdf/session_sweeper.rb +1 -1
- data/lib/bidi2pdf/session_warmer.rb +12 -6
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb +4 -1
- data/lib/bidi2pdf/version.rb +1 -1
- data/sig/bidi2pdf/chrome_sweeper/settings.rbs +3 -2
- data/sig/bidi2pdf/session_registry/heartbeat.rbs +3 -0
- data/sig/bidi2pdf/session_registry.rbs +15 -9
- data/sig/bidi2pdf/session_warmer.rbs +11 -4
- data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rbs +4 -1
- data/sig/bidi2pdf/version.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 523b63ba5c422c92dd950e0045261ebcd9b888d042d930fa312b87b4c94f3e31
|
|
4
|
+
data.tar.gz: b6aeb5c8ba74244bd890434a0c46ed18c92afd827dfaa47a9520fca20039d676
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dbae17f4831478c506f67a0b1e72ca4890b2716c183e5c25b40a68e9009c78f12d8414bae9f9fc4e5f6eab52e51d04c9c217ed0b48cd73f8d54af3d4feca9e6
|
|
7
|
+
data.tar.gz: 000bc92f8ea29444b652f45e48ef9daca4f460e90d71cc731b84e825e32e20296b24df6161febc2efc214bd4fa74577079634a23260785235c4bf977ec49c53b
|
data/CHANGELOG.md
CHANGED
|
@@ -8,10 +8,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
-
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
11
|
+
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.20..HEAD
|
|
12
12
|
|
|
13
13
|
<!-- generated by git-cliff end -->
|
|
14
14
|
|
|
15
|
+
## [0.1.20] - 2026-09-30
|
|
16
|
+
|
|
17
|
+
### 🐛 Fixed
|
|
18
|
+
- Pull only the latest chromedriver image
|
|
19
|
+
|
|
20
|
+
### 🔄 Changed
|
|
21
|
+
- Merge pull request #152 from dieter-medium/fix/chromedriver-image-tag
|
|
22
|
+
|
|
23
|
+
## [0.1.19] - 2026-09-30
|
|
24
|
+
|
|
25
|
+
### 🐛 Fixed
|
|
26
|
+
- Keep lease times to the fraction of a second
|
|
27
|
+
- Let every lease carry its own TTL
|
|
28
|
+
|
|
29
|
+
### 🔄 Changed
|
|
30
|
+
- Merge pull request #149 from dieter-medium/feat/lease-expiry
|
|
31
|
+
|
|
15
32
|
## [0.1.18] - 2026-09-30
|
|
16
33
|
|
|
17
34
|
### 🐛 Fixed
|
|
@@ -514,7 +531,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
514
531
|
|
|
515
532
|
### 🔄 Released
|
|
516
533
|
|
|
517
|
-
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
534
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.20..HEAD)
|
|
535
|
+
- [0.1.20](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.19..v0.1.20)
|
|
536
|
+
- [0.1.19](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.18..v0.1.19)
|
|
518
537
|
- [0.1.18](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.17..v0.1.18)
|
|
519
538
|
- [0.1.17](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.16..v0.1.17)
|
|
520
539
|
- [0.1.16](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.15..v0.1.16)
|
data/README.md
CHANGED
|
@@ -572,7 +572,8 @@ Bidi2pdf::SessionWarmer.shutdown
|
|
|
572
572
|
| `remote_browser_url` | `nil` | Connect each slot to a remote chromedriver instead of starting a local one. |
|
|
573
573
|
| `orphan_age` | `:auto` | Remote only: on start, close sessions other warmers left behind older than this (`:auto` = 2 × `max_idle_age`, `nil` = off). |
|
|
574
574
|
| `registry_dir` | `Dir.tmpdir` | Where the session registry file lives - every process that should clean up after the others must share it. |
|
|
575
|
-
| `sweeper` | `nil` | Remote only: settings for a [`ChromeSweeper`](#leaked-chrome-sessions-bidi2pdfchromesweeper) on the same chromedriver, e.g. `{ scope: :all, max_sessions: :auto, pids_limit: 1024, interval: 60 }`. The warmer's own sessions are never touched; `Bidi2pdf::SessionWarmer.sweep!` sweeps on demand.
|
|
575
|
+
| `sweeper` | `nil` | Remote only: settings for a [`ChromeSweeper`](#leaked-chrome-sessions-bidi2pdfchromesweeper) on the same chromedriver, e.g. `{ scope: :all, max_sessions: :auto, pids_limit: 1024, interval: 60 }`. The warmer's own sessions are never touched; `Bidi2pdf::SessionWarmer.sweep!` sweeps on demand. |
|
|
576
|
+
| `retry_refused_sessions` | `true` | With a `sweeper`: when chromedriver refuses a new session, sweep under pressure and try once more. `false` leaves it to the caller. |
|
|
576
577
|
|
|
577
578
|
#### Leftover sessions on a shared chromedriver
|
|
578
579
|
|
|
@@ -634,9 +635,11 @@ those, it closes nothing more and reports `limit_exceeded`. A session is closed
|
|
|
634
635
|
**Live sessions of other processes (leases).** Several processes often share one chromedriver - Puma
|
|
635
636
|
workers, a job worker - and each has renders in flight and warm spares the others know nothing
|
|
636
637
|
about. So every session bidi2pdf opens is recorded in the registry with a lease, and a heartbeat
|
|
637
|
-
thread in the owning process renews it every 20 s while the
|
|
638
|
-
is
|
|
639
|
-
|
|
638
|
+
thread in the owning process renews it every 20 s (`SessionRegistry::Heartbeat.interval`) while the
|
|
639
|
+
session is open. Every lease carries its own TTL - three heartbeats of the process that wrote it -
|
|
640
|
+
so a sweeper never has to guess how often another process renews. A session whose lease is still
|
|
641
|
+
fresh belongs to a live process: no sweeper in any process closes it, and it is not even inspected.
|
|
642
|
+
The warmer leases its sessions in remote mode too, with or without a sweeper. When the process dies - killed, crashed, OOM - the lease runs out and the
|
|
640
643
|
session becomes a leftover like any other. Only processes that share the registry directory see
|
|
641
644
|
each other's leases: give job workers in another container the same `registry_dir` on a shared
|
|
642
645
|
volume. Sessions other tools opened have no lease; under `scope: :all` only `min_age` protects them.
|
|
@@ -664,7 +667,7 @@ session is refused.
|
|
|
664
667
|
| `dry_run` | `false` | Report what would be closed, close nothing. |
|
|
665
668
|
| `registry_dir` | `Dir.tmpdir`| The registry to read and update - same meaning as the warmer's setting. |
|
|
666
669
|
| `own_sessions` | `-> { [] }` | A callable returning the caller's live session ids; they are never touched. |
|
|
667
|
-
| `lease_ttl` | `60` |
|
|
670
|
+
| `lease_ttl` | `60` | Only for registry entries written by bidi2pdf 0.1.18, which carry no TTL of their own: renewed within this many seconds counts as live. |
|
|
668
671
|
|
|
669
672
|
How it tells a session's age: chromedriver's `GET /sessions` lists every session but no start time,
|
|
670
673
|
and Chrome keeps none either. So the sweeper uses the registry time when there is one and otherwise
|
|
@@ -23,8 +23,9 @@ module Bidi2pdf
|
|
|
23
23
|
# @!attribute threads_per_session [Integer] threads (Docker counts them as pids) of one session.
|
|
24
24
|
# @!attribute interval [Numeric, nil] seconds between sweeps of ChromeSweeper#start's thread.
|
|
25
25
|
# @!attribute dry_run [Boolean] report what would be closed, close nothing.
|
|
26
|
-
# @!attribute lease_ttl [Numeric]
|
|
27
|
-
# a live process
|
|
26
|
+
# @!attribute lease_ttl [Numeric] for registry entries without a TTL of their own (written by
|
|
27
|
+
# bidi2pdf 0.1.18): renewed within this many seconds means a live process holds it. Entries
|
|
28
|
+
# written since carry the TTL their owner's heartbeat promises (SessionRegistry#hold).
|
|
28
29
|
Settings = Data.define(:scope, :orphan_age, :min_age, :unresponsive_checks, :max_sessions, :pids_limit,
|
|
29
30
|
:pids_budget, :threads_per_session, :interval, :dry_run, :lease_ttl) do
|
|
30
31
|
# rubocop:disable-next Metrics/ParameterLists
|
|
@@ -20,6 +20,9 @@ module Bidi2pdf
|
|
|
20
20
|
|
|
21
21
|
def interval = @interval || DEFAULT_INTERVAL
|
|
22
22
|
|
|
23
|
+
# The TTL this process writes into its leases: three missed renewals and it is taken for dead.
|
|
24
|
+
def lease_ttl = interval * 3
|
|
25
|
+
|
|
23
26
|
def hold(registry, session_id)
|
|
24
27
|
synchronize do
|
|
25
28
|
(held[registry.path] ||= [registry, Set.new])[1] << session_id.to_s
|
|
@@ -43,9 +46,9 @@ module Bidi2pdf
|
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
# Renews every held lease now.
|
|
46
|
-
def beat!(now: Time.now.
|
|
49
|
+
def beat!(now: Time.now.to_f)
|
|
47
50
|
synchronize { held.values.map { |registry, ids| [registry, ids.to_a] } }
|
|
48
|
-
.each { |registry, ids| registry.renew(ids, at: now) }
|
|
51
|
+
.each { |registry, ids| registry.renew(ids, at: now, ttl: lease_ttl) }
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
private
|
|
@@ -14,8 +14,10 @@ module Bidi2pdf
|
|
|
14
14
|
# accepted but echoed nowhere - checked against chromedriver 153), hence a file.
|
|
15
15
|
#
|
|
16
16
|
# Liveness is a lease: a session the process #hold s is renewed by its Heartbeat every
|
|
17
|
-
# Heartbeat.interval seconds
|
|
18
|
-
#
|
|
17
|
+
# Heartbeat.interval seconds, and every entry carries its own TTL - the time the owner promises to
|
|
18
|
+
# renew it within (three heartbeats) - so a sweeper in another process never has to guess how
|
|
19
|
+
# often that owner renews. A session whose lease is fresh belongs to a live process - a render in
|
|
20
|
+
# flight or a warm spare - and no sweeper closes it; one whose lease ran out belongs to a dead
|
|
19
21
|
# process. #record alone writes an entry nobody renews.
|
|
20
22
|
#
|
|
21
23
|
# Everything here is best effort and fail-open: a directory that is not writable, a locked-out or
|
|
@@ -25,7 +27,8 @@ module Bidi2pdf
|
|
|
25
27
|
# the directory (Puma workers, a job worker, a spec run) can record at the same time.
|
|
26
28
|
class SessionRegistry
|
|
27
29
|
FILE_PREFIX = "bidi2pdf-sessions-"
|
|
28
|
-
# Seconds a lease stays fresh without a renewal
|
|
30
|
+
# Seconds a lease stays fresh without a renewal when its entry carries no TTL of its own (written
|
|
31
|
+
# by bidi2pdf 0.1.18) - matches that version's fixed 20 s heartbeat.
|
|
29
32
|
DEFAULT_LEASE_TTL = 60
|
|
30
33
|
|
|
31
34
|
attr_reader :path
|
|
@@ -41,7 +44,7 @@ module Bidi2pdf
|
|
|
41
44
|
def hold(session_id)
|
|
42
45
|
return false if session_id.nil?
|
|
43
46
|
|
|
44
|
-
recorded = record(session_id)
|
|
47
|
+
recorded = record(session_id, ttl: Heartbeat.lease_ttl)
|
|
45
48
|
Heartbeat.hold(self, session_id)
|
|
46
49
|
recorded
|
|
47
50
|
end
|
|
@@ -52,12 +55,15 @@ module Bidi2pdf
|
|
|
52
55
|
Heartbeat.release(self, session_id) unless session_id.nil?
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
# Writes an entry, leased from +created_at+ on
|
|
56
|
-
|
|
58
|
+
# Writes an entry, leased from +created_at+ on for +ttl+ seconds (nil: the reader's default) but
|
|
59
|
+
# not renewed (see #hold).
|
|
60
|
+
# Times are stored as fractions of a second: a whole-second timestamp could cost a short lease
|
|
61
|
+
# (0.5 s heartbeats: 1.5 s) up to a second of its life. Entries of whole seconds still read.
|
|
62
|
+
def record(session_id, created_at: Time.now.to_f, ttl: nil)
|
|
57
63
|
return false if session_id.nil?
|
|
58
64
|
|
|
59
65
|
update do |entries|
|
|
60
|
-
entries[session_id.to_s] = { "created_at" => created_at.
|
|
66
|
+
entries[session_id.to_s] = { "created_at" => created_at.to_f, "renewed_at" => created_at.to_f, "ttl" => ttl, "owner" => self.class.owner }.compact
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
|
|
@@ -69,10 +75,13 @@ module Bidi2pdf
|
|
|
69
75
|
update { |entries| entries.delete(session_id.to_s) }
|
|
70
76
|
end
|
|
71
77
|
|
|
72
|
-
# Renews the leases of +session_ids+ that are still recorded.
|
|
73
|
-
def renew(session_ids, at: Time.now.
|
|
78
|
+
# Renews the leases of +session_ids+ that are still recorded, for +ttl+ seconds when given.
|
|
79
|
+
def renew(session_ids, at: Time.now.to_f, ttl: nil)
|
|
74
80
|
update do |entries|
|
|
75
|
-
session_ids.
|
|
81
|
+
session_ids.select { |id| entries.key?(id) }.each do |id|
|
|
82
|
+
entries[id]["renewed_at"] = at.to_f
|
|
83
|
+
entries[id]["ttl"] = ttl if ttl
|
|
84
|
+
end
|
|
76
85
|
end
|
|
77
86
|
end
|
|
78
87
|
|
|
@@ -81,10 +90,10 @@ module Bidi2pdf
|
|
|
81
90
|
read.transform_values { |entry| entry["created_at"] }
|
|
82
91
|
end
|
|
83
92
|
|
|
84
|
-
# The recorded session ids whose lease
|
|
85
|
-
#
|
|
86
|
-
def leased(now: Time.now.
|
|
87
|
-
read.select { |_, entry| entry["renewed_at"].
|
|
93
|
+
# The recorded session ids whose lease is still fresh at +now+ - sessions of a live process. An
|
|
94
|
+
# entry's own TTL wins; +ttl+ is only for entries without one.
|
|
95
|
+
def leased(now: Time.now.to_f, ttl: DEFAULT_LEASE_TTL)
|
|
96
|
+
read.select { |_, entry| entry["renewed_at"].to_f + (entry["ttl"] || ttl) >= now }.keys
|
|
88
97
|
end
|
|
89
98
|
|
|
90
99
|
# The recorded session ids opened at or before +cutoff+ (epoch seconds).
|
|
@@ -134,9 +143,9 @@ module Bidi2pdf
|
|
|
134
143
|
end
|
|
135
144
|
|
|
136
145
|
def normalize(entry)
|
|
137
|
-
return { "created_at" => entry } if entry.is_a?(
|
|
146
|
+
return { "created_at" => entry } if entry.is_a?(Numeric)
|
|
138
147
|
|
|
139
|
-
entry if entry.is_a?(Hash) && entry["created_at"].is_a?(
|
|
148
|
+
entry if entry.is_a?(Hash) && entry["created_at"].is_a?(Numeric)
|
|
140
149
|
end
|
|
141
150
|
|
|
142
151
|
def unavailable!(error)
|
|
@@ -25,7 +25,7 @@ module Bidi2pdf
|
|
|
25
25
|
# Closes recorded sessions opened more than +older_than+ seconds before +now+.
|
|
26
26
|
#
|
|
27
27
|
# @return [Integer] how many sessions were actually closed.
|
|
28
|
-
def sweep(older_than:, now: Time.now.
|
|
28
|
+
def sweep(older_than:, now: Time.now.to_f)
|
|
29
29
|
leftovers = @registry.recorded_before(now - older_than) - @registry.leased(now: now)
|
|
30
30
|
closed = leftovers.count { |id| closed_now?(id) }
|
|
31
31
|
report(closed)
|
|
@@ -65,9 +65,13 @@ module Bidi2pdf
|
|
|
65
65
|
# +orphan_age+, +min_age+, +unresponsive_checks+, +max_sessions+, +pids_limit+, +interval+,
|
|
66
66
|
# ...), or +nil+ (default) for none. This warmer's own sessions are never touched by it; with
|
|
67
67
|
# an +interval+ it sweeps in the background, and a session that fails to start triggers one
|
|
68
|
-
# sweep and one retry. Remote mode only.
|
|
68
|
+
# pressure sweep and one retry (see +retry_refused_sessions+). Remote mode only.
|
|
69
69
|
attr_accessor :sweeper
|
|
70
70
|
|
|
71
|
+
# @return [Boolean] With a +sweeper+: when chromedriver refuses a new session, sweep under
|
|
72
|
+
# pressure and try once more. Default true; false leaves a refused session to the caller.
|
|
73
|
+
attr_accessor :retry_refused_sessions
|
|
74
|
+
|
|
71
75
|
DEFAULT_MAX_IDLE_AGE = 300
|
|
72
76
|
|
|
73
77
|
def initialize
|
|
@@ -80,6 +84,7 @@ module Bidi2pdf
|
|
|
80
84
|
@orphan_age = :auto
|
|
81
85
|
@registry_dir = nil
|
|
82
86
|
@sweeper = nil
|
|
87
|
+
@retry_refused_sessions = true
|
|
83
88
|
end
|
|
84
89
|
|
|
85
90
|
# The orphan age in seconds, or nil when the sweep is off (also when +:auto+ has no
|
|
@@ -306,7 +311,7 @@ module Bidi2pdf
|
|
|
306
311
|
def new_slot
|
|
307
312
|
@slot_factory.call
|
|
308
313
|
rescue Bidi2pdf::SessionNotStartedError
|
|
309
|
-
raise unless @sweeper
|
|
314
|
+
raise unless @sweeper && @config.retry_refused_sessions
|
|
310
315
|
|
|
311
316
|
@sweeper.sweep!(reason: :create_failed, pressure: true)
|
|
312
317
|
@slot_factory.call
|
|
@@ -324,11 +329,12 @@ module Bidi2pdf
|
|
|
324
329
|
Bidi2pdf::ChromeSweeper.new(@config.remote_browser_url, own_sessions: -> { @mutex.synchronize { @own_sessions.dup } }, **options)
|
|
325
330
|
end
|
|
326
331
|
|
|
327
|
-
# Remote mode
|
|
328
|
-
#
|
|
329
|
-
# dies uncleanly. The start-up sweep itself needs the orphan age - see
|
|
332
|
+
# Remote mode: every session is leased, so no sweeper in any process takes a warm spare or a
|
|
333
|
+
# render in flight, and a later start (or a sweeper) can close the ones this process leaves
|
|
334
|
+
# behind if it dies uncleanly. The start-up sweep itself needs the orphan age - see
|
|
335
|
+
# #sweep_leftovers.
|
|
330
336
|
def build_registry
|
|
331
|
-
return unless @config.remote_browser_url
|
|
337
|
+
return unless @config.remote_browser_url
|
|
332
338
|
|
|
333
339
|
Bidi2pdf::SessionRegistry.new(@config.remote_browser_url, dir: @config.registry_dir)
|
|
334
340
|
end
|
|
@@ -5,7 +5,10 @@ module Bidi2pdf
|
|
|
5
5
|
module Testcontainers
|
|
6
6
|
class ChromedriverContainer < ::Testcontainers::DockerContainer
|
|
7
7
|
DEFAULT_CHROMEDRIVER_PORT = 3000
|
|
8
|
-
|
|
8
|
+
# Always with a tag: Docker's API pulls every tag of an image named without one, and since
|
|
9
|
+
# every image build is tagged by its commit that is dozens of ~500 MB images - a test run
|
|
10
|
+
# then sits silently in the pull for many minutes.
|
|
11
|
+
DEFAULT_IMAGE = "dieters877565/chromedriver:latest"
|
|
9
12
|
|
|
10
13
|
attr_reader :docker_file, :build_dir
|
|
11
14
|
|
data/lib/bidi2pdf/version.rb
CHANGED
|
@@ -27,8 +27,9 @@ module Bidi2pdf
|
|
|
27
27
|
# @!attribute threads_per_session [Integer] threads (Docker counts them as pids) of one session.
|
|
28
28
|
# @!attribute interval [Numeric, nil] seconds between sweeps of ChromeSweeper#start's thread.
|
|
29
29
|
# @!attribute dry_run [Boolean] report what would be closed, close nothing.
|
|
30
|
-
# @!attribute lease_ttl [Numeric]
|
|
31
|
-
# a live process
|
|
30
|
+
# @!attribute lease_ttl [Numeric] for registry entries without a TTL of their own (written by
|
|
31
|
+
# bidi2pdf 0.1.18): renewed within this many seconds means a live process holds it. Entries
|
|
32
|
+
# written since carry the TTL their owner's heartbeat promises (SessionRegistry#hold).
|
|
32
33
|
Settings: untyped
|
|
33
34
|
end
|
|
34
35
|
end
|
|
@@ -23,6 +23,9 @@ module Bidi2pdf
|
|
|
23
23
|
|
|
24
24
|
def self.interval: () -> untyped
|
|
25
25
|
|
|
26
|
+
# The TTL this process writes into its leases: three missed renewals and it is taken for dead.
|
|
27
|
+
def self.lease_ttl: () -> untyped
|
|
28
|
+
|
|
26
29
|
def self.hold: (untyped registry, untyped session_id) -> untyped
|
|
27
30
|
|
|
28
31
|
def self.release: (untyped registry, untyped session_id) -> untyped
|
|
@@ -7,8 +7,10 @@ module Bidi2pdf
|
|
|
7
7
|
# accepted but echoed nowhere - checked against chromedriver 153), hence a file.
|
|
8
8
|
#
|
|
9
9
|
# Liveness is a lease: a session the process #hold s is renewed by its Heartbeat every
|
|
10
|
-
# Heartbeat.interval seconds
|
|
11
|
-
#
|
|
10
|
+
# Heartbeat.interval seconds, and every entry carries its own TTL - the time the owner promises to
|
|
11
|
+
# renew it within (three heartbeats) - so a sweeper in another process never has to guess how
|
|
12
|
+
# often that owner renews. A session whose lease is fresh belongs to a live process - a render in
|
|
13
|
+
# flight or a warm spare - and no sweeper closes it; one whose lease ran out belongs to a dead
|
|
12
14
|
# process. #record alone writes an entry nobody renews.
|
|
13
15
|
#
|
|
14
16
|
# Everything here is best effort and fail-open: a directory that is not writable, a locked-out or
|
|
@@ -23,7 +25,8 @@ module Bidi2pdf
|
|
|
23
25
|
|
|
24
26
|
FILE_PREFIX: "bidi2pdf-sessions-"
|
|
25
27
|
|
|
26
|
-
# Seconds a lease stays fresh without a renewal
|
|
28
|
+
# Seconds a lease stays fresh without a renewal when its entry carries no TTL of its own (written
|
|
29
|
+
# by bidi2pdf 0.1.18) - matches that version's fixed 20 s heartbeat.
|
|
27
30
|
DEFAULT_LEASE_TTL: 60
|
|
28
31
|
|
|
29
32
|
attr_reader path: untyped
|
|
@@ -39,20 +42,23 @@ module Bidi2pdf
|
|
|
39
42
|
# a sweeper takes it once the lease ran out.
|
|
40
43
|
def release: (untyped session_id) -> (untyped | nil)
|
|
41
44
|
|
|
42
|
-
# Writes an entry, leased from +created_at+ on
|
|
43
|
-
|
|
45
|
+
# Writes an entry, leased from +created_at+ on for +ttl+ seconds (nil: the reader's default) but
|
|
46
|
+
# not renewed (see #hold).
|
|
47
|
+
# Times are stored as fractions of a second: a whole-second timestamp could cost a short lease
|
|
48
|
+
# (0.5 s heartbeats: 1.5 s) up to a second of its life. Entries of whole seconds still read.
|
|
49
|
+
def record: (untyped session_id, ?created_at: untyped, ?ttl: untyped?) -> (false | untyped)
|
|
44
50
|
|
|
45
51
|
# Forgets a session the caller closed itself (or found gone).
|
|
46
52
|
def forget: (untyped session_id) -> (false | untyped)
|
|
47
53
|
|
|
48
|
-
# Renews the leases of +session_ids+ that are still recorded.
|
|
49
|
-
def renew: (untyped session_ids, ?at: untyped) -> untyped
|
|
54
|
+
# Renews the leases of +session_ids+ that are still recorded, for +ttl+ seconds when given.
|
|
55
|
+
def renew: (untyped session_ids, ?at: untyped, ?ttl: untyped?) -> untyped
|
|
50
56
|
|
|
51
57
|
# Every recorded session: { id => opened at (epoch seconds) }.
|
|
52
58
|
def recorded: () -> untyped
|
|
53
59
|
|
|
54
|
-
# The recorded session ids whose lease
|
|
55
|
-
#
|
|
60
|
+
# The recorded session ids whose lease is still fresh at +now+ - sessions of a live process. An
|
|
61
|
+
# entry's own TTL wins; +ttl+ is only for entries without one.
|
|
56
62
|
def leased: (?now: untyped, ?ttl: untyped) -> untyped
|
|
57
63
|
|
|
58
64
|
# The recorded session ids opened at or before +cutoff+ (epoch seconds).
|
|
@@ -70,6 +70,8 @@ module Bidi2pdf
|
|
|
70
70
|
|
|
71
71
|
@sweeper: untyped
|
|
72
72
|
|
|
73
|
+
@retry_refused_sessions: untyped
|
|
74
|
+
|
|
73
75
|
# @return [Integer] Number of Chrome slots to keep pre-warmed.
|
|
74
76
|
attr_accessor size: untyped
|
|
75
77
|
|
|
@@ -111,9 +113,13 @@ module Bidi2pdf
|
|
|
111
113
|
# +orphan_age+, +min_age+, +unresponsive_checks+, +max_sessions+, +pids_limit+, +interval+,
|
|
112
114
|
# ...), or +nil+ (default) for none. This warmer's own sessions are never touched by it; with
|
|
113
115
|
# an +interval+ it sweeps in the background, and a session that fails to start triggers one
|
|
114
|
-
# sweep and one retry. Remote mode only.
|
|
116
|
+
# pressure sweep and one retry (see +retry_refused_sessions+). Remote mode only.
|
|
115
117
|
attr_accessor sweeper: untyped
|
|
116
118
|
|
|
119
|
+
# @return [Boolean] With a +sweeper+: when chromedriver refuses a new session, sweep under
|
|
120
|
+
# pressure and try once more. Default true; false leaves a refused session to the caller.
|
|
121
|
+
attr_accessor retry_refused_sessions: untyped
|
|
122
|
+
|
|
117
123
|
DEFAULT_MAX_IDLE_AGE: 300
|
|
118
124
|
|
|
119
125
|
def initialize: () -> void
|
|
@@ -216,9 +222,10 @@ module Bidi2pdf
|
|
|
216
222
|
|
|
217
223
|
def build_sweeper: () -> (nil | untyped)
|
|
218
224
|
|
|
219
|
-
# Remote mode
|
|
220
|
-
#
|
|
221
|
-
# dies uncleanly. The start-up sweep itself needs the orphan age - see
|
|
225
|
+
# Remote mode: every session is leased, so no sweeper in any process takes a warm spare or a
|
|
226
|
+
# render in flight, and a later start (or a sweeper) can close the ones this process leaves
|
|
227
|
+
# behind if it dies uncleanly. The start-up sweep itself needs the orphan age - see
|
|
228
|
+
# #sweep_leftovers.
|
|
222
229
|
def build_registry: () -> (nil | untyped)
|
|
223
230
|
|
|
224
231
|
def sweep_leftovers: () -> (nil | untyped)
|
|
@@ -18,7 +18,10 @@ module Bidi2pdf
|
|
|
18
18
|
|
|
19
19
|
DEFAULT_CHROMEDRIVER_PORT: 3000
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# Always with a tag: Docker's API pulls every tag of an image named without one, and since
|
|
22
|
+
# every image build is tagged by its commit that is dozens of ~500 MB images - a test run
|
|
23
|
+
# then sits silently in the pull for many minutes.
|
|
24
|
+
DEFAULT_IMAGE: "dieters877565/chromedriver:latest"
|
|
22
25
|
|
|
23
26
|
attr_reader docker_file: untyped
|
|
24
27
|
|
data/sig/bidi2pdf/version.rbs
CHANGED