Rhino 8.9 layer table window not updating and crashing

I’m encountering an intermittent issue with my plug-in in Rhino 8.9, which wasn’t present in version 8.8. The problem occurs when my plug-in creates a layer and adds an image to it. The image is assigned to the correct layer as expected; however, this newly created layer does not appear in the layer table window.

Rhino 8.9 (See missing layer in table)


Rhino 8.8

When rhino crashes I get the following report:

[ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: An ItemsControl is inconsistent with its items source.\n  See the inner exception for more information.
 ---> System.Exception: Information for developers (use Text Visualizer to read this):
This exception was thrown because the generator for control 'Eto.Wpf.Forms.Controls.EtoDataGrid Items.Count:5' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection.  The following differences were detected:
  Accumulated count 4 is different from actual count 5.  [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset).]
  At index 0:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  At index 1:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  At index 2:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  (... 1 more instances ...)

One or more of the following sources may have raised the wrong events:
     System.Windows.Controls.ItemContainerGenerator
      System.Windows.Controls.ItemCollection
       System.Windows.Data.ListCollectionView
  *     Eto.Wpf.Forms.Controls.EtoGridCollectionView
(The starred sources are considered more likely to be the cause of the problem.)

The most common causes are (a) changing the collection or its Count without raising a corresponding event, and (b) raising an event with an incorrect index or item parameter.

The exception's stack trace describes how the inconsistencies were detected, not how they occurred.  To get a more timely exception, set the attached property 'PresentationTraceSources.TraceLevel' on the generator to value 'High' and rerun the scenario.  One way to do this is to run a command similar to the following:\n   System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator, System.Diagnostics.PresentationTraceLevel.High)
from the Immediate window.  This causes the detection logic to run after every CollectionChanged event, so it will slow down the application.

   --- End of inner exception stack trace ---
   at System.Windows.Controls.ItemContainerGenerator.Verify()
   at System.Windows.Controls.VirtualizingStackPanel.MeasureChild(IItemContainerGenerator& generator, IContainItemStorage& itemStorageProvider, IContainItemStorage& parentItemStorageProvider, Object& parentItem, Boolean& hasUniformOrAverageContainerSizeBeenSet, Double& computedUniformOrAverageContainerSize, Double& computedUniformOrAverageContainerPixelSize, Boolean& computedAreContainersUniformlySized, Boolean& hasAnyContainerSpanChanged, IList& items, Object& item, IList& children, Int32& childIndex, Boolean& visualOrderChanged, Boolean& isHorizontal, Size& childConstraint, Rect& viewport, VirtualizationCacheLength& cacheSize, VirtualizationCacheLengthUnit& cacheUnit, Int64& scrollGeneration, Boolean& foundFirstItemInViewport, Double& firstItemInViewportOffset, Size& stackPixelSize, Size& stackPixelSizeInViewport, Size& stackPixelSizeInCacheBeforeViewport, Size& stackPixelSizeInCacheAfterViewport, Size& stackLogicalSize, Size& stackLogicalSizeInViewport, Size& stackLogicalSizeInCacheBeforeViewport, Size& stackLogicalSizeInCacheAfterViewport, Boolean& mustDisableVirtualization, Boolean isBeforeFirstItem, Boolean isAfterFirstItem, Boolean isAfterLastItem, Boolean skipActualMeasure, Boolean skipGeneration, Boolean& hasBringIntoViewContainerBeenMeasured, Boolean& hasVirtualizingChildren)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverrideImpl(Size constraint, Nullable`1& lastPageSafeOffset, List`1& previouslyMeasuredOffsets, Nullable`1& lastPagePixelSize, Boolean remeasure)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(Size constraint)
   at System.Windows.Controls.Primitives.DataGridRowsPresenter.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
[END ERROR]

@curtisw - is this something you may have knowledge of?

Thanks,
JNash

@curtisw We are seeing this same problem increasingly frequently - any advice?

Did something change in ETO with how it hanldes updates from multiple threads?

Faced this too.

Hm, sorry this is happening more often. I’ve filed that as RH-83535 to take a look. In the meantime is there some way we can replicate the issue on our end? Does it require a certain plugin or steps to reproduce?

Eto does not handle anything from multiple threads, you must do any UI or updates to the document that would cause UI updates on the UI thread only.

1 Like

Hey @jnash, sorry for the late response. Do you have a copy of your plugin or sample that we can use to reproduce this error?

Good Morning @curtisw,

Thanks for the bug report, here is a sample script which will replicate the crash. This crashes instantly with the same kind of error, we have seen this crash intermitently.

using System;
using Rhino;
using System.Drawing;
using System.Threading.Tasks;

	var doc = RhinoDoc.ActiveDoc;
	Parallel.For(1, 10, async i => {
		await Task.Delay(i * 90);
		var layer = doc.Layers.Add("layer" + i, Color.Aqua);
		await Task.Delay(i * 130);
		doc.Layers.Delete(layer, true);
	});

I fully appreciate that no GUI framework nor Eto specifically cannot support update from multiple threads.

However:

  1. It is very common to do geometry work in a background thread and then add it to the document on a new layer - creating the document layer from a background thread.
  2. We also hook Document events to create layers and have no guarentee of which thread we will be on.
  3. There is no documentation that layer creation (or deletion or renaming) must be done on gui thread.
  4. This was never a problem in Rhino 7
  5. This was never an issue in Rhino 8 until around version 8.9 - please may i have access to an older Rhino (say 8.5) to test with?

The Rhino document is not thread safe. You should be creating document objects on the main thread. A layer is a document object.

– Dale

2 Likes

I’m very surprised to hear this. Adding layers to a document with a parallel for loop should bring down any version of Rhino.

Thanks @dale @stevebaer understood.

We will look to re-archiect but it will be a challenge to get the threading right.

In the plugin we only create 1 or 2 layers per run and had not experienced crashes in Rhino 7 nor in Rhino 8 until 8.9

I think Rhino post 8.9 is significantly less forgiving of this activity.

Hello,

we got the same problem as mentioned in the thread here. I put there some sample Code and a plugin for Rhino 7 and 8 to replicate the behavior:

Rhino 8 Layer Viewer crashes while generating layers on seperate Thread - Rhino Developer - McNeel Forum

We found a solution by using RhinoApp.InvokeOnUiThread.

I’m very surprised to hear this. Adding layers to a document with a parallel for loop should bring down any version of Rhino.

Im suprised to hear this, since we never run into issues with layers before.

We also get some issues now with doc. Views and disabeling or enabeling the redraw.

Yeah we’ve been battling this for a few months now.

Pre 8.9 we could reliably create / update layers from a worker thread (although i now understand we should not have been!) but after the ETO updates to the Layer Table Rhino crashes in many inventive ways because at some point - maybe 5,10 or 30 minutes ago we added some geometry from a background thread - and the layer table has been sitting there quietly in an invalid state just waiting to crash Rhino with an exception like this.

This exception was thrown because the generator for control ‘Eto.Wpf.Forms.Controls.EtoDataGrid Items.Count:7’ with name ‘(unnamed)’ has received sequence of CollectionChanged events that do not agree with the current state of the Items collection. The following differences were detected:
Accumulated count 6 is different from actual count 7.

This is a rather nasty issue to debug with customers because of the lag which ETO is bringing down the whole of Rhino. You end up working back through the workflow to find our the operation which is writing from the background thread half an hour ago…

@curtisw is there any way you could get ETO to crash Rhino immediately rather than having the lag?

Today I found yet another case of it in our codebase - we have a nice Serilog Error handler which Draws & Highlights problematic geometry to the document for users - but of course sometimes its called from the background thread :crazy_face: I’ll fix it somehow but its the 4th or 5th case of this I’ve found, again all worked fine before 8.9.

Thankfully I havn’t seen this one yet - @dale @stevebaer Can you confirm whether it is permitted to disable/ enable views and redrawing from a background thread?

What specific function/property are you referring to? I can dig through the code to try and determine if it would be problematic

@david.birch.uk - I see a lot of comments but no way of repeating, nor any crash dumps to review. Can you help with this?

– Dale

Good Morning @dale

You can replicate this using the script below, the layer table will then be unresponsive and then resizing the Rhino window to trigger the crash.
In every crash the Rhino Crash Reporter does not appear.
Only the RhinoDotNetCrash.txt is generated and looks similar to those posted.

using System;
using Rhino;
using System.Drawing;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Rhino.DocObjects;
using Rhino.Geometry;

	var doc = RhinoDoc.ActiveDoc;
    Task.Factory.StartNew(()=> {
            var id = doc.Layers.Add(new Layer() );
    });

Here is a video of the behaviour - and how the Rhino 8.8 is more tolerant of this.
ChangeOfBehaviour.mkv

Thank you @stevebaer

These methods sometimes get called from a backgroud thread:
https://developer.rhino3d.com/api/rhinocommon/rhino.display.rhinoview/redraw
https://developer.rhino3d.com/api/rhinocommon/rhino.display.rhinoviewport/zoomboundingbox
https://developer.rhino3d.com/api/rhinocommon/rhino.display.rhinoview/enabledrawing
https://developer.rhino3d.com/api/rhinocommon/rhino.docobjects.tables.viewtable/enableredraw
https://developer.rhino3d.com/api/rhinocommon/rhino.display.rhinoviewport/zoomextents

@Stephan_Wegewitz could you advise which methods you’ve seen cause the same behaviour?

Anytime we call Rhino logic that affects any UI, from within async code we always use the RhinoApp.InvokeOnUiThread method.

And this works well for us…

1 Like

Thanks @jstevenson I’ve been making heavy use of InvokeOnUiThread method today. It also works from non-async contexts thankfully.

It would be helpful to have a synchronous version of RhinoApp.InvokeOnUiThread which allows returning of a result.

For now I’ve rolled my own and leave it here in case its useful

/// <summary>
/// Layers and objects can only be created on the GUI thread
/// 
/// RhinoApp.InvokeOnUiThread(() => {})
/// does not return values - but does run synchronously. 
///
/// But this version does both: 
/// var result = GuiThreadHelper.ExecuteOnUIThread(() => { return 5}); 
/// </summary>
public class GuiThreadHelper {
	/// <summary>
	/// Set this from plugin constructor (which will be called on GUI thread)
	/// GuiThreadHelper.Context = SynchronizationContext.Current;
	/// </summary>
	public static SynchronizationContext Context;

	public static T ExecuteOnUIThread<T>(Func<T> function) {
		if (!RhinoApp.InvokeRequired) return function();

		var resetEvent = new ManualResetEventSlim(false);
		T   result     = default;
		
		Context.Post(_ => {
			try {
				result = function();
			} finally {
				resetEvent.Set();
			}
		}, null);

		resetEvent.Wait();
		return result;
	}
}

I typically don’t “wait” for calls unless it’s within the scope of the invoke, since the only time we are waiting is so we can then update UI, which must be done on the UI thread…

They have this method, but I’m not loving seeing Thread.Sleep() in there, since that could choke the UI thread…

@david.birch.uk - I can repeat the crash with your code, thanks.

https://mcneel.myjetbrains.com/youtrack/issue/RH-84451

– Dale

1 Like