Skip to content

Commit

Permalink
[GR-18163] Set $! global variable when an at_exit hook raises an exce…
Browse files Browse the repository at this point in the history
…ption

PullRequest: truffleruby/4255
  • Loading branch information
andrykonchin committed Apr 26, 2024
2 parents d68ea09 + 5138732 commit cc2d014
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -20,6 +20,7 @@ Compatibility:
* Add `Regexp.linear_time?` method (#3039, @andrykonchin).
* Allow null encoding pointer in `rb_enc_interned_str_cstr` (@thomasmarshall).
* Allow anonymous memberless Struct (@simonlevasseur).
* Set `$!` when a `Kernel#at_exit` hook raises an exception (#3535, @andrykonchin).

Performance:
* Fix inline caching for Regexp creation from Strings (#3492, @andrykonchin, @eregon).
Expand Down
6 changes: 6 additions & 0 deletions spec/ruby/shared/kernel/at_exit.rb
Expand Up @@ -30,6 +30,12 @@
result.lines.should.include?("The exception matches: true (message=foo)\n")
end

it "gives access to an exception raised in a previous handler" do
code = "#{@method} { print '$!.message = ' + $!.message }; #{@method} { raise 'foo' }"
result = ruby_exe(code, args: "2>&1", exit_status: 1)
result.lines.should.include?("$!.message = foo")
end

it "both exceptions in a handler and in the main script are printed" do
code = "#{@method} { raise 'at_exit_error' }; raise 'main_script_error'"
result = ruby_exe(code, args: "2>&1", exit_status: 1)
Expand Down
1 change: 1 addition & 0 deletions spec/tags/core/kernel/at_exit_tags.txt
Expand Up @@ -7,3 +7,4 @@ slow:Kernel.at_exit decides the exit status if both at_exit and the main script
slow:Kernel.at_exit runs all handlers even if some raise exceptions
slow:Kernel.at_exit runs handlers even if the main script fails to parse
slow:Kernel.at_exit calls the nested handler right after the outer one if a handler is nested into another handler
slow:Kernel.at_exit gives access to an exception raised in a previous handler
1 change: 1 addition & 0 deletions spec/tags/language/END_tags.txt
Expand Up @@ -11,3 +11,4 @@ slow:The END keyword runs only once for multiple calls
slow:The END keyword warns when END is used in a method
slow:The END keyword END blocks and at_exit callbacks are mixed runs them all in reverse order of registration
slow:The END keyword is affected by the toplevel assignment
slow:The END keyword gives access to an exception raised in a previous handler
3 changes: 2 additions & 1 deletion src/main/java/org/truffleruby/RubyContext.java
Expand Up @@ -118,7 +118,7 @@ public final class RubyContext {
private final MarkingService markingService;
private final ObjectSpaceManager objectSpaceManager = new ObjectSpaceManager();
private final SharedObjects sharedObjects = new SharedObjects(this);
private final AtExitManager atExitManager = new AtExitManager(this);
private final AtExitManager atExitManager;
private final CallStackManager callStack;
private final CoreExceptions coreExceptions;
private final EncodingManager encodingManager;
Expand Down Expand Up @@ -202,6 +202,7 @@ public RubyContext(RubyLanguage language, TruffleLanguage.Env env) {
finalizationService = new FinalizationService(referenceProcessor);
markingService = new MarkingService();
dataObjectFinalizationService = new DataObjectFinalizationService(language, referenceProcessor);
atExitManager = new AtExitManager(this, language);

// We need to construct this at runtime
random = createRandomInstance();
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/org/truffleruby/core/kernel/AtExitManager.java
Expand Up @@ -16,6 +16,8 @@

import com.oracle.truffle.api.exception.AbstractTruffleException;
import org.truffleruby.RubyContext;
import org.truffleruby.RubyLanguage;
import org.truffleruby.core.exception.ExceptionOperations;
import org.truffleruby.core.exception.RubyException;
import org.truffleruby.core.exception.RubySystemExit;
import org.truffleruby.core.proc.ProcOperations;
Expand All @@ -31,12 +33,14 @@
public final class AtExitManager {

private final RubyContext context;
private final RubyLanguage language;

private final Deque<RubyProc> atExitHooks = new ConcurrentLinkedDeque<>();
private final Deque<RubyProc> systemExitHooks = new ConcurrentLinkedDeque<>();

public AtExitManager(RubyContext context) {
public AtExitManager(RubyContext context, RubyLanguage language) {
this.context = context;
this.language = language;
}

public void add(RubyProc block, boolean always) {
Expand Down Expand Up @@ -74,7 +78,7 @@ private AbstractTruffleException runExitHooks(Deque<RubyProc> stack) {
try {
ProcOperations.rootCall(block, NoKeywordArgumentsDescriptor.INSTANCE, RubyBaseNode.EMPTY_ARGUMENTS);
} catch (AbstractTruffleException e) {
handleAtExitException(context, e);
handleAtExitException(context, language, e);
lastException = e;
}
}
Expand All @@ -99,7 +103,12 @@ public static boolean isSilentException(RubyContext context, AbstractTruffleExce
rubyException.getLogicalClass() == context.getCoreLibrary().signalExceptionClass;
}

private static void handleAtExitException(RubyContext context, AbstractTruffleException exception) {
private static void handleAtExitException(RubyContext context, RubyLanguage language,
AbstractTruffleException exception) {
// Set $! for the next at_exit handlers
language.getCurrentThread().threadLocalGlobals.setLastException(ExceptionOperations
.getExceptionObject(exception));

if (!isSilentException(context, exception)) {
context.getDefaultBacktraceFormatter().printRubyExceptionOnEnvStderr("", exception);
}
Expand Down

0 comments on commit cc2d014

Please sign in to comment.