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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aac2cabc3b10f9750282cfa834852508cc89b03c6263ceed9eb199e8154d5eea
4
- data.tar.gz: 661574d030c88a3ad8c514954643479513ca85349fa368f5762e7aa7129dd73f
3
+ metadata.gz: baf95ed43a14f3bcfb80f371cfca547bb0605d7da81ec519c6d32d58287ad754
4
+ data.tar.gz: afafda038012638d1a98da325e03c42ac4d0517693abf3832f3e313ad414982a
5
5
  SHA512:
6
- metadata.gz: 53a60f73e417cc8badad04fa10f801e7dc56088d4ab171c73d08790e3502fac4ab1902e3c19d571f031034debccc48afc58767876df2f48a0e3c1f60ef94e24f
7
- data.tar.gz: e00f8a84ebe2c3f91c22ff868d9ce45b8e6d5fad4109f52ad887c793d465254e98ffcdb485c37969f0a9a038554846dffdda51f17a34bd10dc4ca1990ae6476e
6
+ metadata.gz: 6126339d81995e6d7199af287d4a8e909dd57f02f0ba58993f87e842c25716d40e80bad1af5a00a9c910a35e1025b7b5483986b29e57e983ee90ba4552b35540
7
+ data.tar.gz: a5fe6d009ccc270ecb27075da49ed1a8361a1ef59cffcaa4634f9b39a84896caf52e064b2129210c25d683c2a7b40048145a61e8ef206906b9cdc26a2e2c2af5
@@ -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 off by default, opt-in via TINA4_DB_CACHE=true
166
- @cache_enabled = truthy?(ENV["TINA4_DB_CACHE"])
167
- @cache_ttl = (ENV["TINA4_DB_CACHE_TTL"] || "30").to_i
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
@@ -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
@@ -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: 0 = disabled)
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 : 0)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.22"
4
+ VERSION = "3.13.23"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.22
4
+ version: 3.13.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team