Skip to content

Commit

Permalink
System.exit does proper context hard exit. (#10363)
Browse files Browse the repository at this point in the history
The `System.exit 42` component is treated the same way as any other Panic error - it does not interfere with other component evaluation:
![image](https://github.com/user-attachments/assets/516490b5-755f-453e-8dc9-744437dc51bd)

After removing the `System.exit 42` component, the workflow works as expected. I have also tried opening the project with the component and then removing it.
  • Loading branch information
Akirathan authored Jul 18, 2024
1 parent c20eab2 commit 451d7cb
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 9 deletions.
8 changes: 6 additions & 2 deletions engine/runner/src/main/java/org/enso/runner/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -873,8 +873,12 @@ private void runMain(
}
}
} catch (PolyglotException e) {
printPolyglotException(e, rootPkgPath);
throw exitFail();
if (e.isExit()) {
doExit(e.getExitStatus());
} else {
printPolyglotException(e, rootPkgPath);
throw exitFail();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.ExceptionType;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
Expand Down Expand Up @@ -479,6 +481,18 @@ public SourceSection getSourceLocation(Object o) {
return null;
}

public boolean isExitException(AbstractTruffleException ex) {
var interop = InteropLibrary.getUncached();
if (interop.isException(ex)) {
try {
return interop.getExceptionType(ex) == ExceptionType.EXIT;
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
return false;
}

/**
* Returns a human-readable message for a panic exception.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import java.io.File
import java.util.UUID
import java.util.function.Consumer
import java.util.logging.Level

import scala.jdk.OptionConverters.RichOptional
import scala.util.Try

Expand Down Expand Up @@ -324,9 +323,11 @@ object ProgramExecutionSupport {
ctx: RuntimeContext
): PartialFunction[Throwable, Api.ExecutionResult.Diagnostic] = {
case ex: AbstractTruffleException
// exit exception is special, and handled as failure rather than Diagnostics.
if !ctx.executionService.isExitException(ex) &&
// The empty language is allowed because `getLanguage` returns null when
// the error originates in builtin node.
if Option(ctx.executionService.getLanguage(ex))
Option(ctx.executionService.getLanguage(ex))
.forall(_ == LanguageInfo.ID) =>
val section = Option(ctx.executionService.getSourceLocation(ex))
val source = section.flatMap(sec => Option(sec.getSource))
Expand Down Expand Up @@ -357,6 +358,16 @@ object ProgramExecutionSupport {
findFileByModuleName(ex.getModule)
)

case exitEx: AbstractTruffleException
if ctx.executionService.isExitException(exitEx) =>
val section = Option(ctx.executionService.getSourceLocation(exitEx))
val source = section.flatMap(sec => Option(sec.getSource))
val file = source.flatMap(src => findFileByModuleName(src.getName))
Api.ExecutionResult.Failure(
exitEx.getMessage,
file
)

case ex: ServiceException =>
Api.ExecutionResult.Failure(ex.getMessage, None)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5773,6 +5773,62 @@ class RuntimeServerTest
)
}

it should "return error when invoking System.exit" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val metadata = new Metadata
val code =
"""import Standard.Base.System
|
|main =
| System.exit 42
|""".stripMargin.linesIterator.mkString("\n")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)

// create context
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some(
Api.Response(requestId, Api.CreateContextResponse(contextId))
)

// Set sources for the module
context.send(
Api.Request(requestId, Api.OpenFileRequest(mainFile, contents))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)

// push main
context.send(
Api.Request(
requestId,
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(moduleName, moduleName, "main"),
None,
Vector()
)
)
)
)
context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response(
Api.ExecutionFailed(
contextId,
Api.ExecutionResult.Failure(
"Exit was called with exit code 42.",
Some(mainFile)
)
)
)
)
}

it should "return compiler warning unused variable" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.enso.interpreter.runtime.system;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.ExceptionType;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;

/** Thrown when {@code System.exit} call was called during execution. */
@ExportLibrary(InteropLibrary.class)
final class ExitException extends AbstractTruffleException {
private final int exitCode;

ExitException(int code, Node location) {
super(location);
this.exitCode = code;
}

@ExportMessage
boolean isException() {
return true;
}

@ExportMessage
int getExceptionExitStatus() {
return exitCode;
}

@ExportMessage
ExceptionType getExceptionType() {
return ExceptionType.EXIT;
}

@ExportMessage
boolean hasExceptionMessage() {
return true;
}

@ExportMessage
String getExceptionMessage() {
return getMessage();
}

@TruffleBoundary
@Override
public String getMessage() {
return "Exit was called with exit code " + exitCode + ".";
}

@ExportMessage
RuntimeException throwException() {
return this;
}

@ExportMessage
boolean hasExceptionStackTrace() {
return true;
}

@ExportMessage
Object getExceptionStackTrace() {
var node = this.getLocation();
var frame = TruffleStackTraceElement.create(node, node.getRootNode().getCallTarget(), null);
return ArrayLikeHelpers.asVectorWithCheckAt(frame.getGuestObject());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang3.SystemUtils;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.EnsoContext;
Expand Down Expand Up @@ -46,14 +45,15 @@ public static long nanoTime() {
return java.lang.System.nanoTime();
}

@Builtin.Specialize
@Builtin.Method(
description = "Exits the process, returning the provided code.",
autoRegister = false)
@CompilerDirectives.TruffleBoundary
public static void exit(long code) {
var ctx = EnsoContext.get(null);
EnsoLanguage.get(null).disposeContext(ctx);
java.lang.System.exit((int) code);
public static void exit(long code, @Cached ExpectStringNode expectStringNode) {
// expectStringNode is an artificial Node just to provide a location for
// the exception
throw new ExitException((int) code, expectStringNode);
}

@Builtin.Specialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ create_process command arguments input redirect_in redirect_out redirect_err = @
@Builtin_Type
type System_Process_Result
Result exit_code stdout stderr

exit code = @Builtin_Method "System.exit"

0 comments on commit 451d7cb

Please sign in to comment.