/* * 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.event.listener; import com.facebook.buck.event.BuckEvent; import com.google.common.base.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** * Orders {@link BuckEvent}s by the {@link BuckEvent#getNanoTime()} value. This is used to serialize * events coming in from multiple threads. */ public class BuckEventOrderer<T extends BuckEvent> implements AutoCloseable { private final long maximumSkewNanos; private final Consumer<T> eventSinkFunction; private final Map<Long, Deque<T>> perThreadEventQueue; private final PriorityQueue<Deque<T>> oldestEventQueue; private long maximumNanoTime; /** * @param eventSinkFunction events for which we've determined ordering are dispatched here. * @param maximumSkew this is how many events */ public BuckEventOrderer( Consumer<T> eventSinkFunction, long maximumSkew, TimeUnit maximumSkewUnit) { this.maximumSkewNanos = maximumSkewUnit.toNanos(maximumSkew); this.eventSinkFunction = eventSinkFunction; this.perThreadEventQueue = new HashMap<>(); this.oldestEventQueue = new PriorityQueue<>(1, new FirstEventTimestampOrdering()); this.maximumNanoTime = 0L; } public void add(T buckEvent) { Deque<T> queue = getQueueForEvent(buckEvent); boolean shouldAddToEventQueue = queue.isEmpty(); if (!queue.isEmpty() && queue.getLast().getNanoTime() > buckEvent.getNanoTime()) { // We assume inserting events configured at a time in the past is very rare. oldestEventQueue.remove(queue); List<T> mergedEventsList = new ArrayList<>(queue.size() + 1); while (!queue.isEmpty() && queue.getFirst().getNanoTime() <= buckEvent.getNanoTime()) { mergedEventsList.add(queue.removeFirst()); } mergedEventsList.add(buckEvent); mergedEventsList.addAll(queue); queue.clear(); queue.addAll(mergedEventsList); oldestEventQueue.add(queue); } else { queue.add(buckEvent); } if (shouldAddToEventQueue) { oldestEventQueue.add(queue); } if (maximumNanoTime < buckEvent.getNanoTime()) { maximumNanoTime = buckEvent.getNanoTime(); } dispatchEventsOlderThan(maximumNanoTime - maximumSkewNanos); } private void dispatchEventsOlderThan(long upperTimeBound) { while (!oldestEventQueue.isEmpty() && oldestEventQueue.peek().getFirst().getNanoTime() <= upperTimeBound) { Deque<T> queueWithOldestEvent = oldestEventQueue.remove(); long upperTimeBoundForThisQueue = upperTimeBound; if (!oldestEventQueue.isEmpty()) { Deque<T> nextOldestQueue = oldestEventQueue.peek(); Preconditions.checkState( queueWithOldestEvent.getFirst().getNanoTime() <= nextOldestQueue.getFirst().getNanoTime()); if (nextOldestQueue.getFirst().getNanoTime() < upperTimeBoundForThisQueue) { upperTimeBoundForThisQueue = nextOldestQueue.getFirst().getNanoTime(); } } while (!queueWithOldestEvent.isEmpty() && queueWithOldestEvent.getFirst().getNanoTime() <= upperTimeBoundForThisQueue) { eventSinkFunction.accept(queueWithOldestEvent.removeFirst()); } if (!queueWithOldestEvent.isEmpty()) { oldestEventQueue.add(queueWithOldestEvent); } } } @Override public void close() { dispatchEventsOlderThan(maximumNanoTime); } private Deque<T> getQueueForEvent(T buckEvent) { Deque<T> queue = perThreadEventQueue.get(buckEvent.getThreadId()); if (queue == null) { queue = new ArrayDeque<>(); perThreadEventQueue.put(buckEvent.getThreadId(), queue); } return queue; } private class FirstEventTimestampOrdering implements Comparator<Deque<T>> { @Override public int compare(Deque<T> o1, Deque<T> o2) { if (o1 == o2) { return 0; } T o1First = o1.getFirst(); T o2First = o2.getFirst(); int result = Long.compare(o1First.getNanoTime(), o2First.getNanoTime()); if (result == 0) { result = Long.compare(o1First.getEventKey().getValue(), o2First.getEventKey().getValue()); } return result; } } }