Kernel.at_exit isn't executed (Logging)


#1

While I’m trying to add a Logger to my module that automatically catches all Exceptions I defined, I’ve stumbled upon Kernel.at_exit which is what I intend to use (Slide 7). However, even the “hello world” example doesn’t work as intended.

    puts 'start'
    at_exit do
      puts 'inside at_exit'
    end
    puts 'end'
    exit
start
end

This method is referenced in discussions about Sketchup here and here. I’m currently only interested in Windows and it seems to have worked before, but I’m not sure if the problem is on my side or isn’t supported anymore.


#2

SketchUp Ruby is an embedded process. Therefore a handful of certain Kernel methods and Ruby features (that have to do with a running standalone Ruby process,) have no usefulness in an embedded Ruby process.

For example Kernel.exit! immediately closes SketchUp (with no SystemExit exceptions raised, no Kernel.at_exit handlers run, and probably no SketchUp API AppObserver.on_Quit callbacks called. Also, likely that SketchUp may not shut down normally (ie, saving current settings and window positions, etc.)
Note, also, that Kernel.exec will also short-circuit the SketchUp application process and cause an abnormal shutdown.

Calling Kernel.exit has no usefulnees in an embedded Ruby environment, as this does not cause an exit of the Ruby process embedded within SketchUp. (From version 2014+, you can call Sketchup.quit to do this in a cross-platform manner.)

In my opinion, these dangerous or unwanted methods should have been undefined by SketchUp just after loading it’s embedded Ruby.


#3

(1) Kernel.at_exit will not “catch” SystemExit exceptions.

(2) A rescue clause can trap exceptions. By default, rescue clauses trap StandardError and subclass exceptions only. A SystemExit exception is a direct subclass of Exception, so is not by default trapped by rescue clauses that do not specify a class trapping list.

(3) In fact someone else’s rescue clause might trap exceptions before you can, if their code does not re-raise the exception.

(4) So, it would be best if you used a begin ... rescue ... else ... ensure block around your plugin command(s). If you have multiple commands, they each can call common error handling or logging methods elsewhere in your code. (This is a good job for a library that more than one of your plugins could use.)

def some_command()
  # command code
rescue ScriptError, StandardError, SystemExit, SystemStackError => e
  @logger.log(e)
  raise # re-raise it for other debug utilities, etc.
end

So, back to specifically logging exceptions.

Have you looked at using (or mimicking) the standard library Logger ?
http://ruby-doc.org/stdlib-2.0.0/libdoc/logger/rdoc/index.html
The code may give you ideas.

I often create custom exception classes within my plugin sub-modules that only my plugin will ever trap. IE:

module DansTopLevelWrapper
  module SomePlugin
  
    GoofyError = Class::new(Exception)

    def self::some_command()
      # command code
      # somewhere here I detect a situation
      # and explicitly raise a GoofyError:
      fail(GoofyError,"a goofy situation occured",caller) if @situation == true
      # more code if @situation != true
    rescue GoofyError => e
      # recover from GoofyError
    rescue => e
      # recover from StandardError and it's subclasses
    end

  end
end

#4

Yes, creating custom exceptions and catching them in my commands is exactly what I had in mind (“automatically catches all Exceptions I defined”. Using at_exit would have minimized repetition, but this is just me trying things out. Since it was mentioned before in discussions related to Sketchup (and you ;)) I tried this way first.

Thanks for the additional insight, your contributions always help immensely understanding the underlying principle of the integrated ruby interpreter.


#5

OK I looked at that old post on the Google Groups. That was very old. And it was long before the team gave us the Sketchup::quit method. (Which I requested because Rich needed it.)
This was a special situation where Rich was doing batch mode model access on a whole group of files in a directory, one after the other. He needed to have SketchUp close each model file after making some changes, but he was on a Mac which had no way of closing models via the API at that time. (I also requested a Sketchup::Model#close() method for Rich’s needs, which was finally implemented for SUv2015. But I think he has since given up and gone on to other things.)

Also the thread at SketchUcation (also old,) was also (again a special situation) about detecting when the SketchUp application was in the shutdown cycle, and was terminating it’s embedded Ruby process. At that time the current version of SketchUp widely in use on the Mac, were not shutting down Ruby correctly, and any **AppObserver**s onQuit callback were not getting called. This also happened for a few versions on the PC. But I think both bugs were fixed in later versions. (At least I hope so.)