SketchUp 2017 crashes on set_attribute call from UI timer

crash
attribute
ruby
plugin

#1

Hi all,

after updating to SketchUp 2017 our custom plugin started to crash. I managed to extract a small sample of what we are doing in plugin. We are setting attributes on ComponentDefinition objects from UI timer thread periodically (we have custom bookkeeping code to keep in sync with our API). This works fine in 2016 version, but in 2017 it will result in Bug Splat. Interestingly if we replace call to set_attribute with get_attribute it will work correctly. This looks like possible bug in SketchUp 2017, unless there is something we don’t know about changes in latest version. I would be glad if somebody could either confirm this as a bug, or let me know how we can use attributes from timers without SketchUp crashing.

require 'sketchup.rb'

syncTimer = UI.start_timer(2, true) {
	model = Sketchup.active_model

	for i in 0..model.definitions.size - 1
		definition = model.definitions[i]

		# This one will crash
		definition.set_attribute("SketchUpTest","AttributeName","AttributeValue")
		
		# This one won't
		#definition.get_attribute("SketchUpTest","AttributeName","AttributeValue")
	end
}

Edit: Forgot to add - it will crash after I drag new component to a scene from components tab.


#2

No surprise that this causes a crash at all.

You are changing the collection in the midst of iterating it.
This is a no-no. It is caused by your code, not a bug.

Running code like this is poor practice (in any programming language) and no matter what the SketchUp version, whether it crashes or not. (Often no crash occurs but the iteration gets out of sync and members of the collection are skipped, or attributes assigned to the wrong member.)


BTW:

Is more cleanly done as:

for definition in model.definitions
  ...
end

… or :

model.definitions.each do |definition|
  ...
end

#3

Hi, thanks for your response (and for loop tip). I think I oversimplified the example, so I am posting a bit more complex one. If you see possible problem with it let me know, as this one will not modify collection in the middle of iteration, and still will crash SketchUp. I am really new to Ruby/SketchUp so I may be overlooking something simple - but I am still confused by the fact that get_attribute will not crash when set_attribute will, even though from user perspective in this case they do almost the same operation (set attribute value in dictionary, get_attribute will also return value).

require 'sketchup.rb'
require 'thread'

$definitions = []
$lock = Mutex.new

class DefinitionsObserver < Sketchup::DefinitionsObserver
	def onComponentAdded(definitions, definition)
		$lock.synchronize {
			if !$definitions.include?(definition)
				$definitions.push(definition)
			end
		}
	end
end

syncTimer = UI.start_timer(2, true) {
		model = Sketchup.active_model

		$lock.synchronize {
			for definition in $definitions
				
				# This one will crash
				definition.set_attribute("SketchUpTest","AttributeName","AttributeValue")
				
				# This one won't
				#definition.get_attribute("SketchUpTest","AttributeName","AttributeValue")
			end
			
			$definitions.clear
		}
}

$definitionObserver = DefinitionsObserver.new

Sketchup.active_model.definitions.add_observer($definitionObserver)

#4

(1) Because a call to set_attribute() changes the model and creates an entry on the undo stack.

You are doing this within a C++ timer that is not in sync with whatever model operations are happening. So you can be breaking whatever current operation happens to be “doing it’s thing”. Ie causing an early commit, or an abort of the operation.

(2) You are making changes to the model without using an operation and attaching it to the current one. See:
Sketchup::Model#start_operation()

(3) I am not sure if Mutex objects are compatible with SketchUp’s embedded Ruby process. Ie, threads do not work so well.

(4) Do not use global variables. They are never needed. Use class/module @@var instead. (I realize this is a test, but still using them is poor practice.)


#5

This explains a lot. I didn’t know set_attribute also handles undo stack (and I guess one of side-effects corrupts something in SketchUp when called from other thread). I guess that settles my issue. Thanks for you help again.


#6

It doesn’t “handle it”,… ANY change to the model’s entities causes an entry on the undo stack.