org.apache.accumulo.tserver.FileManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.tserver.FileManager.java

Source

/*
 * 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.accumulo.tserver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.accumulo.core.client.SampleNotPresentException;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.data.impl.KeyExtent;
import org.apache.accumulo.core.file.FileOperations;
import org.apache.accumulo.core.file.FileSKVIterator;
import org.apache.accumulo.core.file.blockfile.cache.BlockCache;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.system.InterruptibleIterator;
import org.apache.accumulo.core.iterators.system.SourceSwitchingIterator;
import org.apache.accumulo.core.iterators.system.SourceSwitchingIterator.DataSource;
import org.apache.accumulo.core.iterators.system.TimeSettingIterator;
import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.sample.impl.SamplerConfigurationImpl;
import org.apache.accumulo.server.AccumuloServerContext;
import org.apache.accumulo.server.fs.FileRef;
import org.apache.accumulo.server.fs.VolumeManager;
import org.apache.accumulo.server.problems.ProblemReport;
import org.apache.accumulo.server.problems.ProblemReportingIterator;
import org.apache.accumulo.server.problems.ProblemReports;
import org.apache.accumulo.server.problems.ProblemType;
import org.apache.accumulo.server.util.time.SimpleTimer;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileManager {

    private static final Logger log = LoggerFactory.getLogger(FileManager.class);

    private int maxOpen;

    private static class OpenReader implements Comparable<OpenReader> {
        long releaseTime;
        FileSKVIterator reader;
        String fileName;

        public OpenReader(String fileName, FileSKVIterator reader) {
            this.fileName = fileName;
            this.reader = reader;
            this.releaseTime = System.currentTimeMillis();
        }

        @Override
        public int compareTo(OpenReader o) {
            if (releaseTime < o.releaseTime) {
                return -1;
            } else if (releaseTime > o.releaseTime) {
                return 1;
            } else {
                return 0;
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof OpenReader) {
                return compareTo((OpenReader) obj) == 0;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return fileName.hashCode();
        }
    }

    private Map<String, List<OpenReader>> openFiles;
    private HashMap<FileSKVIterator, String> reservedReaders;

    private Semaphore filePermits;

    private VolumeManager fs;

    // the data cache and index cache are allocated in
    // TabletResourceManager and passed through the file opener to
    // CachableBlockFile which can handle the caches being
    // null if unallocated
    private BlockCache dataCache = null;
    private BlockCache indexCache = null;

    private long maxIdleTime;

    private final AccumuloServerContext context;

    private class IdleFileCloser implements Runnable {

        @Override
        public void run() {

            long curTime = System.currentTimeMillis();

            ArrayList<FileSKVIterator> filesToClose = new ArrayList<>();

            // determine which files to close in a sync block, and then close the
            // files outside of the sync block
            synchronized (FileManager.this) {
                Iterator<Entry<String, List<OpenReader>>> iter = openFiles.entrySet().iterator();
                while (iter.hasNext()) {
                    Entry<String, List<OpenReader>> entry = iter.next();
                    List<OpenReader> ofl = entry.getValue();

                    for (Iterator<OpenReader> oflIter = ofl.iterator(); oflIter.hasNext();) {
                        OpenReader openReader = oflIter.next();

                        if (curTime - openReader.releaseTime > maxIdleTime) {

                            filesToClose.add(openReader.reader);
                            oflIter.remove();
                        }
                    }

                    if (ofl.size() == 0) {
                        iter.remove();
                    }
                }
            }

            closeReaders(filesToClose);

        }

    }

    /**
     *
     * @param dataCache
     *          : underlying file can and should be able to handle a null cache
     * @param indexCache
     *          : underlying file can and should be able to handle a null cache
     */
    public FileManager(AccumuloServerContext context, VolumeManager fs, int maxOpen, BlockCache dataCache,
            BlockCache indexCache) {

        if (maxOpen <= 0)
            throw new IllegalArgumentException("maxOpen <= 0");
        this.context = context;
        this.dataCache = dataCache;
        this.indexCache = indexCache;

        this.filePermits = new Semaphore(maxOpen, true);
        this.maxOpen = maxOpen;
        this.fs = fs;

        this.openFiles = new HashMap<>();
        this.reservedReaders = new HashMap<>();

        this.maxIdleTime = context.getConfiguration().getTimeInMillis(Property.TSERV_MAX_IDLE);
        SimpleTimer.getInstance(context.getConfiguration()).schedule(new IdleFileCloser(), maxIdleTime,
                maxIdleTime / 2);

    }

    private static int countReaders(Map<String, List<OpenReader>> files) {
        int count = 0;

        for (List<OpenReader> list : files.values()) {
            count += list.size();
        }

        return count;
    }

    private List<FileSKVIterator> takeLRUOpenFiles(int numToTake) {

        ArrayList<OpenReader> openReaders = new ArrayList<>();

        for (Entry<String, List<OpenReader>> entry : openFiles.entrySet()) {
            openReaders.addAll(entry.getValue());
        }

        Collections.sort(openReaders);

        ArrayList<FileSKVIterator> ret = new ArrayList<>();

        for (int i = 0; i < numToTake && i < openReaders.size(); i++) {
            OpenReader or = openReaders.get(i);

            List<OpenReader> ofl = openFiles.get(or.fileName);
            if (!ofl.remove(or)) {
                throw new RuntimeException("Failed to remove open reader that should have been there");
            }

            if (ofl.size() == 0) {
                openFiles.remove(or.fileName);
            }

            ret.add(or.reader);
        }

        return ret;
    }

    private static <T> List<T> getFileList(String file, Map<String, List<T>> files) {
        List<T> ofl = files.get(file);
        if (ofl == null) {
            ofl = new ArrayList<>();
            files.put(file, ofl);
        }

        return ofl;
    }

    private void closeReaders(List<FileSKVIterator> filesToClose) {
        for (FileSKVIterator reader : filesToClose) {
            try {
                reader.close();
            } catch (Exception e) {
                log.error("Failed to close file {}", e.getMessage(), e);
            }
        }
    }

    private List<String> takeOpenFiles(Collection<String> files, List<FileSKVIterator> reservedFiles,
            Map<FileSKVIterator, String> readersReserved) {
        List<String> filesToOpen = new LinkedList<>(files);
        for (Iterator<String> iterator = filesToOpen.iterator(); iterator.hasNext();) {
            String file = iterator.next();

            List<OpenReader> ofl = openFiles.get(file);
            if (ofl != null && ofl.size() > 0) {
                OpenReader openReader = ofl.remove(ofl.size() - 1);
                reservedFiles.add(openReader.reader);
                readersReserved.put(openReader.reader, file);
                if (ofl.size() == 0) {
                    openFiles.remove(file);
                }
                iterator.remove();
            }

        }
        return filesToOpen;
    }

    private synchronized String getReservedReadeFilename(FileSKVIterator reader) {
        return reservedReaders.get(reader);
    }

    private List<FileSKVIterator> reserveReaders(KeyExtent tablet, Collection<String> files,
            boolean continueOnFailure) throws IOException {

        if (!tablet.isMeta() && files.size() >= maxOpen) {
            throw new IllegalArgumentException("requested files exceeds max open");
        }

        if (files.size() == 0) {
            return Collections.emptyList();
        }

        List<String> filesToOpen = null;
        List<FileSKVIterator> filesToClose = Collections.emptyList();
        List<FileSKVIterator> reservedFiles = new ArrayList<>();
        Map<FileSKVIterator, String> readersReserved = new HashMap<>();

        if (!tablet.isMeta()) {
            filePermits.acquireUninterruptibly(files.size());
        }

        // now that the we are past the semaphore, we have the authority
        // to open files.size() files

        // determine what work needs to be done in sync block
        // but do the work of opening and closing files outside
        // a synch block
        synchronized (this) {

            filesToOpen = takeOpenFiles(files, reservedFiles, readersReserved);

            int numOpen = countReaders(openFiles);

            if (filesToOpen.size() + numOpen + reservedReaders.size() > maxOpen) {
                filesToClose = takeLRUOpenFiles((filesToOpen.size() + numOpen + reservedReaders.size()) - maxOpen);
            }
        }

        // close files before opening files to ensure we stay under resource
        // limitations
        closeReaders(filesToClose);

        // open any files that need to be opened
        for (String file : filesToOpen) {
            try {
                if (!file.contains(":"))
                    throw new IllegalArgumentException("Expected uri, got : " + file);
                Path path = new Path(file);
                FileSystem ns = fs.getVolumeByPath(path).getFileSystem();
                // log.debug("Opening "+file + " path " + path);
                FileSKVIterator reader = FileOperations.getInstance().newReaderBuilder()
                        .forFile(path.toString(), ns, ns.getConf())
                        .withTableConfiguration(
                                context.getServerConfigurationFactory().getTableConfiguration(tablet))
                        .withBlockCache(dataCache, indexCache).build();
                reservedFiles.add(reader);
                readersReserved.put(reader, file);
            } catch (Exception e) {

                ProblemReports.getInstance(context)
                        .report(new ProblemReport(tablet.getTableId(), ProblemType.FILE_READ, file, e));

                if (continueOnFailure) {
                    // release the permit for the file that failed to open
                    if (!tablet.isMeta()) {
                        filePermits.release(1);
                    }
                    log.warn("Failed to open file {} {}  continuing...", file, e.getMessage());
                } else {
                    // close whatever files were opened
                    closeReaders(reservedFiles);

                    if (!tablet.isMeta()) {
                        filePermits.release(files.size());
                    }

                    log.error("Failed to open file {} {}", file, e.getMessage());
                    throw new IOException("Failed to open " + file, e);
                }
            }
        }

        synchronized (this) {
            // update set of reserved readers
            reservedReaders.putAll(readersReserved);
        }

        return reservedFiles;
    }

    private void releaseReaders(KeyExtent tablet, List<FileSKVIterator> readers, boolean sawIOException) {
        // put files in openFiles

        synchronized (this) {

            // check that readers were actually reserved ... want to make sure a thread does
            // not try to release readers they never reserved
            if (!reservedReaders.keySet().containsAll(readers)) {
                throw new IllegalArgumentException("Asked to release readers that were never reserved ");
            }

            for (FileSKVIterator reader : readers) {
                try {
                    reader.closeDeepCopies();
                } catch (IOException e) {
                    log.warn("{}", e.getMessage(), e);
                    sawIOException = true;
                }
            }

            for (FileSKVIterator reader : readers) {
                String fileName = reservedReaders.remove(reader);
                if (!sawIOException)
                    getFileList(fileName, openFiles).add(new OpenReader(fileName, reader));
            }
        }

        if (sawIOException)
            closeReaders(readers);

        // decrement the semaphore
        if (!tablet.isMeta()) {
            filePermits.release(readers.size());
        }

    }

    static class FileDataSource implements DataSource {

        private SortedKeyValueIterator<Key, Value> iter;
        private ArrayList<FileDataSource> deepCopies;
        private boolean current = true;
        private IteratorEnvironment env;
        private String file;
        private AtomicBoolean iflag;

        FileDataSource(String file, SortedKeyValueIterator<Key, Value> iter) {
            this.file = file;
            this.iter = iter;
            this.deepCopies = new ArrayList<>();
        }

        public FileDataSource(IteratorEnvironment env, SortedKeyValueIterator<Key, Value> deepCopy,
                ArrayList<FileDataSource> deepCopies) {
            this.iter = deepCopy;
            this.env = env;
            this.deepCopies = deepCopies;
            deepCopies.add(this);
        }

        @Override
        public boolean isCurrent() {
            return current;
        }

        @Override
        public DataSource getNewDataSource() {
            current = true;
            return this;
        }

        @Override
        public DataSource getDeepCopyDataSource(IteratorEnvironment env) {
            return new FileDataSource(env, iter.deepCopy(env), deepCopies);
        }

        @Override
        public SortedKeyValueIterator<Key, Value> iterator() {
            return iter;
        }

        void unsetIterator() {
            current = false;
            iter = null;
            for (FileDataSource fds : deepCopies) {
                fds.current = false;
                fds.iter = null;
            }
        }

        void setIterator(SortedKeyValueIterator<Key, Value> iter) {
            current = false;
            this.iter = iter;

            if (iflag != null) {
                ((InterruptibleIterator) this.iter).setInterruptFlag(iflag);
            }

            for (FileDataSource fds : deepCopies) {
                fds.current = false;
                fds.iter = iter.deepCopy(fds.env);
            }
        }

        @Override
        public void setInterruptFlag(AtomicBoolean flag) {
            this.iflag = flag;
            ((InterruptibleIterator) this.iter).setInterruptFlag(iflag);
        }
    }

    public class ScanFileManager {

        private ArrayList<FileDataSource> dataSources;
        private ArrayList<FileSKVIterator> tabletReservedReaders;
        private KeyExtent tablet;
        private boolean continueOnFailure;

        ScanFileManager(KeyExtent tablet) {
            tabletReservedReaders = new ArrayList<>();
            dataSources = new ArrayList<>();
            this.tablet = tablet;

            continueOnFailure = context.getServerConfigurationFactory().getTableConfiguration(tablet)
                    .getBoolean(Property.TABLE_FAILURES_IGNORE);

            if (tablet.isMeta()) {
                continueOnFailure = false;
            }
        }

        private List<FileSKVIterator> openFileRefs(Collection<FileRef> files)
                throws TooManyFilesException, IOException {
            List<String> strings = new ArrayList<>(files.size());
            for (FileRef ref : files)
                strings.add(ref.path().toString());
            return openFiles(strings);
        }

        private List<FileSKVIterator> openFiles(Collection<String> files)
                throws TooManyFilesException, IOException {
            // one tablet can not open more than maxOpen files, otherwise it could get stuck
            // forever waiting on itself to release files

            if (tabletReservedReaders.size() + files.size() >= maxOpen) {
                throw new TooManyFilesException(
                        "Request to open files would exceed max open files reservedReaders.size()="
                                + tabletReservedReaders.size() + " files.size()=" + files.size() + " maxOpen="
                                + maxOpen + " tablet = " + tablet);
            }

            List<FileSKVIterator> newlyReservedReaders = reserveReaders(tablet, files, continueOnFailure);

            tabletReservedReaders.addAll(newlyReservedReaders);
            return newlyReservedReaders;
        }

        public synchronized List<InterruptibleIterator> openFiles(Map<FileRef, DataFileValue> files,
                boolean detachable, SamplerConfigurationImpl samplerConfig) throws IOException {

            List<FileSKVIterator> newlyReservedReaders = openFileRefs(files.keySet());

            ArrayList<InterruptibleIterator> iters = new ArrayList<>();

            for (FileSKVIterator reader : newlyReservedReaders) {
                String filename = getReservedReadeFilename(reader);
                InterruptibleIterator iter;

                FileSKVIterator source = reader;
                if (samplerConfig != null) {
                    source = source.getSample(samplerConfig);
                    if (source == null) {
                        throw new SampleNotPresentException();
                    }
                }

                if (detachable) {
                    FileDataSource fds = new FileDataSource(filename, source);
                    dataSources.add(fds);
                    SourceSwitchingIterator ssi = new SourceSwitchingIterator(fds);
                    iter = new ProblemReportingIterator(context, tablet.getTableId(), filename, continueOnFailure,
                            ssi);
                } else {
                    iter = new ProblemReportingIterator(context, tablet.getTableId(), filename, continueOnFailure,
                            source);
                }
                DataFileValue value = files.get(new FileRef(filename));
                if (value.isTimeSet()) {
                    iter = new TimeSettingIterator(iter, value.getTime());
                }

                iters.add(iter);
            }

            return iters;
        }

        public synchronized void detach() {

            releaseReaders(tablet, tabletReservedReaders, false);
            tabletReservedReaders.clear();

            for (FileDataSource fds : dataSources)
                fds.unsetIterator();
        }

        public synchronized void reattach(SamplerConfigurationImpl samplerConfig) throws IOException {
            if (tabletReservedReaders.size() != 0)
                throw new IllegalStateException();

            Collection<String> files = new ArrayList<>();
            for (FileDataSource fds : dataSources)
                files.add(fds.file);

            List<FileSKVIterator> newlyReservedReaders = openFiles(files);
            Map<String, List<FileSKVIterator>> map = new HashMap<>();
            for (FileSKVIterator reader : newlyReservedReaders) {
                String fileName = getReservedReadeFilename(reader);
                List<FileSKVIterator> list = map.get(fileName);
                if (list == null) {
                    list = new LinkedList<>();
                    map.put(fileName, list);
                }

                list.add(reader);
            }

            for (FileDataSource fds : dataSources) {
                FileSKVIterator reader = map.get(fds.file).remove(0);
                FileSKVIterator source = reader;
                if (samplerConfig != null) {
                    source = source.getSample(samplerConfig);
                    if (source == null) {
                        throw new SampleNotPresentException();
                    }
                }
                fds.setIterator(source);
            }
        }

        public synchronized void releaseOpenFiles(boolean sawIOException) {
            releaseReaders(tablet, tabletReservedReaders, sawIOException);
            tabletReservedReaders.clear();
            dataSources.clear();
        }

        public synchronized int getNumOpenFiles() {
            return tabletReservedReaders.size();
        }
    }

    public ScanFileManager newScanFileManager(KeyExtent tablet) {
        return new ScanFileManager(tablet);
    }
}