/*
* 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 gobblin.util.request_allocation;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* A {@link RequestAllocator} optimized for {@link HierarchicalPrioritizer}s. Processes {@link Requestor}s in order of
* priority. Once the {@link ResourcePool} is full, lower priority {@link Requestor}s will not even be materialized.
*/
@RequiredArgsConstructor
@Slf4j
public class HierarchicalAllocator<T extends Request<T>> implements RequestAllocator<T> {
public static class Factory implements RequestAllocator.Factory {
@Override
public <T extends Request<T>> RequestAllocator<T> createRequestAllocator(RequestAllocatorConfig<T> cofiguration) {
Preconditions.checkArgument(cofiguration.getPrioritizer() instanceof HierarchicalPrioritizer,
"Prioritizer must be a " + HierarchicalPrioritizer.class.getSimpleName());
RequestAllocator<T> underlying = RequestAllocatorUtils.inferFromConfig(cofiguration);
return new HierarchicalAllocator<>((HierarchicalPrioritizer<T>) cofiguration.getPrioritizer(), underlying);
}
}
private final HierarchicalPrioritizer<T> prioritizer;
private final RequestAllocator<T> underlying;
@Override
public AllocatedRequestsIterator<T> allocateRequests(Iterator<? extends Requestor<T>> requestors, ResourcePool resourcePool) {
List<Requestor<T>> requestorList = Lists.newArrayList(requestors);
Comparator<Requestor<T>> requestorComparator = new Comparator<Requestor<T>>() {
@Override
public int compare(Requestor<T> o1, Requestor<T> o2) {
return prioritizer.compareRequestors(o1, o2);
}
};
Collections.sort(requestorList, requestorComparator);
return new HierarchicalIterator(resourcePool, new SingleTierIterator(requestorComparator, requestorList));
}
/**
* Automatically handles allocation for each tier of {@link Requestor}s, computation of correct {@link #totalResourcesUsed()},
* and not materializing next tier once {@link ResourcePool} is full.
*/
private class HierarchicalIterator implements AllocatedRequestsIterator<T> {
private SingleTierIterator singleTierIterator;
private AllocatedRequestsIterator<T> currentIterator;
private ResourcePool resourcePool;
private final ResourceRequirement currentRequirement;
public HierarchicalIterator(ResourcePool resourcePool, SingleTierIterator singleTierIterator) {
this.singleTierIterator = singleTierIterator;
this.resourcePool = resourcePool;
this.currentRequirement = resourcePool.getResourceRequirementBuilder().zero().build();
}
@Override
public boolean hasNext() {
while (this.currentIterator == null || !this.currentIterator.hasNext()) {
if (this.currentIterator != null) {
this.currentRequirement.add(this.currentIterator.totalResourcesUsed());
}
if (this.resourcePool.exceedsSoftBound(this.currentRequirement, true)) {
return false;
}
Optional<SingleTierIterator> tmp = this.singleTierIterator.nextTier();
if (!tmp.isPresent()) {
return false;
}
this.singleTierIterator = tmp.get();
ResourcePool contractedPool = this.resourcePool.contractPool(this.currentRequirement);
this.currentIterator = HierarchicalAllocator.this.underlying.allocateRequests(this.singleTierIterator, contractedPool);
}
return true;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return this.currentIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public ResourceRequirement totalResourcesUsed() {
return ResourceRequirement.add(this.currentRequirement, this.currentIterator.totalResourcesUsed(), null);
}
}
/**
* An {@link Iterator} that only returns entries of the same priority as the first entry it returns. Assumes the input
* {@link List} is sorted.
*/
private class SingleTierIterator implements Iterator<Requestor<T>> {
private final Comparator<Requestor<T>> prioritizer;
private final List<Requestor<T>> requestors;
private final Requestor<T> referenceRequestor;
private int nextRequestorIdx;
public SingleTierIterator(Comparator<Requestor<T>> prioritizer, List<Requestor<T>> requestors) {
this(prioritizer, requestors, 0);
}
private SingleTierIterator(Comparator<Requestor<T>> prioritizer, List<Requestor<T>> requestors, int initialIndex) {
this.prioritizer = prioritizer;
this.requestors = requestors;
if (this.requestors.size() > initialIndex) {
this.referenceRequestor = requestors.get(initialIndex);
} else {
this.referenceRequestor = null;
}
this.nextRequestorIdx = initialIndex;
log.debug("Starting a single tier iterator with reference requestor: {}", this.referenceRequestor);
}
@Override
public boolean hasNext() {
return this.requestors.size() > nextRequestorIdx &&
this.prioritizer.compare(this.referenceRequestor, this.requestors.get(this.nextRequestorIdx)) == 0;
}
@Override
public Requestor<T> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return this.requestors.get(this.nextRequestorIdx++);
}
/**
* @return a {@link SingleTierIterator} for the next tier in the input {@link List}.
*/
Optional<SingleTierIterator> nextTier() {
if (this.nextRequestorIdx < this.requestors.size()) {
return Optional.of(new SingleTierIterator(this.prioritizer, this.requestors, this.nextRequestorIdx));
}
return Optional.absent();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}