8080225: FileInput/OutputStream/FileChannel cleanup should be improved
Reviewed-by: mchung, plevart, bpb
/*
* Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.nio.ch;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.ref.*;
import java.io.FileDescriptor;
import java.io.IOException;
abstract class FileLockTable {
protected FileLockTable() {
}
/**
* Creates and returns a file lock table for a channel that is connected to
* the a system-wide map of all file locks for the Java virtual machine.
*/
public static FileLockTable newSharedFileLockTable(Channel channel,
FileDescriptor fd)
throws IOException
{
return new SharedFileLockTable(channel, fd);
}
/**
* Adds a file lock to the table.
*
* @throws OverlappingFileLockException if the file lock overlaps
* with an existing file lock in the table
*/
public abstract void add(FileLock fl) throws OverlappingFileLockException;
/**
* Remove an existing file lock from the table.
*/
public abstract void remove(FileLock fl);
/**
* Removes all file locks from the table.
*
* @return The list of file locks removed
*/
public abstract List<FileLock> removeAll();
/**
* Replaces an existing file lock in the table.
*/
public abstract void replace(FileLock fl1, FileLock fl2);
}
/**
* A file lock table that is over a system-wide map of all file locks.
*/
class SharedFileLockTable extends FileLockTable {
/**
* A weak reference to a FileLock.
* <p>
* SharedFileLockTable uses a list of file lock references to avoid keeping the
* FileLock (and FileChannel) alive.
*/
private static class FileLockReference extends WeakReference<FileLock> {
private FileKey fileKey;
FileLockReference(FileLock referent,
ReferenceQueue<FileLock> queue,
FileKey key) {
super(referent, queue);
this.fileKey = key;
}
FileKey fileKey() {
return fileKey;
}
}
// The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
// The map value is a list of file locks represented by FileLockReferences.
// All access to the list must be synchronized on the list.
private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap =
new ConcurrentHashMap<FileKey, List<FileLockReference>>();
// reference queue for cleared refs
private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>();
// The connection to which this table is connected
private final Channel channel;
// File key for the file that this channel is connected to
private final FileKey fileKey;
SharedFileLockTable(Channel channel, FileDescriptor fd) throws IOException {
this.channel = channel;
this.fileKey = FileKey.create(fd);
}
@Override
public void add(FileLock fl) throws OverlappingFileLockException {
List<FileLockReference> list = lockMap.get(fileKey);
for (;;) {
// The key isn't in the map so we try to create it atomically
if (list == null) {
list = new ArrayList<FileLockReference>(2);
List<FileLockReference> prev;
synchronized (list) {
prev = lockMap.putIfAbsent(fileKey, list);
if (prev == null) {
// we successfully created the key so we add the file lock
list.add(new FileLockReference(fl, queue, fileKey));
break;
}
}
// someone else got there first
list = prev;
}
// There is already a key. It is possible that some other thread
// is removing it so we re-fetch the value from the map. If it
// hasn't changed then we check the list for overlapping locks
// and add the new lock to the list.
synchronized (list) {
List<FileLockReference> current = lockMap.get(fileKey);
if (list == current) {
checkList(list, fl.position(), fl.size());
list.add(new FileLockReference(fl, queue, fileKey));
break;
}
list = current;
}
}
// process any stale entries pending in the reference queue
removeStaleEntries();
}
private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) {
assert Thread.holdsLock(list);
assert lockMap.get(fk) == list;
if (list.isEmpty()) {
lockMap.remove(fk);
}
}
@Override
public void remove(FileLock fl) {
assert fl != null;
// the lock must exist so the list of locks must be present
List<FileLockReference> list = lockMap.get(fileKey);
if (list == null) return;
synchronized (list) {
int index = 0;
while (index < list.size()) {
FileLockReference ref = list.get(index);
FileLock lock = ref.get();
if (lock == fl) {
assert (lock != null) && (lock.acquiredBy() == channel);
ref.clear();
list.remove(index);
break;
}
index++;
}
}
}
@Override
public List<FileLock> removeAll() {
List<FileLock> result = new ArrayList<FileLock>();
List<FileLockReference> list = lockMap.get(fileKey);
if (list != null) {
synchronized (list) {
int index = 0;
while (index < list.size()) {
FileLockReference ref = list.get(index);
FileLock lock = ref.get();
// remove locks obtained by this channel
if (lock != null && lock.acquiredBy() == channel) {
// remove the lock from the list
ref.clear();
list.remove(index);
// add to result
result.add(lock);
} else {
index++;
}
}
// once the lock list is empty we remove it from the map
removeKeyIfEmpty(fileKey, list);
}
}
return result;
}
@Override
public void replace(FileLock fromLock, FileLock toLock) {
// the lock must exist so there must be a list
List<FileLockReference> list = lockMap.get(fileKey);
assert list != null;
synchronized (list) {
for (int index=0; index<list.size(); index++) {
FileLockReference ref = list.get(index);
FileLock lock = ref.get();
if (lock == fromLock) {
ref.clear();
list.set(index, new FileLockReference(toLock, queue, fileKey));
break;
}
}
}
}
// Check for overlapping file locks
private void checkList(List<FileLockReference> list, long position, long size)
throws OverlappingFileLockException
{
assert Thread.holdsLock(list);
for (FileLockReference ref: list) {
FileLock fl = ref.get();
if (fl != null && fl.overlaps(position, size))
throw new OverlappingFileLockException();
}
}
// Process the reference queue
private void removeStaleEntries() {
FileLockReference ref;
while ((ref = (FileLockReference)queue.poll()) != null) {
FileKey fk = ref.fileKey();
List<FileLockReference> list = lockMap.get(fk);
if (list != null) {
synchronized (list) {
list.remove(ref);
removeKeyIfEmpty(fk, list);
}
}
}
}
}