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