View Javadoc
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 }