at_exit handler *except* for fatal runtime error

S

synergism

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
 
A

Alex Fenton

synergism said:
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
 
B

Brian Candler

synergism said:
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
 
D

DMisener

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)
 
R

Robert Klemme

2008/9/18 synergism said:
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
 
D

DMisener

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 :=).
 
A

ara.t.howard

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)


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/
 
R

Robert Klemme

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
 
R

Robert Dober

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


--=20
C'est v=E9ritablement utile puisque c'est joli.

Antoine de Saint Exup=E9ry
 
D

DMisener

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.
 
A

ara.t.howard

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/
 
D

DMisener

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


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.
 
R

Robert Klemme

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


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


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.

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
 
D

DMisener

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

Probably... :)
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

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
 
A

ara.t.howard

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.

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/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top