/* * Copyright 2016-present Facebook, Inc. * * Licensed 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 com.facebook.buck.cli; import com.facebook.buck.log.Logger; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; /** * Daemon killer that kills the daemon when domain socket used to communicate with it has been * deleted. */ class SocketLossKiller { private static final Logger LOG = Logger.get(SocketLossKiller.class); private final ScheduledExecutorService scheduledExecutorService; private final Path absolutePathToSocket; private final Runnable killTask; private AtomicBoolean isArmed = new AtomicBoolean(); @GuardedBy("this") private @Nullable ScheduledFuture<?> checkSocketTask; public SocketLossKiller( ScheduledExecutorService scheduledExecutorService, Path absolutePathToSocket, Runnable killTask) { this.scheduledExecutorService = scheduledExecutorService; this.absolutePathToSocket = absolutePathToSocket; this.killTask = killTask; } /** * Start the service, recording the current socket file info. * * <p>If the socket does not exist or can't be accessed, the kill task will be called. * * <p>This is meant to be called after the server is started and socket created, such as in {@code * NailMain}. Repeated calls are no-ops. */ public synchronized void arm() { if (isArmed.getAndSet(true)) { // Already armed. return; } try { BasicFileAttributes socketFileAttributes = readFileAttributes(absolutePathToSocket); checkSocketTask = scheduledExecutorService.scheduleAtFixedRate( () -> checkSocket(socketFileAttributes), 0, 5000, TimeUnit.MILLISECONDS); } catch (IOException e) { LOG.error(e, "Failed to read socket when arming SocketLossKiller."); cancelAndKill(); return; } } private void checkSocket(BasicFileAttributes originalAttributes) { BasicFileAttributes newAttributes; try { newAttributes = readFileAttributes(absolutePathToSocket); } catch (NoSuchFileException | FileNotFoundException e) { // File no longer exists. cancelAndKill(); return; } catch (IOException e) { // Some other error occurred. LOG.error(e, "Failed to check socket."); cancelAndKill(); return; } synchronized (this) { // On platforms supporting file key, we make sure file keys are equal. // This may catch more cases than creation time if the socket is deleted and re-created // faster than can be distinguished by the timestamp resolution. // // Check modified time in addition to creation time because Windows lies about creation time // ("file system tunneling"). This feature is is still enabled in Windows 10. // See: https://support.microsoft.com/en-us/kb/172190 if (newAttributes.fileKey() != null && !newAttributes.fileKey().equals(originalAttributes.fileKey())) { cancelAndKill(); } else if (!newAttributes.creationTime().equals(originalAttributes.creationTime()) || !newAttributes.lastModifiedTime().equals(originalAttributes.lastModifiedTime())) { cancelAndKill(); } } } private synchronized void cancelAndKill() { if (checkSocketTask != null) { checkSocketTask.cancel(false); checkSocketTask = null; } killTask.run(); } private static BasicFileAttributes readFileAttributes(Path path) throws IOException { return Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes(); } }