vcl/headless/svpinst.cxx     |    7 +++++++
 vcl/inc/headless/svpinst.hxx |    1 +
 vcl/inc/salinst.hxx          |    4 ++++
 vcl/osx/salinst.cxx          |   22 +---------------------
 vcl/quartz/cgutils.mm        |   26 ++++++++++++++++++++++++++
 5 files changed, 39 insertions(+), 21 deletions(-)

New commits:
commit c9551f82891cffbc55e85ea87ff6dac5b1ed114a
Author:     Caolán McNamara <[email protected]>
AuthorDate: Tue Oct 7 21:14:54 2025 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Oct 8 13:09:11 2025 +0200

    Pre-init NSSpellCHecker to avoid SolarMutex deadlock
    
    in headless vclplug too. Same deadlock can happen in that
    mode as seen originally as:
    
    commit d193d65635de197efe32d97a99540a31a5455c41
    Date:   Thu Sep 8 17:30:38 2022 +0200
    
        tdf#151894 Pre-init NSSpellCHecker to avoid SolarMutex deadlock
    
    current example bts are main on thread 1, vcl launched to run on
    its event loop on 'lo_startmain' side thread.
    
    (lldb) thread backtrace 1
    * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
      * frame #0: 0x00000001966f489c libsystem_kernel.dylib`__psynch_mutexwait 
+ 8
        frame #1: 0x0000000196730e58 
libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait + 84
        frame #2: 0x000000019672e840 
libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow + 220
        frame #3: 0x0000000105ad554c 
libuno_sal.dylib.3`osl_acquireMutex(pMutex=<unavailable>) at mutex.cxx:99:20
        frame #4: 0x000000015a6e8370 
libmergedlo.dylib`SvpSalYieldMutex::doAcquire(unsigned int) [inlined] 
osl::Mutex::acquire(this=0x00000001497e7ec8) at mutex.hxx:63:20
        frame #5: 0x000000015a6e8368 
libmergedlo.dylib`SvpSalYieldMutex::doAcquire(this=0x00000001497e7ec0, 
nLockCount=1) at svpinst.cxx:357:18
        frame #6: 0x000000015952fda8 
libmergedlo.dylib`doc_saveAs(_LibreOfficeKitDocument*, char const*, char 
const*, char const*) [inlined] 
comphelper::SolarMutex::acquire(this=0x00000001497e7ec0, nLockCount=1) at 
solarmutex.hxx:86:5
        frame #7: 0x000000015952fd98 
libmergedlo.dylib`doc_saveAs(_LibreOfficeKitDocument*, char const*, char 
const*, char const*) [inlined] 
osl::Guard<comphelper::SolarMutex>::Guard(this=<unavailable>, 
t=0x00000001497e7ec0) at mutex.hxx:144:17
        frame #8: 0x000000015952fd98 
libmergedlo.dylib`doc_saveAs(_LibreOfficeKitDocument*, char const*, char 
const*, char const*) [inlined] 
SolarMutexGuard::SolarMutexGuard(this=<unavailable>) at svapp.hxx:1344:11
        frame #9: 0x000000015952fd90 
libmergedlo.dylib`doc_saveAs(_LibreOfficeKitDocument*, char const*, char 
const*, char const*) [inlined] 
SolarMutexGuard::SolarMutexGuard(this=<unavailable>) at svapp.hxx:1344:78
        frame #10: 0x000000015952fd90 
libmergedlo.dylib`doc_saveAs(pThis=0x000060000124a9a0, 
sUrl="/tmp/ErrareHumanumEst.fodg", pFormat="fodg", 
pFilterOptions="{\"DecomposePDF\":{\"type\":\"boolean\",\"value\":\"true\"}}") 
at init.cxx:3698:21
        ...
        frame #32: 0x0000000102965334 main + 228
    (lldb) thread backtrace 11
      thread #11, name = 'lo_startmain'
        frame #0: 0x00000001966f53cc libsystem_kernel.dylib`__psynch_cvwait + 8
        frame #1: 0x00000001967340e0 libsystem_pthread.dylib`_pthread_cond_wait 
+ 984
        frame #2: 0x0000000197ddefd0 Foundation`-[NSOperation 
waitUntilFinished] + 488
        frame #3: 0x00000001967e28f8 CoreFoundation`_CFXNotificationPost + 804
        frame #4: 0x0000000197d9c680 Foundation`-[NSNotificationCenter 
postNotificationName:object:userInfo:] + 88
        frame #5: 0x000000019a737554 AppKit`-[NSMenu insertItem:atIndex:] + 892
        frame #6: 0x000000019a8f1e00 
AppKit`-[NSApplication(NSServicesMenuPrivate) _fillSpellCheckerPopupButton:] + 
1380
        frame #7: 0x000000019a8f1550 AppKit`-[NSSpellChecker 
_fillSpellCheckerPopupButton:] + 76
        frame #8: 0x000000019a8f0688 AppKit`-[NSSpellChecker init] + 248
        frame #9: 0x000000019a8f0580 AppKit`__36+[NSSpellChecker 
sharedSpellChecker]_block_invoke + 20
        frame #10: 0x000000019659585c 
libdispatch.dylib`_dispatch_client_callout + 16
        frame #11: 0x000000019657ea28 libdispatch.dylib`_dispatch_once_callout 
+ 32
        frame #12: 0x000000019a8f0568 AppKit`+[NSSpellChecker 
sharedSpellChecker] + 140
        frame #13: 0x0000000158dbb054 
libmergedlo.dylib`MacSpellChecker::getLocales(this=0x00006000006fc500) at 
macspellimp.mm:118:42
        frame #14: 0x0000000158da1e94 
libmergedlo.dylib`LngSvcMgr::GetAvailableSpellSvcs_Impl(this=0x000000014966e020)
 at lngsvcmgr.cxx:975:63
        frame #15: 0x0000000158da46f8 
libmergedlo.dylib`LngSvcMgr::getAvailableServices(this=0x000000014966e020, 
rServiceName=0x000000015c044600, rLocale=0x000000016e3ae1d0) at 
lngsvcmgr.cxx:1369:9
        frame #16: 0x0000000158d9c434 
libmergedlo.dylib`LngSvcMgr::UpdateAll(this=0x000000014966e020) at 
lngsvcmgr.cxx:655:46
        frame #17: 0x0000000158d9bd38 
libmergedlo.dylib`LngSvcMgr::LngSvcMgr(this=0x000000014966e020) at 
lngsvcmgr.cxx:414:5
        frame #18: 0x0000000158da7a70 
libmergedlo.dylib`::linguistic_LngSvcMgr_get_implementation(com::sun::star::uno::XComponentContext
 *, const com::sun::star::uno::Sequence<com::sun::star::uno::Any> &) [inlined] 
LngSvcMgr::LngSvcMgr(this=0x000000014966e020) at lngsvcmgr.cxx:402:1
        frame #19: 0x0000000158da7a6c 
libmergedlo.dylib`linguistic_LngSvcMgr_get_implementation((null)=<unavailable>, 
(null)=<unavailable>) at lngsvcmgr.cxx:1816:30
        frame #20: 0x000000012dc30ee0 
libuno_cppuhelpergcc3.dylib.3`cppuhelper::ServiceManager::Data::Implementation::doCreateInstance(com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext>
 const&) [inlined] 
std::__1::__function::__value_func<com::sun::star::uno::XInterface* 
(com::sun::star::uno::XComponentContext*, 
com::sun::star::uno::Sequence<com::sun::star::uno::Any> 
const&)>::operator()[abi:ne190102](this=0x0000000149747c90, 
__args=0x000000016e3ae310, __args=0x000000016e3ae308) const at function.h:430:12
        frame #21: 0x000000012dc30ec4 
libuno_cppuhelpergcc3.dylib.3`cppuhelper::ServiceManager::Data::Implementation::doCreateInstance(com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext>
 const&) [inlined] std::__1::function<com::sun::star::uno::XInterface* 
(com::sun::star::uno::XComponentContext*, 
com::sun::star::uno::Sequence<com::sun::star::uno::Any> 
const&)>::operator()(this= Function = linguistic_LngSvcMgr_get_implementation , 
__arg=0x0000600000cc86b8, __arg=0x000000016e3ae308) const at function.h:989:10
        frame #22: 0x000000012dc30ec4 
libuno_cppuhelpergcc3.dylib.3`cppuhelper::ServiceManager::Data::Implementation::doCreateInstance(this=0x0000000149747c18,
 context=0x000000015c487110) at servicemanager.cxx:701:13
        frame #23: 0x000000012dc30d4c 
libuno_cppuhelpergcc3.dylib.3`cppuhelper::ServiceManager::Data::Implementation::createInstance(this=0x0000000149747c18,
 context=0x000000015c487110, singletonRequest=false) at 
servicemanager.cxx:666:30
        frame #24: 0x000000012dc34ed8 libuno_cppuhelpergcc3.dylib.3`non-virtual 
thunk to cppuhelper::ServiceManager::createInstanceWithContext(rtl::OUString 
const&, com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext> 
const&) [inlined] 
cppuhelper::ServiceManager::createInstanceWithContext(this=<unavailable>, 
aServiceSpecifier=<unavailable>, Context=0x000000015c487110) at 
servicemanager.cxx:1002:36
        frame #25: 0x000000012dc34eb4 libuno_cppuhelpergcc3.dylib.3`non-virtual 
thunk to cppuhelper::ServiceManager::createInstanceWithContext(rtl::OUString 
const&, com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext> 
const&) at servicemanager.cxx:0
        frame #26: 0x00000001588b29c8 
libmergedlo.dylib`com::sun::star::linguistic2::LinguServiceManager::create(the_context=0x000000015c487110)
 at LinguServiceManager.hpp:38:129
        frame #27: 0x00000001588b3444 libmergedlo.dylib`(anonymous 
namespace)::SpellDummy_Impl::GetSpell_Impl() [inlined] GetLngSvcMgr_Impl() at 
unolingu.cxx:60:52
        frame #28: 0x00000001588b3438 libmergedlo.dylib`(anonymous 
namespace)::SpellDummy_Impl::GetSpell_Impl(this=0x0000600002df4300) at 
unolingu.cxx:216:61
        frame #29: 0x00000001588b3314 libmergedlo.dylib`non-virtual thunk to 
(anonymous namespace)::SpellDummy_Impl::isValid(rtl::OUString const&, short, 
com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue> const&) 
[inlined] (anonymous namespace)::SpellDummy_Impl::isValid(this=<unavailable>, 
rWord=<unavailable>, nLanguage=<unavailable>, rProperties=<unavailable>) at 
unolingu.cxx:248:5
        frame #30: 0x00000001588b3310 libmergedlo.dylib`non-virtual thunk to 
(anonymous namespace)::SpellDummy_Impl::isValid(rtl::OUString const&, short, 
com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue> const&) at 
unolingu.cxx:0
        frame #31: 0x00000001588405ec 
libmergedlo.dylib`ImpEditEngine::DoOnlineSpelling(this=0x0000000149859800, 
pThisNodeOnly=<unavailable>, bSpellAtCursorPos=<unavailable>, 
bInterruptible=<unavailable>) at impedit4.cxx:2480:37
        frame #32: 0x0000000134059498 
libsdlo.dylib`SdDrawDocument::SpellObject(this=0x000000014961cb20, 
pObj=0x0000000149646150) at drawdoc4.cxx:885:16
        frame #33: 0x00000001340591b0 
libsdlo.dylib`SdDrawDocument::OnlineSpellingHdl(this=0x000000014961cb20, 
(null)=<unavailable>) at drawdoc4.cxx:823:17
        frame #34: 0x000000015a52b9b0 
libmergedlo.dylib`Scheduler::CallbackTaskScheduling() at scheduler.cxx:584:20
        frame #35: 0x000000015a6e7bf8 
libmergedlo.dylib`SvpSalInstance::CheckTimeout(bool) [inlined] 
SalTimer::CallCallback(this=<unavailable>) at saltimer.hxx:53:13
        frame #36: 0x000000015a6e7bec 
libmergedlo.dylib`SvpSalInstance::CheckTimeout(this=<unavailable>, 
bExecuteTimers=true) at svpinst.cxx:167:53
        frame #37: 0x000000015a6e863c 
libmergedlo.dylib`SvpSalInstance::ImplYield(this=0x00006000008c50e0, 
bWait=true, bHandleAllCurrentEvents=false) at svpinst.cxx:430:17
        frame #38: 0x000000015a6e8950 
libmergedlo.dylib`SvpSalInstance::DoYield(this=0x00006000008c50e0, bWait=true, 
bHandleAllCurrentEvents=false) at svpinst.cxx:504:21
        frame #39: 0x000000015a53d920 libmergedlo.dylib`ImplYield(i_bWait=true, 
i_bAllEvents=false) at svapp.cxx:389:48
        frame #40: 0x000000015a53d328 libmergedlo.dylib`Application::Yield() at 
svapp.cxx:492:5 [artificial]
        frame #41: 0x000000015a53d210 libmergedlo.dylib`Application::Execute() 
at svapp.cxx:364:13
        frame #42: 0x00000001594ffeb4 
libmergedlo.dylib`desktop::Desktop::Main(this=0x000000016e3aeed0) at 
app.cxx:1680:13
        frame #43: 0x000000015a5475d0 libmergedlo.dylib`ImplSVMain() at 
svmain.cxx:228:35
        frame #44: 0xffffffffffffffff libmergedlo.dylib`SVMain()
        frame #45: 0x000000015951e5e4 libmergedlo.dylib`soffice_main at 
sofficemain.cxx:121:12
        frame #46: 0x00000001595669bc 
libmergedlo.dylib`lo_startmain((null)=<unavailable>) at init.cxx:7860:5
        frame #47: 0x0000000105ae59d8 
libuno_sal.dylib.3`osl_thread_start_Impl(pData=0x00006000005cb330) at 
thread.cxx:240:9
        frame #48: 0x0000000196733c0c libsystem_pthread.dylib`_pthread_start + 
136
    
    Change-Id: I8d25ed364f131434ce4f738604ddcf3f586cf6d9
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192052
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx
index 267c5335a896..4220f5224ff6 100644
--- a/vcl/headless/svpinst.cxx
+++ b/vcl/headless/svpinst.cxx
@@ -106,6 +106,13 @@ SvpSalInstance::SvpSalInstance( 
std::unique_ptr<SalYieldMutex> pMutex )
 #endif
 }
 
+void SvpSalInstance::AfterAppInit()
+{
+#if defined(MACOSX)
+    SalInstance::MacStartupWorkarounds();
+#endif
+}
+
 SvpSalInstance::~SvpSalInstance()
 {
     if( s_pDefaultInstance == this )
diff --git a/vcl/inc/headless/svpinst.hxx b/vcl/inc/headless/svpinst.hxx
index f3d9205a8981..e600259829b9 100644
--- a/vcl/inc/headless/svpinst.hxx
+++ b/vcl/inc/headless/svpinst.hxx
@@ -110,6 +110,7 @@ public:
     static SvpSalInstance*  s_pDefaultInstance;
 
     SvpSalInstance( std::unique_ptr<SalYieldMutex> pMutex );
+    virtual void AfterAppInit() override;
     virtual ~SvpSalInstance() override;
 
     SAL_DLLPRIVATE bool ImplYield(bool bWait, bool bHandleAllCurrentEvents);
diff --git a/vcl/inc/salinst.hxx b/vcl/inc/salinst.hxx
index b8549b23fc5f..f3af5cf3794d 100644
--- a/vcl/inc/salinst.hxx
+++ b/vcl/inc/salinst.hxx
@@ -83,6 +83,10 @@ protected:
     bool m_bSupportsBitmap32 = false;
     bool m_bSupportsOpenGL = false;
 
+#ifdef MACOSX
+    static void MacStartupWorkarounds();
+#endif
+
 public:
     SalInstance(std::unique_ptr<comphelper::SolarMutex> pMutex);
     virtual ~SalInstance();
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
index 6d6cb6d49989..85202f7cf765 100644
--- a/vcl/osx/salinst.cxx
+++ b/vcl/osx/salinst.cxx
@@ -212,27 +212,7 @@ void AquaSalInstance::AfterAppInit()
                                            object: nil ];
 #endif
 
-    // HACK: When the first call to [NSSpellChecker sharedSpellChecker] (in
-    // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm) is done 
both on a thread other
-    // than the main thread and with the SolarMutex erroneously locked, then 
that can lead to
-    // deadlock as [NSSpellChecker sharedSpellChecker] internally calls
-    //   AppKit`-[NSSpellChecker init] ->
-    //   AppKit`-[NSSpellChecker _fillSpellCheckerPopupButton:] ->
-    //   AppKit`-[NSApplication(NSServicesMenuPrivate) 
_fillSpellCheckerPopupButton:] ->
-    //   AppKit`-[NSMenu insertItem:atIndex:] ->
-    //   Foundation`-[NSNotificationCenter 
postNotificationName:object:userInfo:] ->
-    //   CoreFoundation`_CFXNotificationPost ->
-    //   Foundation`-[NSOperation waitUntilFinished]
-    // waiting for work to be done on the main thread, but the main thread is 
typically already
-    // blocked (in some event handling loop) waiting to acquire the 
SolarMutex.  The real solution
-    // would be to fix all the cases where a call to [NSSpellChecker 
sharedSpellChecker] in
-    // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm is done 
while the SolarMutex is
-    // locked (somewhere up the call chain), but that appears to be rather 
difficult (see e.g.
-    // <https://bugs.documentfoundation.org/show_bug.cgi?id=151894> "FILEOPEN 
a Base Document with
-    // customized event for open a startform by 'open document' LO stuck").  
So, at least for now,
-    // chicken out and do that first call to [NSSpellChecker 
sharedSpellChecker] upfront in a
-    // controlled environment:
-    [NSSpellChecker sharedSpellChecker];
+    SalInstance::MacStartupWorkarounds();
 }
 
 SalYieldMutex::SalYieldMutex()
diff --git a/vcl/quartz/cgutils.mm b/vcl/quartz/cgutils.mm
index 50b7fcd6540c..f32df8f44772 100644
--- a/vcl/quartz/cgutils.mm
+++ b/vcl/quartz/cgutils.mm
@@ -20,6 +20,7 @@
 #include <quartz/cgutils.h>
 
 #include <salbmp.hxx>
+#include <salinst.hxx>
 #ifdef MACOSX
 #include <osx/saldata.hxx>
 #else
@@ -136,6 +137,31 @@ bool DefaultMTLDeviceIsSupported()
     return bRet;
 }
 
+void SalInstance::MacStartupWorkarounds()
+{
+    // HACK: When the first call to [NSSpellChecker sharedSpellChecker] (in
+    // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm) is done 
both on a thread other
+    // than the main thread and with the SolarMutex erroneously locked, then 
that can lead to
+    // deadlock as [NSSpellChecker sharedSpellChecker] internally calls
+    //   AppKit`-[NSSpellChecker init] ->
+    //   AppKit`-[NSSpellChecker _fillSpellCheckerPopupButton:] ->
+    //   AppKit`-[NSApplication(NSServicesMenuPrivate) 
_fillSpellCheckerPopupButton:] ->
+    //   AppKit`-[NSMenu insertItem:atIndex:] ->
+    //   Foundation`-[NSNotificationCenter 
postNotificationName:object:userInfo:] ->
+    //   CoreFoundation`_CFXNotificationPost ->
+    //   Foundation`-[NSOperation waitUntilFinished]
+    // waiting for work to be done on the main thread, but the main thread is 
typically already
+    // blocked (in some event handling loop) waiting to acquire the 
SolarMutex.  The real solution
+    // would be to fix all the cases where a call to [NSSpellChecker 
sharedSpellChecker] in
+    // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm is done 
while the SolarMutex is
+    // locked (somewhere up the call chain), but that appears to be rather 
difficult (see e.g.
+    // <https://bugs.documentfoundation.org/show_bug.cgi?id=151894> "FILEOPEN 
a Base Document with
+    // customized event for open a startform by 'open document' LO stuck").  
So, at least for now,
+    // chicken out and do that first call to [NSSpellChecker 
sharedSpellChecker] upfront in a
+    // controlled environment:
+    [NSSpellChecker sharedSpellChecker];
+}
+
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to