Skip to content

Fix main thread crash in userDefaultsDidChange: (#320)#323

Merged
schuyler merged 1 commit into
mainfrom
claude/resolve-issue-320-FUniN
Feb 20, 2026
Merged

Fix main thread crash in userDefaultsDidChange: (#320)#323
schuyler merged 1 commit into
mainfrom
claude/resolve-issue-320-FUniN

Conversation

@schuyler

@schuyler schuyler commented Feb 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace selector-based NSUserDefaultsDidChangeNotification observer with block-based addObserverForName:object:queue:usingBlock: using [NSOperationQueue mainQueue] to guarantee main-thread delivery
  • Store opaque observer token as a property and explicitly remove it in close
  • Add unit test verifying main-thread delivery when notification is posted from a background thread

Problem

NSUserDefaultsDidChangeNotification can fire from background threads (observed on macOS 15 during XCTest bootstrap), causing userDefaultsDidChange: to access NSTextView off the main thread, triggering a Main Thread Checker crash.

Changes

MacDown/Code/Document/MPDocument.m (3 localized changes):

  1. Added userDefaultsObserverToken property in class extension
  2. Replaced observer registration with block-based API + [NSOperationQueue mainQueue] + __weak self
  3. Added explicit token cleanup in close before the blanket removeObserver:self

MacDownTests/MPNotificationTests.m (1 new test):

  • testUserDefaultsNotificationFromBackgroundThreadDeliveredOnMainThread — posts notification from background thread, asserts main-thread delivery

Test plan

  • CI passes on all 4 runners (macOS 14, 15 ARM, 15 Intel, 26)
  • New test validates the fix directly
  • Existing tests unaffected (no regressions)

Related to #320

…efaultsDidChangeNotification

NSUserDefaultsDidChangeNotification can fire from background threads (observed on
macOS 15 during XCTest bootstrap), causing userDefaultsDidChange: to access
NSTextView off the main thread, triggering a Main Thread Checker crash.

Switch from addObserver:selector:name:object: to
addObserverForName:object:queue:usingBlock: with [NSOperationQueue mainQueue]
to guarantee main-thread delivery. Store the returned opaque observer token
as a property and explicitly remove it in close.

Related to #320

https://claude.ai/code/session_01JrSNCCvdharCeVAoFYw3vq
@github-actions

github-actions Bot commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 55.20%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       59.07% (7882/13344) 
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/MPMathJaxListener.m                                                0.00% (0/26)        
        -[MPMathJaxListener callbacks]                                                                                                 0.00% (0/5)         
        -[MPMathJaxListener addCallback:forKey:]                                                                                       0.00% (0/3)         
        -[MPMathJaxListener invokeCallbackForKey:]                                                                                     0.00% (0/8)         
        +[MPMathJaxListener isSelectorExcludedFromWebScript:]                                                                          0.00% (0/5)         
        +[MPMathJaxListener isKeyExcludedFromWebScript:]                                                                               0.00% (0/5)         
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Extension/DOMNode+Text.m                                                   0.00% (0/94)        
        -[NSString(WordCount) numberOfWords]                                                                                           0.00% (0/11)        
        __36-[NSString(WordCount) numberOfWords]_block_invoke                                                                          0.00% (0/3)         
        -[NSString(WordCount) lengthWithoutNewlines]                                                                                   0.00% (0/11)        
        __44-[NSString(WordCount) lengthWithoutNewlines]_block_invoke                                                                  0.00% (0/3)         
        -[NSString(WordCount) lengthWithoutWhitespacesAndNewlines]                                                                     0.00% (0/11)        
        __58-[NSString(WordCount) lengthWithoutWhitespacesAndNewlines]_block_invoke                                                    0.00% (0/3)         
        MPGetChildrenNodetextCount                                                                                                     0.00% (0/6)         
        MPGetNodeTextCount                                                                                                             0.00% (0/38)        
        -[DOMNode(Text) textCount]                                                                                                     0.00% (0/8)         
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/FileURLInlining.m                                                  26.60% (25/94)      
        +[FileURLInlining inlineFromIterable:]                                                                                         0.00% (0/11)        
        -[FileURLInlining initWithURL:]                                                                                                0.00% (0/6)         
        -[FileURLInlining init]                                                                                                        100.00% (3/3)       
        -[FileURLInlining inlineContent]                                                                                               0.00% (0/9)         
        -[FileURLInlining imageContent]                                                                                                0.00% (0/12)        
        +[FileURLInlining mimeTypeForFilePath:]                                                                                        100.00% (22/22)     
        -[FileURLInlining clippingContent]                                                                                             0.00% (0/4)         
        +[FileURLInlining isTextClippingAtPath:]                                                                                       0.00% (0/12)        
        +[FileURLInlining textContentFromClipping:]                                                                                    0.00% (0/15)        
    /Users/runner/work/macdown3000/macdown3000/Dependency/peg-markdown-highlight/HGMarkdownHighlightingStyle.m                         87.69% (57/65)      
        +[HGMarkdownHighlightingStyle colorFromARGBColor:]                                                                             100.00% (6/6)       
        -[HGMarkdownHighlightingStyle initWithType:attributesToAdd:toRemove:fontTraitsToAdd:]                                          88.89% (8/9)        
        -[HGMarkdownHighlightingStyle initWithStyleAttributes:baseFont:]                                                               86.00% (43/50)      
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Extension/hoedown_html_patch.c                                             86.47% (115/133)    
        hoedown_patch_reset_checkbox_index                                                                                             100.00% (3/3)       
        hoedown_patch_get_checkbox_index                                                                                               0.00% (0/3)         
        hoedown_patch_render_blockcode                                                                                                 89.09% (49/55)      
        hoedown_patch_render_listitem                                                                                                  100.00% (44/44)     
        hoedown_patch_render_toc_header                                                                                                67.86% (19/28)      
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Application/MPMainController.m                                             41.78% (94/225)     
        MPOpenBundledFile                                                                                                              95.83% (23/24)      
        treat                                                                                                                          20.00% (6/30)       
        __treat_block_invoke                                                                                                           0.00% (0/3)         
        -[MPMainController applicationDidFinishLaunching:]                                                                             100.00% (6/6)       
        -[MPMainController openUrlSchemeAppleEvent:withReplyEvent:]                                                                    0.00% (0/39)        
        -[MPMainController valueForKey:fromQueryItems:]                                                                                0.00% (0/5)         
        -[MPMainController preferences]                                                                                                100.00% (3/3)       
        -[MPMainController preferencesWindowController]                                                                                0.00% (0/18)        
        -[MPMainController showPreferencesWindow:]                                                                                     0.00% (0/2)         

... (2661 more lines truncated)

📊 **Full coverage report available in workflow artifacts**

@schuyler schuyler merged commit 0d0ebb6 into main Feb 20, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants