VSIX - Deadlock on XmlEditingScope.Complete()
Asked Answered
E

1

10

We are using the classes in the Microsoft.VisualStudio.XmlEditor namespace (https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.xmleditor.aspx) to modify an xml document in an Visual Studio Extension.

For some reason a deadlock occurs after calling the XmlEditingScope.Complete() method. In the statusbar of Visual Studio, we see the message "Waiting for parse to complete..."

This is the stack trace of the deadlocked UI thread:

WindowsBase.dll!System.Windows.Threading.DispatcherSynchronizationContext.Wait(System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)   
mscorlib.dll!System.Threading.SynchronizationContext.InvokeWaitMethodHelper(System.Threading.SynchronizationContext syncContext, System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)     
[Native to Managed Transition]   
[Managed to Native Transition]   
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext)   
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext)  
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseWaitHandle.WaitOne(int millisecondsTimeout, bool exitContext)    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.WaitForParse(System.IAsyncResult result, Microsoft.XmlEditor.StatusBarIndicator indicator)    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.WaitForParse()    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlParserLock.XmlParserLock(Microsoft.XmlEditor.XmlLanguageService service)  
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.Transaction.PushToEditorTreeAndBuffer()  
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.Transaction.Complete()   
XmlEditingScope.Complete() Line 64

And the Visual Studio parse thread:

mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x21 bytes  
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) + 0x28 bytes     
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.LockManager.Lock(object resource, Microsoft.XmlEditor.LockMode mode, Microsoft.XmlEditor.Transaction txId) + 0x14c bytes     
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.TransactionManager.BeginParseSourceTransaction(Microsoft.XmlEditor.XmlSource src, Microsoft.XmlEditor.Transaction parent) + 0x9f bytes   
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.ParseSource(Microsoft.VisualStudio.Package.ParseRequest req) + 0x17d bytes    
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseRequest(Microsoft.VisualStudio.Package.ParseRequest req) + 0x75 bytes    
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseThread() + 0x140 bytes   
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x70 bytes    
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xa7 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x16 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x41 bytes     
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes    
[Native to Managed Transition]  

It's not easy to show all relevant code here, but basically it's just the following code that is executed after a change in a WPF DataGrid control (IEditableObject.EndEdit in ViewModel):

using (var s = store.BeginEditingScope("Test", null))
{
       apply changes in xmlModel.Document... 

       s.Complete();
}

What can I do to prevent this deadlock from happening. Do I need to lock on something before applying the changes? What else could I'm doing wrong?

Estren answered 8/4, 2016 at 7:19 Comment(0)
I
0

It's more a comment but did not fit into a comment field. It's hard to tell the exact reason of why this happens in your case or provide a way to fix it with just information you provided (to provide more help we need minimal example which we can just run and see the problem). However, stacktraces show that this is regular UI deadlock which often happens in almost all UI frameworks because of their "single threaded" nature (all\most actions with UI elements must happen on single thread). Thread A (visual studio parse thread in this case) posts a task to UI thread queue from background thread and waits for it to complete (take for example WPF Dispatcher.Invoke call which does exactly that). It is not necessary that whole task is executed on UI thread for deadlock to happen, only part of it (for example - get actual xml from UI control) is enough. Then you are doing the same on UI thread itself. That is you wait on some wait handle in UI thread (using lock statements on UI thread falls into the same category). This is extremely dangerous and leads to deadlocks you (probably) have in this case.

I will illustrate my point with this small example (WPF):

public partial class MainWindow : Window {
    private DummyXmlParser _test = new DummyXmlParser();
    public MainWindow() {
        InitializeComponent();
        new Thread(() => {
            _test.StartParseInBackground();
            _test.WaitHandle.WaitOne();
        }) {
            IsBackground = true
        }.Start();

        _test.StartParseInBackground();
        // don't do this, will deadlock
        _test.WaitHandle.WaitOne();
    }
}

public class DummyXmlParser {
    public DummyXmlParser() {
        WaitHandle = new ManualResetEvent(false);
    }

    public void StartParseInBackground() {
        Task.Run(() => {
            Thread.Sleep(1000);
            // this gets dispatched to UI thread, but UI thread is blocked by waiting on WaitHandle - deadlock
            Application.Current.Dispatcher.Invoke(() =>
            {
                Application.Current.MainWindow.Title = "Running at UI";
            });
            WaitHandle.Set();
        });
    }

    public ManualResetEvent WaitHandle { get; private set; }
}

In your case it seems XmlEditingScope.Complete runs on UI thread and waits on ParseWaitHandle, which is just behaviour you should avoid. To fix, you can try to avoid executing your code above on UI thread, and instead run on background thread.

Icken answered 11/4, 2016 at 11:29 Comment(2)
Evk, thanks for your answer. I have tried to run XmlEditingScope on a background thread, but then I get a System.AccessViolationException. at Microsoft.VisualStudio.Shell.Interop.IVsQueryEditQuerySave2.QueryEditFiles(UInt32 rgfQueryEdit, Int32 cFiles, String[] rgpszMkDocuments, UInt32[] rgrgf, VSQEQS_FILE_ATTRIBUTE_DATA[] rgFileInfo, UInt32& pfEditVerdict, UInt32& prgfMoreInfo) at Microsoft.XmlEditor.Transaction.CanEditFilesInTransaction() at Microsoft.XmlEditor.Transaction.PushToEditorTreeAndBuffer() at Microsoft.XmlEditor.Transaction.Complete()Estren
Well hard to give more advices without additional information. But at least it now does not deadlock :) And what if you dispatch it to UI thread but via Dispatcher.BeginInvoke (so asynchronously)?Icken

© 2022 - 2024 — McMap. All rights reserved.