Forum: Ruby at_exit handler *except* for fatal runtime error

6d918af75e769584eff64ba7012fde01?d=identicon&s=25 synergism (Guest)
on 2008-09-18 14:45
(Received via mailing list)
How do we execute a handler (I assume via at_exit) when a program
terminates *except* when there was a fatal runtime error (e.g. any
error such as "divide by zero" that terminates the program)?

We thought perhaps 'caller' would give us some clues but it turns out
to be useless.

Ben
F52a217f68b0d4db49ce2ceb06f79691?d=identicon&s=25 Alex Fenton (Guest)
on 2008-09-18 15:05
(Received via mailing list)
synergism wrote:
> How do we execute a handler (I assume via at_exit) when a program
> terminates *except* when there was a fatal runtime error (e.g. any
> error such as "divide by zero" that terminates the program)?

Inspect the value of $! (or $ERROR_INFO) in your at_exit handler. This
will contain the last exception raised. eg

Kernel.at_exit {
   if $!.kind_of? ZeroDivisionError
     puts "don't run"
   else
     puts "run it"
   end
}

5 / 0


hth
a
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-09-18 15:10
synergism wrote:
> How do we execute a handler (I assume via at_exit) when a program
> terminates *except* when there was a fatal runtime error (e.g. any
> error such as "divide by zero" that terminates the program)?

"divide by zero" isn't a fatal error - you can catch it. But if you call
"exit!" then the program will abort without running at_exit handlers.

begin
  0/0
rescue ZeroDivisionError
  exit!
end

So,if by "fatal runtime error" you just mean "any uncaught exception",
then you can just wrap your entire program with

begin
  ... rest of program
rescue Exception
  exit!
end
4ede1597c2d27e0a6364408ffe7d0e77?d=identicon&s=25 DMisener (Guest)
on 2008-09-18 17:12
(Received via mailing list)
Our situation is a little more complicated:

In our particular case we are creating run-time at_exit{} handlers

To give a concrete example:

We have a generic OpenOutputFile(...) routine which opens a "tentative
output" file
(output which is conditional on the module successfully completing).
We generate a temporary work file and at_exit we rename this file to
its "permanent name".
If the program aborts for any reason, this rename should not happen
( we do leave the failed working file around for later problem
investigation)
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 Ara Howard (ahoward)
on 2008-09-18 19:06
(Received via mailing list)
On Sep 18, 2008, at 9:02 AM, DMisener wrote:

> its "permanent name".
> If the program aborts for any reason, this rename should not happen
> ( we do leave the failed working file around for later problem
> investigation)


you can do this

at_exit {
   rename_files unless $!
}

but you can also make it vastly simpler: do not use at exit handlers

setup a global list of renames to occur, for instance

   module Rename
     Map = {}

     def file hash = {}
       src, dst, *ignored = hash.to_a.first
       Map[src.to_s] = dst.to_s
     end

      def files!
        require 'fileutils'
        Map.each{|src,dst| FileUtils.mv src, dst}
        Map.clear
      end

      extend self
   end

then, in the code, add files to be renamed

   Rename.file 'foo' => 'bar'

this does not rename the file, it only notes that it needs to be done
later

now *as the last line of your program do*

   Rename.files!

since this line is the last line of you program, it will never execute
under exceptional or error conditions.  if you prefer you can then
wrap this up with an at_exit handler

at_exit {
   Rename.files! unless $!
}

but be warned - calling exit or exit! itself will set $!


cfp:~ > ruby -e'  begin; exit; ensure; p $!.class; end  '
SystemExit


so, in fact, you have to be very, very, very careful about doing
things in at exit handlers based on 'error' conditions.  this is a bug

at_exit{ cleanup! unless $! }
exit 0

that's why a solution that does not depend on at_exit or $! is much
better - simply defer the renaming until the very end.  it's a sure
thing that you'll introduce bugs otherwise.


in any case it seems like you are working too hard - just 'normal'
ruby code performs *exactly* as per your requirements anyhow

   do_the_work
   rename_the_file      # does *not* execute if previous line raises

cheers.


a @ http://codeforpeople.com/
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-09-18 21:40
(Received via mailing list)
2008/9/18 synergism <synergism@gmail.com>:
> How do we execute a handler (I assume via at_exit) when a program
> terminates *except* when there was a fatal runtime error (e.g. any
> error such as "divide by zero" that terminates the program)?

You can use exit! for this:

18:09:02 ~$ ruby -e 'at_exit { puts "exiting" }'
exiting
18:09:08 ~$ ruby -e 'at_exit { puts "exiting" }; exit 1'
exiting
18:09:14 ~$ ruby -e 'at_exit { puts "exiting" }; raise "Foo"'
exiting
-e:1: Foo (RuntimeError)
18:09:21 ~$ ruby -e 'at_exit { puts "exiting" }; exit! 1'
18:09:27 ~$

Catch specific errors and use exit! to exit the program.  Then your
handlers won't be invoked.

Cheers

robert
4ede1597c2d27e0a6364408ffe7d0e77?d=identicon&s=25 DMisener (Guest)
on 2008-09-18 22:53
(Received via mailing list)
On Sep 18, 1:03 pm, Robert Klemme <shortcut...@googlemail.com> wrote:
> 2008/9/18 synergism <synerg...@gmail.com>:
>
> > How do we execute a handler (I assume via at_exit) when a program
> > terminates *except* when there was a fatal runtime error (e.g. any
> > error such as "divide by zero" that terminates the program)?
>
> You can use exit! for this:
>
...
> -e:1: Foo (RuntimeError)
> 18:09:21 ~$ ruby -e 'at_exit { puts "exiting" }; exit! 1'
> 18:09:27 ~$
>
> Catch specific errors and use exit! to exit the program.  Then your
> handlers won't be invoked.
>

But out above example we want to suppress the  file rename for any
"unanticipated exception" -- which
is just the opposite of the solution you presented :=).
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-09-18 23:16
(Received via mailing list)
On 18.09.2008 18:31, DMisener wrote:
>> 18:09:21 ~$ ruby -e 'at_exit { puts "exiting" }; exit! 1'
>> 18:09:27 ~$
>>
>> Catch specific errors and use exit! to exit the program.  Then your
>> handlers won't be invoked.
>>
>
> But out above example we want to suppress the  file rename for any
> "unanticipated exception" -- which
> is just the opposite of the solution you presented :=).

Oh, come on.  You can easily use that information to build what you
want.

  robert
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2008-09-19 12:09
(Received via mailing list)
On Thu, Sep 18, 2008 at 10:07 PM, Robert Klemme
> Oh, come on.  You can easily use that information to build what you want.
>
>        robert
>
>
Hmm maybe not Robert, my feeling is that they should not do what they
are doing in the at_exit block.
OP can you give us the rationale behind this design. At first sight I
believe you
should do what you want in the main script. Look at Ara's ideas e.g.

Cheers
Robert


--
C'est véritablement utile puisque c'est joli.

Antoine de Saint Exupéry
4ede1597c2d27e0a6364408ffe7d0e77?d=identicon&s=25 DMisener (Guest)
on 2008-09-22 18:30
(Received via mailing list)
On Sep 19, 6:57 am, Robert Dober <robert.do...@gmail.com> wrote:
> On Thu, Sep 18, 2008 at 10:07 PM, Robert Klemme> Oh, come on.  
> You can easily use that information to build what you want.

Actually inverting the logic isn't that easy (at least for me)

> Hmm maybe not Robert, my feeling is that they should not do what they
> are doing in the at_exit block.

I'm certainly not married to the the at_exit approach.

> OP can you give us the rationale behind this design. At first sight I
> believe you should do what you want in the main script. Look at Ara's ideas e.g.

Simple problem:

Create a tentative output working file.  If the program exits
"cleanly" then rename the work file
to its "final" name.  If not leave the work file for possible
examination.

Ideally this File.open_tentative_output_file would be plug
"compatible" with the simple File.open
(i.e. support modes and blocks).

Perhaps I'm trying to make life "too easy" for the application
programmer by eliminating the need for
an explicit File.close_all_pending_tentative_file routine.  That is
really all that I am trying to achieve with the
at_exit{} code.  One less step to remember.  But perhaps the
implementation complexity isn't worth it.

BTW: It sure would be nice to have an on_abort{} handler.
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 Ara Howard (ahoward)
on 2008-09-22 18:32
(Received via mailing list)
On Sep 22, 2008, at 9:51 AM, DMisener wrote:

> BTW: It sure would be nice to have an on_abort{} handler.

there is : 'rescue'

a @ http://codeforpeople.com/
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 Ara Howard (ahoward)
on 2008-09-22 18:36
(Received via mailing list)
On Sep 22, 2008, at 9:51 AM, DMisener wrote:

> Simple problem:
>
> Create a tentative output working file.  If the program exits
> "cleanly" then rename the work file
> to its "final" name.  If not leave the work file for possible
> examination.
>
> Ideally this File.open_tentative_output_file would be plug
> "compatible" with the simple File.open
> (i.e. support modes and blocks).

class TentativeFile
   Ext = 'tentative'

   def open path, *a, &b
     super "#{ path }.#{ Ext }", *a, &b
   end

   def close
      final = path.to_s.sub %r/[.]#{ Ext }$/, ''
      super
   ensure
     File.rename path, final unless $!
   end
end



a @ http://codeforpeople.com/
4ede1597c2d27e0a6364408ffe7d0e77?d=identicon&s=25 DMisener (Guest)
on 2008-09-22 20:47
(Received via mailing list)
On Sep 22, 1:27 pm, "ara.t.howard" <ara.t.how...@gmail.com> wrote:
> > "compatible" with the simple File.open
>       final = path.to_s.sub %r/[.]#{ Ext }$/, ''
>       super
>    ensure
>      File.rename path, final unless $!
>    end
> end


output=TentativeFile.new.open('temp.tmp','w')
output.puts 'Thanks'

without an explict close doesn't automatically rename the file to its
proper form.

With "regular" File.open ... the file is automatically closed without
need of explicit close. That was
the behaviour I was trying to emulate.  I'm starting to think this
"frill" is a lost cause.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-09-22 20:51
(Received via mailing list)
On 22.09.2008 17:56, DMisener wrote:
>
> Ideally this File.open_tentative_output_file would be plug
> "compatible" with the simple File.open
> (i.e. support modes and blocks).
>
> Perhaps I'm trying to make life "too easy" for the application
> programmer by eliminating the need for
> an explicit File.close_all_pending_tentative_file routine.

It rather seems that you're making *your* life too hard. :-)

>  That is
> really all that I am trying to achieve with the
> at_exit{} code.  One less step to remember.  But perhaps the
> implementation complexity isn't worth it.

Why do you need at_exit for this - especially if you want to be
compatible to File.open?

def File.open_tentative_output_file(tmp, final, mode = "w", &b)
   raise "Need a block" unless b
   File.open(tmp, mode, &b)
   File.rename tmp, final
end

Done.

Cheers

  robert
4ede1597c2d27e0a6364408ffe7d0e77?d=identicon&s=25 DMisener (Guest)
on 2008-09-22 21:47
(Received via mailing list)
On Sep 22, 3:47 pm, Robert Klemme <shortcut...@googlemail.com> wrote:
> >> Hmm maybe not Robert, my feeling is that they should not do what they
> > "cleanly" then rename the work file
>
> It rather seems that you're making *your* life too hard. :-)

Probably... :-)

>    raise "Need a block" unless b
>    File.open(tmp, mode, &b)
>    File.rename tmp, final
> end

So the recommendation is that any "successful program completion"
cleanup be coerced into
the execute a block then cleanup construct cited above.

Enforcing the the block is required is the key. I was hoping to relax
that requirement but
you can't have everything.

That might do the trick...

BTW: here was my previous implementation... actually called OutputFile


# Setup ABORT detection preamble

$ABORTING=true

def exit_program *args
    $ABORTING=false
    __original_exit__(*args)
end


....


alias :exit :exit_program


class OutputFile
    def initialize filename,options={},&block
        @filename,@options,@block=filename.dup,options.dup,block
        @mode=options[:mode] || 'w'
        unless append?
            # TODO: Nice if we could validate valid filename here
instead of at "open?" time
            # TODO: support :nosuperceed, :superceed
            File.send(@options[:backup] ? :backup : :delete,@filename)
if File.exists? @filename
            @work_filename=Filename.work filename
        end
        if block_given?
            yield self
            close
        else
            # TODO: Support suppression of autoclose on fatal_error
            at_exit{close unless $ABORTING}
        end
        @file
    end

    def puts *args
        open?
        @file.puts(*args)
    end

    def print *args
        open?
        @file.puts(*args)
    end

    def close
        if @file
            @file.close
            # TODO: what to do if rename fails for any reason
            File.rename @work_filename,@filename if @work_filename
        end
        @file=nil
    end

    private

    def open? # Defer open until first require output request
        return if @file
        # TODO: support wait on busy
        @file=File.open(@work_filename || @filename,@mode)
    end

    def append?
        /a/i.match(@mode)
    end
end

def OutputFile *args,&block
    OutputFile.new(*args,&block)
end
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 Ara Howard (ahoward)
on 2008-09-22 22:07
(Received via mailing list)
On Sep 22, 2008, at 12:36 PM, DMisener wrote:

> the behaviour I was trying to emulate.  I'm starting to think this
> "frill" is a lost cause.
>
>

you've got to play with code at least a little!


cfp:~ > cat a.rb
class TentativeFile < File
    Ext = 'tentative'

    def initialize path, *a, &b
      super "#{ path }.#{ Ext }", *a, &b
      at_exit{ close unless closed? } unless b
    end

    def close
       STDERR.puts "closing #{ path }..."
       final = path.to_s.sub %r/[.]#{ Ext }$/, ''
       super
    ensure
      File.rename path, final unless $!
    end
end

output=TentativeFile.new('temp.tmp','w')
output.puts 'Thanks'


cfp:~ > ruby a.rb
closing temp.tmp.tentative...


cfp:~ > cat temp.tmp
Thanks





a @ http://codeforpeople.com/
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.