Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullReferenceException from SharpVectors.Renderers.Wpf.WpfSvgRendering.AdjustViewbox() #115

Closed
atmgrifter00 opened this issue Oct 24, 2019 · 11 comments
Assignees
Labels

Comments

@atmgrifter00
Copy link

In the most recent nuget package (1.4.0), we can hit the following exception:

System.NullReferenceException: Object reference not set to an instance of an object.
   at SharpVectors.Renderers.Wpf.WpfSvgRendering.AdjustViewbox()
   at SharpVectors.Renderers.Wpf.WpfSvgRendering.AfterRender(WpfDrawingRenderer renderer)
   at SharpVectors.Renderers.Wpf.WpfRenderingHelper.RenderElement(ISvgElement svgElement)
   at SharpVectors.Renderers.Wpf.WpfRenderingHelper.Render(ISvgDocument docElement)
   at SharpVectors.Renderers.Wpf.WpfDrawingRenderer.Render(ISvgDocument node)
   at SharpVectors.Converters.FileSvgReader.LoadFile(TextReader textReader)

Now, I haven't completely wrapped my head around what might be the root cause, but if I were to guess it would be that _context is null here. Looking at how _context is supposed to be initialized it's hard to see how it would ever be not null, as the public constructor for WpfSvgRendering only offers the one argument for initialization which will ultimately pass null to the base constructor to initialize _context with.

@paulushub
Copy link
Contributor

Looking at how _context is supposed to be initialized it's hard to see how it would ever be not null,
as the public constructor for WpfSvgRendering only offers the one argument for initialization
which will ultimately pass null to the base constructor to initialize _context with.

True, but the normal procedure will involve BeforeRender, Render and AfterRender calls, and the call to the base.BeforeRender will normally initialize the _context object as below.

public virtual void BeforeRender(WpfDrawingRenderer renderer)
{
    if (renderer == null)
    {
        return;
    }
    _isReady = false;
    _context = renderer.Context;

    if (_svgElement != null && _context != null)
    {
        if (string.IsNullOrWhiteSpace(_uniqueId))
        {
            _uniqueId = _svgElement.UniqueId;
        }
        _paintContext = new WpfSvgPaintContext(_uniqueId);
        _context.RegisterPaintContext(_paintContext);
    }
}

Looking at your stack, it seems the BeforeRender method is not called in this case. I am looking into it.

@paulushub paulushub self-assigned this Oct 24, 2019
@atmgrifter00
Copy link
Author

Ah, I see. If it helps, this is the code snippet that I believe is responsible for the noted exception call stack:

private void UpdateDrawing()
{
   var wpfSettings = new WpfDrawingSettings();
   wpfSettings.CultureInfo = wpfSettings.NeutralCultureInfo;
   using (var textReader = new StringReader(ImageData))
   {
       using (var fileReader = new FileSvgReader(wpfSettings))
       {
           fileReader.SaveXaml = false;
           fileReader.SaveZaml = false;
           var drawing = fileReader.Read(textReader);
           drawing.Freeze();
           Drawing = drawing;
        }
    }
}

Now, the object that holds on to this method may have been created off the UI-thread, but this doesn't seem inherently problematic, since the drawing instance is frozen.

@astarche
Copy link

It's actually _drawGroup that's null. You can only hit this if you're running the above code in parallel.

Here's the test I use that reproduces ~90% of the time:

const int NumberOfConsumers = 8;
var queue = new ConcurrentQueue<string>();

for (int i = 0; i < 200; ++i)
{
    queue.Enqueue(Image1);
}

var allTasks = new List<Task>();

for (int i = 0; i < NumberOfConsumers; ++i)
{
    allTasks.Add(Task.Run(() =>
    {
        while (queue.TryDequeue(out string imageData))
        {
            UpdateDrawing(imageData);
        }
    }));
}
await Task.WhenAll(allTasks);

I think the issue is with the _cacheRendering dictionary in WpfRendering.Create. When I see the exception, there are 2 threads that each have different elements with the same LocalName ("svg"). That code will give each of them the same WpfSvgRenderingNode, which they then Render on in parallel. This exposes that NullReferenceException if one thread clears _drawGroup with the call to Initialize while the other thread is between the null-check and dereference of _drawGroup in AdjustViewbox.

@paulushub
Copy link
Contributor

I think the issue is with the _cacheRendering dictionary in WpfRendering.Create.

That was my initial thought, since that is the most unique change in the release 1.4. Thanks for investigating this issue.

@atmgrifter00
Copy link
Author

I'd also like point out that we seem to be able to hit a dead-lock from essentially the same code path (being called from two separate threads). The call stacks in the two threads looked like this:

image

@paulushub
Copy link
Contributor

@astarche @atmgrifter00
I have currently resolved both issues and still testing for any further thread safely issue. Your sample code helped a lot, thanks.

@paulushub paulushub added the bug label Oct 26, 2019
paulushub added a commit that referenced this issue Oct 26, 2019
- Renamed some long name folders
- Added support for textArea element for Tiny 1.2 specs
- WPF Rendering: Fixed thread safety issues as reported in #115
- Added WpfTestThreadSafety to test threading support. Codes by @astarche @atmgrifter00
@paulushub
Copy link
Contributor

paulushub commented Oct 26, 2019

@astarche @atmgrifter00
I have packed the above codes in WpfTestThreadSafety sample and used it to fix this issue. If there is no further issue, I will release Nuget package with version 1.4.5 on Monday.

The issue with WpfSvgRendering: It was calling the base.AfterRender too early. Calling the base.AfterRender sets the IsReady property to true, making it available and rightly taken by other threads.

The issue with SvgList: The use of static dictionary. I have removed the static keyword for now, and will look into a better data structure later.

@atmgrifter00
Copy link
Author

We don't know of anything else that would hold up a new release from our (me and @astarche) perspective.

@paulushub
Copy link
Contributor

@atmgrifter00 Thanks for the feedback. The Nuget package is now publicly available and tagged as Version 1.5.0. I had already started tagging the sources as version 1.5.0 so decided to keep that version so as not to break the W3C test logs.
Thanks for the help in tracing and fixing this issue.

@atmgrifter00
Copy link
Author

Well, crud. I've found another issue, but as it's unrelated to the other one, I'm just going to have to file a new one.

@paulushub
Copy link
Contributor

So far it seems the fix to this issue is holding!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants