Segmentation faults in JNA/BridJ etc

787 views Asked by At

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.

1

There are 1 answers

0
Samuel Audet On BEST ANSWER

The quick fix:

export LD_PRELOAD=/path/to/libjsig.so

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

gSystem->ResetSignals();

The discussion can be found on this ROOT message board: https://root.cern.ch/phpBB3/viewtopic.php?t=8231