nondisposable 0.6.0 → 0.6.1
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 +6 -0
- data/README.md +1 -1
- data/lib/nondisposable/domain_list_updater.rb +24 -5
- data/lib/nondisposable/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: 96ee85d55e05d52d472cb9a4b9897e318499282c35818579621daec1bcd7bf9e
|
|
4
|
+
data.tar.gz: f633e949328cb2972dfdff7c44f33072b29dae4b5bd95ab116af24ec504f676c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71c6fbbfcac0534c320decc1cee69147497984a9dc12f19569a2231eacc192a5a2cc328e88decdc5e8f692384bf8ae39705a9c77ff134fb5cccc02fc455c133d
|
|
7
|
+
data.tar.gz: 8abdd8d29e8bfeb5f51da7cc98a6b213a8af428b561a8be6a9d2e84bce470f96a5e2c5bbeeff2f9a4e0dda0d719f5e696562c97b9643278a5d92a5c90ae8ff06
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.6.1] - 2026-10-17
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **A populated table is no longer replaced with the bundled list by an update** (an explicit `rollback` still can; see below). In 0.6.0, when a PRIMARY source with a bundled list (the default `disposable-email-domains`) had never been fetched successfully, `DomainListUpdater` put the list bundled with the gem in its place and published it, even over a populated table. **Who is affected:** any host with the `nondisposable_source_snapshots` table whose domain table was populated before the default source had an accepted snapshot of its own. Typically that means an upgrade from 0.4/0.5, with `Sources::DEFAULT` or a multi-source configuration such as `Sources::RECOMMENDED`. If the default source failed on such a host's first update, the table was replaced by the bundled snapshot (plus any other primary that succeeded), and every domain added upstream since the gem's release stopped being blocked until the next successful run. `update` returned false, but the list had already shrunk. Now the bundled stand-in is used **only on an empty table** (a fresh install). With a populated table, publishing is skipped with an error log and the table is kept as it is, until every primary has an accepted snapshot of its own. **The deliberate exception is `DomainListUpdater.rollback`:** withdrawing a poisoned list must always succeed, so there the bundled list still stands in for a primary without a snapshot, whatever the table holds. The emptiness check also bypasses the query cache, so a stale cached answer can never let the bundled list replace rows it never saw. Reported by a codex review in CarHey.
|
|
6
|
+
|
|
1
7
|
## [0.6.0] - 2026-10-17
|
|
2
8
|
|
|
3
9
|
Ships everything listed under 0.5.0 too: 0.5.0 was never released on its own.
|
data/README.md
CHANGED
|
@@ -408,7 +408,7 @@ Each source is fetched into its own row in `nondisposable_source_snapshots` (sta
|
|
|
408
408
|
- it is less than half the source's high-water mark, the largest size ever accepted for it (kept on its newest good snapshot, so neither pruning nor a series of 49% cuts can wear it down; `reset_baseline` is the human override), or more than three times its last good one;
|
|
409
409
|
- it is identical to a snapshot a human rolled back.
|
|
410
410
|
|
|
411
|
-
Every size is a count of *storable* domains (not junk, not a public suffix, not a known real provider, not a privacy relay), counted the same way whatever your settings, so a payload padded with such lines cannot pass for a full list and changing `block_privacy_relays` or `excluded_domains` never trips a guard. The list is never published empty. A primary that has never been fetched successfully
|
|
411
|
+
Every size is a count of *storable* domains (not junk, not a public suffix, not a known real provider, not a privacy relay), counted the same way whatever your settings, so a payload padded with such lines cannot pass for a full list and changing `block_privacy_relays` or `excluded_domains` never trips a guard. The list is never published empty. A primary that has never been fetched successfully blocks publishing, and the current table is kept as it is. Two exceptions let the default source's bundled list stand in for it: an EMPTY table (a fresh install), and an explicit `rollback`, which must always be able to withdraw a poisoned list. So an update can never replace a fresh seed with less than it, nor a table upgraded from 0.4/0.5 (with any source configuration) with an older bundled snapshot. Additions and removals are logged per source. A source that lists a known real provider, or a public suffix, is logged as a possible poisoning, and the entry is dropped. Accepting a snapshot, publishing and rolling back all happen under the same lock; downloads happen outside it.
|
|
412
412
|
|
|
413
413
|
> [!WARNING]
|
|
414
414
|
> **Two concurrency limits, by design:**
|
|
@@ -303,6 +303,16 @@ module Nondisposable
|
|
|
303
303
|
def publish!(withdrawn: nil)
|
|
304
304
|
snapshots = {}
|
|
305
305
|
missing_primaries = []
|
|
306
|
+
# The bundled list may stand in for a never-fetched primary ONLY on an
|
|
307
|
+
# empty table. A populated table (a host upgrading from 0.4/0.5, whose
|
|
308
|
+
# rows are newer than this gem's release) is kept as it is until every
|
|
309
|
+
# primary has an accepted snapshot of its own: replacing it with the
|
|
310
|
+
# bundled snapshot would silently unblock every domain added upstream
|
|
311
|
+
# since the release (0.6.1; reported by a CarHey codex review).
|
|
312
|
+
# A rollback is the deliberate exception: withdrawing a poisoned list
|
|
313
|
+
# must succeed even when the default source never had a snapshot of
|
|
314
|
+
# its own, so there the stand-in is used whatever the table holds.
|
|
315
|
+
table_empty = DisposableDomain.uncached { DisposableDomain.none? }
|
|
306
316
|
|
|
307
317
|
configured_sources.each do |source|
|
|
308
318
|
snapshot = SourceSnapshot.last_good(source.name)
|
|
@@ -310,7 +320,7 @@ module Nondisposable
|
|
|
310
320
|
snapshots[source] = snapshot.domain_list
|
|
311
321
|
elsif source.name == withdrawn
|
|
312
322
|
log :warn, "#{source.name} has no good snapshot left after the rollback; building the list without it."
|
|
313
|
-
elsif source.primary? && source.bundled_path
|
|
323
|
+
elsif source.primary? && source.bundled_path && (table_empty || withdrawn)
|
|
314
324
|
log :warn, "#{source.name} has no good snapshot yet; using the list bundled with the gem in its place."
|
|
315
325
|
snapshots[source] = Source.parse(File.read(source.bundled_path)).domains
|
|
316
326
|
elsif source.primary?
|
|
@@ -322,7 +332,8 @@ module Nondisposable
|
|
|
322
332
|
|
|
323
333
|
if missing_primaries.any?
|
|
324
334
|
log :error, "Not publishing: primary source(s) #{missing_primaries.join(', ')} have never been fetched successfully. " \
|
|
325
|
-
'Keeping the current list (seeding it from the bundled snapshot if it is empty)
|
|
335
|
+
'Keeping the current list unchanged (seeding it from the bundled snapshot only if it is empty) ' \
|
|
336
|
+
'until every primary has an accepted snapshot of its own.'
|
|
326
337
|
seed!
|
|
327
338
|
return false
|
|
328
339
|
end
|
|
@@ -371,7 +382,9 @@ module Nondisposable
|
|
|
371
382
|
# ---- Seeding and writing ----------------------------------------------
|
|
372
383
|
|
|
373
384
|
def seed!
|
|
374
|
-
|
|
385
|
+
# Callers hold the publish lock, which already disables the query
|
|
386
|
+
# cache; asked uncached here as well so no future caller can forget.
|
|
387
|
+
return false unless DisposableDomain.uncached { DisposableDomain.none? }
|
|
375
388
|
|
|
376
389
|
log :info, 'Domain table is empty; seeding from the blocklist bundled with the gem...'
|
|
377
390
|
domains = Source.parse(bundled_domains.join("\n")).domains
|
|
@@ -465,10 +478,16 @@ module Nondisposable
|
|
|
465
478
|
# time, and two processes publishing the same snapshots write the same
|
|
466
479
|
# rows. Returns the block's value, or nil when another process held the
|
|
467
480
|
# lock for longer than PUBLISH_LOCK_WAIT (it is publishing the same thing).
|
|
481
|
+
#
|
|
482
|
+
# Everything under the lock runs with the query cache OFF: an emptiness
|
|
483
|
+
# check (`DisposableDomain.none?`) answered from a cache filled before
|
|
484
|
+
# another process or thread wrote the table would let the bundled list
|
|
485
|
+
# replace rows it never saw.
|
|
468
486
|
def with_publish_lock(&block)
|
|
487
|
+
uncached = -> { DisposableDomain.uncached(&block) }
|
|
469
488
|
PROCESS_LOCK.synchronize do
|
|
470
489
|
DisposableDomain.connection_pool.with_connection do |connection|
|
|
471
|
-
next
|
|
490
|
+
next uncached.call unless connection.supports_advisory_locks?
|
|
472
491
|
|
|
473
492
|
acquired = false
|
|
474
493
|
deadline = monotonic + PUBLISH_LOCK_WAIT
|
|
@@ -482,7 +501,7 @@ module Nondisposable
|
|
|
482
501
|
next nil unless acquired
|
|
483
502
|
|
|
484
503
|
begin
|
|
485
|
-
|
|
504
|
+
uncached.call
|
|
486
505
|
ensure
|
|
487
506
|
connection.release_advisory_lock(PUBLISH_LOCK_ID)
|
|
488
507
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nondisposable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-18 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|