Do not report exceptions on long running Excel reads (#11916)

* 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.

* Send `PendingInterrupted` on interrupt

The information could be used in GUI to indicate pending execution that
will take tad longer.

* Prettify

* Test `PendingInterrupted` payload

* Add `wasInterrupted` flag to `Pending`

Reduce `PendingInterrupted` to a flag in `Pending`

* fmt
This commit is contained in:
Hubert Plociniczak 2024-12-20 15:42:56 +01:00 committed by GitHub
parent e5a1c5a6fa
commit d87484b9b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 299 additions and 56 deletions

View File

@ -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;
/** Indicates that the expression was computed to a value. */
interface Value {
@ -424,6 +424,8 @@ 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;
}
/** Information about warnings associated with the value. */

View File

@ -230,8 +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.Pending(m, p, i) =>
ContextRegistryProtocol.ExpressionUpdate.Payload.Pending(m, p, i)
case Api.ExpressionUpdate.Payload.DataflowError(trace) =>
ContextRegistryProtocol.ExpressionUpdate.Payload.DataflowError(trace)

View File

@ -231,8 +231,17 @@ object ContextRegistryProtocol {
)
}
case class Pending(message: Option[String], progress: Option[Double])
extends Payload
/** Indicates that an expression is pending a computation
*/
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.
*/
case object PendingInterrupted extends Payload
/** Indicates that the expression was computed to an error.
*
@ -258,6 +267,8 @@ object ContextRegistryProtocol {
val Pending = "Pending"
val PendingInterrupted = "PendingInterrupted"
val DataflowError = "DataflowError"
val Panic = "Panic"
@ -291,6 +302,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 +326,9 @@ object ContextRegistryProtocol {
case PayloadType.Pending =>
Decoder[Payload.Pending].tryDecode(cursor)
case PayloadType.PendingInterrupted =>
Decoder[Payload.PendingInterrupted.type].tryDecode(cursor)
}
}
}

View File

@ -158,11 +158,14 @@ object Runtime {
)
}
/** TBD
/** Indicates that an expression is pending a computation
*/
@named("expressionUpdatePayloadPending")
case class Pending(message: Option[String], progress: Option[Double])
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.
*

View File

@ -95,6 +95,21 @@ 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 =>
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 _ =>
}
}
sendExpressionUpdate(contextId, executionFrame.syncState, value)
sendVisualizationUpdates(
contextId,
@ -377,6 +392,50 @@ 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.Pending(None, None, wasInterrupted = true)
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,

View File

@ -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)
)

View File

@ -479,4 +479,33 @@ 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
.Pending(None, None, wasInterrupted = true)
)
}
)
)
}

View File

@ -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);
}
}

View File

@ -22,6 +22,7 @@ import org.apache.poi.ss.usermodel.Workbook;
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 class ExcelConnectionPool {
*/
public <R> R lockForWriting(
File file, ExcelFileFormat format, File[] accompanyingFiles, Function<WriteHelper, R> action)
throws IOException {
throws IOException, InterruptedException {
synchronized (this) {
if (isCurrentlyWriting) {
throw new IllegalStateException(
@ -242,7 +243,8 @@ public class ExcelConnectionPool {
private ExcelWorkbook workbook;
private IOException initializationException = null;
<T> T withWorkbook(Function<ExcelWorkbook, T> action) throws IOException {
<T> T withWorkbook(FunctionWithException<ExcelWorkbook, T, InterruptedException> action)
throws IOException, InterruptedException {
synchronized (this) {
return action.apply(accessCurrentWorkbook());
}
@ -258,7 +260,7 @@ public class ExcelConnectionPool {
}
}
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.");

View File

@ -181,7 +181,8 @@ public class ExcelRange {
* @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 class ExcelRange {
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;

View File

@ -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();

View File

@ -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 class ReadOnlyExcelConnection implements AutoCloseable {
record = null;
}
public synchronized <T> T withWorkbook(Function<ExcelWorkbook, T> f) throws IOException {
public synchronized <T> T withWorkbook(
FunctionWithException<ExcelWorkbook, T, InterruptedException> f)
throws IOException, InterruptedException {
if (record == null) {
throw new IllegalStateException("ReadOnlyExcelConnection is being used after it was closed.");
}

View File

@ -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 class XSSFReaderSheet implements ExcelSheet {
this.parent = parent;
}
private synchronized void ensureReadSheetData() {
private synchronized void ensureReadSheetData() throws InterruptedException {
if (hasReadSheetData) {
return;
}
@ -70,6 +71,8 @@ public class XSSFReaderSheet implements ExcelSheet {
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 class XSSFReaderSheet implements ExcelSheet {
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)) {

View File

@ -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.HashMap;
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.model.SharedStrings;
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 class XSSFReaderWorkbook implements ExcelWorkbook {
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 class XSSFReaderWorkbook implements ExcelWorkbook {
return path;
}
void withReader(Consumer<XSSFReader> action) throws IOException {
void withReader(ConsumerWithException<XSSFReader, InterruptedException> action)
throws IOException, InterruptedException {
try (var pkg = OPCPackage.open(path, PackageAccess.READ)) {
var reader = new XSSFReader(pkg);
action.accept(reader);
@ -115,7 +117,7 @@ public class XSSFReaderWorkbook implements ExcelWorkbook {
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 @@ public class XSSFReaderWorkbook implements ExcelWorkbook {
read1904DateSetting(workbookDoc);
readSheetInfo(workbookDoc);
readNamedRanges(workbookDoc);
} catch (ClosedByInterruptException e) {
throw new InterruptedException(e.getMessage());
} catch (SAXException
| IOException
| InvalidFormatException
@ -171,7 +175,7 @@ public class XSSFReaderWorkbook implements ExcelWorkbook {
}
}
private synchronized void ensureReadShared() {
private synchronized void ensureReadShared() throws InterruptedException {
if (hasReadShared) {
return;
}
@ -207,6 +211,8 @@ public class XSSFReaderWorkbook implements ExcelWorkbook {
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 class XSSFReaderWorkbook implements ExcelWorkbook {
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;
}

View File

@ -4,7 +4,6 @@ import java.io.File;
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.ExcelSheet;
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 class ExcelReader {
* @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 class ExcelReader {
Integer row_limit,
ExcelFileFormat format,
ProblemAggregator problemAggregator)
throws IOException, InvalidLocationException {
throws IOException, InvalidLocationException, InterruptedException {
return withWorkbook(
file,
format,
@ -130,7 +132,7 @@ public class ExcelReader {
Integer row_limit,
ExcelFileFormat format,
ProblemAggregator problemAggregator)
throws IOException, InvalidLocationException {
throws IOException, InvalidLocationException, InterruptedException {
return withWorkbook(
file,
format,
@ -175,7 +177,7 @@ public class ExcelReader {
Integer row_limit,
ExcelFileFormat format,
ProblemAggregator problemAggregator)
throws IOException, InvalidLocationException {
throws IOException, InvalidLocationException, InterruptedException {
return withWorkbook(
file,
format,
@ -202,7 +204,7 @@ public class ExcelReader {
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 class ExcelReader {
Integer row_limit,
ExcelFileFormat format,
ProblemAggregator problemAggregator)
throws IOException, InvalidLocationException {
throws IOException, InvalidLocationException, InterruptedException {
return withWorkbook(
file,
format,
@ -256,7 +258,10 @@ public class ExcelReader {
}
private static <T> T withWorkbook(
File file, ExcelFileFormat format, Function<ExcelWorkbook, T> action) throws IOException {
File file,
ExcelFileFormat format,
FunctionWithException<ExcelWorkbook, T, InterruptedException> action)
throws IOException, InterruptedException {
try (ReadOnlyExcelConnection connection =
ExcelConnectionPool.INSTANCE.openReadOnlyConnection(file, format)) {
return connection.withWorkbook(action);
@ -270,7 +275,7 @@ public class ExcelReader {
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 @@ public class ExcelReader {
ExcelHeaders.HeaderBehavior headers,
int skipRows,
int rowCount,
ProblemAggregator problemAggregator) {
ProblemAggregator problemAggregator)
throws InterruptedException {
ExcelSheet sheet = workbook.getSheetAt(sheetIndex);

View File

@ -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.
*
* <p>This is a <a href="package-summary.html">functional interface</a> whose functional method is
* {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
* @param <E> the type of the checked exception
*/
@FunctionalInterface
public interface ConsumerWithException<T, E extends Throwable> {
/**
* 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<T, E> andThen(java.util.function.Consumer<? super T> after)
throws E {
Objects.requireNonNull(after);
return (T t) -> {
accept(t);
after.accept(t);
};
}
}

View File

@ -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.
*
* <p>This is a <a href="package-summary.html">functional interface</a> whose functional method is
* {@link #apply(Object)}.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @param <E> the type of the checked exception
*/
@FunctionalInterface
public interface FunctionWithException<T, R, E extends Throwable> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t) throws E;
default <V> FunctionWithException<V, R, E> compose(
FunctionWithException<? super V, ? extends T, E> 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 <V> 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 <V> FunctionWithException<T, V, E> andThen(
FunctionWithException<? super R, ? extends V, E> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
}

View File

@ -53,7 +53,8 @@ public class ExcelWriter {
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 class ExcelWriter {
ExistingDataException,
IllegalStateException,
ColumnNameMismatchException,
ColumnCountMismatchException {
ColumnCountMismatchException,
InterruptedException {
int sheetIndex = workbook.getNumberOfSheets() == 0 ? -1 : workbook.getSheetIndex(sheetName);
if (sheetIndex == -1) {
writeTableToSheet(
@ -169,7 +171,8 @@ public class ExcelWriter {
RangeExceededException,
ExistingDataException,
ColumnNameMismatchException,
ColumnCountMismatchException {
ColumnCountMismatchException,
InterruptedException {
Name name = workbook.getName(rangeNameOrAddress);
ExcelRange excelRange;
try {
@ -194,7 +197,8 @@ public class ExcelWriter {
RangeExceededException,
ExistingDataException,
ColumnNameMismatchException,
ColumnCountMismatchException {
ColumnCountMismatchException,
InterruptedException {
int sheetIndex = workbook.getSheetIndex(range.getSheetName());
if (sheetIndex == -1) {
throw new InvalidLocationException(
@ -263,7 +267,8 @@ public class ExcelWriter {
throws RangeExceededException,
ExistingDataException,
ColumnNameMismatchException,
ColumnCountMismatchException {
ColumnCountMismatchException,
InterruptedException {
Table mappedTable =
switch (existingDataMode) {
case APPEND_BY_INDEX -> ColumnMapper.mapColumnsByPosition(
@ -333,7 +338,7 @@ public class ExcelWriter {
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 @@ public class ExcelWriter {
* @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 @@ public class ExcelWriter {
* @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 @@ public class ExcelWriter {
* @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.