package org.apache.cassandra.io.util;
/*
*
* 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.
*
*/
import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MmappedSegmentedFile extends SegmentedFile
{
// in a perfect world, MAX_SEGMENT_SIZE would be final, but we need to test with a smaller size to stay sane.
public static long MAX_SEGMENT_SIZE = Integer.MAX_VALUE;
/**
* Sorted array of segment offsets and MappedByteBuffers for segments. If mmap is completely disabled, or if the
* segment would be too long to mmap, the value for an offset will be null, indicating that we need to fall back
* to a RandomAccessFile.
*/
private final Segment[] segments;
public MmappedSegmentedFile(String path, long length, Segment[] segments)
{
super(path, length);
this.segments = segments;
}
/**
* @return The segment entry for the given position.
*/
private Segment floor(long position)
{
assert 0 <= position && position < length: position + " vs " + length;
Segment seg = new Segment(position, null);
int idx = Arrays.binarySearch(segments, seg);
assert idx != -1 : "Bad position " + position + " in segments " + Arrays.toString(segments);
if (idx < 0)
// round down to entry at insertion point
idx = -(idx + 2);
return segments[idx];
}
/**
* @return The segment containing the given position: must be closed after use.
*/
public FileDataInput getSegment(long position, int bufferSize)
{
Segment segment = floor(position);
if (segment.right != null)
{
// segment is mmap'd
return new MappedFileDataInput(segment.right, path, (int) (position - segment.left));
}
// not mmap'd: open a braf covering the segment
try
{
// FIXME: brafs are unbounded, so this segment will cover the rest of the file, rather than just the row
BufferedRandomAccessFile file = new BufferedRandomAccessFile(path, "r", bufferSize);
file.seek(position);
return file;
}
catch (IOException e)
{
throw new IOError(e);
}
}
/**
* Overrides the default behaviour to create segments of a maximum size.
*/
static class Builder extends SegmentedFile.Builder
{
// planned segment boundaries
private final List<Long> boundaries;
// offset of the open segment (first segment begins at 0).
private long currentStart = 0;
// current length of the open segment.
// used to allow merging multiple too-large-to-mmap segments, into a single buffered segment.
private long currentSize = 0;
public Builder()
{
super();
boundaries = new ArrayList<Long>();
boundaries.add(0L);
}
@Override
public void addPotentialBoundary(long boundary)
{
if (boundary - currentStart <= MAX_SEGMENT_SIZE)
{
// boundary fits into current segment: expand it
currentSize = boundary - currentStart;
return;
}
// close the current segment to try and make room for the boundary
if (currentSize > 0)
{
currentStart += currentSize;
boundaries.add(currentStart);
}
currentSize = boundary - currentStart;
// if we couldn't make room, the boundary needs its own segment
if (currentSize > MAX_SEGMENT_SIZE)
{
currentStart = boundary;
boundaries.add(currentStart);
currentSize = 0;
}
}
@Override
public SegmentedFile complete(String path)
{
long length = new File(path).length();
// add a sentinel value == length
boundaries.add(Long.valueOf(length));
// create the segments
return new MmappedSegmentedFile(path, length, createSegments(path));
}
private Segment[] createSegments(String path)
{
int segcount = boundaries.size() - 1;
Segment[] segments = new Segment[segcount];
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(path, "r");
for (int i = 0; i < segcount; i++)
{
long start = boundaries.get(i);
long size = boundaries.get(i + 1) - start;
MappedByteBuffer segment = size <= MAX_SEGMENT_SIZE
? raf.getChannel().map(FileChannel.MapMode.READ_ONLY, start, size)
: null;
segments[i] = new Segment(start, segment);
}
}
catch (IOException e)
{
throw new IOError(e);
}
finally
{
FileUtils.closeQuietly(raf);
}
return segments;
}
}
}