Observer changes to model - onTransactionCommit causes Crash

I am building an extension that uses observers to see what the user is doing. When certain model changes are made, the extension makes further changes to the model. I understand that this is a risky thing to do, and I have read in detail about Safer Observer Events, and followed the advice of using a timer.

Essentially, my extension has an events queue that listens for various changes to the model, adds it to the queue (not making any changes), then uses the onTransactionCommit observer, with the UI.timer hack as suggested in the Safer Observer Events example.

I have spent many hours checking my code for any errors, but I keep getting a crash.
After getting a ruby debugger up and running, I have managed to get the following ruby back trace.

from /Users/username/Library/Application Support/SketchUp 2019/SketchUp/Plugins/SkinUp/ruby/CommitObserver.rb:139:in `block in onTransactionCommit'
from /Users/username/Library/Application Support/SketchUp 2019/SketchUp/Plugins/SkinUp/ruby/CommitObserver.rb:139:in `commit_operation'
from /users/username/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `onComponentAdded'
from /users/username/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `instances'

I should note that it doesn’t crash every time - more like once every 6 operations. It always crashes when model.commit_operation is called from my plugin. It appears to crash deep in the SketchUp code on SketchUp CComponentDefinition::GetInstance:, but I can’t get much info other than that.

I have checked my code for bad behaviour, and I am pretty sure that I have checked that an entity exists before doing anything to it throughout my code. It is always the Dynamic Components plugin that seems to be giving me issues with this. The plugin does delete and create components (but not DC ones).

  1. Am I doing something wrong? Or is this more likely to be a bug in Dynamic Components?

  2. Is the approach of making an events queue and only making model changes on onTransactionCommit a bad idea for any reason?

In case you want to inspect the code, the problem function is below:

def onTransactionCommit(model)
  # The following snippet from SaferObserverEvents: https://github.com/SketchUp/sketchup-safe-observer-events/blob/master/src/safer_observer_events.rb
  executed = false
  timer_id = UI.start_timer(0, false) {
    break if executed
    executed = true
    next if  BuildQueue.length == 0
    if (!TK::SkinUp.deactivate_observers)
      TK::SkinUp.deactivate_observers = true
      @@deactivate_commit = true
      # Ensure that the operation is transparent so the Undo stack isn't
      # cluttered.
      status = model.start_operation('SkinUp Entities', true, false, true)
      raise LogicError, "Start Operation Error" if status == false
      new_templates = []
      begin
        new_templates = BuildQueue.process_queue()
        if new_templates.length == 0
          puts caller
        end
      rescue
        # Note: Never abort a transparent operation, it will abort the operation
        # it chains to. Instead, try to clean up and simply commit in order to
        # make sure the operation is closed.
        # https://assets.sketchup.com/files/ewh/Observers2016.pdf
        # TODO: the commit operation triggers a crash in the observers in DCobservers.  Stack trace following commit:
        # /users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `instances'
        #/users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `onComponentAdded'
        model.commit_operation
        @@deactivate_commit = false
        TK::SkinUp.deactivate_observers = false
        raise LogicError, 'SkinUp Build Queue processing failed'
      end
      puts "DEBUG: events processed: " + new_templates.length.to_s
      status = model.commit_operation
      puts "DEBUG: transaction commited"  # crashes before it gets called
      @@deactivate_commit = false
      TK::SkinUp.deactivate_observers = false
    end
  }
end

As of SU2016 the Safer Observer pattern isn’t needed. SketchUp will defer all notifications until the end of an operation. Unless you are supporting older SU versions then it’s not needed.

When running into crashes in observers where the cause can be indirect I would try this:

  1. Disable all other extensions and see if you still crash. (Eliminate external influence)
  2. Add logging around the code you think might trigger it.

Do you have a complete code example that reproduce the crash. This snippet is just a fragment of the whole logic.

(Btw, did you submit the BugSplats? Did you enter any information we can use to look it up?)

What SketchUp version are you seeing this on?

1 Like

Aaaaaah, good to know. I was being overly careful then. Although checking an entity is valid before any operation is still a good idea, I am guessing.

On 1. - disabling Dynamic Components resulted in no more crashes
On 2. logging around the code told me for sure that the crash happened on model.commit_operation . The ruby backtrace told me that after that, DCobservers.rbe’s onComponentAdded then instances caused the crash.

There is quite a lot of code that makes up the whole logic (spread across multiple files)! I wouldn’t want to bore you with going through it, but certainly in the logic components are deleted, loaded, then instances inserted.

I do when I can, unfortunately, my Mac seems to be blocking Bugsplats at the moment:

SU 2019

Are you able to narrow this down to a small example?

Let me know if you manage to report the BugSplat.