Watchdog.java

  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. package org.apache.commons.exec;

  18. import java.time.Duration;
  19. import java.util.Vector;
  20. import java.util.concurrent.ThreadFactory;
  21. import java.util.function.Supplier;

  22. /**
  23.  * Generalization of {@code ExecuteWatchdog}.
  24.  *
  25.  * @see org.apache.commons.exec.ExecuteWatchdog
  26.  */
  27. public class Watchdog implements Runnable {

  28.     /**
  29.      * Builds ExecuteWatchdog instances.
  30.      *
  31.      * @since 1.4.0
  32.      */
  33.     public static final class Builder implements Supplier<Watchdog> {

  34.         private ThreadFactory threadFactory;
  35.         private Duration timeout;

  36.         /**
  37.          * Constructs a new instance.
  38.          */
  39.         public Builder() {
  40.             // empty
  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.          * Sets the thread factory.
  53.          *
  54.          * @param threadFactory the thread factory.
  55.          * @return {@code this} instance.
  56.          */
  57.         public Builder setThreadFactory(final ThreadFactory threadFactory) {
  58.             this.threadFactory = threadFactory;
  59.             return this;
  60.         }

  61.         /**
  62.          * Sets the timeout duration.
  63.          *
  64.          * @param timeout the timeout duration.
  65.          * @return {@code this} instance.
  66.          */
  67.         public Builder setTimeout(final Duration timeout) {
  68.             this.timeout = timeout;
  69.             return this;
  70.         }

  71.     }

  72.     /**
  73.      * Creates a new builder.
  74.      *
  75.      * @return a new builder.
  76.      * @since 1.4.0
  77.      */
  78.     public static Builder builder() {
  79.         return new Builder();
  80.     }

  81.     private final Vector<TimeoutObserver> observers = new Vector<>(1);

  82.     private final long timeoutMillis;

  83.     private boolean stopped;

  84.     /**
  85.      * The thread factory.
  86.      */
  87.     private final ThreadFactory threadFactory;

  88.     /**
  89.      * Constructs a new instance.
  90.      *
  91.      * @param timeoutMillis the timeout duration.
  92.      * @deprecated Use {@link Builder#get()}.
  93.      */
  94.     @Deprecated
  95.     public Watchdog(final long timeoutMillis) {
  96.         this(null, Duration.ofMillis(timeoutMillis));
  97.     }

  98.     /**
  99.      * Constructs a new instance.
  100.      *
  101.      * @param threadFactory the thread factory.
  102.      * @param timeout       the timeout duration.
  103.      */
  104.     private Watchdog(final ThreadFactory threadFactory, final Duration timeout) {
  105.         if (timeout.isNegative() || Duration.ZERO.equals(timeout)) {
  106.             throw new IllegalArgumentException("timeout must not be less than 1.");
  107.         }
  108.         this.timeoutMillis = timeout.toMillis();
  109.         this.threadFactory = threadFactory;
  110.     }

  111.     /**
  112.      * Adds a TimeoutObserver.
  113.      *
  114.      * @param to a TimeoutObserver to add.
  115.      */
  116.     public void addTimeoutObserver(final TimeoutObserver to) {
  117.         observers.addElement(to);
  118.     }

  119.     /**
  120.      * Fires a timeout occurred event for each observer.
  121.      */
  122.     protected final void fireTimeoutOccured() {
  123.         observers.forEach(o -> o.timeoutOccured(this));
  124.     }

  125.     /**
  126.      * Removes a TimeoutObserver.
  127.      *
  128.      * @param to a TimeoutObserver to remove.
  129.      */
  130.     public void removeTimeoutObserver(final TimeoutObserver to) {
  131.         observers.removeElement(to);
  132.     }

  133.     @Override
  134.     public void run() {
  135.         final long startTimeMillis = System.currentTimeMillis();
  136.         boolean isWaiting;
  137.         synchronized (this) {
  138.             long timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
  139.             isWaiting = timeLeftMillis > 0;
  140.             while (!stopped && isWaiting) {
  141.                 try {
  142.                     wait(timeLeftMillis);
  143.                 } catch (final InterruptedException ignore) {
  144.                     // ignore
  145.                 }
  146.                 timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
  147.                 isWaiting = timeLeftMillis > 0;
  148.             }
  149.         }

  150.         // notify the listeners outside of the synchronized block (see EXEC-60)
  151.         if (!isWaiting) {
  152.             fireTimeoutOccured();
  153.         }
  154.     }

  155.     /**
  156.      * Starts a new thread.
  157.      */
  158.     public synchronized void start() {
  159.         stopped = false;
  160.         ThreadUtil.newThread(threadFactory, this, "CommonsExecWatchdog-", true).start();
  161.     }

  162.     /**
  163.      * Requests a thread stop.
  164.      */
  165.     public synchronized void stop() {
  166.         stopped = true;
  167.         notifyAll();
  168.     }

  169. }