/* * Copyright 2015-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.io; import static com.facebook.buck.util.concurrent.MostExecutors.newSingleThreadExecutor; import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import com.facebook.buck.bser.BserDeserializer; import com.facebook.buck.bser.BserSerializer; import com.facebook.buck.log.Logger; import com.facebook.buck.timing.Clock; import com.facebook.buck.util.Console; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; class WatchmanTransportClient implements WatchmanClient, AutoCloseable { private static final Logger LOG = Logger.get(WatchmanTransportClient.class); private static final long POLL_TIME_NANOS = TimeUnit.SECONDS.toNanos(1); private final ListeningExecutorService listeningExecutorService; private final Clock clock; private final Transport transport; private final Console console; private final BserSerializer bserSerializer; private final BserDeserializer bserDeserializer; public WatchmanTransportClient(Console console, Clock clock, Transport transport) { this.listeningExecutorService = listeningDecorator(newSingleThreadExecutor("Watchman")); this.console = console; this.clock = clock; this.transport = transport; this.bserSerializer = new BserSerializer(); this.bserDeserializer = new BserDeserializer(BserDeserializer.KeyOrdering.UNSORTED); } @Override public Optional<Map<String, Object>> queryWithTimeout(long timeoutNanos, Object... query) throws IOException, InterruptedException { return queryListWithTimeout(timeoutNanos, ImmutableList.copyOf(query)); } private Optional<Map<String, Object>> queryListWithTimeout( long timeoutNanos, final List<Object> query) throws IOException, InterruptedException { ListenableFuture<Optional<Map<String, Object>>> future = listeningExecutorService.submit(() -> sendWatchmanQuery(query)); try { long startTimeNanos = clock.nanoTime(); Optional<Map<String, Object>> result = waitForQueryNotifyingUserIfSlow(future, timeoutNanos, POLL_TIME_NANOS, query); long elapsedNanos = clock.nanoTime() - startTimeNanos; LOG.debug("Query %s returned in %d ms", query, TimeUnit.NANOSECONDS.toMillis(elapsedNanos)); return result; } catch (ExecutionException e) { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } else { throw new RuntimeException(e); } } } @Override public void close() throws IOException { LOG.debug("Closing Watchman transport."); transport.close(); listeningExecutorService.shutdown(); } private Optional<Map<String, Object>> waitForQueryNotifyingUserIfSlow( ListenableFuture<Optional<Map<String, Object>>> future, long timeoutNanos, long pollTimeNanos, List<Object> query) throws InterruptedException, ExecutionException { long queryStartNanos = clock.nanoTime(); try { return future.get(Math.min(timeoutNanos, pollTimeNanos), TimeUnit.NANOSECONDS); } catch (TimeoutException e) { long remainingNanos = timeoutNanos - (clock.nanoTime() - queryStartNanos); if (remainingNanos > 0) { console.getStdErr().getRawStream().format("Waiting for Watchman query [%s]...\n", query); try { return future.get(remainingNanos, TimeUnit.NANOSECONDS); } catch (TimeoutException te) { LOG.debug("Timed out"); } } LOG.warn( "Watchman did not respond within %d ms, disabling.", TimeUnit.NANOSECONDS.toMillis(timeoutNanos)); console .getStdErr() .getRawStream() .format( "Timed out after %d ms waiting for Watchman command [%s]. Disabling Watchman.\n", TimeUnit.NANOSECONDS.toMillis(timeoutNanos), query); return Optional.empty(); } } @SuppressWarnings("unchecked") private Optional<Map<String, Object>> sendWatchmanQuery(List<Object> query) throws IOException { LOG.debug("Sending query: %s", query); bserSerializer.serializeToStream(query, transport.getOutputStream()); Object response = bserDeserializer.deserializeBserValue(transport.getInputStream()); LOG.verbose("Got response: %s", response); Map<String, Object> responseMap = (Map<String, Object>) response; if (responseMap == null) { LOG.error("Unrecognized Watchman response: %s", response); return Optional.empty(); } return Optional.of(responseMap); } }