All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.iterative.io.SerializedUpdateBuffer Maven / Gradle / Ivy

The newest version!
/*
 * 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.flink.runtime.iterative.io;

import org.apache.flink.core.memory.MemorySegment;
import org.apache.flink.runtime.io.disk.iomanager.BlockChannelReader;
import org.apache.flink.runtime.io.disk.iomanager.BlockChannelWriter;
import org.apache.flink.runtime.io.disk.iomanager.FileIOChannel;
import org.apache.flink.runtime.io.disk.iomanager.IOManager;
import org.apache.flink.runtime.memory.AbstractPagedInputView;
import org.apache.flink.runtime.memory.AbstractPagedOutputView;

import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * {@link AbstractPagedOutputView} used by the {@code BlockingBackChannel} for
 * transmitting superstep results.
 */
public class SerializedUpdateBuffer extends AbstractPagedOutputView {

	private static final int HEADER_LENGTH = 4;

	private static final float SPILL_THRESHOLD = 0.95f;

	private final LinkedBlockingQueue emptyBuffers;

	private ArrayDeque fullBuffers;

	private BlockChannelWriter currentWriter;

	private final IOManager ioManager;

	private final FileIOChannel.Enumerator channelEnumerator;

	private final int numSegmentsSpillingThreshold;

	private int numBuffersSpilled;

	private final int minBuffersForWriteEnd;

	private final int minBuffersForSpilledReadEnd;

	private final List readEnds;

	private final int totalNumBuffers;

	public SerializedUpdateBuffer() {
		super(-1, HEADER_LENGTH);

		emptyBuffers = null;
		fullBuffers = null;

		ioManager = null;
		channelEnumerator = null;

		numSegmentsSpillingThreshold = -1;
		minBuffersForWriteEnd = -1;
		minBuffersForSpilledReadEnd = -1;
		totalNumBuffers = -1;

		readEnds = Collections.emptyList();
	}

	public SerializedUpdateBuffer(List memSegments, int segmentSize, IOManager ioManager) {
		super(memSegments.remove(memSegments.size() - 1), segmentSize, HEADER_LENGTH);

		totalNumBuffers = memSegments.size() + 1;
		if (totalNumBuffers < 3) {
			throw new IllegalArgumentException("SerializedUpdateBuffer needs at least 3 memory segments.");
		}

		emptyBuffers = new LinkedBlockingQueue(totalNumBuffers);
		fullBuffers = new ArrayDeque(64);

		emptyBuffers.addAll(memSegments);

		int threshold = (int) ((1 - SPILL_THRESHOLD) * totalNumBuffers);
		numSegmentsSpillingThreshold = threshold > 0 ? threshold : 0;
		minBuffersForWriteEnd = Math.max(2, Math.min(16, totalNumBuffers / 2));
		minBuffersForSpilledReadEnd = Math.max(1, Math.min(16, totalNumBuffers / 4));

		if (minBuffersForSpilledReadEnd + minBuffersForWriteEnd > totalNumBuffers) {
			throw new IllegalArgumentException("BUG: Unfulfillable memory assignment.");
		}

		this.ioManager = ioManager;
		channelEnumerator = ioManager.createChannelEnumerator();
		readEnds = new ArrayList();
	}

	@Override
	protected MemorySegment nextSegment(MemorySegment current, int positionInCurrent) throws IOException {
		current.putInt(0, positionInCurrent);

		// check if we keep the segment in memory, or if we spill it
		if (emptyBuffers.size() > numSegmentsSpillingThreshold) {
			// keep buffer in memory
			fullBuffers.addLast(current);
		} else {
			// spill all buffers up to now
			// check, whether we have a channel already
			if (currentWriter == null) {
				currentWriter = ioManager.createBlockChannelWriter(channelEnumerator.next(), emptyBuffers);
			}

			// spill all elements gathered up to now
			numBuffersSpilled += fullBuffers.size();
			while (fullBuffers.size() > 0) {
				currentWriter.writeBlock(fullBuffers.removeFirst());
			}
			currentWriter.writeBlock(current);
			numBuffersSpilled++;
		}

		try {
			return emptyBuffers.take();
		} catch (InterruptedException iex) {
			throw new RuntimeException("Spilling Fifo Queue was interrupted while waiting for next buffer.");
		}
	}

	public void flush() throws IOException {
		advance();
	}

	public ReadEnd switchBuffers() throws IOException {
		// remove exhausted read ends
		for (int i = readEnds.size() - 1; i >= 0; --i) {
			final ReadEnd re = readEnds.get(i);
			if (re.disposeIfDone()) {
				readEnds.remove(i);
			}
		}

		// add the current memorySegment and reset this writer
		final MemorySegment current = getCurrentSegment();
		current.putInt(0, getCurrentPositionInSegment());
		fullBuffers.addLast(current);

		// create the reader
		final ReadEnd readEnd;
		if (numBuffersSpilled == 0 && emptyBuffers.size() >= minBuffersForWriteEnd) {
			// read completely from in-memory segments
			readEnd = new ReadEnd(fullBuffers.removeFirst(), emptyBuffers, fullBuffers, null, null, 0);
		} else {
			int toSpill = Math.min(minBuffersForSpilledReadEnd + minBuffersForWriteEnd - emptyBuffers.size(),
				fullBuffers.size());

			// reader reads also segments on disk
			// grab some empty buffers to re-read the first segment
			if (toSpill > 0) {
				// need to spill to make a buffers available
				if (currentWriter == null) {
					currentWriter = ioManager.createBlockChannelWriter(channelEnumerator.next(), emptyBuffers);
				}

				for (int i = 0; i < toSpill; i++) {
					currentWriter.writeBlock(fullBuffers.removeFirst());
				}
				numBuffersSpilled += toSpill;
			}

			// now close the writer and create the reader
			currentWriter.close();
			final BlockChannelReader reader = ioManager.createBlockChannelReader(currentWriter.getChannelID());

			// gather some memory segments to circulate while reading back the data
			final List readSegments = new ArrayList();
			try {
				while (readSegments.size() < minBuffersForSpilledReadEnd) {
					readSegments.add(emptyBuffers.take());
				}

				// read the first segment
				MemorySegment firstSeg = readSegments.remove(readSegments.size() - 1);
				reader.readBlock(firstSeg);
				firstSeg = reader.getReturnQueue().take();

				// create the read end reading one less buffer, because the first buffer is already read back
				readEnd = new ReadEnd(firstSeg, emptyBuffers, fullBuffers, reader, readSegments,
						numBuffersSpilled - 1);
			} catch (InterruptedException e) {
				throw new RuntimeException(
					"SerializedUpdateBuffer was interrupted while reclaiming memory by spilling.", e);
			}
		}

		// reset the writer
		fullBuffers = new ArrayDeque(64);
		currentWriter = null;
		numBuffersSpilled = 0;
		try {
			seekOutput(emptyBuffers.take(), HEADER_LENGTH);
		} catch (InterruptedException e) {
			throw new RuntimeException("SerializedUpdateBuffer was interrupted while reclaiming memory by spilling.", e);
		}

		// register this read end
		readEnds.add(readEnd);
		return readEnd;
	}

	public List close() {
		if (currentWriter != null) {
			try {
				currentWriter.closeAndDelete();
			} catch (Throwable t) {
				// do nothing
			}
		}

		List freeMem = new ArrayList(64);

		// add all memory allocated to the write end
		freeMem.add(getCurrentSegment());
		clear();
		freeMem.addAll(fullBuffers);
		fullBuffers = null;

		// add memory from non-exhausted read ends
		try {
			for (int i = readEnds.size() - 1; i >= 0; --i) {
				final ReadEnd re = readEnds.remove(i);
				re.forceDispose(freeMem);
			}

			// release all empty segments
			while (freeMem.size() < totalNumBuffers) {
				freeMem.add(emptyBuffers.take());
			}
		} catch (InterruptedException e) {
			throw new RuntimeException("Retrieving memory back from asynchronous I/O was interrupted.", e);
		}

		return freeMem;
	}

	// ============================================================================================

	private static final class ReadEnd extends AbstractPagedInputView {

		private final LinkedBlockingQueue emptyBufferTarget;

		private final Deque fullBufferSource;

		private final BlockChannelReader spilledBufferSource;

		private int spilledBuffersRemaining;

		private int requestsRemaining;

		private ReadEnd(MemorySegment firstMemSegment, LinkedBlockingQueue emptyBufferTarget,
										Deque fullBufferSource, BlockChannelReader spilledBufferSource,
										List emptyBuffers, int numBuffersSpilled)
			throws IOException {
			super(firstMemSegment, firstMemSegment.getInt(0), HEADER_LENGTH);

			this.emptyBufferTarget = emptyBufferTarget;
			this.fullBufferSource = fullBufferSource;

			this.spilledBufferSource = spilledBufferSource;

			requestsRemaining = numBuffersSpilled;
			this.spilledBuffersRemaining = numBuffersSpilled;

			// send the first requests
			while (requestsRemaining > 0 && emptyBuffers.size() > 0) {
				this.spilledBufferSource.readBlock(emptyBuffers.remove(emptyBuffers.size() - 1));
				requestsRemaining--;
			}
		}

		@Override
		protected MemorySegment nextSegment(MemorySegment current) throws IOException {
			// use the buffer to send the next request
			if (requestsRemaining > 0) {
				requestsRemaining--;
				spilledBufferSource.readBlock(current);
			} else {
				emptyBufferTarget.add(current);
			}

			// get the next buffer either from the return queue, or the full buffer source
			if (spilledBuffersRemaining > 0) {
				spilledBuffersRemaining--;
				try {
					return spilledBufferSource.getReturnQueue().take();
				} catch (InterruptedException e) {
					throw new RuntimeException("Read End was interrupted while waiting for spilled buffer.", e);
				}
			} else if (fullBufferSource.size() > 0) {
				return fullBufferSource.removeFirst();
			} else {
				clear();

				// delete the channel, if we had one
				if (spilledBufferSource != null) {
					spilledBufferSource.closeAndDelete();
				}

				throw new EOFException();
			}
		}

		@Override
		protected int getLimitForSegment(MemorySegment segment) {
			return segment.getInt(0);
		}

		private boolean disposeIfDone() {
			if (fullBufferSource.isEmpty() && spilledBuffersRemaining == 0) {
				if (getCurrentSegment() == null || getCurrentPositionInSegment() >= getCurrentSegmentLimit()) {
					if (getCurrentSegment() != null) {
						emptyBufferTarget.add(getCurrentSegment());
						clear();
					}

					if (spilledBufferSource != null) {
						try {
							spilledBufferSource.closeAndDelete();
						} catch (Throwable t) {
							// do nothing
						}
					}
					return true;
				}
			}
			return false;
		}

		private void forceDispose(List freeMemTarget) throws InterruptedException {
			// add the current segment
			final MemorySegment current = getCurrentSegment();
			clear();
			if (current != null) {
				freeMemTarget.add(current);
			}

			// add all remaining memory
			freeMemTarget.addAll(fullBufferSource);

			// add the segments with the requests issued but not returned
			for (int i = spilledBuffersRemaining - requestsRemaining; i > 0; --i) {
				freeMemTarget.add(emptyBufferTarget.take());
			}

			if (spilledBufferSource != null) {
				try {
					spilledBufferSource.closeAndDelete();
				} catch (Throwable t) {
					// do nothing
				}
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy