001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.commons.exec;
019
020import java.time.Duration;
021import java.util.Vector;
022import java.util.concurrent.ThreadFactory;
023import java.util.function.Supplier;
024
025/**
026 * Generalization of {@code ExecuteWatchdog}.
027 *
028 * @see org.apache.commons.exec.ExecuteWatchdog
029 */
030public class Watchdog implements Runnable {
031
032    /**
033     * Builds ExecuteWatchdog instances.
034     *
035     * @since 1.4.0
036     */
037    public static final class Builder implements Supplier<Watchdog> {
038
039        private ThreadFactory threadFactory;
040        private Duration timeout;
041
042        /**
043         * Creates a new configured ExecuteWatchdog.
044         *
045         * @return a new configured ExecuteWatchdog.
046         */
047        @Override
048        public Watchdog get() {
049            return new Watchdog(threadFactory, timeout);
050        }
051
052        /**
053         * Sets the thread factory.
054         *
055         * @param threadFactory the thread factory.
056         * @return this.
057         */
058        public Builder setThreadFactory(final ThreadFactory threadFactory) {
059            this.threadFactory = threadFactory;
060            return this;
061        }
062
063        /**
064         * Sets the timeout duration.
065         *
066         * @param timeout the timeout duration.
067         * @return this.
068         */
069        public Builder setTimeout(final Duration timeout) {
070            this.timeout = timeout;
071            return this;
072        }
073
074    }
075
076    /**
077     * Creates a new builder.
078     *
079     * @return a new builder.
080     * @since 1.4.0
081     */
082    public static Builder builder() {
083        return new Builder();
084    }
085
086    private final Vector<TimeoutObserver> observers = new Vector<>(1);
087
088    private final long timeoutMillis;
089
090    private boolean stopped;
091
092    /**
093     * The thread factory.
094     */
095    private final ThreadFactory threadFactory;
096
097    /**
098     * Constructs a new instance.
099     *
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}