microsoft / qsharp-compiler

Q# compiler, command line tool, and Q# language server
https://docs.microsoft.com/quantum
MIT License
684 stars 170 forks source link

Unhandled exception within CompilationLoader constructor when called from Blazor WebAssembly #674

Open cgranade opened 3 years ago

cgranade commented 3 years ago

Describe the bug

When calling the CompilationLoader constructor from a Blazor WebAssembly project, an incomplete task is disposed internal to the constructor, causing an exception to propagate out to the Blazor runtime.

To Reproduce

Add the following Razor page to a Blazor WebAssembly project, then navigate to /compile in a browser and press "Compile."

<!-- Compile.razor -->
@page "/compile"
@inject ILogger QsLogger
@using Microsoft.Quantum.QsCompiler;
@using Microsoft.Quantum.QsCompiler.SyntaxTree;
@using Microsoft.Quantum.QsCompiler.ReservedKeywords;
@using System.Collections.Immutable;
@using Microsoft.Quantum.QsCompiler.CompilationBuilder;
@using Microsoft.Quantum.QsCompiler.Diagnostics;

<h1>Compile Q#</h1>

<textarea id="source-editor" @bind="currentSource">
</textarea>
<button @onclick="() => BuildQSharp(currentSource)">Compile...</button>

@code {
    private string currentSource = @"
open Microsoft.Quantum.Intrinsic;

@EntryPoint()
operation RunProgram() : Unit {
    // ...
}
";

    public QsCompilation BuildQSharp(string sourceCode)
    {
        var references = new References(ProjectManager.LoadReferencedAssemblies(ImmutableList<string>.Empty));
        var loadOptions = new CompilationLoader.Configuration
        {
            GenerateFunctorSupport = true,
            IsExecutable = true,
            AssemblyConstants = new Dictionary<string, string>
            {
                [AssemblyConstants.ProcessorArchitecture] = string.Empty
            },
            RuntimeCapabilities = AssemblyConstants.RuntimeCapabilities.Unknown
        };
        var loaded = new CompilationLoader(
            _ => new Dictionary<Uri, string>
            {
                [new Uri("file:///snippets.qs")] = sourceCode
            }
            .ToImmutableDictionary(),
            _ => references,
            loadOptions,
            QsLogger
        );
        return loaded.CompilationOutput;
    }
}
// Logging.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Microsoft.Quantum.QsCompiler.Diagnostics;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace BlazorApp1
{

    public class DebugConsoleLogger : LogTracker
    {
        private IJSRuntime runtime;
        public DebugConsoleLogger(IJSRuntime runtime)
        {
            this.runtime = runtime;
        }

        protected override void Print(Diagnostic msg)
        {
            runtime.InvokeVoidAsync("console.log", msg);
        }
    }

}

Expected behavior

Expected the call to CompilationLoader to complete without error, but the following exception is printed to the browser console log instead:

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).
System.InvalidOperationException: A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).
  at System.Threading.Tasks.Task.Dispose (System.Boolean disposing) <0x4459f00 + 0x00038> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Dispose () <0x4459e40 + 0x00008> in <filename unknown>:0 
  at System.Linq.Parallel.QueryTaskGroupState.QueryEnd (System.Boolean userInitiatedDispose) <0x4454590 + 0x00138> in <filename unknown>:0 
  at System.Linq.Parallel.MergeEnumerator`1[TInputOutput].Dispose () <0x444d9a8 + 0x00020> in <filename unknown>:0 
  at System.Linq.Parallel.AsynchronousChannelMergeEnumerator`1[T].Dispose () <0x444d8d8 + 0x0000c> in <filename unknown>:0 
  at System.Linq.Parallel.QueryOpeningEnumerator`1[TOutput].Dispose () <0x444c0d8 + 0x00030> in <filename unknown>:0 
  at System.Collections.Immutable.DisposableEnumeratorAdapter`2[T,TEnumerator].Dispose () <0x444be88 + 0x00014> in <filename unknown>:0 
  at System.Collections.Immutable.ImmutableHashSet`1[T].Union (System.Collections.Generic.IEnumerable`1[T] other, System.Collections.Immutable.ImmutableHashSet`1+MutationInput[T] origin) <0x43f4120 + 0x00130> in <filename unknown>:0 
  at System.Collections.Immutable.ImmutableHashSet`1[T].Union (System.Collections.Generic.IEnumerable`1[T] items, System.Boolean avoidWithComparer) <0x43f36f0 + 0x00064> in <filename unknown>:0 
  at System.Collections.Immutable.ImmutableHashSet`1[T].Union (System.Collections.Generic.IEnumerable`1[T] other) <0x43f33f8 + 0x00016> in <filename unknown>:0 
  at System.Collections.Immutable.ImmutableHashSet.ToImmutableHashSet[TSource] (System.Collections.Generic.IEnumerable`1[T] source, System.Collections.Generic.IEqualityComparer`1[T] equalityComparer) <0x43d7088 + 0x00046> in <filename unknown>:0 
  at System.Collections.Immutable.ImmutableHashSet.ToImmutableHashSet[TSource] (System.Collections.Generic.IEnumerable`1[T] source) <0x43d6dd0 + 0x00006> in <filename unknown>:0 
  at Microsoft.Quantum.QsCompiler.CompilationBuilder.CompilationUnitManager.InitializeFileManagers (System.Collections.Generic.IDictionary`2[TKey,TValue] files, System.Action`1[T] publishDiagnostics, System.Action`1[T] onException) <0x43497b8 + 0x00110> in <filename unknown>:0 
  at Microsoft.Quantum.QsCompiler.CompilationLoader..ctor (Microsoft.Quantum.QsCompiler.CompilationLoader+SourceLoader loadSources, Microsoft.Quantum.QsCompiler.CompilationLoader+ReferenceLoader loadReferences, System.Nullable`1[T] options, Microsoft.Quantum.QsCompiler.Diagnostics.ILogger logger) <0x42d9098 + 0x001e6> in <filename unknown>:0 
  at BlazorApp1.Pages.Compile.BuildQSharp (System.String sourceCode) [0x0006b] in C:\Users\cgran\source\scratch\BlazorApp1\BlazorApp1\Pages\Compile.razor:39 
  at BlazorApp1.Pages.Compile.<BuildRenderTree>b__0_1 () [0x00000] in C:\Users\cgran\source\scratch\BlazorApp1\BlazorApp1\Pages\Compile.razor:14 
  at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync[T] (System.MulticastDelegate delegate, T arg) <0x4082fb0 + 0x0006e> in <filename unknown>:0 
  at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync (System.Object arg) <0x4082ef0 + 0x0000a> in <filename unknown>:0 
  at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync (Microsoft.AspNetCore.Components.EventCallbackWorkItem callback, System.Object arg) <0x4082e50 + 0x0000a> in <filename unknown>:0 
  at Microsoft.AspNetCore.Components.EventCallback.InvokeAsync (System.Object arg) <0x40829d8 + 0x00040> in <filename unknown>:0 
  at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync (System.UInt64 eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo fieldInfo, System.EventArgs eventArgs) <0x4081f50 + 0x000a8> in <filename unknown>:0

System information

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <RazorLangVersion>3.0</RazorLangVersion>
    <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
    <PackageReference Include="Microsoft.Quantum.Compiler" Version="0.12.20100504" />
  </ItemGroup>

  <ItemGroup>
    <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
  </ItemGroup>

</Project>

Additional context

filipw commented 3 years ago

@cgranade I also started building a WebAssembly playground for Q# but ran into a different issue. The one your reported here no longer happens on .NET 5.0 RC2 but there is now a new one, further down

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Cannot wait on monitors on this runtime.
System.PlatformNotSupportedException: Cannot wait on monitors on this runtime.
   at System.Threading.Monitor.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj)
   at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout, Boolean exitContext)
   at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout)
   at System.Threading.ManualResetEventSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.SpinThenBlockingWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.InternalWaitCore(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.InternalWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Microsoft.Quantum.QsCompiler.CompilationBuilder.ProcessingQueue.QueueForExecution[Compilation](Func`1 execute, Compilation& result)
   at Microsoft.Quantum.QsCompiler.CompilationBuilder.CompilationUnitManager.FlushAndExecute[Compilation](Func`1 execute)
   at Microsoft.Quantum.QsCompiler.CompilationBuilder.CompilationUnitManager.Build()
   at Microsoft.Quantum.QsCompiler.CompilationLoader..ctor(SourceLoader loadSources, ReferenceLoader loadReferences, Nullable`1 options, ILogger logger)
   at Strathweb.Samples.BlazorQSharpInteractive.Pages.Compile.RunQSharp(String sourceCode)
   at Strathweb.Samples.BlazorQSharpInteractive.Pages.Compile.<BuildRenderTree>b__0_1()
   at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync[Object](MulticastDelegate delegate, Object arg)
   at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync(Object arg)
   at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, Object arg)
   at Microsoft.AspNetCore.Components.EventCallback.InvokeAsync(Object arg)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
filipw commented 3 years ago

This is caused by doing sync over async here https://github.com/microsoft/qsharp-compiler/blob/edc3d1557d62dc3cf1175caf6386e78aee91eb03/src/QsCompiler/CompilationManager/CompilationUnitManager.cs#L505.

I already provided feedback a while ago to @bettinaheim that it would be good to get rid of blocking calls and have a fully async compiler API - this will be another reason why it's needed 😀

bettinaheim commented 3 years ago

@filipw Do you have a suggestion for the concrete case here? If I remember correctly, the runSynchonously is used only to enable a flush that guarantees that all processing that is currently queued finishes before a certain action is performed.

cgranade commented 3 years ago

In case anyone is interested in looking into this, the feature/allow-disable-parallel branch has some partial progress towards resolving this issue, as discussed at #744.