tina4ruby 3.13.104 → 3.13.105
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 +20 -0
- data/lib/tina4/cli.rb +7 -4
- data/lib/tina4/orm.rb +17 -5
- data/lib/tina4/queue.rb +36 -6
- data/lib/tina4/queue_backends/lite_backend.rb +18 -1
- data/lib/tina4/queue_backends/mongo_backend.rb +38 -10
- data/lib/tina4/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ab6d7ce15da93e814204665a8690be0cc2ac58dc1152fa75f050b91fcb0c4f0
|
|
4
|
+
data.tar.gz: 35fc27eb9d71c17a0049d983d34ed6c1584e014338192e5ace087e5e2bec0746
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2e66595ddcf7eca7d498428149ca38a83caf4871d8d5e29e31ef9d72f7f38f2ed9c3afe638ba2891bf9ffda5b00dbbf54883ddc704bf06787f9f06060b0603b
|
|
7
|
+
data.tar.gz: c45ab7892609d2be7e0b3dc432a8d54bef9dd8281e3594198b9fa93e9170ceeb60372624c69d49978884ef596cb7ff35eeb720216c4d240a176e372ffa33d803
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ number means the same thing everywhere.
|
|
|
6
6
|
**The authoritative release notes for every shipped version live in the documentation:**
|
|
7
7
|
https://tina4.com/ruby/36-releases
|
|
8
8
|
|
|
9
|
+
## 3.13.105
|
|
10
|
+
|
|
11
|
+
Bug release. Route inspection stops touching the app; Firebird's migration
|
|
12
|
+
ledger tolerates whatever case the driver hands back; PHP loses a colon-in-
|
|
13
|
+
filename that broke Windows checkouts.
|
|
14
|
+
|
|
15
|
+
### Route inspection scans, never boots
|
|
16
|
+
|
|
17
|
+
- `tina4 routes` now walks canonical route files and never executes the
|
|
18
|
+
application entrypoint or starts the server. Feature 115 / ADR-0058.
|
|
19
|
+
- Fixes the case where `tina4 routes --override` would boot the app on the
|
|
20
|
+
same port and kill whatever process was already holding it (tina4-python
|
|
21
|
+
issue #104).
|
|
22
|
+
|
|
23
|
+
### Firebird migration ledger is case-agnostic
|
|
24
|
+
|
|
25
|
+
- `tina4_migration` reads and writes work regardless of the case the
|
|
26
|
+
Firebird driver returns for the `migration_name` column.
|
|
27
|
+
- Uses the atomic sequence table pattern already in place for other engines.
|
|
28
|
+
|
|
9
29
|
## 3.13.103
|
|
10
30
|
|
|
11
31
|
### Metrics reports what it can prove
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -1013,8 +1013,8 @@ module Tina4
|
|
|
1013
1013
|
|
|
1014
1014
|
def cmd_routes(_argv = nil)
|
|
1015
1015
|
require_relative "../tina4"
|
|
1016
|
-
Tina4.
|
|
1017
|
-
load_routes(Dir.pwd)
|
|
1016
|
+
Tina4::Env.load_env(Dir.pwd)
|
|
1017
|
+
load_routes(Dir.pwd, entrypoints: false)
|
|
1018
1018
|
|
|
1019
1019
|
puts "\nRegistered Routes:"
|
|
1020
1020
|
puts "-" * 60
|
|
@@ -2959,7 +2959,7 @@ module Tina4
|
|
|
2959
2959
|
|
|
2960
2960
|
# ── shared helpers ────────────────────────────────────────────────────
|
|
2961
2961
|
|
|
2962
|
-
def load_routes(root_dir)
|
|
2962
|
+
def load_routes(root_dir, entrypoints: true)
|
|
2963
2963
|
route_dirs = %w[src/routes routes src/api api src/orm orm]
|
|
2964
2964
|
route_dirs.each do |dir|
|
|
2965
2965
|
route_dir = File.join(root_dir, dir)
|
|
@@ -2967,7 +2967,10 @@ module Tina4
|
|
|
2967
2967
|
Dir.glob(File.join(route_dir, "**/*.rb")).sort.each { |f| load f }
|
|
2968
2968
|
end
|
|
2969
2969
|
|
|
2970
|
-
|
|
2970
|
+
return unless entrypoints
|
|
2971
|
+
|
|
2972
|
+
# Runtime and console boot load the application entrypoint. Inspection
|
|
2973
|
+
# commands opt out so they can never start a server or take over a port.
|
|
2971
2974
|
app_file = File.join(root_dir, "app.rb")
|
|
2972
2975
|
load app_file if File.exist?(app_file)
|
|
2973
2976
|
|
data/lib/tina4/orm.rb
CHANGED
|
@@ -480,13 +480,25 @@ module Tina4
|
|
|
480
480
|
|
|
481
481
|
# Invalidate every cached query that touches this model's table.
|
|
482
482
|
#
|
|
483
|
-
# Tag-scoped
|
|
484
|
-
#
|
|
485
|
-
#
|
|
486
|
-
#
|
|
487
|
-
#
|
|
483
|
+
# Tag-scoped in the ORM layer (a cached JOIN on another model that reads
|
|
484
|
+
# this table is busted too because it carries this table's tag; a query
|
|
485
|
+
# that never touches this table is left intact), then cascaded to the
|
|
486
|
+
# DB layer on this model's bound connection so an out-of-band write /
|
|
487
|
+
# deliberate refresh / race-with-another-process cannot leave stale rows
|
|
488
|
+
# in db.fetch's persistent cache. Called after every ORM write
|
|
489
|
+
# (save/delete/force_delete/restore) so a read-after-write never serves
|
|
490
|
+
# a stale/deleted row (CACHE-DEC-01). 3.13.105 (parity port of PY-06-22)
|
|
491
|
+
# added the DB-layer cascade — previously the two cache layers disagreed
|
|
492
|
+
# under TINA4_AUTO_CACHING=true + TINA4_DB_CACHE=true.
|
|
488
493
|
def clear_cache
|
|
489
494
|
query_cache.clear_tag(table_name.to_s.downcase)
|
|
495
|
+
begin
|
|
496
|
+
get_db.cache_clear
|
|
497
|
+
rescue StandardError
|
|
498
|
+
# A resolvable DB is not guaranteed at every clear_cache call site
|
|
499
|
+
# (module-import time in odd bootstraps, tests that mutate bindings);
|
|
500
|
+
# never let a cache-clear crash a save/delete.
|
|
501
|
+
end
|
|
490
502
|
nil
|
|
491
503
|
end
|
|
492
504
|
|
data/lib/tina4/queue.rb
CHANGED
|
@@ -171,7 +171,14 @@ module Tina4
|
|
|
171
171
|
@backend.close
|
|
172
172
|
end
|
|
173
173
|
|
|
174
|
-
# Get jobs that failed but are still
|
|
174
|
+
# Get jobs that failed at least once but are still being retried
|
|
175
|
+
# (0 < attempts < max_retries). These live in the pending queue under
|
|
176
|
+
# the auto-retry lifecycle (fail() re-queues them with an incremented
|
|
177
|
+
# attempts count and a retry_backoff delay) so pop() picks them up
|
|
178
|
+
# again. They are NOT counted by size("failed") — that alias counts
|
|
179
|
+
# the dead-letter store, matching dead_letters(). To include retryable-
|
|
180
|
+
# failed jobs in a total, use size("pending"). Terminal failures are
|
|
181
|
+
# returned by dead_letters().
|
|
175
182
|
def failed # -> list[dict]
|
|
176
183
|
refuse_operation!("failed()") unless @backend.respond_to?(:failed)
|
|
177
184
|
@backend.failed(@topic, max_retries: @max_retries)
|
|
@@ -179,13 +186,24 @@ module Tina4
|
|
|
179
186
|
|
|
180
187
|
# Retry a specific failed job by ID, or all dead-letter jobs if no id given.
|
|
181
188
|
# Returns true if re-queued.
|
|
189
|
+
#
|
|
190
|
+
# The no-arg branch is materialised by the backend (LiteBackend#retry_job
|
|
191
|
+
# iterates every dead-letter file with .each, MongoBackend materialises
|
|
192
|
+
# find(...).to_a before iterating) — no generator inside any() /
|
|
193
|
+
# short-circuit reduce can silently leave dead letters behind (parity port
|
|
194
|
+
# of PY-12-04, 3.13.105).
|
|
182
195
|
def retry(job_id = nil, delay_seconds: 0) # -> bool
|
|
183
196
|
refuse_operation!("retry()") unless @backend.respond_to?(:retry_job)
|
|
184
197
|
@backend.retry_job(@topic, job_id: job_id, delay_seconds: delay_seconds)
|
|
185
198
|
end
|
|
186
199
|
|
|
187
|
-
# Get
|
|
188
|
-
#
|
|
200
|
+
# Get jobs that exceeded max_retries — terminal failures.
|
|
201
|
+
#
|
|
202
|
+
# Same set counted by size("failed") / size("dead") / size("dead_letter")
|
|
203
|
+
# (three aliases for the dead-letter store). To LIST retryable-but-
|
|
204
|
+
# attempted jobs (attempts > 0 AND attempts < max_retries) that are still
|
|
205
|
+
# being auto-retried, use failed() — those live in the pending queue and
|
|
206
|
+
# are NOT dead letters. Pass max_retries to override the queue's default.
|
|
189
207
|
def dead_letters(max_retries: nil) # -> list[dict]
|
|
190
208
|
refuse_operation!("dead_letters()") unless @backend.respond_to?(:dead_letters)
|
|
191
209
|
@backend.dead_letters(@topic, max_retries: max_retries || @max_retries)
|
|
@@ -328,9 +346,21 @@ module Tina4
|
|
|
328
346
|
attach(@backend.find_by_id(topic || @topic, id))
|
|
329
347
|
end
|
|
330
348
|
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
349
|
+
# Count jobs by status.
|
|
350
|
+
#
|
|
351
|
+
# "pending" counts jobs waiting to be popped — INCLUDES retryable-but-
|
|
352
|
+
# attempted ones, because they live in the pending queue under the
|
|
353
|
+
# auto-retry lifecycle (see failed()).
|
|
354
|
+
# "reserved" counts jobs a consumer has popped but not yet
|
|
355
|
+
# completed/failed (in-flight against the visibility timeout).
|
|
356
|
+
# "completed" counts jobs the consumer has finished successfully
|
|
357
|
+
# (0 on the lite/file backend which deletes on complete; backends that
|
|
358
|
+
# track completion expose completed_count).
|
|
359
|
+
# "failed", "dead", "dead_letter" are ALIASES that all count the
|
|
360
|
+
# dead-letter store — jobs whose attempts >= max_retries and that have
|
|
361
|
+
# given up. Use dead_letters() to list them. Retryable-but-attempted
|
|
362
|
+
# jobs are NOT counted by size("failed"); use failed() to list them or
|
|
363
|
+
# size("pending") to include them in a total.
|
|
334
364
|
def size(status: "pending")
|
|
335
365
|
case status.to_s
|
|
336
366
|
when "pending"
|
|
@@ -164,9 +164,26 @@ module Tina4
|
|
|
164
164
|
|
|
165
165
|
# Explicit re-queue requested by the caller (job.retry()). Always
|
|
166
166
|
# re-enqueues regardless of the retry limit — a manual override, distinct
|
|
167
|
-
# from the automatic fail() path.
|
|
167
|
+
# from the automatic fail() path. Cleans up BOTH the reservation record
|
|
168
|
+
# and any dead-letter file for this id, so a caller who iterates
|
|
169
|
+
# dead_letters and calls job.retry on each doesn't leave the dead-letter
|
|
170
|
+
# directory carrying duplicates (3.13.105, parity port of PY-12-05).
|
|
171
|
+
# Aligns with retry_job(id) which had always unlinked the dead-letter
|
|
172
|
+
# file — two spellings of the same intent that previously diverged.
|
|
173
|
+
# Increments attempts, clears the error.
|
|
168
174
|
def retry(job, delay_seconds: 0)
|
|
169
175
|
clear_reservation(job.topic, job.id)
|
|
176
|
+
# Drop any dead-letter file for this id BEFORE the re-queue: if this
|
|
177
|
+
# job came from dead_letters it lives in @dead_letter_dir and would
|
|
178
|
+
# otherwise stay on disk while a fresh pending file appears in
|
|
179
|
+
# topic_path, so the next dead_letters call reports the job again
|
|
180
|
+
# and a consumer processes it twice.
|
|
181
|
+
begin
|
|
182
|
+
File.unlink(job_file(@dead_letter_dir, job.id))
|
|
183
|
+
rescue Errno::ENOENT
|
|
184
|
+
# No dead-letter file for this id (manual .retry on a live job) —
|
|
185
|
+
# nothing to clean up.
|
|
186
|
+
end
|
|
170
187
|
job.attempts += 1
|
|
171
188
|
requeue_job(job, delay_seconds: delay_seconds, error: nil)
|
|
172
189
|
end
|
|
@@ -291,8 +291,19 @@ module Tina4
|
|
|
291
291
|
.map { |doc| job_from_doc(doc) }
|
|
292
292
|
end
|
|
293
293
|
|
|
294
|
+
# Delete every doc in this queue whose status matches.
|
|
295
|
+
#
|
|
296
|
+
# 3.13.105 (parity port): route the dead-letter statuses ("dead",
|
|
297
|
+
# "failed", "dead_letter") to the ".dead_letter" topic namespace where
|
|
298
|
+
# they actually live — pre-fix, purge("dead") delete_many'd on
|
|
299
|
+
# {topic: topic, status: "dead"}, matching zero docs and silently
|
|
300
|
+
# returning 0 while the dead letters sat untouched in the sibling
|
|
301
|
+
# namespace. Every path scopes by status so a purge never nukes docs it
|
|
302
|
+
# was not asked to remove, and every path returns deleted_count so a
|
|
303
|
+
# caller can log/assert what was purged.
|
|
294
304
|
def purge(topic, status)
|
|
295
|
-
|
|
305
|
+
namespace = dead_status?(status) ? "#{topic}.dead_letter" : topic
|
|
306
|
+
result = collection.delete_many(topic: namespace, status: status.to_s)
|
|
296
307
|
result.deleted_count
|
|
297
308
|
end
|
|
298
309
|
|
|
@@ -326,20 +337,31 @@ module Tina4
|
|
|
326
337
|
result.modified_count
|
|
327
338
|
end
|
|
328
339
|
|
|
329
|
-
# Move
|
|
330
|
-
#
|
|
340
|
+
# Move dead-lettered jobs back to their main topic as pending.
|
|
341
|
+
#
|
|
342
|
+
# With +job_id+, revives that ONE dead letter and returns true when
|
|
343
|
+
# found (false otherwise). With no id, iterates every dead letter for
|
|
344
|
+
# the topic and revives ALL — materialised into an array before
|
|
345
|
+
# reducing, so a truthy first result never short-circuits away the
|
|
346
|
+
# rest (the class of bug the parity port of PY-12-04 pins). Ruby's
|
|
347
|
+
# dead-letter model stores the SAME document with topic flipped to
|
|
348
|
+
# "<topic>.dead_letter", so reviving is the reverse in-place update:
|
|
349
|
+
# flip the topic back, reset status/available_at/error. Mirrors
|
|
350
|
+
# LiteBackend#retry_job.
|
|
331
351
|
def retry_job(topic, job_id: nil, delay_seconds: 0)
|
|
332
352
|
filter = { topic: "#{topic}.dead_letter", status: "dead" }
|
|
333
353
|
filter[:_id] = job_id if job_id
|
|
334
|
-
|
|
335
|
-
return false
|
|
354
|
+
docs = collection.find(filter).to_a
|
|
355
|
+
return false if docs.empty?
|
|
336
356
|
|
|
337
357
|
available = delay_seconds.to_f > 0 ? (Time.now.utc + delay_seconds.to_f) : Time.now.utc
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
358
|
+
docs.each do |doc|
|
|
359
|
+
collection.update_one(
|
|
360
|
+
{ _id: doc["_id"] },
|
|
361
|
+
{ "$set" => { topic: topic, status: "pending", error: nil,
|
|
362
|
+
reserved_at: nil, available_at: available } }
|
|
363
|
+
)
|
|
364
|
+
end
|
|
343
365
|
true
|
|
344
366
|
end
|
|
345
367
|
|
|
@@ -358,6 +380,12 @@ module Tina4
|
|
|
358
380
|
|
|
359
381
|
private
|
|
360
382
|
|
|
383
|
+
DEAD_STATES = %w[failed dead dead_letter].freeze
|
|
384
|
+
|
|
385
|
+
def dead_status?(status)
|
|
386
|
+
DEAD_STATES.include?(status.to_s)
|
|
387
|
+
end
|
|
388
|
+
|
|
361
389
|
# A stored document as a plain Hash with string keys, CARRYING attempts
|
|
362
390
|
# and error.
|
|
363
391
|
#
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.105
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|