/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* 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 org.codelibs.fess.helper;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.PreDestroy;
import org.apache.commons.io.IOUtils;
import org.codelibs.fess.exception.FessSystemException;
import org.codelibs.fess.util.InputStreamThread;
import org.codelibs.fess.util.JobProcess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProcessHelper {
private static final Logger logger = LoggerFactory.getLogger(ProcessHelper.class);
private final ConcurrentHashMap<String, JobProcess> runningProcessMap = new ConcurrentHashMap<>();
private int processDestroyTimeout = 10;
@PreDestroy
public void destroy() {
for (final String sessionId : runningProcessMap.keySet()) {
if (logger.isInfoEnabled()) {
logger.info("Stopping process " + sessionId);
}
if (destroyProcess(sessionId) == 0) {
if (logger.isInfoEnabled()) {
logger.info("Stopped process " + sessionId);
}
}
}
}
public synchronized JobProcess startProcess(final String sessionId, final List<String> cmdList, final Consumer<ProcessBuilder> pbCall) {
final ProcessBuilder pb = new ProcessBuilder(cmdList);
pbCall.accept(pb);
destroyProcess(sessionId);
JobProcess jobProcess;
try {
jobProcess = new JobProcess(pb.start());
destroyProcess(sessionId, runningProcessMap.putIfAbsent(sessionId, jobProcess));
return jobProcess;
} catch (final IOException e) {
throw new FessSystemException("Crawler Process terminated.", e);
}
}
public int destroyProcess(final String sessionId) {
final JobProcess jobProcess = runningProcessMap.remove(sessionId);
return destroyProcess(sessionId, jobProcess);
}
public boolean isProcessRunning() {
return !runningProcessMap.isEmpty();
}
protected int destroyProcess(final String sessionId, final JobProcess jobProcess) {
if (jobProcess != null) {
final InputStreamThread ist = jobProcess.getInputStreamThread();
try {
ist.interrupt();
} catch (final Exception e) {
logger.warn("Could not interrupt a thread of an input stream.", e);
}
final CountDownLatch latch = new CountDownLatch(3);
final Process process = jobProcess.getProcess();
new Thread(() -> {
try {
IOUtils.closeQuietly(process.getInputStream());
} catch (final Exception e) {
logger.warn("Could not close a process input stream.", e);
} finally {
latch.countDown();
}
}, "ProcessCloser-input-" + sessionId).start();
new Thread(() -> {
try {
IOUtils.closeQuietly(process.getErrorStream());
} catch (final Exception e) {
logger.warn("Could not close a process error stream.", e);
} finally {
latch.countDown();
}
}, "ProcessCloser-error-" + sessionId).start();
new Thread(() -> {
try {
IOUtils.closeQuietly(process.getOutputStream());
} catch (final Exception e) {
logger.warn("Could not close a process output stream.", e);
} finally {
latch.countDown();
}
}, "ProcessCloser-output-" + sessionId).start();
try {
latch.await(10, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
logger.warn("Interrupted to wait a process.", e);
}
try {
process.destroyForcibly().waitFor(processDestroyTimeout, TimeUnit.SECONDS);
return process.exitValue();
} catch (final Exception e) {
logger.error("Could not destroy a process correctly.", e);
}
}
return -1;
}
public Set<String> getRunningSessionIdSet() {
return runningProcessMap.keySet();
}
public void setProcessDestroyTimeout(final int processDestroyTimeout) {
this.processDestroyTimeout = processDestroyTimeout;
}
}