Pipeline runner crashes with AttributeError when member.preferences is None during roster iteration
Summary
The pipeline runner crashes with AttributeError: 'NoneType' object has no attribute 'delivery_mode' when iterating the member roster to build the recipient list. This causes the entire mailing to be shunted, preventing delivery to all recipients.
Version
GNU Mailman 3.3.9
Steps to Reproduce
This is a race condition that occurs under the following circumstances:
- A message is approved for delivery to a large mailing list (70k+ members)
- The pipeline runner begins iterating the member roster via
member_recipientshandler - Concurrently, the bounce runner processes a bounce for a member and removes them (or deletes their preferences record)
- The pipeline runner encounters the now-orphaned member record with
preferences = None - The
Member._lookup()method callsgetattr(self.preferences, preference)which raisesAttributeError
Traceback
Uncaught runner exception: 'NoneType' object has no attribute 'delivery_mode'
Traceback (most recent call last):
File "/usr/lib/python3.11/site-packages/mailman/core/runner.py", line 179, in _one_iteration
self._process_one_file(msg, msgdata)
File "/usr/lib/python3.11/site-packages/mailman/core/runner.py", line 272, in _process_one_file
keepqueued = self._dispose(mlist, msg, msgdata)
File "/usr/lib/python3.11/site-packages/mailman/runners/pipeline.py", line 37, in _dispose
process(mlist, msg, msgdata, pipeline)
File "/usr/lib/python3.11/site-packages/mailman/core/pipelines.py", line 53, in process
handler.process(mlist, msg, msgdata)
File "/usr/lib/python3.11/site-packages/mailman/handlers/member_recipients.py", line 84, in process
recipients = set(member.address.email
File "/usr/lib/python3.11/site-packages/mailman/handlers/member_recipients.py", line 84, in <genexpr>
recipients = set(member.address.email
File "/usr/lib/python3.11/site-packages/mailman/model/roster.py", line 247, in members
yield from self._get_members(DeliveryMode.regular)
File "/usr/lib/python3.11/site-packages/mailman/model/roster.py", line 234, in _get_members
if member.delivery_mode in delivery_modes:
File "/usr/lib/python3.11/site-packages/mailman/model/member.py", line 217, in delivery_mode
return self._lookup('delivery_mode')
File "/usr/lib/python3.11/site-packages/mailman/model/member.py", line 176, in _lookup
pref = getattr(self.preferences, preference)
AttributeError: 'NoneType' object has no attribute 'delivery_mode'Impact
- The entire mailing is shunted and no recipients receive the message
- On high-volume lists (70k+ members), this can happen frequently due to the long iteration time increasing the window for concurrent modifications
- The affected member is typically already removed by the time an administrator investigates, making the issue appear transient
Evidence of Race Condition
- The member that triggered the crash was no longer a member when checked after the incident
- The crash occurred at the same time as bounce processing activity
- The issue is not reproducible on demand but occurs periodically during large list deliveries
Possible Fix
In src/mailman/model/member.py, the _lookup method should handle self.preferences being None gracefully:
def _lookup(self, preference, default=None):
if self.preferences is None:
log.warning(
'Member %s on list %s has no preferences record, '
'using default for %s',
self.address.email if self.address else 'unknown',
self.list_id, preference)
return default
pref = getattr(self.preferences, preference)
if pref is not None:
return pref
# ... rest of method unchangedThis allows the roster iteration to skip the affected member rather than crashing the entire pipeline. The member will be excluded from the recipient list for that send (which is acceptable since they are in the process of being removed anyway).