1 /*
   2  * Copyright (c) 2005, 2018, 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 /**
  36  * A file lock table that is over a system-wide map of all file locks.
  37  */
  38 class FileLockTable {
  39     /**
  40      * A weak reference to a FileLock.
  41      * <p>
  42      * FileLockTable uses a list of file lock references to avoid keeping the
  43      * FileLock (and FileChannel) alive.
  44      */
  45     private static class FileLockReference extends WeakReference<FileLock> {
  46         private FileKey fileKey;
  47 
  48         FileLockReference(FileLock referent,
  49                           ReferenceQueue<FileLock> queue,
  50                           FileKey key) {
  51             super(referent, queue);
  52             this.fileKey = key;
  53         }
  54 
  55         FileKey fileKey() {
  56             return fileKey;
  57         }
  58     }
  59 
  60     // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
  61     // The map value is a list of file locks represented by FileLockReferences.
  62     // All access to the list must be synchronized on the list.
  63     private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap =
  64         new ConcurrentHashMap<FileKey, List<FileLockReference>>();
  65 
  66     // reference queue for cleared refs
  67     private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>();
  68 
  69     // The connection to which this table is connected
  70     private final Channel channel;
  71 
  72     // File key for the file that this channel is connected to
  73     private final FileKey fileKey;
  74 
  75     // Locks obtained for this channel
  76     private final Set<FileLock> locks;
  77 
  78     /**
  79      * Creates and returns a file lock table for a channel that is connected to
  80      * the a system-wide map of all file locks for the Java virtual machine.
  81      */
  82     public static FileLockTable newFileLockTable(Channel channel,
  83                                                  FileDescriptor fd)
  84         throws IOException
  85     {
  86         return new FileLockTable(channel, fd);
  87     }
  88 
  89     FileLockTable(Channel channel, FileDescriptor fd) throws IOException {
  90         this.channel = channel;
  91         this.fileKey = FileKey.create(fd);
  92         this.locks = ConcurrentHashMap.newKeySet();
  93     }
  94 
  95     public void add(FileLock fl) throws OverlappingFileLockException {
  96         List<FileLockReference> list = lockMap.get(fileKey);
  97 
  98         for (;;) {
  99 
 100             // The key isn't in the map so we try to create it atomically
 101             if (list == null) {
 102                 list = new ArrayList<FileLockReference>(2);
 103                 List<FileLockReference> prev;
 104                 synchronized (list) {
 105                     prev = lockMap.putIfAbsent(fileKey, list);
 106                     if (prev == null) {
 107                         // we successfully created the key so we add the file lock
 108                         list.add(new FileLockReference(fl, queue, fileKey));
 109                         locks.add(fl);
 110                         break;
 111                     }
 112                 }
 113                 // someone else got there first
 114                 list = prev;
 115             }
 116 
 117             // There is already a key. It is possible that some other thread
 118             // is removing it so we re-fetch the value from the map. If it
 119             // hasn't changed then we check the list for overlapping locks
 120             // and add the new lock to the list.
 121             synchronized (list) {
 122                 List<FileLockReference> current = lockMap.get(fileKey);
 123                 if (list == current) {
 124                     checkList(list, fl.position(), fl.size());
 125                     list.add(new FileLockReference(fl, queue, fileKey));
 126                     locks.add(fl);
 127                     break;
 128                 }
 129                 list = current;
 130             }
 131 
 132         }
 133 
 134         // process any stale entries pending in the reference queue
 135         removeStaleEntries();
 136     }
 137 
 138     private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) {
 139         assert Thread.holdsLock(list);
 140         assert lockMap.get(fk) == list;
 141         if (list.isEmpty()) {
 142             lockMap.remove(fk);
 143         }
 144     }
 145 
 146     public void remove(FileLock fl) {
 147         assert fl != null;
 148 
 149         // the lock must exist so the list of locks must be present
 150         List<FileLockReference> list = lockMap.get(fileKey);
 151         if (list == null) return;
 152 
 153         synchronized (list) {
 154             int index = 0;
 155             while (index < list.size()) {
 156                 FileLockReference ref = list.get(index);
 157                 FileLock lock = ref.get();
 158                 if (lock == fl) {
 159                     assert (lock != null) && (lock.acquiredBy() == channel);
 160                     ref.clear();
 161                     list.remove(index);
 162                     locks.remove(fl);
 163                     break;
 164                 }
 165                 index++;
 166             }
 167         }
 168     }
 169 
 170     public List<FileLock> removeAll() {
 171         List<FileLock> result = new ArrayList<FileLock>();
 172         List<FileLockReference> list = lockMap.get(fileKey);
 173         if (list != null) {
 174             synchronized (list) {
 175                 int index = 0;
 176                 while (index < list.size()) {
 177                     FileLockReference ref = list.get(index);
 178                     FileLock lock = ref.get();
 179 
 180                     // remove locks obtained by this channel
 181                     if (lock != null && lock.acquiredBy() == channel) {
 182                         // remove the lock from the list
 183                         ref.clear();
 184                         list.remove(index);
 185                         locks.remove(lock);
 186 
 187                         // add to result
 188                         result.add(lock);
 189                     } else {
 190                         index++;
 191                     }
 192                 }
 193 
 194                 // once the lock list is empty we remove it from the map
 195                 removeKeyIfEmpty(fileKey, list);
 196             }
 197         }
 198         return result;
 199     }
 200 
 201     public void replace(FileLock fromLock, FileLock toLock) {
 202         // the lock must exist so there must be a list
 203         List<FileLockReference> list = lockMap.get(fileKey);
 204         assert list != null;
 205 
 206         synchronized (list) {
 207             for (int index=0; index<list.size(); index++) {
 208                 FileLockReference ref = list.get(index);
 209                 FileLock lock = ref.get();
 210                 if (lock == fromLock) {
 211                     ref.clear();
 212                     locks.remove(fromLock);
 213                     list.set(index, new FileLockReference(toLock, queue, fileKey));
 214                     locks.add(toLock);
 215                     break;
 216                 }
 217             }
 218         }
 219     }
 220 
 221     // Check for overlapping file locks
 222     private void checkList(List<FileLockReference> list, long position, long size)
 223         throws OverlappingFileLockException
 224     {
 225         assert Thread.holdsLock(list);
 226         for (FileLockReference ref: list) {
 227             FileLock fl = ref.get();
 228             if (fl != null && fl.overlaps(position, size))
 229                 throw new OverlappingFileLockException();
 230         }
 231     }
 232 
 233     // Process the reference queue
 234     private void removeStaleEntries() {
 235         FileLockReference ref;
 236         while ((ref = (FileLockReference)queue.poll()) != null) {
 237             FileKey fk = ref.fileKey();
 238             List<FileLockReference> list = lockMap.get(fk);
 239             if (list != null) {
 240                 synchronized (list) {
 241                     list.remove(ref);
 242                     removeKeyIfEmpty(fk, list);
 243                 }
 244             }
 245         }
 246     }
 247 }