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 }