legion-data 1.10.8 → 1.10.9
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 +8 -0
- data/lib/legion/data/connection.rb +54 -4
- data/lib/legion/data/settings.rb +22 -0
- data/lib/legion/data/version.rb +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: 8d929f0ed083e64145e6022ee40af61b677b852513e071ec9a986267837953a2
|
|
4
|
+
data.tar.gz: ed00e7cb0c7670378212cc1f8df39c893f0efbaf7a71961188a411a1956dcab9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdf3cf290669b970b523c3de8e5d6b42b9b14d877bd7260155c2067a708745cd59120605059dc1d5af85fd94f0a63d0c1e4e39ae4b7870409357572085b1d5f2
|
|
7
|
+
data.tar.gz: 3b1c0bb52ba3bfcc0249f6a6a4c0185b213f7e6cd8201989f5cd0279be1d9e1df1e364c0479d48920397f7e693ff5a63f557048acdaaefecdd32651e212922c7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.9] - 2026-07-31
|
|
4
|
+
### Fixed
|
|
5
|
+
- **Postgres session timeouts**: Apply `statement_timeout` (default 5000ms) and `lock_timeout` (default 2000ms) via `after_connect` hook on every new Postgres connection. Prevents unbounded `SELECT` or lock-wait from hanging the process indefinitely — the root cause of ~1h50m connection wedges.
|
|
6
|
+
- **TCP keepalives on Postgres**: Wire libpq `keepalives`, `keepalives_idle` (10s), `keepalives_interval` (5s), `keepalives_count` (3), and `tcp_user_timeout` (15000ms) through the Sequel connection hash. Detects half-open TCP sockets within ~25s instead of relying on OS defaults (often 2+ hours).
|
|
7
|
+
- **Force-disconnect on shutdown**: `shutdown` now waits up to `shutdown_timeout` (default 5s) for checked-out connections to return, then force-disconnects all remaining connections. Previously, `disconnect` only closed idle pool connections — a connection stuck in `select()` survived forever, hanging process exit.
|
|
8
|
+
- **Adapter timeout defaults tightened**: Postgres `connect_timeout` reduced from 20s to 5s; MySQL `connect_timeout` reduced from 120s to 5s, `read_timeout` and `write_timeout` default to 5s. The old 20s/120s values allowed connection attempts to block worker threads far too long under network partition.
|
|
9
|
+
- **MySQL read/write timeout wiring preserved**: `read_timeout` and `write_timeout` remain in `ADAPTER_KEYS[:mysql2]` where Sequel's mysql2 adapter honors them. For Postgres, the equivalent is `statement_timeout` (query bound) plus TCP keepalives (socket bound) — `read_timeout` is not a libpq/Sequel-postgres concept, so it is correctly absent from the Postgres key list.
|
|
10
|
+
|
|
3
11
|
## [1.10.8] - 2026-07-23
|
|
4
12
|
### Fixed
|
|
5
13
|
- Detect auth failures (`role does not exist`, `password authentication failed`) on Sequel pool connections and trigger immediate Vault lease reissue via LeaseManager instead of retrying dead credentials forever. 30-second cooldown prevents reissue storms during bulk credential rotation.
|
|
@@ -19,14 +19,18 @@ module Legion
|
|
|
19
19
|
|
|
20
20
|
ADAPTER_KEYS = {
|
|
21
21
|
sqlite: %i[timeout readonly disable_dqs],
|
|
22
|
-
postgres: %i[connect_timeout sslmode sslrootcert search_path
|
|
22
|
+
postgres: %i[connect_timeout sslmode sslrootcert search_path
|
|
23
|
+
keepalives keepalives_idle keepalives_interval keepalives_count
|
|
24
|
+
tcp_user_timeout],
|
|
23
25
|
mysql2: %i[connect_timeout read_timeout write_timeout encoding sql_mode]
|
|
24
26
|
}.freeze
|
|
25
27
|
|
|
26
28
|
ADAPTER_DEFAULTS = {
|
|
27
29
|
sqlite: { timeout: 5000, readonly: false, disable_dqs: true },
|
|
28
|
-
postgres: { connect_timeout:
|
|
29
|
-
|
|
30
|
+
postgres: { connect_timeout: 5, sslmode: 'disable',
|
|
31
|
+
keepalives: 1, keepalives_idle: 10, keepalives_interval: 5,
|
|
32
|
+
keepalives_count: 3, tcp_user_timeout: 15_000 },
|
|
33
|
+
mysql2: { connect_timeout: 5, read_timeout: 5, write_timeout: 5, encoding: 'utf8mb4' }
|
|
30
34
|
}.freeze
|
|
31
35
|
|
|
32
36
|
QUERY_LOG_DIR = File.expand_path('~/.legionio/logs').freeze
|
|
@@ -272,7 +276,10 @@ module Legion
|
|
|
272
276
|
end
|
|
273
277
|
|
|
274
278
|
def shutdown
|
|
275
|
-
@sequel
|
|
279
|
+
if @sequel
|
|
280
|
+
timeout = Legion::Settings[:data][:shutdown_timeout]
|
|
281
|
+
force_disconnect_pool(timeout: timeout)
|
|
282
|
+
end
|
|
276
283
|
@query_file_logger&.close
|
|
277
284
|
@query_file_logger = nil
|
|
278
285
|
@fallback_active = false
|
|
@@ -430,6 +437,28 @@ module Legion
|
|
|
430
437
|
data_settings[:dev_mode] == true && data_settings[:dev_fallback] != false
|
|
431
438
|
end
|
|
432
439
|
|
|
440
|
+
def force_disconnect_pool(timeout:)
|
|
441
|
+
pool = @sequel.pool
|
|
442
|
+
in_use = pool.size - (pool.respond_to?(:available_connections) ? pool.available_connections.size : 0)
|
|
443
|
+
|
|
444
|
+
if in_use.positive?
|
|
445
|
+
log.warn("Legion::Data shutdown: #{in_use} connection(s) still checked out, waiting up to #{timeout}s")
|
|
446
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + timeout
|
|
447
|
+
while ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) < deadline
|
|
448
|
+
break if pool.size <= (pool.respond_to?(:available_connections) ? pool.available_connections.size : 0)
|
|
449
|
+
|
|
450
|
+
sleep 0.1
|
|
451
|
+
end
|
|
452
|
+
remaining = pool.size - (pool.respond_to?(:available_connections) ? pool.available_connections.size : 0)
|
|
453
|
+
log.warn("Legion::Data shutdown: force-disconnecting #{remaining} wedged connection(s)") if remaining.positive?
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
@sequel.disconnect
|
|
457
|
+
rescue StandardError => e
|
|
458
|
+
handle_exception(e, level: :warn, handled: true, operation: :force_disconnect_pool)
|
|
459
|
+
@sequel&.disconnect rescue nil # rubocop:disable Style/RescueModifier
|
|
460
|
+
end
|
|
461
|
+
|
|
433
462
|
def sqlite_path
|
|
434
463
|
path = Legion::Settings[:data][:creds][:database] || 'legionio.db'
|
|
435
464
|
return path if File.absolute_path?(path)
|
|
@@ -513,6 +542,11 @@ module Legion
|
|
|
513
542
|
tuning[:connection_expiration] = data[:connection_expiration]
|
|
514
543
|
tuning[:connection_expiration_timeout] = data[:connection_expiration_timeout]
|
|
515
544
|
|
|
545
|
+
# Session timeouts (postgres)
|
|
546
|
+
tuning[:statement_timeout] = data[:statement_timeout]
|
|
547
|
+
tuning[:lock_timeout] = data[:lock_timeout]
|
|
548
|
+
tuning[:shutdown_timeout] = data[:shutdown_timeout]
|
|
549
|
+
|
|
516
550
|
# Adapter-specific (only keys relevant to current adapter)
|
|
517
551
|
defaults = ADAPTER_DEFAULTS.fetch(adapter, {})
|
|
518
552
|
ADAPTER_KEYS.fetch(adapter, []).each do |key|
|
|
@@ -621,6 +655,7 @@ module Legion
|
|
|
621
655
|
if adapter == :postgres
|
|
622
656
|
Sequel.extension(:pg_array)
|
|
623
657
|
@sequel.extension(:pg_array)
|
|
658
|
+
install_postgres_session_timeouts
|
|
624
659
|
end
|
|
625
660
|
|
|
626
661
|
if data[:connection_validation] != false
|
|
@@ -638,6 +673,21 @@ module Legion
|
|
|
638
673
|
handle_exception(e, level: :warn, handled: true, operation: :configure_extensions, adapter: adapter)
|
|
639
674
|
end
|
|
640
675
|
|
|
676
|
+
def install_postgres_session_timeouts
|
|
677
|
+
data = Legion::Settings[:data]
|
|
678
|
+
stmt_timeout = data[:statement_timeout]
|
|
679
|
+
lck_timeout = data[:lock_timeout]
|
|
680
|
+
|
|
681
|
+
@sequel.pool.after_connect = proc do |conn|
|
|
682
|
+
conn.exec("SET statement_timeout = '#{stmt_timeout.to_i}ms'") if stmt_timeout
|
|
683
|
+
conn.exec("SET lock_timeout = '#{lck_timeout.to_i}ms'") if lck_timeout
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
log.info("Postgres session timeouts: statement_timeout=#{stmt_timeout}ms, lock_timeout=#{lck_timeout}ms")
|
|
687
|
+
rescue StandardError => e
|
|
688
|
+
handle_exception(e, level: :warn, handled: true, operation: :install_postgres_session_timeouts)
|
|
689
|
+
end
|
|
690
|
+
|
|
641
691
|
def install_auth_failure_hook
|
|
642
692
|
Legion::Data::AuthFailureHandler.install(@sequel)
|
|
643
693
|
rescue StandardError => e
|
data/lib/legion/data/settings.rb
CHANGED
|
@@ -58,6 +58,28 @@ module Legion
|
|
|
58
58
|
connection_expiration: true,
|
|
59
59
|
connection_expiration_timeout: 14_400,
|
|
60
60
|
|
|
61
|
+
# Postgres session timeouts (milliseconds, applied via SET on each new connection).
|
|
62
|
+
# statement_timeout bounds any single query; lock_timeout bounds lock acquisition.
|
|
63
|
+
# These prevent a wedged SELECT or lock wait from hanging the process indefinitely.
|
|
64
|
+
statement_timeout: 5000,
|
|
65
|
+
lock_timeout: 2000,
|
|
66
|
+
|
|
67
|
+
# TCP keepalives for Postgres (libpq connection params).
|
|
68
|
+
# Detect half-open sockets where the peer vanished without FIN/RST.
|
|
69
|
+
# keepalives_idle: seconds before first keepalive probe after idle
|
|
70
|
+
# keepalives_interval: seconds between probes
|
|
71
|
+
# keepalives_count: failed probes before declaring connection dead
|
|
72
|
+
# tcp_user_timeout: total ms for unacked data before kernel drops connection (Linux)
|
|
73
|
+
keepalives: 1,
|
|
74
|
+
keepalives_idle: 10,
|
|
75
|
+
keepalives_interval: 5,
|
|
76
|
+
keepalives_count: 3,
|
|
77
|
+
tcp_user_timeout: 15_000,
|
|
78
|
+
|
|
79
|
+
# Shutdown: max seconds to wait for checked-out connections to return before
|
|
80
|
+
# force-disconnecting them.
|
|
81
|
+
shutdown_timeout: 5,
|
|
82
|
+
|
|
61
83
|
# Adapter-specific (nil = use adapter built-in default)
|
|
62
84
|
connect_timeout: nil,
|
|
63
85
|
read_timeout: nil,
|
data/lib/legion/data/version.rb
CHANGED