/**
* Copyright (C) 2011
* Michael Mosmann <michael@mosmann.de>
* Martin Jöhren <m.joehren@googlemail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.flapdoodle.embedmongo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.flapdoodle.embedmongo.config.MongodConfig;
public class MongodProcess {
private static final int MONGODB_RETURN_CODE_EXIT_KILL = 12;
private static final Logger _logger = Logger.getLogger(MongodProcess.class.getName());
private final MongodConfig _config;
private final MongodExecutable _mongodExecutable;
private Process _process;
private ConsoleOutput _consoleOutput;
private File _dbDir;
boolean _processKilled = false;
boolean _stopped = false;
public MongodProcess(MongodConfig config, MongodExecutable mongodExecutable) throws IOException {
_config = config;
_mongodExecutable = mongodExecutable;
try {
File dbDir;
if (config.getDatabaseDir() != null) {
dbDir = Files.createOrCheckDir(config.getDatabaseDir());
} else {
dbDir = Files.createTempDir("embedmongo-db");
_dbDir = dbDir;
}
ProcessBuilder processBuilder = new ProcessBuilder(getCommandLine(_config, _mongodExecutable.getFile(), dbDir));
processBuilder.redirectErrorStream();
_process = processBuilder.start();
Runtime.getRuntime().addShutdownHook(new JobKiller());
InputStream inputStream = _process.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream);
if (LogWatch.waitForStart(reader, "waiting for connections on port", "failed", 20000)) {
_consoleOutput = new ConsoleOutput(reader);
_consoleOutput.setDaemon(true);
_consoleOutput.start();
} else {
throw new IOException("Could not start mongod process");
}
} catch (IOException iox) {
stop();
throw iox;
}
}
private static List<String> getCommandLine(MongodConfig config, File mongodExecutable, File dbDir) {
List<String> ret = new ArrayList<String>();
ret.addAll(Arrays.asList(mongodExecutable.getAbsolutePath(), "-v", "--port", "" + config.getPort(), "--dbpath", ""
+ dbDir.getAbsolutePath(), "--noprealloc", "--nohttpinterface", "--smallfiles"));
if (config.isIpv6()) {
ret.add("--ipv6");
}
return ret;
}
public synchronized void stop() {
if (!_stopped) {
if (_process != null) {
try {
// streams need to be closed, otherwise process may block
// see http://kylecartmell.com/?p=9
_process.getErrorStream().close();
_process.getInputStream().close();
_process.getOutputStream().close();
} catch (IOException e) {
_logger.severe(e.getMessage());
} finally {
_process.destroy();
}
}
waitForProcessGotKilled();
if ((_dbDir != null) && (!Files.forceDelete(_dbDir)))
_logger.warning("Could not delete temp db dir: " + _dbDir);
if (_mongodExecutable.getFile() != null) {
if (!Files.forceDelete(_mongodExecutable.getFile())) {
_stopped = true;
_logger.warning("Could not delete mongod executable NOW: " + _mongodExecutable.getFile());
}
}
}
}
/**
* It may happen in tests, that the process is currently using some files in
* the temp directory, e.g. journal files (journal/j._0) and got killed at
* that time, so it takes a bit longer to kill the process. So we just wait
* for a second (in 10 ms steps) that the process got really killed.
*/
private void waitForProcessGotKilled() {
final Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
try {
_process.waitFor();
} catch (InterruptedException e) {
_logger.severe(e.getMessage());
} finally {
_processKilled = true;
timer.cancel();
}
}
}, 0, 10);
// wait for max. 1 second that process got killed
int countDown = 100;
while (!_processKilled && (countDown-- > 0))
try {
Thread.sleep(10);
} catch (InterruptedException e) {
_logger.severe(e.getMessage());
}
if (!_processKilled) {
timer.cancel();
throw new IllegalStateException("Couldn't kill mongod process!");
}
}
class JobKiller extends Thread {
@Override
public void run() {
MongodProcess.this.stop();
}
}
static class LogWatch extends Thread {
private final InputStreamReader _reader;
private final StringBuilder _output = new StringBuilder();
private final String _success;
private final String _failure;
private boolean _initWithSuccess = false;
private LogWatch(InputStreamReader reader, String success, String failure) {
_reader = reader;
_success = success;
_failure = failure;
}
@Override
public void run() {
try {
int read;
char[] buf = new char[512];
while ((read = _reader.read(buf)) != -1) {
CharSequence line = new String(buf, 0, read);
System.out.print(line);
_output.append(line);
if (_output.indexOf(_success) != -1) {
_initWithSuccess = true;
break;
}
if (_output.indexOf(_failure) != -1) {
_initWithSuccess = false;
break;
}
}
} catch (IOException iox) {
_logger.log(Level.SEVERE, "out", iox);
}
synchronized (this) {
notify();
}
}
public boolean isInitWithSuccess() {
return _initWithSuccess;
}
public static boolean waitForStart(InputStreamReader reader, String success, String failed, long timeout) {
LogWatch logWatch = new LogWatch(reader, success, failed);
logWatch.start();
synchronized (logWatch) {
try {
logWatch.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return logWatch.isInitWithSuccess();
}
}
static class ConsoleOutput extends Thread {
private final InputStreamReader _reader;
public ConsoleOutput(InputStreamReader reader) {
_reader = reader;
}
@Override
public void run() {
try {
int read;
char[] buf = new char[512];
while ((read = _reader.read(buf)) != -1) {
System.out.print(new String(buf, 0, read));
}
} catch (IOException iox) {
// _logger.log(Level.SEVERE,"out",iox);
}
}
}
}