Open georgeclaghorn opened 2 years ago
Thank you @georgeclaghorn . I'll have to add this to the test suite and think about a solution. First thing that comes to mind is when a Ruby application closes there is some finally
code that can be run... so I believe something like this might be implemented per thread (which is how raising works in Ruby by interrupting the current thread).
As far as a result return type that may not be easy to implement with Ruby if you're talking about catching exceptions because raised exceptions interrupt the current thread process one would have to write code to look for that, but then if you have multiple layers of Ruby code trying to catch then you run into the issue of multiple handlers for that scenario.
But this is definitely worth investigating and resolving.
rb_raise
uses libclongjmp
under the hood to enter the Ruby VM's exception handling code. This is how raising a Ruby exception short-circuits execution of the current function in C and Rust. Pretty cool!Importantly for Rust, though, this bypasses the drops that the compiler inserts for owned values at the end of each wrapping scope. This means anything in scope when Rutie
VM::raise
is called will not be dropped. The result is leaked memory, file descriptors, sockets, mutexes, you name it—anything practicing RAII and relying on automaticDrop::drop
calls at scope boundaries to clean up.Consider the following minimal Ruby native extension written with Rutie:
If I run
Sandbox.example "test", false
in IRB, the following is printed:No exception is raised, so
Sandbox.example
allocates and properly drops aWrapper
struct.If I run
Sandbox.example "test", true
, however, the result is this:Yikes. Note that, unlike in the previous example,
Dropping Wrapper
is not printed.Sandbox.example
allocated aWrapper
struct, but because it subsequently raised an exception viaVM::raise
, theWrapper
was not dropped. 🚨To further demonstrate, if I run
loop { Sandbox.example "a" * 1_000_000, true rescue nil }
, the IRB process’s memory usage grows unbounded. This does not happen if I passfalse
as the second argument toSandbox.example
.One possible solution: the PyO3 library, which provides Rust bindings for Python, deals with this by requiring Rust implementations of Python functions to return
PyResult
, similar to Rust standard libraryResult
.Ok
values are treated as returns.Err
values are treated as exceptions and safely raised by the library. This seems idiomatic to me, but I’m not sure how tractable or palatable it is for Rutie.