Date: prev next · Thread: first prev next last
2013 Archives by date, by thread · List index


Hi,

I have submitted a patch for review:

    https://gerrit.libreoffice.org/3310

To pull it, you can do:

    git pull ssh://gerrit.libreoffice.org:29418/core refs/changes/10/3310/1

fdo#63391 deadlock on opening .odb file that auto-connects to the database

Let foo.odb be a database file that has a macro that connects to the
Database on "Open Document" event (and needs to prompt user for
user/password).

There was a race condition between two actions:

 1) the asynchronous treatment of "OnFirstControllerConnected" in dbaui::OApplicationController,
    which tries to get dbaui::OApplicationController's mutex

 2) the StarBasic macro calling dbaui::OApplicationController::connect
    which needs to display a dialog (to get username and password),
    and thus puts that dialog in the main thread's event queue
    and waits for it ... with dbaui::OApplicationController's mutex held

Now, if "1)" is before "2)" in the event queue of the the main thread,
*but* "1)" is executed *after* "2)" has taken the lock, there is a deadlock.

Fix:

 1) Make OnFirstControllerConnected synchronous.
    Make sure (by taking mutex in dbaui::OApplicationController::attachFrame, its ancestor in the 
call graph)
    that nothing else will happen with the OApplicationController as long as it is not finished.
    ---> it does not need to take mutex itself anymore

    This avoids the "order in the asynchronous events" dependency.

 2) Change dbaui::OApplicationController::ensureConnection to do the user prompting
    WITHOUT HOLDING the mutex, and use the mutex "only" to protect actually assigning
    the connection to m_xDataSourceConnection.

    Theoretically, in some race condition, we could connect twice and then discard one connection 
<shrug>.
    ensureConnection will never return the discarded connection, though.

    (I think I got that right with respect to 
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)

    This keeps it from locking on another condition while holding the mutex.

Change-Id: Iab1bbec5d5df12bb89d027d43e498c78c92ffc32
---
M dbaccess/source/ui/app/AppController.cxx
M dbaccess/source/ui/app/AppController.hxx
M dbaccess/source/ui/app/AppControllerDnD.cxx
M dbaccess/source/ui/app/AppControllerGen.cxx
4 files changed, 63 insertions(+), 22 deletions(-)



diff --git a/dbaccess/source/ui/app/AppController.cxx b/dbaccess/source/ui/app/AppController.cxx
index 53b6555..c943ed3 100644
--- a/dbaccess/source/ui/app/AppController.cxx
+++ b/dbaccess/source/ui/app/AppController.cxx
@@ -304,7 +304,6 @@
     ,m_aTableCopyHelper(this)
     ,m_pClipbordNotifier(NULL)
     ,m_nAsyncDrop(0)
-    ,m_aControllerConnectedEvent( LINK( this, OApplicationController, OnFirstControllerConnected ) 
)
     ,m_aSelectContainerEvent( LINK( this, OApplicationController, OnSelectContainer ) )
     ,m_ePreviewMode(E_PREVIEWNONE)
     ,m_eCurrentType(E_NONE)
@@ -361,8 +360,6 @@
 //--------------------------------------------------------------------
 void SAL_CALL OApplicationController::disposing()
 {
-    m_aControllerConnectedEvent.CancelCall();
-
     
::std::for_each(m_aCurrentContainers.begin(),m_aCurrentContainers.end(),XContainerFunctor(this));
     m_aCurrentContainers.clear();
     m_pSubComponentManager->disposing();
@@ -2644,14 +2641,12 @@
         return;
     }
 
-    m_aControllerConnectedEvent.Call();
+    OnFirstControllerConnected();
 }
 
 // -----------------------------------------------------------------------------
-IMPL_LINK( OApplicationController, OnFirstControllerConnected, void*, /**/ )
+void OApplicationController::OnFirstControllerConnected()
 {
-    ::osl::MutexGuard aGuard( getMutex() );
-
     if ( !m_xModel.is() )
     {
         OSL_FAIL( "OApplicationController::OnFirstControllerConnected: too late!" );
@@ -2665,7 +2660,7 @@
         // no need to show this warning, obviously the document supports embedding scripts
         // into itself, so there are no "old-style" forms/reports which have macros/scripts
         // themselves
-        return 0L;
+        return;
     }
 
     try
@@ -2674,12 +2669,12 @@
         // In this case, we should not show the warning, again.
         ::comphelper::NamedValueCollection aModelArgs( m_xModel->getArgs() );
         if ( aModelArgs.getOrDefault( "SuppressMigrationWarning", sal_False ) )
-            return 0L;
+            return;
 
         // also, if the document is read-only, then no migration is possible, and the
         // respective menu entry is hidden. So, don't show the warning in this case, too.
         if ( Reference< XStorable >( m_xModel, UNO_QUERY_THROW )->isReadonly() )
-            return 0L;
+            return;
 
         SQLWarning aWarning;
         aWarning.Message = OUString( ModuleRes( STR_SUB_DOCS_WITH_SCRIPTS ) );
@@ -2695,12 +2690,14 @@
         DBG_UNHANDLED_EXCEPTION();
     }
 
-    return 1L;
+    return;
 }
 
 // -----------------------------------------------------------------------------
 void SAL_CALL OApplicationController::attachFrame( const Reference< XFrame > & i_rxFrame ) throw( 
RuntimeException )
 {
+    ::osl::MutexGuard aGuard( getMutex() );
+
     OApplicationController_CBASE::attachFrame( i_rxFrame );
     if ( getFrame().is() )
         onAttachedFrame();
diff --git a/dbaccess/source/ui/app/AppController.hxx b/dbaccess/source/ui/app/AppController.hxx
index faf029d..bf34876 100644
--- a/dbaccess/source/ui/app/AppController.hxx
+++ b/dbaccess/source/ui/app/AppController.hxx
@@ -121,8 +121,7 @@
         OTableCopyHelper        m_aTableCopyHelper;
         TransferableClipboardListener*
                                 m_pClipbordNotifier;        // notifier for changes in the 
clipboard
-        sal_uLong                   m_nAsyncDrop;
-        OAsyncronousLink        m_aControllerConnectedEvent;
+        sal_uLong               m_nAsyncDrop;
         OAsyncronousLink        m_aSelectContainerEvent;
         PreviewMode             m_ePreviewMode;             // the mode of the preview
         ElementType             m_eCurrentType;
@@ -458,6 +457,7 @@
         virtual ::com::sun::star::uno::Reference< ::com::sun::star::sdbc::XConnection > SAL_CALL 
getActiveConnection() throw (::com::sun::star::uno::RuntimeException);
         virtual ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Reference< 
::com::sun::star::lang::XComponent > > SAL_CALL getSubComponents() throw 
(::com::sun::star::uno::RuntimeException);
         virtual ::sal_Bool SAL_CALL isConnected(  ) throw 
(::com::sun::star::uno::RuntimeException);
+        // DO NOT CALL with getMutex() held!!
         virtual void SAL_CALL connect(  ) throw (::com::sun::star::sdbc::SQLException, 
::com::sun::star::uno::RuntimeException);
         virtual ::com::sun::star::beans::Pair< ::sal_Int32, OUString > SAL_CALL 
identifySubComponent( const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XComponent >& 
SubComponent ) throw (::com::sun::star::lang::IllegalArgumentException, 
::com::sun::star::uno::RuntimeException);
         virtual ::sal_Bool SAL_CALL closeSubComponents(  ) throw 
(::com::sun::star::uno::RuntimeException);
@@ -480,6 +480,8 @@
 
             If an error occurs, then this is either stored in the location pointed to by 
<arg>_pErrorInfo</arg>,
             or, if <code>_pErrorInfo</code> is <NULL/>, then the error is displayed to the user.
+
+            DO NOT CALL with getMutex() held!!
         */
         const SharedConnection& ensureConnection( ::dbtools::SQLExceptionInfo* _pErrorInfo = NULL 
);
 
@@ -541,7 +543,7 @@
         DECL_LINK( OnAsyncDrop, void* );
         DECL_LINK( OnCreateWithPilot, void* );
         DECL_LINK( OnSelectContainer, void* );
-        DECL_LINK( OnFirstControllerConnected, void* );
+        void OnFirstControllerConnected();
 
     protected:
         using OApplicationController_CBASE::connect;
diff --git a/dbaccess/source/ui/app/AppControllerDnD.cxx 
b/dbaccess/source/ui/app/AppControllerDnD.cxx
index f0623e7..8ac9711 100644
--- a/dbaccess/source/ui/app/AppControllerDnD.cxx
+++ b/dbaccess/source/ui/app/AppControllerDnD.cxx
@@ -327,20 +327,63 @@
     }
 }
 // -----------------------------------------------------------------------------
+// DO NOT CALL with getMutex() held!!
 const SharedConnection& OApplicationController::ensureConnection( ::dbtools::SQLExceptionInfo* 
_pErrorInfo )
 {
-    SolarMutexGuard aSolarGuard;
-    ::osl::MutexGuard aGuard( getMutex() );
 
-    if ( !m_xDataSourceConnection.is() )
+    // This looks like double checked locking, but it is not,
+    // because every access (read *or* write) to  m_xDataSourceConnection
+    // is mutexed.
+    // See http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
+    // for what I'm refering to.
+    // We cannot use the TLS (thread-local storage) solution
+    // since support for TLS is not up to the snuff on Windows :-(
+
     {
-        WaitObject aWO(getView());
+        ::osl::MutexGuard aGuard( getMutex() );
+
+        if ( m_xDataSourceConnection.is() )
+            return m_xDataSourceConnection;
+    }
+
+    WaitObject aWO(getView());
+    Reference<XConnection> conn;
+    {
+        SolarMutexGuard aSolarGuard;
+
         OUString sConnectingContext( ModuleRes( STR_COULDNOTCONNECT_DATASOURCE ) );
         sConnectingContext = sConnectingContext.replaceFirst("$name$", getStrippedDatabaseName());
 
-        m_xDataSourceConnection.reset( connect( getDatabaseName(), sConnectingContext, _pErrorInfo 
) );
+        // do the connection *without* holding getMutex() to avoid deadlock
+        // when we are not in the main thread and we need username/password
+        // (and thus to display a dialog, which will be done by the main thread)
+        // and there is an event that needs getMutex() *before* us in the main thread's queue
+        // See fdo#63391
+        conn.set( connect( getDatabaseName(), sConnectingContext, _pErrorInfo ) );
+    }
+
+    if (conn.is())
+    {
+        ::osl::MutexGuard aGuard( getMutex() );
         if ( m_xDataSourceConnection.is() )
         {
+            Reference< XComponent > comp (conn, UNO_QUERY);
+            if(comp.is())
+            {
+                try
+                {
+                    comp->dispose();
+                }
+                catch( const Exception& )
+                {
+                    OSL_FAIL( "dbaui::OApplicationController::ensureConnection could not dispose 
of temporary unused connection" );
+                }
+            }
+            conn.clear();
+        }
+        else
+        {
+            m_xDataSourceConnection.reset(conn);
             SQLExceptionInfo aError;
             try
             {
@@ -362,11 +405,13 @@
                 }
                 else
                 {
+                    SolarMutexGuard aSolarGuard;
                     showError( aError );
                 }
             }
         }
     }
+
     return m_xDataSourceConnection;
 }
 // -----------------------------------------------------------------------------
diff --git a/dbaccess/source/ui/app/AppControllerGen.cxx 
b/dbaccess/source/ui/app/AppControllerGen.cxx
index c308d96..271a6b8 100644
--- a/dbaccess/source/ui/app/AppControllerGen.cxx
+++ b/dbaccess/source/ui/app/AppControllerGen.cxx
@@ -377,9 +377,6 @@
 // -----------------------------------------------------------------------------
 void SAL_CALL OApplicationController::connect(  ) throw (SQLException, RuntimeException)
 {
-    SolarMutexGuard aSolarGuard;
-    ::osl::MutexGuard aGuard( getMutex() );
-
     SQLExceptionInfo aError;
     SharedConnection xConnection = ensureConnection( &aError );
     if ( !xConnection.is() )

-- 
To view, visit https://gerrit.libreoffice.org/3310
To unsubscribe, visit https://gerrit.libreoffice.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Iab1bbec5d5df12bb89d027d43e498c78c92ffc32
Gerrit-PatchSet: 1
Gerrit-Project: core
Gerrit-Branch: master
Gerrit-Owner: Lionel Elie Mamane <lionel@mamane.lu>


Context


Privacy Policy | Impressum (Legal Info) | Copyright information: Unless otherwise specified, all text and images on this website are licensed under the Creative Commons Attribution-Share Alike 3.0 License. This does not include the source code of LibreOffice, which is licensed under the Mozilla Public License (MPLv2). "LibreOffice" and "The Document Foundation" are registered trademarks of their corresponding registered owners or are in actual use as trademarks in one or more countries. Their respective logos and icons are also subject to international copyright laws. Use thereof is explained in our trademark policy.