/** * 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 com.facebook.infrastructure.io; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Method; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.zip.Adler32; import org.apache.log4j.Logger; import com.facebook.infrastructure.config.DatabaseDescriptor; import com.facebook.infrastructure.db.FileUtils; import com.facebook.infrastructure.utils.LogUtil; import bak.pcj.map.AbstractLongKeyLongMap; import bak.pcj.map.LongKeyLongChainedHashMap; /** * This class manages the persistence of checksums and keeps * them in memory. It maintains a mapping of data files on * disk to their corresponding checksum files. It is also * loads the checksums in memory on start up. * * @author alakshman * */ class ChecksumManager { private static Logger logger_ = Logger.getLogger(ChecksumManager.class); /* Keeps a mapping of checksum manager instances to data file */ private static Map<String, ChecksumManager> chksumMgrs_ = new HashMap<String, ChecksumManager>(); private static Lock lock_ = new ReentrantLock(); private static final String checksumPrefix_ = "Checksum-"; private static final int bufferSize_ = 8*1024*1024; private static final long chunkMask_ = 0x00000000FFFFFFFFL; private static final long fileIdMask_ = 0x7FFFFFFF00000000L; /* Map where checksums are cached. */ private static AbstractLongKeyLongMap chksums_ = new LongKeyLongChainedHashMap(); public static ChecksumManager instance(String dataFile) throws IOException { ChecksumManager chksumMgr = chksumMgrs_.get(dataFile); if ( chksumMgr == null ) { lock_.lock(); try { if ( chksumMgr == null ) { chksumMgr = new ChecksumManager(dataFile); chksumMgrs_.put(dataFile, chksumMgr); } } finally { lock_.unlock(); } } return chksumMgr; } /** * On start read all the check sum files on disk and * pull them into memory. * @throws IOException */ public static void onStart() throws IOException { String[] directories = DatabaseDescriptor.getAllDataFileLocations(); List<File> allFiles = new ArrayList<File>(); for ( String directory : directories ) { File file = new File(directory); File[] files = file.listFiles(); for ( File f : files ) { if ( f.getName().contains(ChecksumManager.checksumPrefix_) ) { allFiles.add(f); } } } for ( File file : allFiles ) { int fId = SequenceFile.getFileId(file.getName()); ChecksumReader chksumRdr = new ChecksumReader(file.getAbsolutePath(), 0L, file.length()); int chunk = 0; while ( !chksumRdr.isEOF() ) { long value = chksumRdr.readLong(); long key = ChecksumManager.key(fId, ++chunk); chksums_.put(key, value); } } } /** * On delete of this dataFile remove the checksums associated with * this file from memory, remove the check sum manager instance. * * @param dataFile data file that is being deleted. * @throws IOException */ public static void onFileDelete(String dataFile) throws IOException { File f = new File(dataFile); long size = f.length(); int fileId = SequenceFile.getFileId(f.getName()); int chunks = (int)(size >> 16L); for ( int i = 0; i < chunks; ++i ) { long key = ChecksumManager.key(fileId, i); chksums_.remove(key); } /* remove the check sum manager instance */ chksumMgrs_.remove(dataFile); String chksumFile = f.getParent() + System.getProperty("file.separator") + checksumPrefix_ + fileId + ".db"; FileUtils.delete(chksumFile); } private static long key(int fileId, int chunkId) { long key = 0; key |= fileId; key <<= 32; key |= chunkId; return key; } private RandomAccessFile raf_; private Adler32 adler_ = new Adler32(); ChecksumManager(String dataFile) throws IOException { File file = new File(dataFile); String directory = file.getParent(); String f = file.getName(); short fId = SequenceFile.getFileId(f); String chkSumFile = directory + System.getProperty("file.separator") + checksumPrefix_ + fId + ".db"; raf_ = new RandomAccessFile(chkSumFile, "rw"); } /** * Log the checksum for the the specified file and chunk * within the file. * @param fileId id associated with the file * @param chunkId chunk within the file. * @param chksum calculated checksum for the chunk * @throws IOException */ void logChecksum(int fileId, int chunkId, byte[] buffer) { try { adler_.update(buffer); long chksum = adler_.getValue(); adler_.reset(); /* log checksums to disk */ raf_.writeLong(chksum); /* add the chksum to memory */ long key = ChecksumManager.key(fileId, chunkId); chksums_.put(key, chksum); } catch ( IOException ex ) { logger_.warn( LogUtil.throwableToString(ex) ); } } /** * Validate checksums for the data in the buffer for the region * that is encapsulated in the section object * @param bufOut buffer containing the data. * @param section coordinates indicating the region on disk. * @throws IOException */ void validateChecksum(byte[] buffer, int chunkId, String file) throws IOException { int fId = SequenceFile.getFileId(file); long key = ChecksumManager.key(fId, chunkId); adler_.update(buffer); long currentChksum = adler_.getValue(); adler_.reset(); long oldChksum = chksums_.get(key); if ( currentChksum != oldChksum ) { System.out.println("EXCEPTION:" + chunkId); throw new IOException("Checksums do not match for this chunk."); } else { System.out.println(chunkId); } } /** * Get the checksum for the specified file's chunk * @param fileId id associated with the file. * @param chunkId chunk within the file. * @return associated checksum for the chunk */ long getChecksum(int fileId, int chunkId) { long key = ChecksumManager.key(fileId, chunkId); return chksums_.get(key); } public static void main(String[] args) throws Throwable { ChecksumReader rdr = new ChecksumReader("C:\\Engagements\\Cassandra\\Checksum-1.db"); while ( !rdr.isEOF() ) { System.out.println(rdr.readLong()); } rdr.close(); } } /** * ChecksumReader is used to memory map the checksum files and * load the data into memory. * * @author alakshman * */ class ChecksumReader { private static Logger logger_ = Logger.getLogger(ChecksumReader.class); private String filename_; private MappedByteBuffer buffer_; ChecksumReader(String filename) throws IOException { filename_ = filename; File f = new File(filename); map(0, f.length()); } ChecksumReader(String filename, long start, long end) throws IOException { filename_ = filename; map(start, end); } public void map() throws IOException { RandomAccessFile file = new RandomAccessFile(filename_, "rw"); try { buffer_ = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length() ); buffer_.load(); } finally { file.close(); } } public void map(long start, long end) throws IOException { if ( start < 0 || end < 0 || end < start ) throw new IllegalArgumentException("Invalid values for start and end."); RandomAccessFile file = new RandomAccessFile(filename_, "rw"); try { if ( end == 0 ) end = file.length(); buffer_ = file.getChannel().map(FileChannel.MapMode.READ_ONLY, start, end); buffer_.load(); } finally { file.close(); } } void unmap(final Object buffer) { AccessController.doPrivileged( new PrivilegedAction<MappedByteBuffer>() { public MappedByteBuffer run() { try { Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]); getCleanerMethod.setAccessible(true); sun.misc.Cleaner cleaner = (sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]); cleaner.clean(); } catch(Throwable e) { logger_.debug( LogUtil.throwableToString(e) ); } return null; } }); } public long readLong() throws IOException { return buffer_.getLong(); } public boolean isEOF() { return ( buffer_.remaining() == 0 ); } public void close() throws IOException { unmap(buffer_); } }