/** * 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 org.apache.drill.exec.work.batch; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.drill.common.AutoCloseables; import org.apache.drill.common.concurrent.AutoCloseableLock; import org.apache.drill.exec.exception.FragmentSetupException; import org.apache.drill.exec.ops.FragmentContext; import org.apache.drill.exec.proto.BitControl.Collector; import org.apache.drill.exec.proto.BitControl.PlanFragment; import org.apache.drill.exec.record.RawFragmentBatch; import org.apache.drill.exec.rpc.data.IncomingDataBatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; /** * Determines when a particular fragment has enough data for each of its receiving exchanges to commence execution. Also monitors whether we've collected all incoming data. */ public class IncomingBuffers implements AutoCloseable { static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(IncomingBuffers.class); private volatile boolean closed = false; private final AtomicInteger streamsRemaining = new AtomicInteger(0); private final AtomicInteger remainingRequired; private final Map<Integer, DataCollector> collectorMap; private final FragmentContext context; /** * Lock used to manage close and data acceptance. We should only create a local reference to incoming data in the case * that the incoming buffers are !closed. As such, we need to make sure that we aren't in the process of closing the * incoming buffers when data is arriving. The read lock can be shared by many incoming batches but the write lock * must be exclusive to the close method. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final AutoCloseableLock sharedIncomingBatchLock = new AutoCloseableLock(lock.readLock()); private final AutoCloseableLock exclusiveCloseLock = new AutoCloseableLock(lock.writeLock()); public IncomingBuffers(PlanFragment fragment, FragmentContext context) { this.context = context; Map<Integer, DataCollector> collectors = Maps.newHashMap(); remainingRequired = new AtomicInteger(fragment.getCollectorCount()); for(int i =0; i < fragment.getCollectorCount(); i++){ Collector collector = fragment.getCollector(i); DataCollector newCollector = collector.getSupportsOutOfOrder() ? new MergingCollector(remainingRequired, collector, context) : new PartitionedCollector(remainingRequired, collector, context); collectors.put(collector.getOppositeMajorFragmentId(), newCollector); } logger.debug("Came up with a list of {} required fragments. Fragments {}", remainingRequired.get(), collectors); collectorMap = ImmutableMap.copyOf(collectors); // Determine the total number of incoming streams that will need to be completed before we are finished. int totalStreams = 0; for (DataCollector bc : collectorMap.values()) { totalStreams += bc.getTotalIncomingFragments(); } assert totalStreams >= remainingRequired.get() : String.format("Total Streams %d should be more than the minimum number of streams to commence (%d). It isn't.", totalStreams, remainingRequired.get()); streamsRemaining.set(totalStreams); } public boolean batchArrived(final IncomingDataBatch incomingBatch) throws FragmentSetupException, IOException { // we want to make sure that we only generate local record batch reference in the case that we're not closed. // Otherwise we would leak memory. try (AutoCloseableLock lock = sharedIncomingBatchLock.open()) { if (closed) { return false; } if (incomingBatch.getHeader().getIsLastBatch()) { streamsRemaining.decrementAndGet(); } final int sendMajorFragmentId = incomingBatch.getHeader().getSendingMajorFragmentId(); DataCollector collector = collectorMap.get(sendMajorFragmentId); if (collector == null) { throw new FragmentSetupException(String.format( "We received a major fragment id that we were not expecting. The id was %d. %s", sendMajorFragmentId, Arrays.toString(collectorMap.values().toArray()))); } synchronized (collector) { final RawFragmentBatch newRawFragmentBatch = incomingBatch.newRawFragmentBatch(context.getAllocator()); boolean decrementedToZero = collector .batchArrived(incomingBatch.getHeader().getSendingMinorFragmentId(), newRawFragmentBatch); newRawFragmentBatch.release(); // we should only return true if remaining required has been decremented and is currently equal to zero. return decrementedToZero; } } } public int getRemainingRequired() { int rem = remainingRequired.get(); if (rem < 0) { return 0; } return rem; } public RawBatchBuffer[] getBuffers(int senderMajorFragmentId) { return collectorMap.get(senderMajorFragmentId).getBuffers(); } public boolean isDone() { return streamsRemaining.get() < 1; } @Override public void close() throws Exception { try (AutoCloseableLock lock = exclusiveCloseLock.open()) { closed = true; AutoCloseables.close(collectorMap.values()); } } }