I've rebuilt my Java/C++ project several times using JNI, JNA, BridJ, and JavaCPP, and every time I encounter random (unpredictable) segmentation faults. I have verified that a pure-C++ executable using this library never causes segmentation faults, and in the case of BridJ, narrowed it down to Java's garbage collector by explicitly invoking it.
One thought was that these libraries are creating Java-side pointer objects that call free
or delete
when they get garbage collected (through finalize
) instead of treating the pointers that C++ returns as borrowed references, as they should in this application.
But I've tried one additional test (not represented below): I turned every pointer in the API into an int64_t
(long
in Java) and explicitly cast as the appropriate type in C++. I still see the rare segfaults.
So my question is broad: what could be causing this issue? I'll accept answers using JNA or BridJ because I can easily switch between these two. I think I'm missing a fundamental issue because this problem is so pervasive in all the libraries I've tried.
For concreteness, here's my JNA version. I'm linking against the CERN ROOT library. RootTreeReader.h is:
#ifndef ROOTTREEREADER_H
#define ROOTTREEREADER_H
#include <TFile.h>
#include <TTreeReader.h>
#include <TTreeReaderValue.h>
#include <TTreeReaderArray.h>
using namespace ROOT::Internal;
extern "C" {
TFile *newFile(const char *fileLocation);
TTreeReader *newReader(TFile *file, const char *treeLocation);
bool readerNext(TTreeReader *reader);
TTreeReaderValueBase *newValue_float(TTreeReader *reader, const char *name);
float getValue_float(TTreeReaderValueBase *value);
}
#endif // ROOTTREEREADER_H
RootTreeReader.cpp is:
#include <string>
#include "RootTreeReader.h"
TFile *newFile(const char *fileLocation) {
return TFile::Open(fileLocation);
}
TTreeReader *newReader(TFile *file, const char *treeLocation) {
return new TTreeReader(treeLocation, file);
}
bool readerNext(TTreeReader *reader) {
return reader->Next();
}
TTreeReaderValueBase *newValue_float(TTreeReader *reader, const char *name) {
return new TTreeReaderValue<float>(*reader, name);
}
float getValue_float(TTreeReaderValueBase *value) {
return *((float*)value->GetAddress());
}
Their Makefile is
all: RootTreeReader.cpp
mkdir -p ../../../target/native/linux-x86-64
g++ RootTreeReader.cpp -o ../../../target/native/linux-x86-64/libRootTreeReader.so \
-fPIC -shared \
-Wl,--no-as-needed $(shell root-config --cflags --ldflags --libs) -lTreePlayer
JNAerator generates the following RootTreeReaderLibrary.java:
package org.dianahep.scaroot;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
/**
* JNA Wrapper for library <b>RootTreeReader</b><br>
* This file was autogenerated by <a href="http://jnaerator.googlecode.com/">JNAerator</a>,<br>
* a tool written by <a href="http://ochafik.com/">Olivier Chafik</a> that <a href="http://code.google.com/p/jnaerator/wiki/CreditsAndLicense">uses a few opensource projects.</a>.<br>
* For help, please visit <a href="http://nativelibs4java.googlecode.com/">NativeLibs4Java</a> , <a href="http://rococoa.dev.java.net/">Rococoa</a>, or <a href="http://jna.dev.java.net/">JNA</a>.
*/
public interface RootTreeReaderLibrary extends Library {
public static final String JNA_LIBRARY_NAME = "RootTreeReader";
public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(RootTreeReaderLibrary.JNA_LIBRARY_NAME);
public static final RootTreeReaderLibrary INSTANCE = (RootTreeReaderLibrary)Native.loadLibrary(RootTreeReaderLibrary.JNA_LIBRARY_NAME, RootTreeReaderLibrary.class);
/**
* Original signature : <code>TFile* newFile(const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:5</i><br>
* @deprecated use the safer methods {@link #newFile(java.lang.String)} and {@link #newFile(com.sun.jna.Pointer)} instead
*/
@Deprecated
RootTreeReaderLibrary.TFile newFile(Pointer fileLocation);
/**
* Original signature : <code>TFile* newFile(const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:5</i>
*/
RootTreeReaderLibrary.TFile newFile(String fileLocation);
/**
* Original signature : <code>TTreeReader* newReader(TFile*, const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:6</i><br>
* @deprecated use the safer methods {@link #newReader(org.dianahep.scaroot.RootTreeReaderLibrary.TFile, java.lang.String)} and {@link #newReader(org.dianahep.scaroot.RootTreeReaderLibrary.TFile, com.sun.jna.Pointer)} instead
*/
@Deprecated
RootTreeReaderLibrary.TTreeReader newReader(RootTreeReaderLibrary.TFile file, Pointer treeLocation);
/**
* Original signature : <code>TTreeReader* newReader(TFile*, const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:6</i>
*/
RootTreeReaderLibrary.TTreeReader newReader(RootTreeReaderLibrary.TFile file, String treeLocation);
/**
* Original signature : <code>bool readerNext(TTreeReader*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:7</i>
*/
byte readerNext(RootTreeReaderLibrary.TTreeReader reader);
/**
* Original signature : <code>TTreeReaderValueBase* newValue_float(TTreeReader*, const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:9</i><br>
* @deprecated use the safer methods {@link #newValue_float(org.dianahep.scaroot.RootTreeReaderLibrary.TTreeReader, java.lang.String)} and {@link #newValue_float(org.dianahep.scaroot.RootTreeReaderLibrary.TTreeReader, com.sun.jna.Pointer)} instead
*/
@Deprecated
RootTreeReaderLibrary.TTreeReaderValueBase newValue_float(RootTreeReaderLibrary.TTreeReader reader, Pointer name);
/**
* Original signature : <code>TTreeReaderValueBase* newValue_float(TTreeReader*, const char*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:9</i>
*/
RootTreeReaderLibrary.TTreeReaderValueBase newValue_float(RootTreeReaderLibrary.TTreeReader reader, String name);
/**
* Original signature : <code>float getValue_float(TTreeReaderValueBase*)</code><br>
* <i>native declaration : src/main/cpp/RootTreeReader.h:11</i>
*/
float getValue_float(RootTreeReaderLibrary.TTreeReaderValueBase value);
public static class TFile extends PointerType {
public TFile(Pointer address) {
super(address);
}
public TFile() {
super();
}
};
public static class TTreeReader extends PointerType {
public TTreeReader(Pointer address) {
super(address);
}
public TTreeReader() {
super();
}
};
public static class TTreeReaderValueBase extends PointerType {
public TTreeReaderValueBase(Pointer address) {
super(address);
}
public TTreeReaderValueBase() {
super();
}
};
}
and I call it like this:
Native.setProtected(true)
println("Native.isProtected", Native.isProtected) // it's true on Linux; I've tried this with and without
val lib = RootTreeReaderLibrary.INSTANCE
println("one")
val file = lib.newFile("TrackResonanceNtuple.root")
println("two")
val reader = lib.newReader(file, "TrackResonanceNtuple/twoMuon")
println("three")
val mass = lib.newValue_float(reader, "mass_mumu")
println("four")
var counter = 0
while (lib.readerNext(reader) > 0) {
val num = lib.getValue_float(mass)
println(num)
counter += 1
// if (counter % 1000 == 0) {
// println("gc start")
// System.gc()
// println("gc end")
// }
}
println("five")
It segfaults rarely with or without explicit garbage collector invocations. A BridJ version of this segfaults rarely without and frequently with explicit garbage collector invocations.
The quick fix:
The problem is that CERN ROOT tries to install handlers for the same signals as the JDK. Oracle recommends to preload
libjsig.so
, which provides a "signal-chaining facility", to work around these kinds of issues:https://docs.oracle.com/javase/6/docs/technotes/guides/vm/signal-chaining.html
Edit from Jim:
Specifically for the ROOT framework, you can turn off signal handling by calling
The discussion can be found on this ROOT message board: https://root.cern.ch/phpBB3/viewtopic.php?t=8231