1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.exec;
19
20 import java.time.Duration;
21 import java.util.Vector;
22 import java.util.concurrent.ThreadFactory;
23 import java.util.function.Supplier;
24
25 /**
26 * Generalization of {@code ExecuteWatchdog}.
27 *
28 * @see org.apache.commons.exec.ExecuteWatchdog
29 */
30 public class Watchdog implements Runnable {
31
32 /**
33 * Builds ExecuteWatchdog instances.
34 *
35 * @since 1.4.0
36 */
37 public static final class Builder implements Supplier<Watchdog> {
38
39 private ThreadFactory threadFactory;
40 private Duration timeout;
41
42 /**
43 * Constructs a new instance.
44 */
45 public Builder() {
46 // empty
47 }
48
49 /**
50 * Creates a new configured ExecuteWatchdog.
51 *
52 * @return a new configured ExecuteWatchdog.
53 */
54 @Override
55 public Watchdog get() {
56 return new Watchdog(threadFactory, timeout);
57 }
58
59 /**
60 * Sets the thread factory.
61 *
62 * @param threadFactory the thread factory.
63 * @return {@code this} instance.
64 */
65 public Builder setThreadFactory(final ThreadFactory threadFactory) {
66 this.threadFactory = threadFactory;
67 return this;
68 }
69
70 /**
71 * Sets the timeout duration.
72 *
73 * @param timeout the timeout duration.
74 * @return {@code this} instance.
75 */
76 public Builder setTimeout(final Duration timeout) {
77 this.timeout = timeout;
78 return this;
79 }
80
81 }
82
83 /**
84 * Creates a new builder.
85 *
86 * @return a new builder.
87 * @since 1.4.0
88 */
89 public static Builder builder() {
90 return new Builder();
91 }
92
93 private final Vector<TimeoutObserver> observers = new Vector<>(1);
94
95 private final long timeoutMillis;
96
97 private boolean stopped;
98
99 /**
100 * The thread factory.
101 */
102 private final ThreadFactory threadFactory;
103
104 /**
105 * Constructs a new instance.
106 *
107 * @param timeoutMillis the timeout duration.
108 * @deprecated Use {@link Builder#get()}.
109 */
110 @Deprecated
111 public Watchdog(final long timeoutMillis) {
112 this(null, Duration.ofMillis(timeoutMillis));
113 }
114
115 /**
116 * Constructs a new instance.
117 *
118 * @param threadFactory the thread factory.
119 * @param timeout the timeout duration.
120 */
121 private Watchdog(final ThreadFactory threadFactory, final Duration timeout) {
122 if (timeout.isNegative() || Duration.ZERO.equals(timeout)) {
123 throw new IllegalArgumentException("timeout must not be less than 1.");
124 }
125 this.timeoutMillis = timeout.toMillis();
126 this.threadFactory = threadFactory;
127 }
128
129 /**
130 * Adds a TimeoutObserver.
131 *
132 * @param to a TimeoutObserver to add.
133 */
134 public void addTimeoutObserver(final TimeoutObserver to) {
135 observers.addElement(to);
136 }
137
138 /**
139 * Fires a timeout occurred event for each observer.
140 */
141 protected final void fireTimeoutOccured() {
142 observers.forEach(o -> o.timeoutOccured(this));
143 }
144
145 /**
146 * Removes a TimeoutObserver.
147 *
148 * @param to a TimeoutObserver to remove.
149 */
150 public void removeTimeoutObserver(final TimeoutObserver to) {
151 observers.removeElement(to);
152 }
153
154 @Override
155 public void run() {
156 final long startTimeMillis = System.currentTimeMillis();
157 boolean isWaiting;
158 synchronized (this) {
159 long timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
160 isWaiting = timeLeftMillis > 0;
161 while (!stopped && isWaiting) {
162 try {
163 wait(timeLeftMillis);
164 } catch (final InterruptedException ignore) {
165 // ignore
166 }
167 timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
168 isWaiting = timeLeftMillis > 0;
169 }
170 }
171
172 // notify the listeners outside of the synchronized block (see EXEC-60)
173 if (!isWaiting) {
174 fireTimeoutOccured();
175 }
176 }
177
178 /**
179 * Starts a new thread.
180 */
181 public synchronized void start() {
182 stopped = false;
183 ThreadUtil.newThread(threadFactory, this, "CommonsExecWatchdog-", true).start();
184 }
185
186 /**
187 * Requests a thread stop.
188 */
189 public synchronized void stop() {
190 stopped = true;
191 notifyAll();
192 }
193
194 }