From d8736a1dcceae7dc4a6daceb49097bfb2e724a0d Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 11:44:33 +0100 Subject: [PATCH 1/6] Do not report exceptions on long running Excel reads This change introduces two modifications: - `ClosedByInterruptException` is wrapped in `InterruptedException` instead of `RuntimeException` - when instrumentation encounters `InterruptedException` it bails early Having `ClosedByInterruptException` wrapped in `RuntimeException` meant that it is being reported as a regular `HostException` in the engine and to the user. Instead it should be treated specially since we know that it is caused by cancelling a long-running job. Since it is a statically checked exception it has to be declared and the information has to be propagated through various lambda constructs (thanks Java!). The above change alone meant that an error is not reported for `Data.read` nodes but any values dependent on it would still report `No_Such_Method` error when the exception is reported as a value. Hence the early bail out mechanism. --- .../job/ProgramExecutionSupport.scala | 9 ++++ .../control/ThreadInterruptedException.java | 8 ++- .../enso/table/excel/ExcelConnectionPool.java | 10 ++-- .../java/org/enso/table/excel/ExcelRange.java | 5 +- .../java/org/enso/table/excel/ExcelSheet.java | 6 +-- .../table/excel/ReadOnlyExcelConnection.java | 6 ++- .../excel/xssfreader/XSSFReaderSheet.java | 13 +++-- .../excel/xssfreader/XSSFReaderWorkbook.java | 20 +++++--- .../java/org/enso/table/read/ExcelReader.java | 28 ++++++---- .../table/util/ConsumerWithException.java | 45 ++++++++++++++++ .../table/util/FunctionWithException.java | 51 +++++++++++++++++++ .../org/enso/table/write/ExcelWriter.java | 25 +++++---- 12 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 std-bits/table/src/main/java/org/enso/table/util/ConsumerWithException.java create mode 100644 std-bits/table/src/main/java/org/enso/table/util/FunctionWithException.java diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 283ed9c2893d..b26771ff3403 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -95,6 +95,15 @@ object ProgramExecutionSupport { val onComputedValueCallback: Consumer[ExpressionValue] = { value => if (callStack.isEmpty) { logger.log(Level.FINEST, s"ON_COMPUTED ${value.getExpressionId}") + + if (VisualizationResult.isInterruptedException(value.getValue)) { + value.getValue match { + case e: AbstractTruffleException => + // Bail out early. Any references to the value will return `No_Such_Method` exception + throw new ThreadInterruptedException(e); + case _ => + } + } sendExpressionUpdate(contextId, executionFrame.syncState, value) sendVisualizationUpdates( contextId, diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/ThreadInterruptedException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/ThreadInterruptedException.java index 2693b6ee1411..cd004b800607 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/ThreadInterruptedException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/control/ThreadInterruptedException.java @@ -1,4 +1,10 @@ package org.enso.interpreter.runtime.control; /** Thrown when guest code discovers a thread interrupt. */ -public class ThreadInterruptedException extends RuntimeException {} +public class ThreadInterruptedException extends RuntimeException { + public ThreadInterruptedException() {} + + public ThreadInterruptedException(Throwable e) { + super(e); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java b/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java index af8a43263ac3..5221576f113a 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java @@ -22,6 +22,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.enso.base.cache.ReloadDetector; import org.enso.table.excel.xssfreader.XSSFReaderWorkbook; +import org.enso.table.util.FunctionWithException; public class ExcelConnectionPool { public static final ExcelConnectionPool INSTANCE = new ExcelConnectionPool(); @@ -29,7 +30,7 @@ public class ExcelConnectionPool { private ExcelConnectionPool() {} public ReadOnlyExcelConnection openReadOnlyConnection(File file, ExcelFileFormat format) - throws IOException { + throws IOException, InterruptedException { synchronized (this) { if (isCurrentlyWriting) { throw new IllegalStateException( @@ -134,7 +135,7 @@ public R writeWorkbook(File file, Function writeAction) throws */ public R lockForWriting( File file, ExcelFileFormat format, File[] accompanyingFiles, Function action) - throws IOException { + throws IOException, InterruptedException { synchronized (this) { if (isCurrentlyWriting) { throw new IllegalStateException( @@ -242,7 +243,8 @@ static class ConnectionRecord { private ExcelWorkbook workbook; private IOException initializationException = null; - T withWorkbook(Function action) throws IOException { + T withWorkbook(FunctionWithException action) + throws IOException, InterruptedException { synchronized (this) { return action.apply(accessCurrentWorkbook()); } @@ -258,7 +260,7 @@ public void close() throws IOException { } } - void reopen(boolean throwOnFailure) throws IOException { + void reopen(boolean throwOnFailure) throws IOException, InterruptedException { synchronized (this) { if (workbook != null) { throw new IllegalStateException("The workbook is already open."); diff --git a/std-bits/table/src/main/java/org/enso/table/excel/ExcelRange.java b/std-bits/table/src/main/java/org/enso/table/excel/ExcelRange.java index 552f3385fd27..4d6526829d1d 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/ExcelRange.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/ExcelRange.java @@ -181,7 +181,8 @@ public static ExcelRange forRows(String sheetName, int topRow, int bottomRow) { * @param sheet ExcelSheet containing the range refers to. * @return Expanded range covering the connected table of cells. */ - public static ExcelRange expandSingleCell(ExcelRange excelRange, ExcelSheet sheet) { + public static ExcelRange expandSingleCell(ExcelRange excelRange, ExcelSheet sheet) + throws InterruptedException { ExcelRow currentRow = sheet.get(excelRange.getTopRow()); if (currentRow == null || currentRow.isEmpty(excelRange.getLeftColumn())) { return new ExcelRange( @@ -337,7 +338,7 @@ public int getRowCount() { return isWholeColumn() ? Integer.MAX_VALUE : bottomRow - topRow + 1; } - public int getLastNonEmptyRow(ExcelSheet sheet) { + public int getLastNonEmptyRow(ExcelSheet sheet) throws InterruptedException { int lastRow = Math.min(sheet.getLastRow(), isWholeColumn() ? sheet.getLastRow() : bottomRow) + 1; diff --git a/std-bits/table/src/main/java/org/enso/table/excel/ExcelSheet.java b/std-bits/table/src/main/java/org/enso/table/excel/ExcelSheet.java index 4d2dd42a2a32..4dbe433d5d62 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/ExcelSheet.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/ExcelSheet.java @@ -12,10 +12,10 @@ public interface ExcelSheet { String getName(); /** Gets the initial row index within the sheet (1-based). */ - int getFirstRow(); + int getFirstRow() throws InterruptedException; /** Gets the final row index within the sheet (1-based). */ - int getLastRow(); + int getLastRow() throws InterruptedException; /** * Gets the row at the given index within the sheet (1-based) @@ -23,7 +23,7 @@ public interface ExcelSheet { * @param row the row index (1-based)/ * @return the row object or null if the row index is out of range or doesn't exist. */ - ExcelRow get(int row); + ExcelRow get(int row) throws InterruptedException; /** Gets the underlying Apache POI Sheet object - may be null. Provided for Writer use only. */ Sheet getSheet(); diff --git a/std-bits/table/src/main/java/org/enso/table/excel/ReadOnlyExcelConnection.java b/std-bits/table/src/main/java/org/enso/table/excel/ReadOnlyExcelConnection.java index 3cbac859648a..894771c96670 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/ReadOnlyExcelConnection.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/ReadOnlyExcelConnection.java @@ -1,7 +1,7 @@ package org.enso.table.excel; import java.io.IOException; -import java.util.function.Function; +import org.enso.table.util.FunctionWithException; public class ReadOnlyExcelConnection implements AutoCloseable { @@ -27,7 +27,9 @@ public synchronized void close() throws IOException { record = null; } - public synchronized T withWorkbook(Function f) throws IOException { + public synchronized T withWorkbook( + FunctionWithException f) + throws IOException, InterruptedException { if (record == null) { throw new IllegalStateException("ReadOnlyExcelConnection is being used after it was closed."); } diff --git a/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderSheet.java b/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderSheet.java index cdb79cbdbd5b..2fc288ab0716 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderSheet.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderSheet.java @@ -1,6 +1,7 @@ package org.enso.table.excel.xssfreader; import java.io.IOException; +import java.nio.channels.ClosedByInterruptException; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; @@ -33,7 +34,7 @@ public XSSFReaderSheet(int sheetIdx, String sheetName, String relId, XSSFReaderW this.parent = parent; } - private synchronized void ensureReadSheetData() { + private synchronized void ensureReadSheetData() throws InterruptedException { if (hasReadSheetData) { return; } @@ -70,6 +71,8 @@ protected void onCell(int rowNumber, short columnNumber, String ref, CellValue v try { var sheet = reader.getSheet(relId); xmlReader.parse(new InputSource(sheet)); + } catch (ClosedByInterruptException e) { + throw new InterruptedException(e.getMessage()); } catch (SAXException | InvalidFormatException | IOException e) { throw new RuntimeException(e); } @@ -94,25 +97,25 @@ public String getName() { return sheetName; } - public String getDimensions() { + public String getDimensions() throws InterruptedException { ensureReadSheetData(); return dimensions; } @Override - public int getFirstRow() { + public int getFirstRow() throws InterruptedException { ensureReadSheetData(); return firstRow; } @Override - public int getLastRow() { + public int getLastRow() throws InterruptedException { ensureReadSheetData(); return lastRow; } @Override - public ExcelRow get(int row) { + public ExcelRow get(int row) throws InterruptedException { ensureReadSheetData(); if (!rowData.containsKey(row)) { diff --git a/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderWorkbook.java b/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderWorkbook.java index 6502057ff416..46f1d441914f 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderWorkbook.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/xssfreader/XSSFReaderWorkbook.java @@ -1,6 +1,7 @@ package org.enso.table.excel.xssfreader; import java.io.IOException; +import java.nio.channels.ClosedByInterruptException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -8,7 +9,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.xpath.XPathConstants; @@ -26,6 +26,7 @@ import org.apache.poi.xssf.usermodel.XSSFRelation; import org.enso.table.excel.ExcelSheet; import org.enso.table.excel.ExcelWorkbook; +import org.enso.table.util.ConsumerWithException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -90,7 +91,7 @@ public Iterator getPrefixes(String namespaceURI) { private SharedStrings sharedStrings; private XSSFReaderFormats styles; - public XSSFReaderWorkbook(String path) throws IOException { + public XSSFReaderWorkbook(String path) throws IOException, InterruptedException { this.path = path; // Read the workbook data @@ -101,7 +102,8 @@ public String getPath() { return path; } - void withReader(Consumer action) throws IOException { + void withReader(ConsumerWithException action) + throws IOException, InterruptedException { try (var pkg = OPCPackage.open(path, PackageAccess.READ)) { var reader = new XSSFReader(pkg); action.accept(reader); @@ -115,7 +117,7 @@ private record SheetInfo(int index, int sheetId, String name, String relID, bool private record NamedRange(String name, String formula) {} - private void readWorkbookData() throws IOException { + private void readWorkbookData() throws IOException, InterruptedException { withReader( reader -> { try { @@ -124,6 +126,8 @@ private void readWorkbookData() throws IOException { read1904DateSetting(workbookDoc); readSheetInfo(workbookDoc); readNamedRanges(workbookDoc); + } catch (ClosedByInterruptException e) { + throw new InterruptedException(e.getMessage()); } catch (SAXException | IOException | InvalidFormatException @@ -171,7 +175,7 @@ private void read1904DateSetting(Document workbookDoc) throws XPathExpressionExc } } - private synchronized void ensureReadShared() { + private synchronized void ensureReadShared() throws InterruptedException { if (hasReadShared) { return; } @@ -207,6 +211,8 @@ public int getUniqueCount() { styles = new XSSFReaderFormats(stylesTable); hasReadShared = true; + } catch (ClosedByInterruptException e) { + throw new InterruptedException(e.getMessage()); } catch (InvalidFormatException | IOException e) { throw new RuntimeException(e); } @@ -258,12 +264,12 @@ public String getNameFormula(String name) { return namedRange == null ? null : namedRange.formula; } - public SharedStrings getSharedStrings() { + public SharedStrings getSharedStrings() throws InterruptedException { ensureReadShared(); return sharedStrings; } - public XSSFReaderFormats getStyles() { + public XSSFReaderFormats getStyles() throws InterruptedException { ensureReadShared(); return styles; } diff --git a/std-bits/table/src/main/java/org/enso/table/read/ExcelReader.java b/std-bits/table/src/main/java/org/enso/table/read/ExcelReader.java index 6f6b289e8998..f4587fc6cb1e 100644 --- a/std-bits/table/src/main/java/org/enso/table/read/ExcelReader.java +++ b/std-bits/table/src/main/java/org/enso/table/read/ExcelReader.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.poi.ss.util.CellReference; @@ -24,6 +23,7 @@ import org.enso.table.excel.ExcelWorkbook; import org.enso.table.excel.ReadOnlyExcelConnection; import org.enso.table.problems.ProblemAggregator; +import org.enso.table.util.FunctionWithException; import org.graalvm.polyglot.Context; /** A table reader for MS Excel files. */ @@ -36,7 +36,8 @@ public class ExcelReader { * @return a String[] containing the sheet names. * @throws IOException when the action fails */ - public static String[] readSheetNames(File file, ExcelFileFormat format) throws IOException { + public static String[] readSheetNames(File file, ExcelFileFormat format) + throws IOException, InterruptedException { return withWorkbook(file, format, ExcelReader::readSheetNames); } @@ -65,7 +66,8 @@ public static String[] readSheetNames(ExcelWorkbook workbook) { * @return a String[] containing the range names. * @throws IOException when the action fails */ - public static String[] readRangeNames(File file, ExcelFileFormat format) throws IOException { + public static String[] readRangeNames(File file, ExcelFileFormat format) + throws IOException, InterruptedException { return withWorkbook(file, format, ExcelWorkbook::getRangeNames); } @@ -89,7 +91,7 @@ public static Table readSheetByName( Integer row_limit, ExcelFileFormat format, ProblemAggregator problemAggregator) - throws IOException, InvalidLocationException { + throws IOException, InvalidLocationException, InterruptedException { return withWorkbook( file, format, @@ -130,7 +132,7 @@ public static Table readSheetByIndex( Integer row_limit, ExcelFileFormat format, ProblemAggregator problemAggregator) - throws IOException, InvalidLocationException { + throws IOException, InvalidLocationException, InterruptedException { return withWorkbook( file, format, @@ -175,7 +177,7 @@ public static Table readRangeByName( Integer row_limit, ExcelFileFormat format, ProblemAggregator problemAggregator) - throws IOException, InvalidLocationException { + throws IOException, InvalidLocationException, InterruptedException { return withWorkbook( file, format, @@ -202,7 +204,7 @@ public static Table readRangeByName( int skip_rows, Integer row_limit, ProblemAggregator problemAggregator) - throws InvalidLocationException { + throws InvalidLocationException, InterruptedException { int sheetIndex = workbook.getSheetIndex(rangeNameOrAddress); if (sheetIndex != -1) { return readTable( @@ -247,7 +249,7 @@ public static Table readRange( Integer row_limit, ExcelFileFormat format, ProblemAggregator problemAggregator) - throws IOException, InvalidLocationException { + throws IOException, InvalidLocationException, InterruptedException { return withWorkbook( file, format, @@ -256,7 +258,10 @@ public static Table readRange( } private static T withWorkbook( - File file, ExcelFileFormat format, Function action) throws IOException { + File file, + ExcelFileFormat format, + FunctionWithException action) + throws IOException, InterruptedException { try (ReadOnlyExcelConnection connection = ExcelConnectionPool.INSTANCE.openReadOnlyConnection(file, format)) { return connection.withWorkbook(action); @@ -270,7 +275,7 @@ public static Table readRange( int skip_rows, Integer row_limit, ProblemAggregator problemAggregator) - throws InvalidLocationException { + throws InvalidLocationException, InterruptedException { int sheetIndex = workbook.getSheetIndex(excelRange.getSheetName()); if (sheetIndex == -1) { throw new InvalidLocationException( @@ -294,7 +299,8 @@ private static Table readTable( ExcelHeaders.HeaderBehavior headers, int skipRows, int rowCount, - ProblemAggregator problemAggregator) { + ProblemAggregator problemAggregator) + throws InterruptedException { ExcelSheet sheet = workbook.getSheetAt(sheetIndex); diff --git a/std-bits/table/src/main/java/org/enso/table/util/ConsumerWithException.java b/std-bits/table/src/main/java/org/enso/table/util/ConsumerWithException.java new file mode 100644 index 000000000000..9f9343854bbd --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/util/ConsumerWithException.java @@ -0,0 +1,45 @@ +package org.enso.table.util; + +import java.util.Objects; + +/** + * Same as {@link java.util.function.Consumer} except that a one can declare a checked exception, E. + * Represents an operation that accepts a single input argument and returns no result. Unlike most + * other functional interfaces, {@code Consumer} is expected to operate via side-effects. + * + *

This is a functional interface whose functional method is + * {@link #accept(Object)}. + * + * @param the type of the input to the operation + * @param the type of the checked exception + */ +@FunctionalInterface +public interface ConsumerWithException { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t) throws E; + + /** + * Returns a composed {@code Consumer} that performs, in sequence, this operation followed by the + * {@code after} operation. If performing either operation throws an exception, it is relayed to + * the caller of the composed operation. If performing this operation throws an exception, the + * {@code after} operation will not be performed. + * + * @param after the operation to perform after this operation + * @return a composed {@code Consumer} that performs in sequence this operation followed by the + * {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default ConsumerWithException andThen(java.util.function.Consumer after) + throws E { + Objects.requireNonNull(after); + return (T t) -> { + accept(t); + after.accept(t); + }; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/util/FunctionWithException.java b/std-bits/table/src/main/java/org/enso/table/util/FunctionWithException.java new file mode 100644 index 000000000000..5d41f4686cb2 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/util/FunctionWithException.java @@ -0,0 +1,51 @@ +package org.enso.table.util; + +import java.util.Objects; +import java.util.function.Function; + +/** + * Same as {@link Function} except that a one can declare a checked exception, E. Represents a + * function that accepts one argument and produces a result. + * + *

This is a functional interface whose functional method is + * {@link #apply(Object)}. + * + * @param the type of the input to the function + * @param the type of the result of the function + * @param the type of the checked exception + */ +@FunctionalInterface +public interface FunctionWithException { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t) throws E; + + default FunctionWithException compose( + FunctionWithException before) { + Objects.requireNonNull(before); + return (V v) -> apply(before.apply(v)); + } + + /** + * Returns a composed function that first applies this function to its input, and then applies the + * {@code after} function to the result. If evaluation of either function throws an exception, it + * is relayed to the caller of the composed function. + * + * @param the type of output of the {@code after} function, and of the composed function + * @param after the function to apply after this function is applied + * @return a composed function that first applies this function and then applies the {@code after} + * function + * @throws NullPointerException if after is null + * @see #compose(Function) + */ + default FunctionWithException andThen( + FunctionWithException after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java b/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java index 2ced1f7a65bd..48dbc780c444 100644 --- a/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java +++ b/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java @@ -53,7 +53,8 @@ public static void writeTableToSheet( ExistingDataException, IllegalStateException, ColumnNameMismatchException, - ColumnCountMismatchException { + ColumnCountMismatchException, + InterruptedException { if (sheetIndex == 0 || sheetIndex > workbook.getNumberOfSheets()) { int i = 1; while (workbook.getSheet("Sheet" + i) != null) { @@ -116,7 +117,8 @@ public static void writeTableToSheet( ExistingDataException, IllegalStateException, ColumnNameMismatchException, - ColumnCountMismatchException { + ColumnCountMismatchException, + InterruptedException { int sheetIndex = workbook.getNumberOfSheets() == 0 ? -1 : workbook.getSheetIndex(sheetName); if (sheetIndex == -1) { writeTableToSheet( @@ -169,7 +171,8 @@ public static void writeTableToRange( RangeExceededException, ExistingDataException, ColumnNameMismatchException, - ColumnCountMismatchException { + ColumnCountMismatchException, + InterruptedException { Name name = workbook.getName(rangeNameOrAddress); ExcelRange excelRange; try { @@ -194,7 +197,8 @@ public static void writeTableToRange( RangeExceededException, ExistingDataException, ColumnNameMismatchException, - ColumnCountMismatchException { + ColumnCountMismatchException, + InterruptedException { int sheetIndex = workbook.getSheetIndex(range.getSheetName()); if (sheetIndex == -1) { throw new InvalidLocationException( @@ -263,7 +267,8 @@ private static void appendRangeWithTable( throws RangeExceededException, ExistingDataException, ColumnNameMismatchException, - ColumnCountMismatchException { + ColumnCountMismatchException, + InterruptedException { Table mappedTable = switch (existingDataMode) { case APPEND_BY_INDEX -> ColumnMapper.mapColumnsByPosition( @@ -333,7 +338,7 @@ private static void updateRangeWithTable( Long rowLimit, ExcelHeaders.HeaderBehavior headers, ExcelSheet sheet) - throws RangeExceededException, ExistingDataException { + throws RangeExceededException, ExistingDataException, InterruptedException { boolean writeHeaders = headers == ExcelHeaders.HeaderBehavior.USE_FIRST_ROW_AS_HEADERS; int requiredRows = Math.min(table.rowCount(), rowLimit == null ? Integer.MAX_VALUE : rowLimit.intValue()) @@ -383,7 +388,8 @@ private static void updateRangeWithTable( * @param sheet Sheet containing the range. * @return True if range is empty and clear is False, otherwise returns False. */ - private static boolean rangeIsNotEmpty(Workbook workbook, ExcelRange range, ExcelSheet sheet) { + private static boolean rangeIsNotEmpty(Workbook workbook, ExcelRange range, ExcelSheet sheet) + throws InterruptedException { ExcelRange fullRange = range.getAbsoluteRange(workbook); for (int row = fullRange.getTopRow(); row <= fullRange.getBottomRow(); row++) { ExcelRow excelRow = sheet.get(row); @@ -401,7 +407,8 @@ private static boolean rangeIsNotEmpty(Workbook workbook, ExcelRange range, Exce * @param range The range to clear. * @param sheet Sheet containing the range. */ - private static void clearRange(Workbook workbook, ExcelRange range, ExcelSheet sheet) { + private static void clearRange(Workbook workbook, ExcelRange range, ExcelSheet sheet) + throws InterruptedException { ExcelRange fullRange = range.getAbsoluteRange(workbook); for (int row = fullRange.getTopRow(); row <= fullRange.getBottomRow(); row++) { ExcelRow excelRow = sheet.get(row); @@ -547,7 +554,7 @@ private static void writeValueToCell( * @return EXCEL_COLUMN_NAMES if the range has headers, otherwise USE_FIRST_ROW_AS_HEADERS. */ private static ExcelHeaders.HeaderBehavior shouldWriteHeaders( - ExcelSheet excelSheet, int topRow, int startCol, int endCol) { + ExcelSheet excelSheet, int topRow, int startCol, int endCol) throws InterruptedException { ExcelRow row = excelSheet.get(topRow); // If the first row is missing or empty, should write headers. From e218c94f3aa92dbfe6c9d78ff07084d8d2ae22db Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 15:13:15 +0100 Subject: [PATCH 2/6] Send `PendingInterrupted` on interrupt The information could be used in GUI to indicate pending execution that will take tad longer. --- .../protocol-language-server.md | 6 ++- .../runtime/ContextEventsListener.scala | 3 ++ .../runtime/ContextRegistryProtocol.scala | 19 +++++++ .../org/enso/polyglot/runtime/Runtime.scala | 9 +++- .../job/ProgramExecutionSupport.scala | 51 ++++++++++++++++++- 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 7006d840adf4..222c1f7ea1b6 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -392,7 +392,7 @@ interface ExpressionUpdate { An information about the computed value. ```typescript -type ExpressionUpdatePayload = Value | DatafalowError | Panic | Pending; +type ExpressionUpdatePayload = Value | DataflowError | Panic | Pending | PendingInterrupted; /** Indicates that the expression was computed to a value. */ interface Value { @@ -426,6 +426,10 @@ interface Pending { progress?: number; } +/** Indicates that the computation of the expression has been interrupted + * and will retried */ +interface PendingInterrupted { } + /** Information about warnings associated with the value. */ interface Warnings { /** The number of attached warnings. */ diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index 154a4328a8e5..3e1bc979d2f2 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -233,6 +233,9 @@ final class ContextEventsListener( case Api.ExpressionUpdate.Payload.Pending(m, p) => ContextRegistryProtocol.ExpressionUpdate.Payload.Pending(m, p) + case Api.ExpressionUpdate.Payload.PendingInterrupted => + ContextRegistryProtocol.ExpressionUpdate.Payload.PendingInterrupted + case Api.ExpressionUpdate.Payload.DataflowError(trace) => ContextRegistryProtocol.ExpressionUpdate.Payload.DataflowError(trace) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 29e083f50699..18ae79874b94 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -231,9 +231,15 @@ object ContextRegistryProtocol { ) } + /** Indicates that an expression is pending a computation + */ case class Pending(message: Option[String], progress: Option[Double]) extends Payload + /** Indicates that an expression's computation has been interrupted and shall be retried. + */ + case object PendingInterrupted extends Payload + /** Indicates that the expression was computed to an error. * * @param trace the list of expressions leading to the root error. @@ -258,6 +264,8 @@ object ContextRegistryProtocol { val Pending = "Pending" + val PendingInterrupted = "PendingInterrupted" + val DataflowError = "DataflowError" val Panic = "Panic" @@ -291,6 +299,14 @@ object ContextRegistryProtocol { .deepMerge( Json.obj(CodecField.Type -> PayloadType.Pending.asJson) ) + case m: Payload.PendingInterrupted.type => + Encoder[Payload.PendingInterrupted.type] + .apply(m) + .deepMerge( + Json.obj( + CodecField.Type -> PayloadType.PendingInterrupted.asJson + ) + ) } implicit val decoder: Decoder[Payload] = @@ -307,6 +323,9 @@ object ContextRegistryProtocol { case PayloadType.Pending => Decoder[Payload.Pending].tryDecode(cursor) + + case PayloadType.PendingInterrupted => + Decoder[Payload.PendingInterrupted.type].tryDecode(cursor) } } } diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 9d9ab4ccab90..b3e22129ba43 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -158,11 +158,16 @@ object Runtime { ) } - /** TBD + /** Indicates that an expression is pending a computation */ @named("expressionUpdatePayloadPending") case class Pending(message: Option[String], progress: Option[Double]) - extends Payload; + extends Payload + + /** Indicates that an expression's computation has been interrupted and shall be retried. + */ + @named("expressionUpdatePayloadPendingInterrupted") + case object PendingInterrupted extends Payload /** Indicates that the expression was computed to an error. * diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index b26771ff3403..3afe4ea136e1 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -99,7 +99,13 @@ object ProgramExecutionSupport { if (VisualizationResult.isInterruptedException(value.getValue)) { value.getValue match { case e: AbstractTruffleException => - // Bail out early. Any references to the value will return `No_Such_Method` exception + sendInterruptedExpressionUpdate( + contextId, + executionFrame.syncState, + value + ) + // Bail out early. Any references to this value that do not expect + // Interrupted error will likely return `No_Such_Method` otherwise. throw new ThreadInterruptedException(e); case _ => } @@ -386,6 +392,49 @@ object ProgramExecutionSupport { Api.ExecutionResult.Failure(ex.getMessage, None) } + private def sendInterruptedExpressionUpdate( + contextId: ContextId, + syncState: UpdatesSynchronizationState, + value: ExpressionValue + )(implicit ctx: RuntimeContext): Unit = { + val expressionId = value.getExpressionId + val methodCall = toMethodCall(value) + if ( + !syncState.isExpressionSync(expressionId) || + (methodCall.isDefined && !syncState.isMethodPointerSync( + expressionId + )) + ) { + val payload = Api.ExpressionUpdate.Payload.PendingInterrupted + ctx.endpoint.sendToClient( + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + value.getExpressionId, + Option(value.getTypes).map(_.toVector), + methodCall, + value.getProfilingInfo.map { case e: ExecutionTime => + Api.ProfilingInfo.ExecutionTime(e.getNanoTimeElapsed) + }.toVector, + value.wasCached(), + value.isTypeChanged || value.isFunctionCallChanged, + payload + ) + ) + ) + ) + ) + + syncState.setExpressionSync(expressionId) + ctx.state.expressionExecutionState.setExpressionExecuted(expressionId) + if (methodCall.isDefined) { + syncState.setMethodPointerSync(expressionId) + } + } + } + private def sendExpressionUpdate( contextId: ContextId, syncState: UpdatesSynchronizationState, From f8cc6ef059fed01ec05969485e809ec1bdf23e71 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 15:21:24 +0100 Subject: [PATCH 3/6] Prettify --- docs/language-server/protocol-language-server.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 222c1f7ea1b6..991cf525d376 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -392,7 +392,12 @@ interface ExpressionUpdate { An information about the computed value. ```typescript -type ExpressionUpdatePayload = Value | DataflowError | Panic | Pending | PendingInterrupted; +type ExpressionUpdatePayload = + | Value + | DataflowError + | Panic + | Pending + | PendingInterrupted; /** Indicates that the expression was computed to a value. */ interface Value { @@ -428,7 +433,7 @@ interface Pending { /** Indicates that the computation of the expression has been interrupted * and will retried */ -interface PendingInterrupted { } +interface PendingInterrupted {} /** Information about warnings associated with the value. */ interface Warnings { From 1e49569ca4c4493de75778155e1546770b84c87e Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 15:58:32 +0100 Subject: [PATCH 4/6] Test `PendingInterrupted` payload --- .../instrument/RuntimeAsyncCommandsTest.scala | 7 ++--- .../test/instrument/TestMessages.scala | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala index 59d323a64a31..a86699d67d83 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala @@ -500,16 +500,15 @@ class RuntimeAsyncCommandsTest responses should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( + TestMessages.pendingInterrupted( contextId, - vId, - ConstantsGen.INTEGER, methodCall = Some( MethodCall( MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "loop"), Vector(1) ) - ) + ), + vId ), context.executionComplete(contextId) ) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala index e0dd02287428..0f318c97929d 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala @@ -479,4 +479,32 @@ object TestMessages { ) ) + /** Create an pending interrupted response. + * + * @param contextId an identifier of the context + * @param expressionIds a list of pending expressions + * @return the expression update response + */ + def pendingInterrupted( + contextId: UUID, + methodCall: Option[Api.MethodCall], + expressionIds: UUID* + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + expressionIds.toSet.map { expressionId => + Api.ExpressionUpdate( + expressionId, + None, + methodCall, + Vector(Api.ProfilingInfo.ExecutionTime(0)), + false, + true, + Api.ExpressionUpdate.Payload.PendingInterrupted + ) + } + ) + ) + } From 9cf67eb7f40e93de0494b38d7a11974b05b43099 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 16:28:33 +0100 Subject: [PATCH 5/6] Add `wasInterrupted` flag to `Pending` Reduce `PendingInterrupted` to a flag in `Pending` --- docs/language-server/protocol-language-server.md | 13 +++---------- .../runtime/ContextEventsListener.scala | 7 ++----- .../runtime/ContextRegistryProtocol.scala | 2 +- .../scala/org/enso/polyglot/runtime/Runtime.scala | 7 +------ .../instrument/job/ProgramExecutionSupport.scala | 2 +- .../interpreter/test/instrument/TestMessages.scala | 2 +- 6 files changed, 9 insertions(+), 24 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 991cf525d376..e47567953a2a 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -392,12 +392,7 @@ interface ExpressionUpdate { An information about the computed value. ```typescript -type ExpressionUpdatePayload = - | Value - | DataflowError - | Panic - | Pending - | PendingInterrupted; +type ExpressionUpdatePayload = Value | DataflowError | Panic | Pending; /** Indicates that the expression was computed to a value. */ interface Value { @@ -429,12 +424,10 @@ interface Pending { /** Optional amount of already done work as a number between `0.0` to `1.0`. */ progress?: number; + /** Indicates whether the computation of the expression has been interrupted and will be retried. */ + wasInterrupted: boolean; } -/** Indicates that the computation of the expression has been interrupted - * and will retried */ -interface PendingInterrupted {} - /** Information about warnings associated with the value. */ interface Warnings { /** The number of attached warnings. */ diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index 3e1bc979d2f2..e6596b450334 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -230,11 +230,8 @@ final class ContextEventsListener( functionSchema.map(toProtocolFunctionSchema) ) - case Api.ExpressionUpdate.Payload.Pending(m, p) => - ContextRegistryProtocol.ExpressionUpdate.Payload.Pending(m, p) - - case Api.ExpressionUpdate.Payload.PendingInterrupted => - ContextRegistryProtocol.ExpressionUpdate.Payload.PendingInterrupted + case Api.ExpressionUpdate.Payload.Pending(m, p, i) => + ContextRegistryProtocol.ExpressionUpdate.Payload.Pending(m, p, i) case Api.ExpressionUpdate.Payload.DataflowError(trace) => ContextRegistryProtocol.ExpressionUpdate.Payload.DataflowError(trace) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 18ae79874b94..bc725d0dee9a 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -233,7 +233,7 @@ object ContextRegistryProtocol { /** Indicates that an expression is pending a computation */ - case class Pending(message: Option[String], progress: Option[Double]) + case class Pending(message: Option[String], progress: Option[Double], wasInterrupted: Boolean) extends Payload /** Indicates that an expression's computation has been interrupted and shall be retried. diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index b3e22129ba43..6efab2c28b30 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -161,14 +161,9 @@ object Runtime { /** Indicates that an expression is pending a computation */ @named("expressionUpdatePayloadPending") - case class Pending(message: Option[String], progress: Option[Double]) + case class Pending(message: Option[String], progress: Option[Double], wasInterrupted: Boolean = false) extends Payload - /** Indicates that an expression's computation has been interrupted and shall be retried. - */ - @named("expressionUpdatePayloadPendingInterrupted") - case object PendingInterrupted extends Payload - /** Indicates that the expression was computed to an error. * * @param trace the list of expressions leading to the root error. diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 3afe4ea136e1..eb6e64dc2553 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -405,7 +405,7 @@ object ProgramExecutionSupport { expressionId )) ) { - val payload = Api.ExpressionUpdate.Payload.PendingInterrupted + val payload = Api.ExpressionUpdate.Payload.Pending(None, None, wasInterrupted = true) ctx.endpoint.sendToClient( Api.Response( Api.ExpressionUpdates( diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala index 0f318c97929d..580d15de120f 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala @@ -501,7 +501,7 @@ object TestMessages { Vector(Api.ProfilingInfo.ExecutionTime(0)), false, true, - Api.ExpressionUpdate.Payload.PendingInterrupted + Api.ExpressionUpdate.Payload.Pending(None, None, wasInterrupted = true) ) } ) From 260bb125df69079db8137e8a4594a6c94db56001 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 Dec 2024 22:47:48 +0100 Subject: [PATCH 6/6] fmt --- .../languageserver/runtime/ContextRegistryProtocol.scala | 7 +++++-- .../src/main/scala/org/enso/polyglot/runtime/Runtime.scala | 7 +++++-- .../instrument/job/ProgramExecutionSupport.scala | 3 ++- .../enso/interpreter/test/instrument/TestMessages.scala | 3 ++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index bc725d0dee9a..aef9df5d3167 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -233,8 +233,11 @@ object ContextRegistryProtocol { /** Indicates that an expression is pending a computation */ - case class Pending(message: Option[String], progress: Option[Double], wasInterrupted: Boolean) - extends Payload + case class Pending( + message: Option[String], + progress: Option[Double], + wasInterrupted: Boolean + ) extends Payload /** Indicates that an expression's computation has been interrupted and shall be retried. */ diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 6efab2c28b30..cec87900947b 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -161,8 +161,11 @@ object Runtime { /** Indicates that an expression is pending a computation */ @named("expressionUpdatePayloadPending") - case class Pending(message: Option[String], progress: Option[Double], wasInterrupted: Boolean = false) - extends Payload + case class Pending( + message: Option[String], + progress: Option[Double], + wasInterrupted: Boolean = false + ) extends Payload /** Indicates that the expression was computed to an error. * diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index eb6e64dc2553..53d89c8eaa7d 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -405,7 +405,8 @@ object ProgramExecutionSupport { expressionId )) ) { - val payload = Api.ExpressionUpdate.Payload.Pending(None, None, wasInterrupted = true) + val payload = + Api.ExpressionUpdate.Payload.Pending(None, None, wasInterrupted = true) ctx.endpoint.sendToClient( Api.Response( Api.ExpressionUpdates( diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala index 580d15de120f..b997f500651c 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala @@ -501,7 +501,8 @@ object TestMessages { Vector(Api.ProfilingInfo.ExecutionTime(0)), false, true, - Api.ExpressionUpdate.Payload.Pending(None, None, wasInterrupted = true) + Api.ExpressionUpdate.Payload + .Pending(None, None, wasInterrupted = true) ) } )