Skip to content

Commit 6b884a1

Browse files
committed
Catch R exceptions in Online
1 parent 56bd7b6 commit 6b884a1

7 files changed

Lines changed: 75 additions & 20 deletions

File tree

RCaller/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ description = 'RCaller is a software library which simplifies performing data an
1313

1414
dependencies {
1515
implementation 'org.apache.commons:commons-lang3:3.12.0'
16-
compileOnly 'org.apache.arrow:arrow-vector:[3.0.0,)'
16+
compileOnly 'org.apache.arrow:arrow-vector:[5.0.0,)'
1717

18-
testImplementation 'junit:junit:4.13.1'
19-
testImplementation 'org.apache.arrow:arrow-vector:[3.0.0,)'
20-
testImplementation 'org.apache.arrow:arrow-memory-netty:[3.0.0,)'
18+
testImplementation 'junit:junit:4.13.2'
19+
testImplementation 'org.apache.arrow:arrow-vector:[5.0.0,)'
20+
testImplementation 'org.apache.arrow:arrow-memory-netty:[5.0.0,)'
2121
}
2222

2323
java.sourceCompatibility = JavaVersion.VERSION_11

RCaller/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<dependency>
6565
<groupId>junit</groupId>
6666
<artifactId>junit</artifactId>
67-
<version>4.13.1</version>
67+
<version>4.13.2</version>
6868
<scope>test</scope>
6969
</dependency>
7070
<dependency>
@@ -75,13 +75,13 @@
7575
<dependency>
7676
<groupId>org.apache.arrow</groupId>
7777
<artifactId>arrow-vector</artifactId>
78-
<version>[3.0.0,)</version>
78+
<version>[5.0.0,)</version>
7979
<scope>provided</scope>
8080
</dependency>
8181
<dependency>
8282
<groupId>org.apache.arrow</groupId>
8383
<artifactId>arrow-memory-netty</artifactId>
84-
<version>[3.0.0,)</version>
84+
<version>[5.0.0,)</version>
8585
<scope>test</scope>
8686
</dependency>
8787
</dependencies>

RCaller/src/main/java/com/github/rcaller/io/arrow/ArrowImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ public long[] getAsLongArray(String name) {
329329
}
330330
}
331331

332+
@Override
332333
public double[][] getAsDoubleMatrix(String name) {
333334
var vector = findVector(name);
334335
if (vector instanceof FixedSizeListVector || vector instanceof ListVector) {

RCaller/src/main/java/com/github/rcaller/rstuff/RCaller.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import java.io.*;
3737
import java.nio.channels.Channels;
3838
import java.nio.channels.FileChannel;
39+
import java.util.ArrayList;
40+
import java.util.List;
3941
import java.util.Map;
4042
import java.util.Random;
4143
import java.util.logging.Level;
@@ -288,7 +290,7 @@ public void runAndReturnResultOnline(String var) throws ExecutionException {
288290
logger.log(Level.INFO, "Retrying online R execution");
289291
}
290292

291-
File outputFile, resultReadyControlFile;
293+
File outputFile, errorFile, resultReadyControlFile;
292294

293295
if (rCallerOptions.getrExecutable() == null) {
294296
if (handleRFailure("RExecutable is not defined.Please set this" + " variable to full path of R executable binary file.")) {
@@ -298,6 +300,7 @@ public void runAndReturnResultOnline(String var) throws ExecutionException {
298300

299301
try {
300302
outputFile = tempFileService.createOutputFile();
303+
errorFile = tempFileService.createControlFile();
301304
resultReadyControlFile = tempFileService.createControlFile();
302305
} catch (Exception e) {
303306
if (handleRFailure("Can not create a temporary file for storing the R results: " + e.getMessage())) {
@@ -308,7 +311,6 @@ public void runAndReturnResultOnline(String var) throws ExecutionException {
308311
}
309312

310313
rCode.appendStandardCodeToAppend(outputFile, var);
311-
rCode.appendEndSignalCode(resultReadyControlFile);
312314
if (rInput == null || rOutput == null || rError == null || process == null) {
313315
try {
314316
startOnlineProcess();
@@ -321,7 +323,8 @@ public void runAndReturnResultOnline(String var) throws ExecutionException {
321323
}
322324

323325
try {
324-
rInput.write(rCode.toString().getBytes(Globals.standardCharset));
326+
var script = rCode.toTryCatchScript(errorFile) + rCode.createEndSignalCode(resultReadyControlFile);
327+
rInput.write(script.getBytes(Globals.standardCharset));
325328
rInput.flush();
326329
} catch (IOException e) {
327330
rInput = null;
@@ -337,12 +340,21 @@ public void runAndReturnResultOnline(String var) throws ExecutionException {
337340
}
338341

339342
// an error might occur before any output is written
340-
if (!isProcessAlive() && errorMessageSaver.getMessage().length() > 0) {
341-
if (handleRFailure("R stderr: " + errorMessageSaver.getMessage())) {
343+
if (!isProcessAlive()) {
344+
if (handleRFailure("R process died, stderr: " + errorMessageSaver.getMessage())) {
342345
continue;
343346
}
344347
}
345348

349+
if (errorFile.length() > 0) {
350+
parser.setIPCResource(errorFile.toURI());
351+
parser.parse();
352+
String errorMessage = parser.getAsStringArray("exception")[0];
353+
String stack = String.join("\n----\n",List.of(parser.getAsStringArray("stacktrace")));
354+
355+
throw new ExecutionException("R code throw an error:\n" + errorMessage + "\nDetailed stack:\n----\n" + stack);
356+
}
357+
346358
parser.setIPCResource(outputFile.toURI());
347359

348360
try {

RCaller/src/main/java/com/github/rcaller/rstuff/RCode.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,21 @@ public final void clear() {
100100
addRCode(RCodeIO.getInterprocessDependencies(rCallerOptions));
101101
}
102102

103+
/**
104+
* Adding R code that exports toplevel var to outputFile for reading by Java. Internal use only.
105+
* @param outputFile file to be created
106+
* @param var name of variable to be exported
107+
*/
103108
public void appendStandardCodeToAppend(File outputFile, String var) {
104109
addRCode(RCodeIO.getVariableExporting(rCallerOptions, var, URI.create(outputFile.getAbsolutePath())));
105110
}
106111

112+
static String createEndSignalCode(File outputFile) {
113+
return ("cat(1, file=\"" + outputFile.getPath().replace("\\", "/") + "\")\n");
114+
}
115+
107116
public void appendEndSignalCode(File outputFile) {
108-
addRCode("cat(1, file=\"" + outputFile.getPath().replace("\\", "/") + "\")\n");
117+
addRCode(createEndSignalCode(outputFile));
109118
}
110119

111120
public void clearOnline(){
@@ -245,7 +254,7 @@ public void R_source(String sourceFile) {
245254
}
246255

247256
public void deleteTempFiles(){
248-
if(tempFileService != null){
257+
if (tempFileService != null){
249258
tempFileService.deleteRCallerTempFiles();
250259
}
251260
}
@@ -254,4 +263,29 @@ public void deleteTempFiles(){
254263
public String toString() {
255264
return this.code.toString();
256265
}
266+
267+
/**
268+
* Wrap current code to standard tryCatch function.
269+
* Error handler saves details to errorOutputFile if the error occurs.
270+
* @param errorOutputFile
271+
* @return
272+
*/
273+
String toTryCatchScript(File errorOutputFile) {
274+
//Using code snippet "An improved “error handler”" with withCallingHandlers nested in tryCatch
275+
//from https://cran.r-project.org/web/packages/tryCatchLog/vignettes/tryCatchLog-intro.html
276+
277+
var script = new StringBuilder("tryCatch( withCallingHandlers({\n");
278+
script.append(this.code);
279+
280+
script.append("}, error=function(e) {\n" +
281+
" stacktrace <- as.character(sys.calls())\n" +
282+
" exception <- as.character(e)\n" +
283+
" caught <- list()\n" +
284+
" caught[[1]] <- exception\n" +
285+
" caught[[2]] <- stacktrace[6:(length(stacktrace)-2)]\n" + //remove wrapping steps
286+
" names(caught) <- c(\"exception\", \"stacktrace\")\n");
287+
script.append(RCodeIO.getVariableExporting(rCallerOptions, "caught", URI.create(errorOutputFile.getAbsolutePath()))).append("\n");
288+
script.append("}), error = function(e) { })\n");
289+
return script.toString();
290+
}
257291
}

RCaller/src/main/resources/arrow_bridge.R

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ require("arrow")
33
send_element_by_arrow <- function(obj, name, stream) {
44
if (is.data.frame(obj)) {
55
#Export by Arrow as is
6-
write_ipc_stream(obj, stream)
6+
arrow::write_ipc_stream(obj, stream)
77
return()
88
}
99
if (is.array(obj)) {
@@ -22,7 +22,7 @@ send_element_by_arrow <- function(obj, name, stream) {
2222
schema_fixed_size <- schema(matrix_column=fixed_size_list_of(type = batch_typedetect$schema$fields[[1]]$type$value_type, columns))
2323
batch <- record_batch(matrix_column=matrix_jagged_fixedsize, schema=schema_fixed_size)
2424
names(batch) <- name
25-
write_ipc_stream(batch, stream)
25+
arrow::write_ipc_stream(batch, stream)
2626
return()
2727
}
2828
if (length(dim(obj)) > 2) {
@@ -40,7 +40,7 @@ send_element_by_arrow <- function(obj, name, stream) {
4040
#Union typed and nested lists might not work
4141
batch <- record_batch(list_column=obj)
4242
names(batch) <- name
43-
write_ipc_stream(batch, stream)
43+
arrow::write_ipc_stream(batch, stream)
4444
return()
4545
} else if (length(names(obj)) > 0) {
4646
#Export each field separatly
@@ -68,15 +68,15 @@ send_element_by_arrow <- function(obj, name, stream) {
6868
#export filled vector with auto type detect
6969
batch <- record_batch(vector_column=obj)
7070
names(batch) <- name
71-
write_ipc_stream(batch, stream)
71+
arrow::write_ipc_stream(batch, stream)
7272
return()
7373
} else if (length(obj) == 0) {
7474
#export empty element
7575
obj <- c(1)
7676
type_example_batch <- record_batch(empty_column=obj)
7777
length(obj) <- 0
7878
empty_batch <- record_batch(empty_column=obj, schema=type_example_batch$schema)
79-
write_ipc_stream(empty_batch, stream)
79+
arrow::write_ipc_stream(empty_batch, stream)
8080
return()
8181
# } else {
8282
# stop("Probably unsupported output")

RCaller/src/test/java/com/github/rcaller/RunOnlineTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,14 @@ public void stopAsyncTest() {
211211
}
212212
}
213213

214+
@Test(expected = ExecutionException.class)
215+
public void exceptionCatchTest() {
216+
RCaller rcaller = RCaller.create();
217+
RCode code = rcaller.getRCode();
218+
code.addRCode("a <- log(\"not a number\")");
219+
rcaller.runAndReturnResultOnline("a");
220+
}
221+
214222
@Test
215223
public void rHaltedTest() {
216224
System.out.println("R HALTED TEST");
@@ -226,7 +234,7 @@ public void rHaltedTest() {
226234
rcaller.runAndReturnResultOnline("a");
227235
} catch (ExecutionException ex) {
228236
Logger.getLogger(RunOnlineTest.class.getName()).log(Level.SEVERE, ex.getMessage());
229-
if (ex.getMessage().contains("R stderr:")) {
237+
if (ex.getMessage().contains("R code throw an error:")) {
230238
exceptionThrown = true;
231239
}
232240
rcaller.stopRCallerOnline();

0 commit comments

Comments
 (0)