274 lines
9.1 KiB
Java
274 lines
9.1 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|