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    *      http://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           * Creates a new configured ExecuteWatchdog.
44           *
45           * @return a new configured ExecuteWatchdog.
46           */
47          @Override
48          public Watchdog get() {
49              return new Watchdog(threadFactory, timeout);
50          }
51  
52          /**
53           * Sets the thread factory.
54           *
55           * @param threadFactory the thread factory.
56           * @return this.
57           */
58          public Builder setThreadFactory(final ThreadFactory threadFactory) {
59              this.threadFactory = threadFactory;
60              return this;
61          }
62  
63          /**
64           * Sets the timeout duration.
65           *
66           * @param timeout the timeout duration.
67           * @return this.
68           */
69          public Builder setTimeout(final Duration timeout) {
70              this.timeout = timeout;
71              return this;
72          }
73  
74      }
75  
76      /**
77       * Creates a new builder.
78       *
79       * @return a new builder.
80       * @since 1.4.0
81       */
82      public static Builder builder() {
83          return new Builder();
84      }
85  
86      private final Vector<TimeoutObserver> observers = new Vector<>(1);
87  
88      private final long timeoutMillis;
89  
90      private boolean stopped;
91  
92      /**
93       * The thread factory.
94       */
95      private final ThreadFactory threadFactory;
96  
97      /**
98       * Constructs a new instance.
99       *
100      * @param timeoutMillis the timeout duration.
101      * @deprecated Use {@link Builder#get()}.
102      */
103     @Deprecated
104     public Watchdog(final long timeoutMillis) {
105         this(null, Duration.ofMillis(timeoutMillis));
106     }
107 
108     /**
109      * Constructs a new instance.
110      *
111      * @param threadFactory the thread factory.
112      * @param timeout       the timeout duration.
113      */
114     private Watchdog(final ThreadFactory threadFactory, final Duration timeout) {
115         if (timeout.isNegative() || Duration.ZERO.equals(timeout)) {
116             throw new IllegalArgumentException("timeout must not be less than 1.");
117         }
118         this.timeoutMillis = timeout.toMillis();
119         this.threadFactory = threadFactory;
120     }
121 
122     /**
123      * Adds a TimeoutObserver.
124      *
125      * @param to a TimeoutObserver to add.
126      */
127     public void addTimeoutObserver(final TimeoutObserver to) {
128         observers.addElement(to);
129     }
130 
131     /**
132      * Fires a timeout occurred event for each observer.
133      */
134     protected final void fireTimeoutOccured() {
135         observers.forEach(o -> o.timeoutOccured(this));
136     }
137 
138     /**
139      * Removes a TimeoutObserver.
140      *
141      * @param to a TimeoutObserver to remove.
142      */
143     public void removeTimeoutObserver(final TimeoutObserver to) {
144         observers.removeElement(to);
145     }
146 
147     @Override
148     public void run() {
149         final long startTimeMillis = System.currentTimeMillis();
150         boolean isWaiting;
151         synchronized (this) {
152             long timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
153             isWaiting = timeLeftMillis > 0;
154             while (!stopped && isWaiting) {
155                 try {
156                     wait(timeLeftMillis);
157                 } catch (final InterruptedException ignore) {
158                     // ignore
159                 }
160                 timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
161                 isWaiting = timeLeftMillis > 0;
162             }
163         }
164 
165         // notify the listeners outside of the synchronized block (see EXEC-60)
166         if (!isWaiting) {
167             fireTimeoutOccured();
168         }
169     }
170 
171     /**
172      * Starts a new thread.
173      */
174     public synchronized void start() {
175         stopped = false;
176         ThreadUtil.newThread(threadFactory, this, "CommonsExecWatchdog-", true).start();
177     }
178 
179     /**
180      * Requests a thread stop.
181      */
182     public synchronized void stop() {
183         stopped = true;
184         notifyAll();
185     }
186 
187 }