tina4ruby 3.13.22 → 3.13.23
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/lib/tina4/database.rb +83 -3
- data/lib/tina4/rack_app.rb +7 -0
- data/lib/tina4/response_cache.rb +2 -2
- data/lib/tina4/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: baf95ed43a14f3bcfb80f371cfca547bb0605d7da81ec519c6d32d58287ad754
|
|
4
|
+
data.tar.gz: afafda038012638d1a98da325e03c42ac4d0517693abf3832f3e313ad414982a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6126339d81995e6d7199af287d4a8e909dd57f02f0ba58993f87e842c25716d40e80bad1af5a00a9c910a35e1025b7b5483986b29e57e983ee90ba4552b35540
|
|
7
|
+
data.tar.gz: a5fe6d009ccc270ecb27075da49ed1a8361a1ef59cffcaa4634f9b39a84896caf52e064b2129210c25d683c2a7b40048145a61e8ef206906b9cdc26a2e2c2af5
|
data/lib/tina4/database.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
require "json"
|
|
3
3
|
require "uri"
|
|
4
4
|
require "digest"
|
|
5
|
+
require "weakref"
|
|
5
6
|
|
|
6
7
|
module Tina4
|
|
7
8
|
# Thread-safe connection pool with round-robin rotation.
|
|
@@ -68,6 +69,43 @@ module Tina4
|
|
|
68
69
|
class Database
|
|
69
70
|
attr_reader :driver, :driver_name, :connected, :pool
|
|
70
71
|
|
|
72
|
+
# Live Database instances, so the request dispatcher can reset the
|
|
73
|
+
# request-scoped query cache on every connection at the start of a request.
|
|
74
|
+
# WeakRefs avoid keeping closed connections (or short-lived script
|
|
75
|
+
# connections) alive — parity with Python's weakref.WeakSet. Guarded by a
|
|
76
|
+
# mutex because connections can be created from multiple threads.
|
|
77
|
+
@instances = []
|
|
78
|
+
@instances_mutex = Mutex.new
|
|
79
|
+
|
|
80
|
+
class << self
|
|
81
|
+
# Register a live connection in the class-level WeakRef registry.
|
|
82
|
+
def register_instance(db)
|
|
83
|
+
@instances_mutex.synchronize do
|
|
84
|
+
@instances << WeakRef.new(db)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Clear the request-scoped query cache on every live Database instance.
|
|
89
|
+
#
|
|
90
|
+
# The request dispatcher calls this at the start of each HTTP request so
|
|
91
|
+
# request-scoped caching never serves rows across requests (zero
|
|
92
|
+
# cross-request staleness). Persistent-mode connections are left alone.
|
|
93
|
+
# Dead WeakRefs (closed/GC'd connections) are pruned as we go.
|
|
94
|
+
def reset_request_caches
|
|
95
|
+
@instances_mutex.synchronize do
|
|
96
|
+
@instances.reject! do |ref|
|
|
97
|
+
begin
|
|
98
|
+
inst = ref.__getobj__
|
|
99
|
+
inst.cache_new_request
|
|
100
|
+
false
|
|
101
|
+
rescue WeakRef::RefError, StandardError
|
|
102
|
+
true # dead reference (or errored) — prune it
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
71
109
|
DRIVERS = {
|
|
72
110
|
"sqlite" => "Tina4::Drivers::SqliteDriver",
|
|
73
111
|
"sqlite3" => "Tina4::Drivers::SqliteDriver",
|
|
@@ -162,14 +200,33 @@ module Tina4
|
|
|
162
200
|
# driver for every call so the whole transaction runs on one connection.
|
|
163
201
|
@tx_pin_key = :"tina4_pinned_adapter_#{object_id}"
|
|
164
202
|
|
|
165
|
-
# Query cache
|
|
166
|
-
|
|
167
|
-
|
|
203
|
+
# Query cache. One store, two layers (parity with Python connection.py):
|
|
204
|
+
# • request-scoped (DEFAULT ON, off-switch TINA4_AUTO_CACHING=false) —
|
|
205
|
+
# dedupes identical SELECTs to protect the DB from rapid repeat reads.
|
|
206
|
+
# Cleared at the START of every HTTP request (so it never serves rows
|
|
207
|
+
# across requests) AND on any write, with a short safety TTL (5s) for
|
|
208
|
+
# non-request contexts (scripts/workers).
|
|
209
|
+
# • persistent (opt-in, TINA4_DB_CACHE=true) — cross-request TTL cache
|
|
210
|
+
# that is NOT cleared per request; entries expire by TINA4_DB_CACHE_TTL.
|
|
211
|
+
@cache_persistent = truthy?(ENV["TINA4_DB_CACHE"])
|
|
212
|
+
# Default true; honour the same truthy semantics the framework uses
|
|
213
|
+
# (mirrors Python's is_truthy(get("TINA4_AUTO_CACHING", "true"))).
|
|
214
|
+
@cache_request_scoped = truthy?(ENV["TINA4_AUTO_CACHING"] || "true")
|
|
215
|
+
@cache_enabled = @cache_persistent || @cache_request_scoped
|
|
216
|
+
@cache_ttl = if @cache_persistent
|
|
217
|
+
(ENV["TINA4_DB_CACHE_TTL"] || "30").to_i
|
|
218
|
+
else
|
|
219
|
+
(ENV["TINA4_AUTO_CACHING_TTL"] || "5").to_i
|
|
220
|
+
end
|
|
168
221
|
@query_cache = {} # key => { expires_at:, value: }
|
|
169
222
|
@cache_hits = 0
|
|
170
223
|
@cache_misses = 0
|
|
171
224
|
@cache_mutex = Mutex.new
|
|
172
225
|
|
|
226
|
+
# Register this connection so Tina4::Database.reset_request_caches can
|
|
227
|
+
# clear its request-scoped entries at the start of every HTTP request.
|
|
228
|
+
Tina4::Database.register_instance(self)
|
|
229
|
+
|
|
173
230
|
if @pool_size > 0
|
|
174
231
|
# Pooled mode — create a ConnectionPool with lazy driver creation
|
|
175
232
|
@pool = ConnectionPool.new(
|
|
@@ -235,9 +292,11 @@ module Tina4
|
|
|
235
292
|
@cache_mutex.synchronize do
|
|
236
293
|
{
|
|
237
294
|
enabled: @cache_enabled,
|
|
295
|
+
mode: cache_mode,
|
|
238
296
|
hits: @cache_hits,
|
|
239
297
|
misses: @cache_misses,
|
|
240
298
|
size: @query_cache.size,
|
|
299
|
+
backend: "memory",
|
|
241
300
|
ttl: @cache_ttl
|
|
242
301
|
}
|
|
243
302
|
end
|
|
@@ -251,6 +310,16 @@ module Tina4
|
|
|
251
310
|
end
|
|
252
311
|
end
|
|
253
312
|
|
|
313
|
+
# Clear the request-scoped query cache at the start of an HTTP request.
|
|
314
|
+
#
|
|
315
|
+
# No-op in persistent mode (TINA4_DB_CACHE=true) so cross-request entries
|
|
316
|
+
# survive up to their TTL. Cumulative hit/miss counters are preserved.
|
|
317
|
+
def cache_new_request
|
|
318
|
+
return unless @cache_request_scoped && !@cache_persistent
|
|
319
|
+
|
|
320
|
+
@cache_mutex.synchronize { @query_cache.clear }
|
|
321
|
+
end
|
|
322
|
+
|
|
254
323
|
# Fetch rows and return the records array directly.
|
|
255
324
|
#
|
|
256
325
|
# Symmetric with fetch_one. Cross-framework parity with Python
|
|
@@ -677,6 +746,17 @@ module Tina4
|
|
|
677
746
|
%w[true 1 yes on].include?((val || "").to_s.strip.downcase)
|
|
678
747
|
end
|
|
679
748
|
|
|
749
|
+
# "persistent" / "request" / "off" — mirrors Python connection.py.
|
|
750
|
+
def cache_mode
|
|
751
|
+
if @cache_persistent
|
|
752
|
+
"persistent"
|
|
753
|
+
elsif @cache_request_scoped
|
|
754
|
+
"request"
|
|
755
|
+
else
|
|
756
|
+
"off"
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
|
|
680
760
|
def cache_key(sql, params)
|
|
681
761
|
Digest::SHA256.hexdigest(sql + params.to_s)
|
|
682
762
|
end
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -43,6 +43,13 @@ module Tina4
|
|
|
43
43
|
path = env["PATH_INFO"] || "/"
|
|
44
44
|
request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
45
45
|
|
|
46
|
+
# Request-scoped query cache boundary (v3.13.23). Tina4 Ruby runs a
|
|
47
|
+
# long-running Rack server, so the request-scoped DB cache (default-on)
|
|
48
|
+
# would otherwise serve rows from a previous request. Clear it on every
|
|
49
|
+
# live connection at the very start of each request, before any routing.
|
|
50
|
+
# No-op for persistent-mode (TINA4_DB_CACHE=true) connections.
|
|
51
|
+
Tina4::Database.reset_request_caches if defined?(Tina4::Database)
|
|
52
|
+
|
|
46
53
|
# Fast-path: CORS preflight. Real CORS preflight requests carry an
|
|
47
54
|
# Origin header AND an Access-Control-Request-Method header — the
|
|
48
55
|
# browser is asking "may I send this method?" before the actual
|
data/lib/tina4/response_cache.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Tina4
|
|
|
22
22
|
# Environment:
|
|
23
23
|
# TINA4_CACHE_BACKEND — memory | redis | file (default: memory)
|
|
24
24
|
# TINA4_CACHE_URL — redis://localhost:6379 (redis only)
|
|
25
|
-
# TINA4_CACHE_TTL — default TTL in seconds (default:
|
|
25
|
+
# TINA4_CACHE_TTL — default TTL in seconds (default: 60)
|
|
26
26
|
# TINA4_CACHE_MAX_ENTRIES — maximum cache entries (default: 1000)
|
|
27
27
|
#
|
|
28
28
|
class ResponseCache
|
|
@@ -36,7 +36,7 @@ module Tina4
|
|
|
36
36
|
# @param cache_dir [String, nil] File cache directory
|
|
37
37
|
def initialize(ttl: nil, max_entries: nil, status_codes: [200],
|
|
38
38
|
backend: nil, cache_url: nil, cache_dir: nil)
|
|
39
|
-
@ttl = ttl || (ENV["TINA4_CACHE_TTL"] ? ENV["TINA4_CACHE_TTL"].to_i :
|
|
39
|
+
@ttl = ttl || (ENV["TINA4_CACHE_TTL"] ? ENV["TINA4_CACHE_TTL"].to_i : 60)
|
|
40
40
|
@max_entries = max_entries || (ENV["TINA4_CACHE_MAX_ENTRIES"] ? ENV["TINA4_CACHE_MAX_ENTRIES"].to_i : 1000)
|
|
41
41
|
@status_codes = status_codes
|
|
42
42
|
@backend_name = backend || ENV.fetch("TINA4_CACHE_BACKEND", "memory").downcase.strip
|
data/lib/tina4/version.rb
CHANGED