| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. You may obtain a |
| * copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.geode.internal.process; |
| |
| import static org.apache.commons.lang3.Validate.notEmpty; |
| import static org.apache.commons.lang3.Validate.notNull; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.logging.internal.executors.LoggingThread; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| |
| /** |
| * Invokes a ControlRequestHandler when a control file has been created. |
| * |
| * @since GemFire 8.0 |
| */ |
| class ControlFileWatchdog implements Runnable { |
| private static final Logger logger = LogService.getLogger(); |
| |
| private static final long STOP_TIMEOUT_MILLIS = 60 * 1000; |
| private static final long LOOP_INTERVAL_MILLIS = 1000; |
| |
| private final File directory; |
| private final File file; |
| private final ControlRequestHandler requestHandler; |
| private final boolean stopAfterRequest; |
| |
| private Thread thread; |
| private boolean alive; |
| |
| ControlFileWatchdog(final File directory, final String fileName, |
| final ControlRequestHandler requestHandler, final boolean stopAfterRequest) { |
| notNull(directory, "Invalid directory '" + directory + "' specified"); |
| notEmpty(fileName, "Invalid fileName '" + fileName + "' specified"); |
| notNull(requestHandler, "Invalid requestHandler '" + requestHandler + "' specified"); |
| |
| this.directory = directory; |
| this.file = new File(directory, fileName); |
| this.requestHandler = requestHandler; |
| this.stopAfterRequest = stopAfterRequest; |
| } |
| |
| @Override |
| public void run() { |
| try { // always set alive before stopping |
| while (isAlive()) { |
| doWork(); |
| } |
| } finally { |
| synchronized (this) { |
| alive = false; |
| } |
| } |
| } |
| |
| private void doWork() { |
| try { // handle handle exceptions |
| if (file.exists()) { |
| try { // always check stopAfterRequest after handleRequest |
| handleRequest(); |
| } finally { |
| if (stopAfterRequest) { |
| stopMe(); |
| } |
| } |
| } |
| Thread.sleep(LOOP_INTERVAL_MILLIS); |
| } catch (InterruptedException ignored) { |
| Thread.currentThread().interrupt(); |
| // allow to loop around and check isAlive() |
| } catch (IOException ignored) { |
| logger.error( |
| "Unable to control process with {}. Please add tools.jar from JDK to classpath for improved process control.", |
| file); |
| // allow to loop around and check isAlive() |
| } |
| } |
| |
| private void handleRequest() throws IOException { |
| try { // always delete file after invoking handler |
| requestHandler.handleRequest(); |
| } finally { |
| try { |
| file.delete(); |
| } catch (SecurityException e) { |
| logger.warn("Unable to delete {}", file, e); |
| } |
| } |
| } |
| |
| void start() { |
| synchronized (this) { |
| if (thread == null) { |
| thread = new LoggingThread(createThreadName(), this); |
| alive = true; |
| deleteExistingStatusResponseFile(); |
| thread.start(); |
| } |
| } |
| } |
| |
| void stop() throws InterruptedException { |
| Thread stopping = null; |
| synchronized (this) { |
| if (thread != null) { |
| alive = false; |
| if (thread != Thread.currentThread()) { |
| thread.interrupt(); |
| stopping = thread; |
| } |
| thread = null; |
| } |
| } |
| if (stopping != null) { |
| stopping.join(STOP_TIMEOUT_MILLIS); |
| } |
| } |
| |
| boolean isAlive() { |
| synchronized (this) { |
| return alive; |
| } |
| } |
| |
| private void stopMe() { |
| synchronized (this) { |
| if (thread != null) { |
| alive = false; |
| thread = null; |
| } |
| } |
| } |
| |
| private void deleteExistingStatusResponseFile() { |
| try { |
| Files.deleteIfExists(file.toPath()); |
| } catch (IOException e) { |
| logger.warn("Unable to delete file", e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(getClass().getSimpleName()); |
| sb.append('@').append(System.identityHashCode(this)).append('{'); |
| sb.append("directory=").append(directory); |
| sb.append(", file=").append(file); |
| sb.append(", alive=").append(alive); // not synchronized |
| sb.append(", stopAfterRequest=").append(stopAfterRequest); |
| return sb.append('}').toString(); |
| } |
| |
| private String createThreadName() { |
| return getClass().getSimpleName() + '@' + Integer.toHexString(hashCode()) + " monitoring " |
| + file.getName(); |
| } |
| |
| /** |
| * Defines the callback to be invoked when the control file exists. |
| */ |
| interface ControlRequestHandler { |
| void handleRequest() throws IOException; |
| } |
| } |