How do I raise a fatal exception ruby?
Asked Answered
P

3

8

Ruby has a fatal exception, but there is no guidance on how to raise it and I cannot figure it out. How do I raise a fatal exception in Ruby?

Pitapat answered 7/1, 2017 at 5:33 Comment(6)
I don't know if this is authoritative, but tutorialspoint.com states, "There is one other exception at this level, Fatal, but the Ruby interpreter only uses this internally." ("this level" being "interrupt", "No memory error" and so on). That would mean you can't raise it, though maybe you could take an action that does. If you could, I don't know if that exception would be reported. There is also the question, "Why raise it?".Hatchet
If your program really, really has to stop, Process.kill('KILL', $$) to shoot your own process with an unblockable signal. This is usually a really bad idea, mind you, as you probably want to let whatever ensure blocks are defined kick in properly.Cha
@Cha I have never tried that, but the documentation states that ensure blocks are to be executed.Fanning
Almost always, raising either StandardError or an exception derived from StandardError is all that is needed to stop your program. Can you please explain why you want to raise fatal?Colorimeter
@WayneConrad I was just experimenting with different exception. I wouldn't put this in production code.Pitapat
@mudasobwa rb_bug is an example of an immediate and complete halt: "Terminates the interpreter immediately. This function should be called under the situation caused by the bug in the interpreter. No exception handling nor ensure execution will be done." A hard kill is similar.Cha
S
11

Sure you can.

Try this

FatalError = ObjectSpace.each_object(Class).find { |klass| klass < Exception && klass.inspect == 'fatal' }

And then

raise FatalError.new("famous last words")

How does this work?

  • fatal is an internal class without associated top-level constant
  • ObjectSpace.each_object(Class) enumerates over all classes
  • find { ... } finds an exception class named "fatal"

NB though, despite its name fatal is not special, it can be rescued. If you are looking for a way to end your program maybe best call the global exit method?

begin
  raise FatalError.new
rescue Exception => e
  puts "Not so fatal after all..."
end
Standice answered 7/1, 2017 at 11:13 Comment(6)
While this is a neat trick (+1 for that), it's not something one should use in production code. Either exit directly or raise a normal exception.Whig
I didn't see your answer, and came here all happy about my ObjectSpace trick. 10h too late! Please rename each and you'll get my vote ;)Nambypamby
Also, fatal doesn't seem special. It doesn't kill the process, and can still be rescued.Nambypamby
@holgerjust despite its name fatal is not special, it can be rescued. I don't see a reason why someone should ban this from production code.Standice
Well, why would you go all these length to throw an exception you can't easily rescue on its own instead of using your own Exception sub-class? fatal is a Ruby-internal Exception and should be used that way. For your own exceptions, you should use your own Exception classes, which almost always should inherit from StandardError in order to be rescuable by a simple rescue. Just because you can doesn't mean you should.Whig
fatal isn't special, just a subclass of Exception, but raising it in the way as above is like calling rb_raise(rb_eFatal, ...), not rb_fatal(...), you can see they use different tags here, the last cannot be rescuedWiggly
B
1

Short answer is, you can, but probably shouldn't. This exception is reserved for Ruby internals. It is effectively hidden to users by being a constant with an all lowercase identifier. (Ruby won't do a constant lookup unless the identifier starts with an uppercase character.)

fatal
NameError: undefined local variable or method `fatal' for main:Object

The same is true when using Object#const_get:

Object.const_get(:fatal)
NameError: wrong constant name fatal

If this exception class was intended for us to use, then it would be readily available, and not hidden away.

Britain answered 7/1, 2017 at 6:18 Comment(3)
“Short answer is, you can't.”—strictly speaking, one still can write a native extension, that #include <ruby.h> and rb_fatal("%s", msg); afterwards.Fanning
You can use ObjectSpace to get the class.Standice
And there seems to be a misunderstanding on how classes relate to constants. It just so happens that each class is assigned to a constant with the same name. But this is just a convention, for example you can do class A; end; a = A.new; A = 12345; [a.class.name, A] # => ["A", 12345]Standice
W
0

For MRI, you can reproduce the way fatal is defined via ffi and convert C API VALUEs with fiddle, but note that simply raising it raise fatal, '...' is like calling rb_raise(rb_eFatal, ...), not rb_fatal(...), you can see they use different tags here, the last cannot be rescued (rescue fatal won't work too)

require 'ffi'
require 'fiddle'

module RbFFI
  extend FFI::Library
  ffi_lib FFI::CURRENT_PROCESS
end

RbFFI.attach_function :rb_define_class, [:string, :ulong], :ulong
RbFFI.attach_function :rb_raise, [:ulong, :string, :varargs], :void
RbFFI.attach_function :rb_fatal, [:string, :varargs], :void

rb_eException = Fiddle.dlwrap Exception # Just a hacky way to convert get VALUE representation of Ruby object
rb_eFatal = RbFFI.rb_define_class('fatal', rb_eException)
fatal = Fiddle.dlunwrap(rb_eFatal) # Just a hacky way to convert get Ruby object from VALUE representation

at_exit do
  puts '-----------------'
  puts 'at_exit'
end

def test(name, &block)
  puts '-----------------'
  puts "test: #{name}"
  begin
    block.call
  rescue Exception => e
    puts "rescued #{e.message} (#{e.class})"
  ensure
    puts "ensure: #{name}"
  end
  puts "done: #{name}"
end

test('1') { RbFFI.rb_raise rb_eFatal, 'rb_raise rb_eFatal' }

test('2') { raise fatal, 'raise fatal' }

test('3') { RbFFI.rb_fatal 'rb_fatal' }

produces

-----------------
test: 1
rescued rb_raise rb_eFatal (fatal)
ensure: 1
done: 1
-----------------
test: 2
rescued raise fatal (fatal)
ensure: 2
done: 2
-----------------
test: 3
ensure: 3
-----------------
at_exit
/path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:47:in `invoke': rb_fatal (fatal)
    from /path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:47:in `call'
    from /path/to/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/ffi-1.16.3/lib/ffi/variadic.rb:62:in `rb_fatal'
    from fatal.rb:38:in `block in <main>'
    from fatal.rb:26:in `test'
    from fatal.rb:38:in `<main>'
Wiggly answered 2/9 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.