Problem
async_mark_unique_records_older_than_1s_to_expire walks the cache and calls _async_set_created_ttl(record, now, 1), which mutates record.created and record.ttl in place via record._set_created_ttl(...), then re-_async_adds the same object. The code itself flags this — line 345 says "It would be better if we made a copy instead of mutating the record in place, but records currently don't have a copy method." Listeners (RecordUpdate.old references obtained from cache.async_get_unique elsewhere in the same dispatch tick), ServiceInfo instances holding references to cached records, and anything in _handlers/multicast_outgoing_queue.AnswerGroup.answers that keys on a DNSAddress/DNSPointer instance will silently see their TTL/created flip to the new values mid-dispatch. The RFC-mandated "expire in 1s" path (RFC 6762 §10.2) is therefore not isolated from in-flight consumers.
Why This Matters
Subtle correctness drift in hot path: any code path that captured a DNSRecord reference and decided "this record is fresh, use it" can have the same object turn stale between two of its own lines. It's also hostile to free-threading (3.14t) — concurrent reads of record.created / record.ttl while another thread is in the middle of _set_created_ttl are an unsynchronized write race.
Suggested Fix
Add a copy_with_ttl(now, ttl) (or _cdef-typed _clone_with_created_ttl) method on DNSRecord (and update the .pxd so the new method is visible on the cython hot path), then in _async_set_created_ttl replace the in-place mutation with replacement = record.copy_with_ttl(now, 1); self._async_add(replacement). The old record will fall out of the cache when the new one supersedes it via async_add_records. Update the comment to remove the "It would be better" footnote once done.
Details
|
|
| Severity |
🟡 Medium |
| Category |
robustness |
| Location |
src/zeroconf/_cache.py:325-348 |
| Effort |
🛠️ Moderate effort |
🤖 Created by Kōan from audit session
Problem
async_mark_unique_records_older_than_1s_to_expirewalks the cache and calls_async_set_created_ttl(record, now, 1), which mutatesrecord.createdandrecord.ttlin place viarecord._set_created_ttl(...), then re-_async_adds the same object. The code itself flags this — line 345 says "It would be better if we made a copy instead of mutating the record in place, but records currently don't have a copy method." Listeners (RecordUpdate.oldreferences obtained fromcache.async_get_uniqueelsewhere in the same dispatch tick),ServiceInfoinstances holding references to cached records, and anything in_handlers/multicast_outgoing_queue.AnswerGroup.answersthat keys on aDNSAddress/DNSPointerinstance will silently see their TTL/created flip to the new values mid-dispatch. The RFC-mandated "expire in 1s" path (RFC 6762 §10.2) is therefore not isolated from in-flight consumers.Why This Matters
Subtle correctness drift in hot path: any code path that captured a
DNSRecordreference and decided "this record is fresh, use it" can have the same object turn stale between two of its own lines. It's also hostile to free-threading (3.14t) — concurrent reads ofrecord.created/record.ttlwhile another thread is in the middle of_set_created_ttlare an unsynchronized write race.Suggested Fix
Add a
copy_with_ttl(now, ttl)(or_cdef-typed_clone_with_created_ttl) method onDNSRecord(and update the.pxdso the new method is visible on the cython hot path), then in_async_set_created_ttlreplace the in-place mutation withreplacement = record.copy_with_ttl(now, 1); self._async_add(replacement). The old record will fall out of the cache when the new one supersedes it viaasync_add_records. Update the comment to remove the "It would be better" footnote once done.Details
src/zeroconf/_cache.py:325-348🤖 Created by Kōan from audit session