saucecontrol / PhotoSauce

MagicScaler high-performance, high-quality image processing pipeline for .NET
http://photosauce.net/
MIT License
589 stars 49 forks source link

Exception trying to process any image #36

Closed sixten closed 4 years ago

sixten commented 4 years ago

I've been trying to bootstrap a simple WebRSize site so that I can play with this project, as a potential replacement for our current imaging pipeline, but I'm having trouble actually processing images. I'm expecting (and hoping) that there's just a step missing somewhere in my setup, but I haven't found it yet.

Here's the exception stack trace:

[TypeInitializationException: Type constructor threw an exception.]
   PhotoSauce.Interop.Wic.IWICImagingFactory.CreateDecoderFromStream(IStream pIStream, Guid[] pguidVendor, WICDecodeOptions metadataOptions) +0
   PhotoSauce.MagicScaler.<>c.<Create>b__20_0(IStream stm) +72
   PhotoSauce.MagicScaler.WicImageContainer.createDecoder(Func`2 factory, T arg) +67
   PhotoSauce.MagicScaler.WicImageContainer.Create(Stream inStream, WicPipelineContext ctx) +200
   PhotoSauce.MagicScaler.ImageFileInfo.Load(Stream imgStream, DateTime lastModified) +485
   PhotoSauce.WebRSize.<<GetImageInfoAsync>b__0>d.MoveNext() +996
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +29
   PhotoSauce.WebRSize.<mapRequest>d__6.MoveNext() +2474
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar) +72
   System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +429
   System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +50
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +163

Here's what I've done:

I've verified in applicationhost.config that the site is running in integrated mode, on a CLR v4 app pool.

Here's the WebRSize configuration from web.config:

  <webrsize>
    <diskCache enabled="false" path="/webrsizecache"/>
    <imageFolders>
      <add name="images" path="/images/" />
    </imageFolders>
  </webrsize>

As far as I can tell, WIC is built into Windows these days, so there's no installer I'm forgetting to run. But this really feels like a problem where there's just some piece missing from my project / environment. Any idea what's going on?

saucecontrol commented 4 years ago

Howdy! Thanks for the detailed report. Your repro steps look good, and it's clear from the stack trace that the HttpModule is intercepting the request and attempting to process it, so the config is right.

Where I'm lost is the actual exception in that stack trace. The TypeInitializationException is being thrown by a static constructor, and it looks like you've gotten as far the WIC factory, which leads me to believe the static constructor in question is the implicit one created here: https://github.com/saucecontrol/PhotoSauce/blob/master/src/MagicScaler/Utilities/WinCodecExtensions.cs#L9

That's doing nothing more than instantiating a WIC COM component, and as you correctly pointed out, WIC is built in to Windows 10, so I can't imagine that failing. You may be able to get more information by examining the InnerException of that TypeInitializationException.

Failing that, it may be easier to troubleshoot by stepping back a layer and using the MagicScaler library directly from a console app. At the point of failure, WebRSize is just invoking ImageFileInfo.Load([anOpenStream)], so you might be able to repro with just that.

sixten commented 4 years ago

OK, I added TypeInitializationException to Visual Studio's break list and tried again. It does have an inner exception:

Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

   at System.SpanHelpers.PerTypeValues`1.MeasureArrayAdjustment()
   at System.SpanHelpers.PerTypeValues`1..cctor()

The System.Runtime.CompilerServices.Unsafe NuGet package was added to the project when I added WebRSize, and that DLL is present in the /bin directory of the app.

I find versioning for this stuff super confusing, so I'm not 100% sure whether the fact that it's looking for 4.0.4.1 should be a red flag or not.

saucecontrol commented 4 years ago

Ah, yes... that would do it, although I have no idea why it happens at that specific place. Looks like there's a version conflict between the System.Runtime.CompilerServices.Unsafe version referenced by System.Memory and PhotoSauce.MagicScaler. I thought they had resolved the issues around that, but you can still have problems especially if you're using a web site vs web application project.

The following assemblyBinding config should fix you up:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <!-- NuGet package version 4.5.0, assembly version 4.0.3.0 -->
        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" />
        <bindingRedirect oldVersion="4.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <!-- NuGet package version 4.5.3, assembly version 4.0.1.1 -->
        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" />
        <bindingRedirect oldVersion="4.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
      </dependentAssembly>
      <dependentAssembly>
        <!-- NuGet package version 4.5.0, assembly version 4.1.4.0 -->
        <assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" />
        <bindingRedirect oldVersion="4.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
      </dependentAssembly>
      <dependentAssembly>
        <!-- NuGet package version 4.7.0, assembly version 4.0.6.0 -->
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" />
        <bindingRedirect oldVersion="4.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Those are redirects to all the latest versions of those assemblies, which should have been pulled down as transitive references from WebRSize.

Note that the assembly version and package version don't necessarily have anything to do with one another, which does make it super confusing to fix a mismatch.

sixten commented 4 years ago

Sure enough, that seems to have fixed things. Thanks so much! Looking forward to being able to try this out properly!

saucecontrol commented 4 years ago

Great, thanks for reporting it! I'll update the docs with this info since it's not at all obvious from the exception.

sixten commented 4 years ago

For posterity, in case it might be useful to others: when I converted my example project to use package references (instead of packages.config), it appears that the binding redirects became unnecessary.

(I only recently discovered that it was possible to do this in a .NET Framework ASP.NET project that can't be fully converted to the new SDK style project format. Though the official migration tool in VS2017 doesn't support it.)