vcl/win/dtrans/WinClipboard.cxx | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-)
New commits: commit 0aaf48d7b3213aba969be24f90d1b9cb39411a3c Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Tue Nov 5 15:58:07 2024 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Wed Nov 13 21:37:33 2024 +0500 Related: tdf#163730 Release the object in separate thread Using a local build having both commits 9f53d40fd19b22fe1bdbf64e8ce751cf53f4f517 "Related: tdf#163730 Avoid deadlock", 2024-11-02, and 3015db08c9a8a1b29af1018044955c905c9015aa "Related: tdf#163730 Avoid potential deadlock", 2024-11-03, I got another deadlock (unfortunately, I didn't copy call stacks), where main thread, holding soler mutex, called CWinClipboard::setContents, and that tried to lock solar mutex in CMtaOleClipboard::run thread, which released the m_foreignContent data. This change releases m_foreignContent in a separate thread, which has no dependencies, and so its wait wouldn't block other threads. Change-Id: If4c486e6f3ba9201c4c9c6991992e38929ab3b81 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176047 Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Tested-by: Jenkins (cherry picked from commit e1beaf844f5d6fdb0a72cdd70f2bbe29b699fe46) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176066 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/vcl/win/dtrans/WinClipboard.cxx b/vcl/win/dtrans/WinClipboard.cxx index 3603cf55f5d9..1fd2a9412346 100644 --- a/vcl/win/dtrans/WinClipboard.cxx +++ b/vcl/win/dtrans/WinClipboard.cxx @@ -53,6 +53,23 @@ namespace { CWinClipboard* s_pCWinClipbImpl = nullptr; std::mutex s_aClipboardSingletonMutex; + +unsigned __stdcall releaseAsyncProc(void* p) +{ + static_cast<css::datatransfer::XTransferable*>(p)->release(); + return 0; +} + +void releaseAsync(css::uno::Reference<css::datatransfer::XTransferable>& ref) +{ + if (!ref) + return; + auto pInterface = ref.get(); + pInterface->acquire(); + ref.clear(); // before starting the thread, to avoid race + if (auto handle = _beginthreadex(nullptr, 0, releaseAsyncProc, pInterface, 0, nullptr)) + CloseHandle(reinterpret_cast<HANDLE>(handle)); +} } /*XEventListener,*/ @@ -172,9 +189,6 @@ void SAL_CALL CWinClipboard::setContents( const uno::Reference<datatransfer::XTransferable>& xTransferable, const uno::Reference<datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) { - // The object must be destroyed only outside of the mutex lock, because it may call - // CWinClipboard::onReleaseDataObject in another thread of this process synchronously - css::uno::Reference<css::datatransfer::XTransferable> prev_foreignContent; std::unique_lock aGuard(m_aMutex); if (m_bDisposed) @@ -183,7 +197,10 @@ void SAL_CALL CWinClipboard::setContents( IDataObjectPtr pIDataObj; - prev_foreignContent = std::move(m_foreignContent); // clear m_foreignContent + // The object must be destroyed only outside of the mutex lock, and in a different thread, + // because it may call CWinClipboard::onReleaseDataObject, or try to lock solar mutex, in + // another thread of this process synchronously + releaseAsync(m_foreignContent); // clear m_foreignContent assert(!m_foreignContent.is()); m_pCurrentOwnClipContent = nullptr; @@ -287,39 +304,37 @@ void SAL_CALL CWinClipboard::removeClipboardListener( void CWinClipboard::handleClipboardContentChanged() { - // The object must be destroyed only outside of the mutex lock, because it may call - // CWinClipboard::onReleaseDataObject in another thread of this process synchronously - css::uno::Reference<css::datatransfer::XTransferable> old_foreignContent; - { - std::unique_lock aGuard(m_aMutex); - if (m_bDisposed) - return; + std::unique_lock aGuard(m_aMutex); + if (m_bDisposed) + return; - old_foreignContent = std::move(m_foreignContent); // clear m_foreignContent - assert(!m_foreignContent.is()); - // If new own content assignment is pending, do it; otherwise, clear it. - // This makes sure that there will be no stuck clipboard content. - m_pCurrentOwnClipContent = std::exchange(m_pNewOwnClipContent, nullptr); + // The object must be destroyed only outside of the mutex lock, and in a different thread, + // because it may call CWinClipboard::onReleaseDataObject, or try to lock solar mutex, in + // another thread of this process synchronously + releaseAsync(m_foreignContent); // clear m_foreignContent + assert(!m_foreignContent.is()); + // If new own content assignment is pending, do it; otherwise, clear it. + // This makes sure that there will be no stuck clipboard content. + m_pCurrentOwnClipContent = std::exchange(m_pNewOwnClipContent, nullptr); - if (!maClipboardListeners.getLength(aGuard)) - return; + if (!maClipboardListeners.getLength(aGuard)) + return; - try - { - uno::Reference<datatransfer::XTransferable> rXTransf(getContents_noLock()); - datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this), - rXTransf); - maClipboardListeners.notifyEach( - aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent); - } - catch (const lang::DisposedException&) - { - OSL_FAIL("Service Manager disposed"); + try + { + uno::Reference<datatransfer::XTransferable> rXTransf(getContents_noLock()); + datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this), + rXTransf); + maClipboardListeners.notifyEach( + aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent); + } + catch (const lang::DisposedException&) + { + OSL_FAIL("Service Manager disposed"); - aGuard.unlock(); - // no further clipboard changed notifications - unregisterClipboardViewer(); - } + aGuard.unlock(); + // no further clipboard changed notifications + unregisterClipboardViewer(); } } commit 3cce721d1a93e78d857fd4a8e41301a99844e638 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun Nov 3 22:49:28 2024 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Wed Nov 13 21:37:33 2024 +0500 Related: tdf#163730 Avoid potential deadlock Similar to commit 43e5118496ae0c9b8f81a54574874eda7d439dbb (Related: tdf#163730 Avoid deadlock, 2024-11-03). I haven't seen this scenario myself, but seems likely to be possible. Change-Id: Ie6bb69e7ebe12a69e4dabee9103de32611235807 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175971 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> (cherry picked from commit 3015db08c9a8a1b29af1018044955c905c9015aa) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175920 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/vcl/win/dtrans/WinClipboard.cxx b/vcl/win/dtrans/WinClipboard.cxx index 5760155beaa1..3603cf55f5d9 100644 --- a/vcl/win/dtrans/WinClipboard.cxx +++ b/vcl/win/dtrans/WinClipboard.cxx @@ -172,6 +172,9 @@ void SAL_CALL CWinClipboard::setContents( const uno::Reference<datatransfer::XTransferable>& xTransferable, const uno::Reference<datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) { + // The object must be destroyed only outside of the mutex lock, because it may call + // CWinClipboard::onReleaseDataObject in another thread of this process synchronously + css::uno::Reference<css::datatransfer::XTransferable> prev_foreignContent; std::unique_lock aGuard(m_aMutex); if (m_bDisposed) @@ -180,7 +183,8 @@ void SAL_CALL CWinClipboard::setContents( IDataObjectPtr pIDataObj; - m_foreignContent.clear(); + prev_foreignContent = std::move(m_foreignContent); // clear m_foreignContent + assert(!m_foreignContent.is()); m_pCurrentOwnClipContent = nullptr; if (xTransferable.is()) @@ -284,7 +288,7 @@ void SAL_CALL CWinClipboard::removeClipboardListener( void CWinClipboard::handleClipboardContentChanged() { // The object must be destroyed only outside of the mutex lock, because it may call - // CWinClipboard::onReleaseDataObject in another thread of this process + // CWinClipboard::onReleaseDataObject in another thread of this process synchronously css::uno::Reference<css::datatransfer::XTransferable> old_foreignContent; { std::unique_lock aGuard(m_aMutex); commit 41875ef8c667d77260f238b9acf9513523b15153 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun Nov 3 00:10:17 2024 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Wed Nov 13 21:37:33 2024 +0500 Related: tdf#163730 Avoid deadlock Seen locally, with main thread querying clipboard state: vclplug_winlo.dll!std::unique_lock<std::mutex>::unique_lock<std::mutex>(std::mutex & _Mtx) Line 145 vclplug_winlo.dll!CWinClipboard::getContents() Line 109 vcllo.dll!TransferableDataHelper::CreateFromClipboard(const com::sun::star::uno::Reference<com::sun::star::datatransfer::clipboard::XClipboard> & rClipboard) Line 2162 vcllo.dll!TransferableDataHelper::CreateFromSystemClipboard(vcl::Window * pWindow) Line 2188 swlo.dll!SwBaseShell::StateClpbrd(SfxItemSet & rSet) Line 602 swlo.dll!SfxStubSwBaseShellStateClpbrd(SfxShell * pShell, SfxItemSet & rSet) Line 2220 sfxlo.dll!SfxDispatcher::FillState_(const SfxSlotServer & rSvr, SfxItemSet & rState, const SfxSlot * pRealSlot) Line 1726 sfxlo.dll!SfxBindings::Update_Impl(SfxStateCache & rCache) Line 267 sfxlo.dll!SfxBindings::NextJob_Impl(const Timer * pTimer) Line 1280 sfxlo.dll!SfxBindings::NextJob(Timer * pTimer) Line 1225 sfxlo.dll!SfxBindings::LinkStubNextJob(void * instance, Timer * data) Line 1220 vcllo.dll!Link<Timer *,void>::Call(Timer * data) Line 111 vcllo.dll!Timer::Invoke() Line 75 vcllo.dll!Scheduler::CallbackTaskScheduling() Line 509 vcllo.dll!SalTimer::CallCallback() Line 53 vclplug_winlo.dll!WinSalTimer::ImplHandleElapsedTimer() Line 169 vclplug_winlo.dll!ImplSalYield(bool bWait, bool bHandleAllCurrentEvents) Line 525 vclplug_winlo.dll!WinSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) Line 581 vcllo.dll!ImplYield(bool i_bWait, bool i_bAllEvents) Line 385 vcllo.dll!Application::Yield() Line 473 vcCMtaOleClipboard::runllo.dll!Application::Execute() Line 361 sofficeapp.dll!desktop::Desktop::Main() Line 1679 CMtaOleClipboard::clipboardChangeNotifier thread holding CWinClipboard's mutex in handleClipboardContentChanged, and waiting for the destruction of IDataObject released from m_foreignContent (which was redirected to CMtaOleClipboard::run thread): vclplug_winlo.dll!sal::systools::COMReference<IDataObject>::release(IDataObject * ptr) Line 235 vclplug_winlo.dll!sal::systools::COMReference<IDataObject>::~COMReference<IDataObject>() Line 163 vclplug_winlo.dll!CAPNDataObject::~CAPNDataObject() Line 97 vclplug_winlo.dll!CAPNDataObject::`scalar deleting destructor'(unsigned int) vclplug_winlo.dll!CAPNDataObject::Release() Line 137 vclplug_winlo.dll!sal::systools::COMReference<IDataObject>::release(IDataObject * ptr) Line 235 vclplug_winlo.dll!sal::systools::COMReference<IDataObject>::~COMReference<IDataObject>() Line 163 vclplug_winlo.dll!CDOTransferable::~CDOTransferable() vclplug_winlo.dll!CDOTransferable::`scalar deleting destructor'(unsigned int) cppuhelper3MSC.dll!cppu::OWeakObject::release() Line 230 vclplug_winlo.dll!cppu::WeakImplHelper<com::sun::star::datatransfer::XTransferable>::release() Line 115 vclplug_winlo.dll!com::sun::star::uno::Reference<com::sun::star::datatransfer::XTransferable>::clear() Line 234 vclplug_winlo.dll!CWinClipboard::handleClipboardContentChanged() Line 291 vclplug_winlo.dll!CWinClipboard::onClipboardContentChanged() Line 385 vclplug_winlo.dll!CMtaOleClipboard::clipboardChangedNotifierThreadProc(void * pParam) Line 721 and CMtaOleClipboard::run thread waiting for CWinClipboard's mutex in CWinClipboard::onReleaseDataObject: vclplug_winlo.dll!std::_Mutex_base::lock() Line 52 vclplug_winlo.dll!std::unique_lock<std::mutex>::unique_lock<std::mutex>(std::mutex & _Mtx) Line 145 vclplug_winlo.dll!CWinClipboard::onReleaseDataObject(CXNotifyingDataObject & theCaller) Line 362 vclplug_winlo.dll!CXNotifyingDataObject::Release() Line 75 ole32.dll!CClipDataObject::Release() Line 960 combase.dll!... rpcrt4.dll!Invoke() rpcrt4.dll!Ndr64StubWorker(void *,void *,struct _RPC_MESSAGE *,struct _MIDL_SERVER_INFO_ *,long (*const *)(void),struct _MIDL_SYNTAX_INFO *,unsigned long *) rpcrt4.dll!NdrStubCall3() combase.dll!... user32.dll!UserCallWinProcCheckWow(struct _ACTIVATION_CONTEXT *,__int64 (*)(struct tagWND *,unsigned int,unsigned __int64,__int64),struct HWND__ *,enum _WM_VALUE,unsigned __int64,__int64,void *,int) user32.dll!DispatchMessageWorker() vclplug_winlo.dll!CMtaOleClipboard::run() Line 655 vclplug_winlo.dll!CMtaOleClipboard::oleThreadProc(void * pParam) Line 673 Caused by changes in commit 2e0664015255ffc0f76a11a9cb254564b34de496 (tdf#148647: make sure to update own content on Win clipboard change, 2024-07-14). Change-Id: I26d35726f3d3f650a2db2ac63709ed820a60fc4e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175956 Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Tested-by: Jenkins (cherry picked from commit 9f53d40fd19b22fe1bdbf64e8ce751cf53f4f517) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175919 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/vcl/win/dtrans/WinClipboard.cxx b/vcl/win/dtrans/WinClipboard.cxx index be0266addd18..5760155beaa1 100644 --- a/vcl/win/dtrans/WinClipboard.cxx +++ b/vcl/win/dtrans/WinClipboard.cxx @@ -283,33 +283,39 @@ void SAL_CALL CWinClipboard::removeClipboardListener( void CWinClipboard::handleClipboardContentChanged() { - std::unique_lock aGuard(m_aMutex); - if (m_bDisposed) - return; + // The object must be destroyed only outside of the mutex lock, because it may call + // CWinClipboard::onReleaseDataObject in another thread of this process + css::uno::Reference<css::datatransfer::XTransferable> old_foreignContent; + { + std::unique_lock aGuard(m_aMutex); + if (m_bDisposed) + return; - m_foreignContent.clear(); - // If new own content assignment is pending, do it; otherwise, clear it. - // This makes sure that there will be no stuck clipboard content. - m_pCurrentOwnClipContent = std::exchange(m_pNewOwnClipContent, nullptr); + old_foreignContent = std::move(m_foreignContent); // clear m_foreignContent + assert(!m_foreignContent.is()); + // If new own content assignment is pending, do it; otherwise, clear it. + // This makes sure that there will be no stuck clipboard content. + m_pCurrentOwnClipContent = std::exchange(m_pNewOwnClipContent, nullptr); - if (!maClipboardListeners.getLength(aGuard)) - return; + if (!maClipboardListeners.getLength(aGuard)) + return; - try - { - uno::Reference<datatransfer::XTransferable> rXTransf(getContents_noLock()); - datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this), - rXTransf); - maClipboardListeners.notifyEach( - aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent); - } - catch (const lang::DisposedException&) - { - OSL_FAIL("Service Manager disposed"); + try + { + uno::Reference<datatransfer::XTransferable> rXTransf(getContents_noLock()); + datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this), + rXTransf); + maClipboardListeners.notifyEach( + aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent); + } + catch (const lang::DisposedException&) + { + OSL_FAIL("Service Manager disposed"); - aGuard.unlock(); - // no further clipboard changed notifications - unregisterClipboardViewer(); + aGuard.unlock(); + // no further clipboard changed notifications + unregisterClipboardViewer(); + } } }