1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.helper;
17
18 import java.io.IOException;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.CountDownLatch;
23 import java.util.concurrent.TimeUnit;
24 import java.util.function.Consumer;
25
26 import javax.annotation.PreDestroy;
27
28 import org.apache.commons.io.IOUtils;
29 import org.codelibs.fess.exception.FessSystemException;
30 import org.codelibs.fess.util.InputStreamThread;
31 import org.codelibs.fess.util.JobProcess;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 public class ProcessHelper {
36 private static final Logger logger = LoggerFactory.getLogger(ProcessHelper.class);
37
38 private final ConcurrentHashMap<String, JobProcess> runningProcessMap = new ConcurrentHashMap<>();
39
40 private int processDestroyTimeout = 10;
41
42 @PreDestroy
43 public void destroy() {
44 for (final String sessionId : runningProcessMap.keySet()) {
45 if (logger.isInfoEnabled()) {
46 logger.info("Stopping process " + sessionId);
47 }
48 if (destroyProcess(sessionId) == 0) {
49 if (logger.isInfoEnabled()) {
50 logger.info("Stopped process " + sessionId);
51 }
52 }
53 }
54 }
55
56 public synchronized JobProcess startProcess(final String sessionId, final List<String> cmdList, final Consumer<ProcessBuilder> pbCall) {
57 final ProcessBuilder pb = new ProcessBuilder(cmdList);
58 pbCall.accept(pb);
59 destroyProcess(sessionId);
60 JobProcess jobProcess;
61 try {
62 jobProcess = new JobProcess(pb.start());
63 destroyProcess(sessionId, runningProcessMap.putIfAbsent(sessionId, jobProcess));
64 return jobProcess;
65 } catch (final IOException e) {
66 throw new FessSystemException("Crawler Process terminated.", e);
67 }
68 }
69
70 public int destroyProcess(final String sessionId) {
71 final JobProcess jobProcess = runningProcessMap.remove(sessionId);
72 return destroyProcess(sessionId, jobProcess);
73 }
74
75 public boolean isProcessRunning() {
76 return !runningProcessMap.isEmpty();
77 }
78
79 protected int destroyProcess(final String sessionId, final JobProcess jobProcess) {
80 if (jobProcess != null) {
81 final InputStreamThread ist = jobProcess.getInputStreamThread();
82 try {
83 ist.interrupt();
84 } catch (final Exception e) {
85 logger.warn("Could not interrupt a thread of an input stream.", e);
86 }
87
88 final CountDownLatch latch = new CountDownLatch(3);
89 final Process process = jobProcess.getProcess();
90 new Thread(() -> {
91 try {
92 IOUtils.closeQuietly(process.getInputStream());
93 } catch (final Exception e) {
94 logger.warn("Could not close a process input stream.", e);
95 } finally {
96 latch.countDown();
97 }
98 }, "ProcessCloser-input-" + sessionId).start();
99 new Thread(() -> {
100 try {
101 IOUtils.closeQuietly(process.getErrorStream());
102 } catch (final Exception e) {
103 logger.warn("Could not close a process error stream.", e);
104 } finally {
105 latch.countDown();
106 }
107 }, "ProcessCloser-error-" + sessionId).start();
108 new Thread(() -> {
109 try {
110 IOUtils.closeQuietly(process.getOutputStream());
111 } catch (final Exception e) {
112 logger.warn("Could not close a process output stream.", e);
113 } finally {
114 latch.countDown();
115 }
116 }, "ProcessCloser-output-" + sessionId).start();
117
118 try {
119 latch.await(10, TimeUnit.SECONDS);
120 } catch (final InterruptedException e) {
121 logger.warn("Interrupted to wait a process.", e);
122 }
123 try {
124 process.destroyForcibly().waitFor(processDestroyTimeout, TimeUnit.SECONDS);
125 return process.exitValue();
126 } catch (final Exception e) {
127 logger.error("Could not destroy a process correctly.", e);
128 }
129 }
130 return -1;
131 }
132
133 public Set<String> getRunningSessionIdSet() {
134 return runningProcessMap.keySet();
135 }
136
137 public void setProcessDestroyTimeout(final int processDestroyTimeout) {
138 this.processDestroyTimeout = processDestroyTimeout;
139 }
140
141 }