/*
* Copyright 2011 Revelytix, Inc.
*
* Licensed 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.mulgara.util.io;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.apache.log4j.Logger;
import sun.misc.Cleaner;
/**
* Common utilities for working with memory-mapped files. The main purpose of these
* utilities is to provide a central place for workarounds for known issues with
* memory mapping under Windows.
*
* @author Alex Hall
*/
public class MappingUtil {
private static final Logger logger = Logger.getLogger(MappingUtil.class);
/** System property to determine operating system. */
private static final String OS_PROP = "os.name";
/** Only look up the operating system once and then cache it. */
private static final boolean IS_WINDOWS;
static {
String os = System.getProperty(OS_PROP);
IS_WINDOWS = (os != null && os.toLowerCase().indexOf("win") >= 0);
}
/** The maximum number of attempts for truncating a mapped file. */
private static final int MAX_RETRIES = 10;
/** Determines whether we are running under Windows. */
public static boolean isWindows() {
return IS_WINDOWS;
}
/**
* Truncate the given file to the given size. This method will re-attempt
* the truncation several times, to account for the fact that the truncation fails
* in Windows if there exist mapped buffers which have not yet been cleaned up
* by the garbage collector.
*
* All calls in Mulgara to FileChannel.truncate() should pass through this method.
*
* This method does nothing if the given size is greater than or equal to the current
* size of the file.
*
* @param fc The file channel to truncate.
* @param size The new size after truncation.
* @throws IOException if the truncation still failed after attempting to clean up system resources.
*/
public static void truncate(FileChannel fc, long size) throws IOException {
long s = fc.size();
if (s <= size) return;
int retries = MAX_RETRIES;
for (;;) {
try {
fc.truncate(size);
break;
} catch (IOException e) {
// on failure, attempt to clean up all resources keeping the file open
if (retries-- == 0) {
logger.error("Unable to truncate mapped file of size " + s + " to size " + size, e);
throw e;
}
systemCleanup();
}
}
}
/**
* Prompt the system to clean up outstanding objects, thereby releasing unique resources
* for re-use. This is required for MappedByteBuffers as the Java NIO cannot release the
* resources explicitly without putting a guard on every access (thereby compromising the
* speed advantages of memory mapping) or allowing continuing access to memory that is
* no longer accessible. Therefore, the resources must be released implicitly (by setting
* all references null) and then calling this code to prompt the system to clean the
* resources up. Depending on the host OS, this method may need to be called several times.
* Linux typically only requires 1 or 2 invocations, while Windows regularly needs more than
* 2 and can require >6.
*/
public static void systemCleanup() {
System.gc();
try { Thread.sleep(100); } catch (InterruptedException ie) { }
System.runFinalization();
}
/**
* Releases the mapped byte buffer, attempting to pre-emptively clean up the associated
* mapping under Windows.
* @param buffer A mapped byte buffer.
*/
public static void release(MappedByteBuffer buffer) {
if (isWindows()) clean(buffer);
}
/**
* Releases an array of mapped byte buffers, setting the references to null to allow
* them to be garbage collected.
* @param buffers An array of buffers.
*/
public static void release(MappedByteBuffer[] buffers) {
if (buffers == null) return;
for (int i = 0; i < buffers.length; i++) {
release(buffers[i]);
buffers[i] = null;
}
}
/**
* Attempts to pre-emptively invoke the cleaner method on the given object instead
* of waiting for the garbage collector to do it. We do this under Windows because
* errors can result if a mapped byte buffer is cleaned after its associated file
* is closed, truncated, deleted, etc.
* @param buffer The buffer to release.
*/
private static void clean(final Object buffer) {
if (buffer != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
getCleanerMethod.setAccessible(true);
Cleaner cleaner = (Cleaner)getCleanerMethod.invoke(buffer, new Object[0]);
cleaner.clean();
} catch (Exception e) {
logger.warn("Error cleaning buffer", e);
}
return null;
}
});
}
}
}