Home | History | Annotate | Download | only in ch
      1 /*
      2  * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package sun.nio.ch;
     27 
     28 import java.nio.channels.*;
     29 import java.util.*;
     30 import java.util.concurrent.ConcurrentHashMap;
     31 import java.lang.ref.*;
     32 import java.io.FileDescriptor;
     33 import java.io.IOException;
     34 
     35 abstract class FileLockTable {
     36     protected FileLockTable() {
     37     }
     38 
     39     /**
     40      * Creates and returns a file lock table for a channel that is connected to
     41      * the a system-wide map of all file locks for the Java virtual machine.
     42      */
     43     public static FileLockTable newSharedFileLockTable(Channel channel,
     44                                                        FileDescriptor fd)
     45         throws IOException
     46     {
     47         return new SharedFileLockTable(channel, fd);
     48     }
     49 
     50     /**
     51      * Adds a file lock to the table.
     52      *
     53      * @throws OverlappingFileLockException if the file lock overlaps
     54      *         with an existing file lock in the table
     55      */
     56     public abstract void add(FileLock fl) throws OverlappingFileLockException;
     57 
     58     /**
     59      * Remove an existing file lock from the table.
     60      */
     61     public abstract void remove(FileLock fl);
     62 
     63     /**
     64      * Removes all file locks from the table.
     65      *
     66      * @return  The list of file locks removed
     67      */
     68     public abstract List<FileLock> removeAll();
     69 
     70     /**
     71      * Replaces an existing file lock in the table.
     72      */
     73     public abstract void replace(FileLock fl1, FileLock fl2);
     74 }
     75 
     76 
     77 /**
     78  * A file lock table that is over a system-wide map of all file locks.
     79  */
     80 class SharedFileLockTable extends FileLockTable {
     81 
     82     /**
     83      * A weak reference to a FileLock.
     84      * <p>
     85      * SharedFileLockTable uses a list of file lock references to avoid keeping the
     86      * FileLock (and FileChannel) alive.
     87      */
     88     private static class FileLockReference extends WeakReference<FileLock> {
     89         private FileKey fileKey;
     90 
     91         FileLockReference(FileLock referent,
     92                           ReferenceQueue<FileLock> queue,
     93                           FileKey key) {
     94             super(referent, queue);
     95             this.fileKey = key;
     96         }
     97 
     98         FileKey fileKey() {
     99             return fileKey;
    100         }
    101     }
    102 
    103     // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
    104     // The map value is a list of file locks represented by FileLockReferences.
    105     // All access to the list must be synchronized on the list.
    106     private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap =
    107         new ConcurrentHashMap<FileKey, List<FileLockReference>>();
    108 
    109     // reference queue for cleared refs
    110     private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>();
    111 
    112     // The connection to which this table is connected
    113     private final Channel channel;
    114 
    115     // File key for the file that this channel is connected to
    116     private final FileKey fileKey;
    117 
    118     SharedFileLockTable(Channel channel, FileDescriptor fd) throws IOException {
    119         this.channel = channel;
    120         this.fileKey = FileKey.create(fd);
    121     }
    122 
    123     @Override
    124     public void add(FileLock fl) throws OverlappingFileLockException {
    125         List<FileLockReference> list = lockMap.get(fileKey);
    126 
    127         for (;;) {
    128 
    129             // The key isn't in the map so we try to create it atomically
    130             if (list == null) {
    131                 list = new ArrayList<FileLockReference>(2);
    132                 List<FileLockReference> prev;
    133                 synchronized (list) {
    134                     prev = lockMap.putIfAbsent(fileKey, list);
    135                     if (prev == null) {
    136                         // we successfully created the key so we add the file lock
    137                         list.add(new FileLockReference(fl, queue, fileKey));
    138                         break;
    139                     }
    140                 }
    141                 // someone else got there first
    142                 list = prev;
    143             }
    144 
    145             // There is already a key. It is possible that some other thread
    146             // is removing it so we re-fetch the value from the map. If it
    147             // hasn't changed then we check the list for overlapping locks
    148             // and add the new lock to the list.
    149             synchronized (list) {
    150                 List<FileLockReference> current = lockMap.get(fileKey);
    151                 if (list == current) {
    152                     checkList(list, fl.position(), fl.size());
    153                     list.add(new FileLockReference(fl, queue, fileKey));
    154                     break;
    155                 }
    156                 list = current;
    157             }
    158 
    159         }
    160 
    161         // process any stale entries pending in the reference queue
    162         removeStaleEntries();
    163     }
    164 
    165     private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) {
    166         assert Thread.holdsLock(list);
    167         assert lockMap.get(fk) == list;
    168         if (list.isEmpty()) {
    169             lockMap.remove(fk);
    170         }
    171     }
    172 
    173     @Override
    174     public void remove(FileLock fl) {
    175         assert fl != null;
    176 
    177         // the lock must exist so the list of locks must be present
    178         List<FileLockReference> list = lockMap.get(fileKey);
    179         if (list == null) return;
    180 
    181         synchronized (list) {
    182             int index = 0;
    183             while (index < list.size()) {
    184                 FileLockReference ref = list.get(index);
    185                 FileLock lock = ref.get();
    186                 if (lock == fl) {
    187                     assert (lock != null) && (lock.acquiredBy() == channel);
    188                     ref.clear();
    189                     list.remove(index);
    190                     break;
    191                 }
    192                 index++;
    193             }
    194         }
    195     }
    196 
    197     @Override
    198     public List<FileLock> removeAll() {
    199         List<FileLock> result = new ArrayList<FileLock>();
    200         List<FileLockReference> list = lockMap.get(fileKey);
    201         if (list != null) {
    202             synchronized (list) {
    203                 int index = 0;
    204                 while (index < list.size()) {
    205                     FileLockReference ref = list.get(index);
    206                     FileLock lock = ref.get();
    207 
    208                     // remove locks obtained by this channel
    209                     if (lock != null && lock.acquiredBy() == channel) {
    210                         // remove the lock from the list
    211                         ref.clear();
    212                         list.remove(index);
    213 
    214                         // add to result
    215                         result.add(lock);
    216                     } else {
    217                         index++;
    218                     }
    219                 }
    220 
    221                 // once the lock list is empty we remove it from the map
    222                 removeKeyIfEmpty(fileKey, list);
    223             }
    224         }
    225         return result;
    226     }
    227 
    228     @Override
    229     public void replace(FileLock fromLock, FileLock toLock) {
    230         // the lock must exist so there must be a list
    231         List<FileLockReference> list = lockMap.get(fileKey);
    232         assert list != null;
    233 
    234         synchronized (list) {
    235             for (int index=0; index<list.size(); index++) {
    236                 FileLockReference ref = list.get(index);
    237                 FileLock lock = ref.get();
    238                 if (lock == fromLock) {
    239                     ref.clear();
    240                     list.set(index, new FileLockReference(toLock, queue, fileKey));
    241                     break;
    242                 }
    243             }
    244         }
    245     }
    246 
    247     // Check for overlapping file locks
    248     private void checkList(List<FileLockReference> list, long position, long size)
    249         throws OverlappingFileLockException
    250     {
    251         assert Thread.holdsLock(list);
    252         for (FileLockReference ref: list) {
    253             FileLock fl = ref.get();
    254             if (fl != null && fl.overlaps(position, size))
    255                 throw new OverlappingFileLockException();
    256         }
    257     }
    258 
    259     // Process the reference queue
    260     private void removeStaleEntries() {
    261         FileLockReference ref;
    262         while ((ref = (FileLockReference)queue.poll()) != null) {
    263             FileKey fk = ref.fileKey();
    264             List<FileLockReference> list = lockMap.get(fk);
    265             if (list != null) {
    266                 synchronized (list) {
    267                     list.remove(ref);
    268                     removeKeyIfEmpty(fk, list);
    269                 }
    270             }
    271         }
    272     }
    273 }
    274