/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.regressionsuites;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import org.voltdb.BackendTarget;
import org.voltdb.VoltDB;
/**
* This class gives an abstract interface for processes run using valgrind. It has two
* important member functions, waitForShutdown() and destroy(). These are noops when
* not running valgrind. When a valgrind process is run these operations manage the
* valgrind process and its output.
*/
public class EEProcess {
private Process m_eeProcess;
private String m_eePID = null;
private Thread m_stderrParser = null;
private Thread m_stdoutParser = null;
private int m_port;
private int m_siteCount;
private final boolean verbose = true;
//
// Valgrind will write its output in this file. The %s will be
// replaced by the literal string "%p" or else the stringified
// version of the pid. The former is used to tell valgrind itself
// the name. The latter is used to open up the file.
//
private static String VALGRIND_OUTPUT_FILE_PATTERN = "valgrind_%s.xml";
// The output file where valgrind will leave it's output. Creators of this
// class (usually LocalCluster) will be responsible for cleaning this up.
private File m_valgrindOutputFile = null;
public int port() {
return m_port;
}
EEProcess(final BackendTarget target, int siteCount, String logfile) {
if (target != BackendTarget.NATIVE_EE_VALGRIND_IPC) {
return;
}
if (verbose) {
System.out.println("Running " + target);
}
final ArrayList<String> args = new ArrayList<>();
final String voltdbIPCPath = System.getenv("VOLTDBIPC_PATH");
args.add("valgrind");
args.add("--leak-check=full");
args.add("--show-reachable=yes");
args.add("--num-callers=32");
args.add("--error-exitcode=-1");
args.add("--suppressions=tests/ee/test_utils/vdbsuppressions.supp");
args.add("--xml=yes");
// We will write valgrind output to a file. The %p is replaced by
// valgrind with the process id of the launched process.
args.add(String.format("--xml-file=%s", String.format(VALGRIND_OUTPUT_FILE_PATTERN, "%p")));
/*
* VOLTDBIPC_PATH is set as part of the regression suites and ant
* check In that scenario junit will handle logging of Valgrind
* output. stdout and stderr is forwarded from the backend.
*/
if (voltdbIPCPath == null) {
args.add("--quiet");
args.add("--log-file=" + logfile);
}
args.add(voltdbIPCPath == null ? "./voltdbipc" : voltdbIPCPath);
args.add(String.valueOf(siteCount));
final ProcessBuilder pb = new ProcessBuilder(args);
//pb.redirectErrorStream(true);
try {
m_eeProcess = pb.start();
final Process p = m_eeProcess;
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
p.destroy();
}
});
} catch (final IOException e) {
VoltDB.crashLocalVoltDB(e.getMessage(), true, e);
}
final BufferedReader stderr = new BufferedReader(new InputStreamReader(
m_eeProcess.getErrorStream()));
/*
* This block attempts to read the PID and then waits for the
* listening message indicating that the IPC EE is ready to accept a
* connection on a socket
*/
final BufferedReader stdout = new BufferedReader(new InputStreamReader(
m_eeProcess.getInputStream()));
try {
boolean failure = false;
// expecting "== pid = NUMBER ==" to be line 1, where NUMBER is the C++ process's PID
String pidString = stdout.readLine();
if (pidString == null) {
failure = true;
} else {
if (verbose) {
System.out.println("PID string \"" + pidString + "\"");
}
pidString = pidString.substring("== pid = ".length());
pidString = pidString.substring(0, pidString.indexOf(" =="));
m_eePID = pidString;
}
// expecting "== eecount = NUMBER ==" to be line 2, where NUMBER is expected EE threads
String siteCountString = stdout.readLine();
if (siteCountString == null) {
failure = true;
} else {
if (verbose) {
System.out.println("Site count string \"" + siteCountString + "\"");
}
siteCountString = siteCountString.substring("== eecount = ".length());
siteCountString = siteCountString.substring(0, siteCountString.indexOf(" =="));
int siteCount2 = Integer.valueOf(siteCountString);
assert(siteCount2 == siteCount);
m_siteCount = siteCount;
}
// expecting "== port = NUMBER ==" to be line 3, where NUMBER is listening port
String portString = stdout.readLine();
if (portString == null) {
failure = true;
} else {
if (verbose) {
System.out.println("Port string \"" + portString + "\"");
}
portString = portString.substring("== port = ".length());
portString = portString.substring(0,
portString.indexOf(" =="));
m_port = Integer.valueOf(portString);
}
while (true) {
String line = null;
if (!failure) {
line = stdout.readLine();
}
if (line != null && !failure) {
if (verbose) {
System.out.println("[ipc=" + m_eePID + "]:::" + line);
}
if (line.contains("listening")) {
break;
} else {
continue;
}
} else {
while ((line = stderr.readLine()) != null) {
if (verbose) {
System.err.println(line);
}
}
try {
m_eeProcess.waitFor();
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
VoltDB.crashLocalVoltDB("[ipc=" + m_eePID
+ "] Returned end of stream and exit value "
+ m_eeProcess.exitValue(), false, null);
}
}
} catch (final IOException e) {
e.printStackTrace();
return;
}
m_valgrindOutputFile = new File(String.format(VALGRIND_OUTPUT_FILE_PATTERN, m_eePID));
/*
* Create threads that echo output from stdout and stderr prefixed by
* a unique ID for the spawned EE process.
*/
final Process p = m_eeProcess;
m_stdoutParser = new Thread() {
@Override
public void run() {
while (true) {
try {
final String line = stdout.readLine();
if (line != null) {
if (verbose) {
System.out.println("[ipc=" + p.hashCode()
+ "]:::" + line);
}
} else {
try {
p.waitFor();
} catch (final InterruptedException e) {
e.printStackTrace();
}
if (verbose) {
System.out
.println("[ipc="
+ m_eePID
+ "] Returned end of stream and exit value "
+ p.exitValue());
}
return;
}
} catch (final IOException e) {
e.printStackTrace();
return;
}
}
}
};
m_stderrParser = new Thread() {
@Override
public void run() {
while (true) {
try {
final String line = stderr.readLine();
if (line != null) {
if (verbose) {
System.err.println("[ipc=" + p.hashCode()
+ "]:::" + line);
}
} else {
try {
p.waitFor();
} catch (final InterruptedException e) {
e.printStackTrace();
}
if (verbose) {
System.out
.println("[ipc="
+ m_eePID
+ "] Returned end of stream and exit value "
+ p.exitValue());
}
return;
}
} catch (final IOException e) {
e.printStackTrace();
return;
}
}
}
};
m_stdoutParser.setDaemon(false);
m_stdoutParser.start();
m_stderrParser.setDaemon(false);
m_stderrParser.start();
}
public void destroy() {
if (m_eeProcess != null) {
m_eeProcess.destroy();
}
}
// In some tests there is no client initiated and the IPC server will hang
// waiting for the incoming messages.
// Push EOFs to IPC processes in this case can help end the hanging situation.
private void signalShutDown() {
Socket ipcSocket;
PrintWriter ipcWriter;
try {
// Need to send as many EOFs as m_siteCount.
for (int i=0; i<m_siteCount; i++) {
// The connection will be closed once an EOF is sent.
// So we need to re-open the connection if we want to send another EOF.
ipcSocket = new Socket("localhost", m_port);
if (! ipcSocket.isConnected()) {
ipcSocket.close();
return;
}
ipcWriter = new PrintWriter(ipcSocket.getOutputStream());
// 0x04 is the code for EOF.
ipcWriter.write("\004");
ipcWriter.flush();
ipcWriter.close();
ipcSocket.close();
}
} catch (IOException e) {}
}
/**
* Shut down the EE process and return a file containing XML valgrind output.
* @return a File
* @throws InterruptedException
*/
public File waitForShutdown() throws InterruptedException {
if (m_eeProcess != null) {
boolean done = false;
while (!done) {
try {
signalShutDown();
m_eeProcess.waitFor();
done = true;
} catch (InterruptedException e) {
System.out
.println("Interrupted waiting for EE IPC process to die. Wait again.");
}
}
}
if (m_stdoutParser != null) {
m_stdoutParser.join();
}
if (m_stderrParser != null) {
m_stderrParser.join();
}
return m_valgrindOutputFile;
}
}