mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 05:29:10 +02:00
Support cause:
in Thread#raise
and Fiber#raise
. (#13967)
* Add support for `cause:` argument to `Fiber#raise` and `Thread#raise`. The implementation behaviour is consistent with `Kernel#raise` and `Exception#initialize` methods, allowing the `cause:` argument to be passed to `Fiber#raise` and `Thread#raise`. This change ensures that the `cause:` argument is handled correctly, providing a more consistent and expected behavior when raising exceptions in fibers and threads. [Feature #21360] * Shared specs for Fiber/Thread/Kernel raise. --------- Co-authored-by: Samuel Williams <samuel.williams@shopify.com>
This commit is contained in:
parent
2e0a782936
commit
64f508ade8
12 changed files with 450 additions and 74 deletions
11
NEWS.md
11
NEWS.md
|
@ -104,6 +104,16 @@ Note: We're only listing outstanding class updates.
|
|||
* Update Unicode to Version 16.0.0 and Emoji Version 16.0.
|
||||
[[Feature #19908]][[Feature #20724]] (also applies to Regexp)
|
||||
|
||||
* Thread
|
||||
|
||||
* Introduce support for `Thread#raise(cause:)` argument similar to
|
||||
`Kernel#raise`. [[Feature #21360]]
|
||||
|
||||
* Fiber
|
||||
|
||||
* Introduce support for `Fiber#raise(cause:)` argument similar to
|
||||
`Kernel#raise`. [[Feature #21360]]
|
||||
|
||||
* Fiber::Scheduler
|
||||
|
||||
* Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a
|
||||
|
@ -243,3 +253,4 @@ The following bundled gems are updated.
|
|||
[Feature #21262]: https://bugs.ruby-lang.org/issues/21262
|
||||
[Feature #21287]: https://bugs.ruby-lang.org/issues/21287
|
||||
[Feature #21347]: https://bugs.ruby-lang.org/issues/21347
|
||||
[Feature #21360]: https://bugs.ruby-lang.org/issues/21360
|
||||
|
|
|
@ -4094,6 +4094,7 @@ cont.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
|
|||
cont.$(OBJEXT): $(top_srcdir)/internal/compilers.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/cont.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/error.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/eval.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/gc.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/imemo.h
|
||||
cont.$(OBJEXT): $(top_srcdir)/internal/namespace.h
|
||||
|
@ -19292,6 +19293,7 @@ thread.$(OBJEXT): $(top_srcdir)/internal/class.h
|
|||
thread.$(OBJEXT): $(top_srcdir)/internal/compilers.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/cont.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/error.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/eval.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/gc.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/hash.h
|
||||
thread.$(OBJEXT): $(top_srcdir)/internal/imemo.h
|
||||
|
|
5
cont.c
5
cont.c
|
@ -30,6 +30,7 @@ extern int madvise(caddr_t, size_t, int);
|
|||
#include "internal/cont.h"
|
||||
#include "internal/thread.h"
|
||||
#include "internal/error.h"
|
||||
#include "internal/eval.h"
|
||||
#include "internal/gc.h"
|
||||
#include "internal/proc.h"
|
||||
#include "internal/sanitizers.h"
|
||||
|
@ -3218,9 +3219,9 @@ fiber_raise(rb_fiber_t *fiber, VALUE exception)
|
|||
}
|
||||
|
||||
VALUE
|
||||
rb_fiber_raise(VALUE fiber, int argc, const VALUE *argv)
|
||||
rb_fiber_raise(VALUE fiber, int argc, VALUE *argv)
|
||||
{
|
||||
VALUE exception = rb_make_exception(argc, argv);
|
||||
VALUE exception = rb_exception_setup(argc, argv);
|
||||
|
||||
return fiber_raise(fiber_ptr(fiber), exception);
|
||||
}
|
||||
|
|
141
eval.c
141
eval.c
|
@ -703,49 +703,142 @@ rb_interrupt(void)
|
|||
rb_exc_raise(rb_exc_new(rb_eInterrupt, 0, 0));
|
||||
}
|
||||
|
||||
enum {raise_opt_cause, raise_max_opt}; /*< \private */
|
||||
|
||||
static int
|
||||
extract_raise_opts(int argc, VALUE *argv, VALUE *opts)
|
||||
extract_raise_options(int argc, VALUE *argv, VALUE *cause)
|
||||
{
|
||||
int i;
|
||||
// Keyword arguments:
|
||||
static ID keywords[1] = {0};
|
||||
if (!keywords[0]) {
|
||||
CONST_ID(keywords[0], "cause");
|
||||
}
|
||||
|
||||
if (argc > 0) {
|
||||
VALUE opt;
|
||||
argc = rb_scan_args(argc, argv, "*:", NULL, &opt);
|
||||
if (!NIL_P(opt)) {
|
||||
if (!RHASH_EMPTY_P(opt)) {
|
||||
ID keywords[1];
|
||||
CONST_ID(keywords[0], "cause");
|
||||
rb_get_kwargs(opt, keywords, 0, -1-raise_max_opt, opts);
|
||||
if (!RHASH_EMPTY_P(opt)) argv[argc++] = opt;
|
||||
return argc;
|
||||
VALUE options;
|
||||
argc = rb_scan_args(argc, argv, "*:", NULL, &options);
|
||||
|
||||
if (!NIL_P(options)) {
|
||||
if (!RHASH_EMPTY_P(options)) {
|
||||
// Extract optional cause keyword argument, leaving any other options alone:
|
||||
rb_get_kwargs(options, keywords, 0, -2, cause);
|
||||
|
||||
// If there were any other options, add them back to the arguments:
|
||||
if (!RHASH_EMPTY_P(options)) argv[argc++] = options;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < raise_max_opt; ++i) {
|
||||
opts[i] = Qundef;
|
||||
}
|
||||
|
||||
return argc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete exception setup for cross-context raises (Thread#raise, Fiber#raise).
|
||||
* Handles keyword extraction, validation, exception creation, and cause assignment.
|
||||
*
|
||||
* @param[in] argc Number of arguments
|
||||
* @param[in] argv Argument array (will be modified for keyword extraction)
|
||||
* @return Prepared exception object with cause applied
|
||||
*/
|
||||
VALUE
|
||||
rb_exception_setup(int argc, VALUE *argv)
|
||||
{
|
||||
rb_execution_context_t *ec = GET_EC();
|
||||
|
||||
// Extract cause keyword argument:
|
||||
VALUE cause = Qundef;
|
||||
argc = extract_raise_options(argc, argv, &cause);
|
||||
|
||||
// Validate cause-only case:
|
||||
if (argc == 0 && !UNDEF_P(cause)) {
|
||||
rb_raise(rb_eArgError, "only cause is given with no arguments");
|
||||
}
|
||||
|
||||
// Create exception:
|
||||
VALUE exception;
|
||||
if (argc == 0) {
|
||||
exception = rb_exc_new(rb_eRuntimeError, 0, 0);
|
||||
}
|
||||
else {
|
||||
exception = rb_make_exception(argc, argv);
|
||||
}
|
||||
|
||||
VALUE resolved_cause = Qnil;
|
||||
|
||||
// Resolve cause with validation:
|
||||
if (UNDEF_P(cause)) {
|
||||
// No explicit cause - use automatic cause chaining from calling context:
|
||||
resolved_cause = rb_ec_get_errinfo(ec);
|
||||
|
||||
// Prevent self-referential cause (e.g. `raise $!`):
|
||||
if (resolved_cause == exception) {
|
||||
resolved_cause = Qnil;
|
||||
}
|
||||
}
|
||||
else if (NIL_P(cause)) {
|
||||
// Explicit nil cause - prevent chaining:
|
||||
resolved_cause = Qnil;
|
||||
}
|
||||
else {
|
||||
// Explicit cause - validate and assign:
|
||||
if (!rb_obj_is_kind_of(cause, rb_eException)) {
|
||||
rb_raise(rb_eTypeError, "exception object expected");
|
||||
}
|
||||
|
||||
if (cause == exception) {
|
||||
// Prevent self-referential cause (e.g. `raise error, cause: error`) - although I'm not sure this is good behaviour, it's inherited from `Kernel#raise`.
|
||||
resolved_cause = Qnil;
|
||||
}
|
||||
else {
|
||||
// Check for circular causes:
|
||||
VALUE current_cause = cause;
|
||||
while (!NIL_P(current_cause)) {
|
||||
// We guarantee that the cause chain is always terminated. Then, creating an exception with an existing cause is not circular as long as exception is not an existing cause of any other exception.
|
||||
if (current_cause == exception) {
|
||||
rb_raise(rb_eArgError, "circular causes");
|
||||
}
|
||||
if (THROW_DATA_P(current_cause)) {
|
||||
break;
|
||||
}
|
||||
current_cause = rb_attr_get(current_cause, id_cause);
|
||||
}
|
||||
resolved_cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply cause to exception object (duplicate if frozen):
|
||||
if (!UNDEF_P(resolved_cause)) {
|
||||
if (OBJ_FROZEN(exception)) {
|
||||
exception = rb_obj_dup(exception);
|
||||
}
|
||||
rb_ivar_set(exception, id_cause, resolved_cause);
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_f_raise(int argc, VALUE *argv)
|
||||
{
|
||||
VALUE err;
|
||||
VALUE opts[raise_max_opt], *const cause = &opts[raise_opt_cause];
|
||||
VALUE cause = Qundef;
|
||||
argc = extract_raise_options(argc, argv, &cause);
|
||||
|
||||
argc = extract_raise_opts(argc, argv, opts);
|
||||
VALUE exception;
|
||||
|
||||
// Bare re-raise case:
|
||||
if (argc == 0) {
|
||||
if (!UNDEF_P(*cause)) {
|
||||
// Cause was extracted, but no arguments were provided:
|
||||
if (!UNDEF_P(cause)) {
|
||||
rb_raise(rb_eArgError, "only cause is given with no arguments");
|
||||
}
|
||||
err = get_errinfo();
|
||||
if (!NIL_P(err)) {
|
||||
|
||||
// Otherwise, re-raise the current exception:
|
||||
exception = get_errinfo();
|
||||
if (!NIL_P(exception)) {
|
||||
argc = 1;
|
||||
argv = &err;
|
||||
argv = &exception;
|
||||
}
|
||||
}
|
||||
rb_raise_jump(rb_make_exception(argc, argv), *cause);
|
||||
|
||||
rb_raise_jump(rb_make_exception(argc, argv), cause);
|
||||
|
||||
UNREACHABLE_RETURN(Qnil);
|
||||
}
|
||||
|
|
|
@ -275,7 +275,7 @@ VALUE rb_fiber_transfer_kw(VALUE fiber, int argc, const VALUE *argv, int kw_spla
|
|||
* @exception rb_eFiberError `fiber` is terminated etc.
|
||||
* @return (See rb_fiber_resume() for details)
|
||||
*/
|
||||
VALUE rb_fiber_raise(VALUE fiber, int argc, const VALUE *argv);
|
||||
VALUE rb_fiber_raise(VALUE fiber, int argc, VALUE *argv);
|
||||
|
||||
RBIMPL_SYMBOL_EXPORT_END()
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ extern ID ruby_static_id_status;
|
|||
VALUE rb_refinement_module_get_refined_class(VALUE module);
|
||||
void rb_class_modify_check(VALUE);
|
||||
NORETURN(VALUE rb_f_raise(int argc, VALUE *argv));
|
||||
VALUE rb_exception_setup(int argc, VALUE *argv);
|
||||
void rb_refinement_setup(struct rb_refinements_data *data, VALUE module, VALUE klass);
|
||||
void rb_vm_using_module(VALUE module);
|
||||
VALUE rb_top_main_class(const char *method);
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
module FiberSpecs
|
||||
|
||||
class NewFiberToRaise
|
||||
def self.raise(*args)
|
||||
fiber = Fiber.new { Fiber.yield }
|
||||
def self.raise(*args, **kwargs, &block)
|
||||
fiber = Fiber.new do
|
||||
if block_given?
|
||||
block.call do
|
||||
Fiber.yield
|
||||
end
|
||||
else
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
|
||||
fiber.resume
|
||||
fiber.raise(*args)
|
||||
|
||||
fiber.raise(*args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ require_relative '../../shared/kernel/raise'
|
|||
|
||||
describe "Fiber#raise" do
|
||||
it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise
|
||||
it_behaves_like :kernel_raise_across_contexts, :raise, FiberSpecs::NewFiberToRaise
|
||||
end
|
||||
|
||||
describe "Fiber#raise" do
|
||||
|
|
|
@ -6,6 +6,30 @@ module ThreadSpecs
|
|||
end
|
||||
end
|
||||
|
||||
class NewThreadToRaise
|
||||
def self.raise(*args, **kwargs, &block)
|
||||
thread = Thread.new do
|
||||
Thread.current.report_on_exception = false
|
||||
|
||||
if block_given?
|
||||
block.call do
|
||||
sleep
|
||||
end
|
||||
else
|
||||
sleep
|
||||
end
|
||||
end
|
||||
|
||||
Thread.pass until thread.stop?
|
||||
|
||||
thread.raise(*args, **kwargs)
|
||||
|
||||
thread.join
|
||||
ensure
|
||||
thread.kill if thread.alive?
|
||||
end
|
||||
end
|
||||
|
||||
class Status
|
||||
attr_reader :thread, :inspect, :status, :to_s
|
||||
def initialize(thread)
|
||||
|
|
|
@ -3,6 +3,9 @@ require_relative 'fixtures/classes'
|
|||
require_relative '../../shared/kernel/raise'
|
||||
|
||||
describe "Thread#raise" do
|
||||
it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise
|
||||
it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise
|
||||
|
||||
it "ignores dead threads and returns nil" do
|
||||
t = Thread.new { :dead }
|
||||
Thread.pass while t.alive?
|
||||
|
|
|
@ -104,43 +104,24 @@ describe :kernel_raise, shared: true do
|
|||
end
|
||||
|
||||
it "re-raises a previously rescued exception without overwriting the backtrace" do
|
||||
# This spec is written using #backtrace and matching the line number
|
||||
# from the string, as backtrace_locations is a more advanced
|
||||
# method that is not always supported by implementations.
|
||||
#
|
||||
initial_raise_line = nil
|
||||
raise_again_line = nil
|
||||
raised_again = nil
|
||||
exception = nil
|
||||
|
||||
if defined?(FiberSpecs::NewFiberToRaise) and @object == FiberSpecs::NewFiberToRaise
|
||||
fiber = Fiber.new do
|
||||
begin
|
||||
initial_raise_line = __LINE__; Fiber.yield
|
||||
rescue => raised
|
||||
begin
|
||||
raise_again_line = __LINE__; Fiber.yield raised
|
||||
rescue => raised_again
|
||||
raised_again
|
||||
end
|
||||
end
|
||||
end
|
||||
fiber.resume
|
||||
raised = fiber.raise 'raised'
|
||||
raised_again = fiber.raise raised
|
||||
else
|
||||
begin
|
||||
initial_raise_line = __LINE__; @object.raise 'raised'
|
||||
rescue => raised
|
||||
begin
|
||||
raise_again_line = __LINE__; @object.raise raised
|
||||
rescue => raised_again
|
||||
raised_again
|
||||
end
|
||||
end
|
||||
begin
|
||||
raise "raised"
|
||||
rescue => exception
|
||||
# Ignore.
|
||||
end
|
||||
|
||||
raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:")
|
||||
raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:")
|
||||
backtrace = exception.backtrace
|
||||
|
||||
begin
|
||||
raised_exception = @object.raise(exception)
|
||||
rescue => raised_exception
|
||||
# Ignore.
|
||||
end
|
||||
|
||||
raised_exception.backtrace.should == backtrace
|
||||
raised_exception.should == exception
|
||||
end
|
||||
|
||||
it "allows Exception, message, and backtrace parameters" do
|
||||
|
@ -159,4 +140,259 @@ describe :kernel_raise, shared: true do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
ruby_version_is "3.5" do
|
||||
it "allows cause keyword argument" do
|
||||
cause = StandardError.new("original error")
|
||||
result = nil
|
||||
|
||||
-> do
|
||||
@object.raise("new error", cause: cause)
|
||||
end.should raise_error(RuntimeError, "new error") do |error|
|
||||
error.cause.should == cause
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an ArgumentError when only cause is given" do
|
||||
cause = StandardError.new("cause")
|
||||
-> do
|
||||
@object.raise(cause: cause)
|
||||
end.should raise_error(ArgumentError, "only cause is given with no arguments")
|
||||
end
|
||||
|
||||
it "raises an ArgumentError when only cause is given and is nil" do
|
||||
-> do
|
||||
@object.raise(cause: nil)
|
||||
end.should raise_error(ArgumentError, "only cause is given with no arguments")
|
||||
end
|
||||
|
||||
it "raises a TypeError when given cause is not an instance of Exception" do
|
||||
cause = Object.new
|
||||
-> do
|
||||
@object.raise("message", cause: cause)
|
||||
end.should raise_error(TypeError, "exception object expected")
|
||||
end
|
||||
|
||||
it "doesn't set given cause when it equals the raised exception" do
|
||||
cause = StandardError.new("cause")
|
||||
result = nil
|
||||
|
||||
-> do
|
||||
@object.raise(cause, cause: cause)
|
||||
end.should raise_error(StandardError, "cause") do |error|
|
||||
error.should == cause
|
||||
error.cause.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
it "accepts cause equal an exception" do
|
||||
error = RuntimeError.new("message")
|
||||
result = nil
|
||||
|
||||
-> do
|
||||
@object.raise(error, cause: error)
|
||||
end.should raise_error(RuntimeError, "message") do |error|
|
||||
error.cause.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
it "rejects circular causes" do
|
||||
-> {
|
||||
begin
|
||||
raise "Error 1"
|
||||
rescue => error1
|
||||
begin
|
||||
raise "Error 2"
|
||||
rescue => error2
|
||||
begin
|
||||
raise "Error 3"
|
||||
rescue => error3
|
||||
@object.raise(error1, cause: error3)
|
||||
end
|
||||
end
|
||||
end
|
||||
}.should raise_error(ArgumentError, "circular causes")
|
||||
end
|
||||
|
||||
it "supports exception class with message and cause" do
|
||||
cause = StandardError.new("cause message")
|
||||
result = nil
|
||||
|
||||
-> do
|
||||
@object.raise(ArgumentError, "argument error message", cause: cause)
|
||||
end.should raise_error(ArgumentError, "argument error message") do |error|
|
||||
error.should be_kind_of(ArgumentError)
|
||||
error.message.should == "argument error message"
|
||||
error.cause.should == cause
|
||||
end
|
||||
end
|
||||
|
||||
it "supports exception class with message, backtrace and cause" do
|
||||
cause = StandardError.new("cause message")
|
||||
backtrace = ["line1", "line2"]
|
||||
result = nil
|
||||
|
||||
-> do
|
||||
@object.raise(ArgumentError, "argument error message", backtrace, cause: cause)
|
||||
end.should raise_error(ArgumentError, "argument error message") do |error|
|
||||
error.should be_kind_of(ArgumentError)
|
||||
error.message.should == "argument error message"
|
||||
error.cause.should == cause
|
||||
error.backtrace.should == backtrace
|
||||
end
|
||||
end
|
||||
|
||||
it "supports automatic cause chaining" do
|
||||
-> do
|
||||
begin
|
||||
raise "first error"
|
||||
rescue
|
||||
# No explicit cause - should chain automatically:
|
||||
@object.raise("second error")
|
||||
end
|
||||
end.should raise_error(RuntimeError, "second error") do |error|
|
||||
error.cause.should be_kind_of(RuntimeError)
|
||||
error.cause.message.should == "first error"
|
||||
end
|
||||
end
|
||||
|
||||
it "supports cause: nil to prevent automatic cause chaining" do
|
||||
-> do
|
||||
begin
|
||||
raise "first error"
|
||||
rescue
|
||||
# Explicit nil prevents chaining:
|
||||
@object.raise("second error", cause: nil)
|
||||
end
|
||||
end.should raise_error(RuntimeError, "second error") do |error|
|
||||
error.cause.should == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe :kernel_raise_across_contexts, shared: true do
|
||||
ruby_version_is "3.5" do
|
||||
describe "with cause keyword argument" do
|
||||
it "uses the cause from the calling context" do
|
||||
original_cause = nil
|
||||
result = nil
|
||||
|
||||
# We have no cause ($!) and we don't specify one explicitly either:
|
||||
@object.raise("second error") do |&block|
|
||||
begin
|
||||
begin
|
||||
raise "first error"
|
||||
rescue => original_cause
|
||||
# We have a cause here ($!) but we should ignore it:
|
||||
block.call
|
||||
end
|
||||
rescue => result
|
||||
# Ignore.
|
||||
end
|
||||
end
|
||||
|
||||
result.should be_kind_of(RuntimeError)
|
||||
result.message.should == "second error"
|
||||
result.cause.should == nil
|
||||
end
|
||||
|
||||
it "accepts a cause keyword argument that overrides the last exception" do
|
||||
original_cause = nil
|
||||
override_cause = StandardError.new("override cause")
|
||||
result = nil
|
||||
|
||||
begin
|
||||
raise "outer error"
|
||||
rescue
|
||||
# We have an existing cause, but we want to override it:
|
||||
@object.raise("second error", cause: override_cause) do |&block|
|
||||
begin
|
||||
begin
|
||||
raise "first error"
|
||||
rescue => original_cause
|
||||
# We also have an existing cause here:
|
||||
block.call
|
||||
end
|
||||
rescue => result
|
||||
# Ignore.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result.should be_kind_of(RuntimeError)
|
||||
result.message.should == "second error"
|
||||
result.cause.should == override_cause
|
||||
end
|
||||
|
||||
it "supports automatic cause chaining from calling context" do
|
||||
result = nil
|
||||
|
||||
@object.raise("new error") do |&block|
|
||||
begin
|
||||
begin
|
||||
raise "original error"
|
||||
rescue
|
||||
block.call # Let the context yield/sleep
|
||||
end
|
||||
rescue => result
|
||||
# Ignore.
|
||||
end
|
||||
end
|
||||
|
||||
result.should be_kind_of(RuntimeError)
|
||||
result.message.should == "new error"
|
||||
# Calling context has no current exception:
|
||||
result.cause.should == nil
|
||||
end
|
||||
|
||||
it "supports explicit cause: nil to prevent cause chaining" do
|
||||
result = nil
|
||||
|
||||
begin
|
||||
raise "calling context error"
|
||||
rescue
|
||||
@object.raise("new error", cause: nil) do |&block|
|
||||
begin
|
||||
begin
|
||||
raise "target context error"
|
||||
rescue
|
||||
block.call # Let the context yield/sleep
|
||||
end
|
||||
rescue => result
|
||||
# Ignore.
|
||||
end
|
||||
end
|
||||
|
||||
result.should be_kind_of(RuntimeError)
|
||||
result.message.should == "new error"
|
||||
result.cause.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
it "raises TypeError when cause is not an Exception" do
|
||||
-> {
|
||||
@object.raise("error", cause: "not an exception") do |&block|
|
||||
begin
|
||||
block.call # Let the context yield/sleep
|
||||
rescue
|
||||
# Ignore - we expect the TypeError to be raised in the calling context
|
||||
end
|
||||
end
|
||||
}.should raise_error(TypeError, "exception object expected")
|
||||
end
|
||||
|
||||
it "raises ArgumentError when only cause is given with no arguments" do
|
||||
-> {
|
||||
@object.raise(cause: StandardError.new("cause")) do |&block|
|
||||
begin
|
||||
block.call # Let the context yield/sleep
|
||||
rescue
|
||||
# Ignore - we expect the ArgumentError to be raised in the calling context
|
||||
end
|
||||
end
|
||||
}.should raise_error(ArgumentError, "only cause is given with no arguments")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
thread.c
14
thread.c
|
@ -78,6 +78,7 @@
|
|||
#include "internal/class.h"
|
||||
#include "internal/cont.h"
|
||||
#include "internal/error.h"
|
||||
#include "internal/eval.h"
|
||||
#include "internal/gc.h"
|
||||
#include "internal/hash.h"
|
||||
#include "internal/io.h"
|
||||
|
@ -2710,18 +2711,11 @@ rb_threadptr_ready(rb_thread_t *th)
|
|||
static VALUE
|
||||
rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv)
|
||||
{
|
||||
VALUE exc;
|
||||
|
||||
if (rb_threadptr_dead(target_th)) {
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
if (argc == 0) {
|
||||
exc = rb_exc_new(rb_eRuntimeError, 0, 0);
|
||||
}
|
||||
else {
|
||||
exc = rb_make_exception(argc, argv);
|
||||
}
|
||||
VALUE exception = rb_exception_setup(argc, argv);
|
||||
|
||||
/* making an exception object can switch thread,
|
||||
so we need to check thread deadness again */
|
||||
|
@ -2729,9 +2723,9 @@ rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv)
|
|||
return Qnil;
|
||||
}
|
||||
|
||||
rb_ec_setup_exception(GET_EC(), exc, Qundef);
|
||||
rb_threadptr_pending_interrupt_enque(target_th, exc);
|
||||
rb_threadptr_pending_interrupt_enque(target_th, exception);
|
||||
rb_threadptr_interrupt(target_th);
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue