Friday, September 18, 2026

SpamSieve 3.3.2

SpamSieve 3.3.2 is an update of my Mac e-mail spam filter that improves remote training, can export more rule information, and improves uninstallation, help, and more. The most significant news is that Microsoft has changed plans again. They will no longer be preventing users from continuing to use Legacy Outlook and in fact will keep it available for download as part of the combined Outlook app. This gives more time to adapt to their change in plans for AppleScript. Exchange (EWS) support remains an issue. Microsoft is still planning to cut off Legacy Outlook users, and Apple has yet to announce when Mail will support Graph. So far, I’ve not heard that it’s enabled in the macOS 27.2 beta.

We’re participating in a seasonal festival of artisanal software called SummerFest. For a limited time, you can get a 25% discount on new purchases of SpamSieve, EagleFiler, and other top Mac apps from our indie developer friends.

Some interesting engineering issues were:

Previously:

9 Comments RSS · Twitter · Mastodon


> both Gemini and ChatGPT were convinced that this is a well-known exception to standard memory management rules. They said it’s never safe to use managed objects outside of a pool they were fetched in because they depend on internal Core Data objects that the pool will have released

Sounds like a bunch of LLM hallucinating bullshit. If the objects depend on internal objects who are not properly retained by the dependents, that's a bug in the framework.

If you are able to reproduce the crash eventually, enable MallocStackLoggingNoCompact and NSZombieEnabled and see what causes the release.


@Léo Yeah, I just thought it was interesting that they had the same hallucination. I think it’s probably a framework bug either way. The question is how to work around it.


“Gemini and ChatGPT”

Are you using their free version? Paid-only models from Claude and ChatGPT are substantially better and handle pushback well. Agentic coding tools (Claude Code and Codex) can generate hypotheses and validate them by running them on your machine. (Here that would be trying to reproduce the crash with brute force on two Xcode SDKs, I guess.) All for $20 per month.

Here’s what Claude (Opus 5 on “extra” reasoning) has to say via its chat: https://claude.ai/share/385b8fd7-599f-45f0-8410-c2668e38a194

That answer is above my Cocoa skills, but it looks like other good answers I get from Claude Opus, and quite unlike your Gemini and ChatGPT bout.


@Michael What I usually do in such situations is to first find the stack trace of when the object is released, to try and figure out what causes the cascade. Sometimes it's not pretty because objects get released in a follow up runloop, but sometimes it does illuminate what is going on. But this is very difficult without a consistent reproduction.


@Alexandre The agentic brute-forcing sounds like a good next step if my guess turns out to be wrong. I was just using the more advanced free models. Thanks for running it through Opus 5, but there’s nothing new there, I’m afraid. That’s basically the same stuff I got when pushing back. _PFAutoreleasePoolThunk was in the stack trace, so it was the first thing I investigated myself. The explanation about the corpora agreeing is unsatisfying because in both cases they were making bad inferences, not just repeating something that was actually in the training data.

@Léo It’s always released by the reference queue, either when I create a pool that catches and releases the thunk or when Core Data itself processes pending changes at the end of an async block. I think the real issue is why the object is being scheduled for delayed release or perhaps inappropriately released inside the framework before that. If I could reproduce this I could watch all the releases and try to figure out what it’s doing internally. But even then this is all happening outside my code (the particular objects are only fetched in one place, from a single queue, and never passed to other code) so I think it ultimately still becomes a question of how to avoid triggering whatever the framework is doing.


Sorry if I presume stuff, as I don't have access to the code, but you mentioned you `refreshAllObjects()`. What if you delay the refresh by a runloop? Years ago when I worked on a large project with Core Data (in ObjC back then), this was almost a silver bullet in cases where unexplained behavior was happening. Rejigger the runloops. Maybe if you delay after release, the framework will just recreate the required objects, rather than used the ones deemed for release.


@Léo This is happening on a context’s queue, so I don’t think there is a runloop. The suggestion to delay makes sense—I could schedule the refresh on a subsequent async block. But so far I’m only seeing this crash with objects that don’t actually need to be refreshed, so I just removed the refresh entirely.

I do appreciate everyone helping me think through this. Ironically, this seems like a case where maybe SwiftData would help because it doesn’t rely on refreshing to break cycles. I think the accessors just look up the related objects as needed rather than storing references.


@Michael Yes, that's what I meant. On the main queue, a single async block runs on the next runloop cycle. With a free standing queue, an async behaves in a similar manner, even if there is no runloop attached.

What objects are you refreshing? From what I remember, once store changes are imported to the context that owns the stale objects, the context updates its own objects to the latest known state automatically. Anyone listening with KVO on specific objects, or using NSFetchedResultsController to hear about changes en masse, get notified on the context's queue. NSFetchedResultsController might have been main queue only since it is intended mostly for synchronized UI updates.


@Léo I have other contexts observing changes and am not using NSFetchedResultsController, but that’s not relevant. Just think of a simple case where a private context queue is inserting a graph of related objects. If they have relationships they’ll mutually retain each other and never get released after saving, even if I’m not holding onto any of them. So I either need to reset the context (which would break other objects that I am hanging onto) or refresh the unreferenced objects to break the cycles and allow them to be deallocated.

Leave a Comment