/*
* Copyright 2013-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.event;
import com.facebook.buck.log.CommandThreadFactory;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.timing.Clock;
import com.facebook.buck.util.concurrent.MostExecutors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/** Thin wrapper around guava event bus. */
public class DefaultBuckEventBus implements com.facebook.buck.event.BuckEventBus {
private static final Logger LOG = Logger.get(BuckEventBus.class);
public static final int DEFAULT_SHUTDOWN_TIMEOUT_MS = 15000;
private static final Supplier<Long> DEFAULT_THREAD_ID_SUPPLIER =
() -> Thread.currentThread().getId();
private final Clock clock;
private final ExecutorService executorService;
private final EventBus eventBus;
private final Supplier<Long> threadIdSupplier;
private final BuildId buildId;
private final int shutdownTimeoutMillis;
public DefaultBuckEventBus(Clock clock, BuildId buildId) {
this(clock, true, buildId, DEFAULT_SHUTDOWN_TIMEOUT_MS);
}
@VisibleForTesting
public DefaultBuckEventBus(
Clock clock, boolean async, BuildId buildId, int shutdownTimeoutMillis) {
this.clock = clock;
this.executorService =
async
? MostExecutors.newSingleThreadExecutor(
new CommandThreadFactory(BuckEventBus.class.getSimpleName()))
: MoreExecutors.newDirectExecutorService();
this.eventBus = new EventBus("buck-build-events");
this.threadIdSupplier = DEFAULT_THREAD_ID_SUPPLIER;
this.buildId = buildId;
this.shutdownTimeoutMillis = shutdownTimeoutMillis;
}
private void dispatch(final BuckEvent event) {
executorService.submit(() -> eventBus.post(event));
}
@Override
public void post(BuckEvent event) {
timestamp(event);
dispatch(event);
}
/** Post event to the EventBus using the timestamp given by atTime. */
@Override
public void post(BuckEvent event, BuckEvent atTime) {
event.configure(
atTime.getTimestamp(),
atTime.getNanoTime(),
atTime.getThreadUserNanoTime(),
threadIdSupplier.get(),
buildId);
dispatch(event);
}
@Override
public void register(Object object) {
eventBus.register(object);
}
@Override
public void postWithoutConfiguring(BuckEvent event) {
Preconditions.checkState(event.isConfigured());
eventBus.post(event);
}
@VisibleForTesting
Clock getClock() {
return clock;
}
/**
* An id that every event posted to this event bus will share. For long-running processes, like
* the daemon, the build id makes it possible to distinguish when events come from different
* invocations of Buck.
*
* <p>In practice, this should be a short string, because it may be sent over the wire frequently.
*/
@Override
public BuildId getBuildId() {
return buildId;
}
/**
* {@link ExecutorService#awaitTermination(long, java.util.concurrent.TimeUnit)} is called to wait
* for events which have been posted, but which have been queued by the {@link EventBus}, to be
* delivered. This allows listeners to record or report as much information as possible. This aids
* debugging when close is called during exception processing.
*/
@Override
public void close() throws IOException {
executorService.shutdown();
try {
if (!executorService.awaitTermination(shutdownTimeoutMillis, TimeUnit.MILLISECONDS)) {
LOG.warn(
Joiner.on(System.lineSeparator())
.join(
"The BuckEventBus failed to shut down within the standard timeout.",
"Your build might have succeeded, but some messages were probably lost.",
"Here's some debugging information:",
executorService.toString()));
executorService.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Timestamp event. A timestamped event cannot subsequently being posted and is useful only to
* pass its timestamp on to another posted event.
*/
@Override
public void timestamp(BuckEvent event) {
Long threadId = threadIdSupplier.get();
event.configure(
clock.currentTimeMillis(),
clock.nanoTime(),
clock.threadUserNanoTime(threadId),
threadId,
buildId);
}
}