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 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 final FileKey fileKey;
  90         final long position;
  91         final long size;
  92         private volatile boolean invalid;
  93 
  94         FileLockReference(FileLock referent,
  95                           ReferenceQueue<FileLock> queue,
  96                           FileKey key) {
  97             super(referent, queue);
  98             this.fileKey = key;
  99             this.position = referent.position();
 100             this.size = referent.size();
 101         }
 102 
 103         FileKey fileKey() {
 104             return fileKey;
 105         }
 106 
 107         void invalidate() {
 108             invalid = true;
 109         }
 110 
 111         boolean isValid() {
 112             return !invalid;
 113         }
 114 
 115         boolean overlaps(long position, long size) {
 116             if (position + size <= this.position)
 117                 return false;               // That is below this
 118             if (this.position + this.size <= position)
 119                 return false;               // This is below that
 120             return true;
 121         }
 122     }
 123 
 124     // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
 125     // The map value is a list of file locks represented by FileLockReferences.
 126     // All access to the list must be synchronized on the list.
 127     private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap =
 128         new ConcurrentHashMap<FileKey, List<FileLockReference>>();
 129 
 130     // reference queue for cleared refs
 131     private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>();
 132 
 133     // The connection to which this table is connected
 134     private final Channel channel;
 135 
 136     // File key for the file that this channel is connected to
 137     private final FileKey fileKey;
 138 
 139     SharedFileLockTable(Channel channel, FileDescriptor fd) throws IOException {
 140         this.channel = channel;
 141         this.fileKey = FileKey.create(fd);
 142     }
 143 
 144     @Override
 145     public void add(FileLock fl) throws OverlappingFileLockException {
 146         List<FileLockReference> list = lockMap.get(fileKey);
 147 
 148         for (;;) {
 149 
 150             // The key isn't in the map so we try to create it atomically
 151             if (list == null) {
 152                 list = new ArrayList<FileLockReference>(2);
 153                 List<FileLockReference> prev;
 154                 synchronized (list) {
 155                     prev = lockMap.putIfAbsent(fileKey, list);
 156                     if (prev == null) {
 157                         // we successfully created the key so we add the file lock
 158                         list.add(new FileLockReference(fl, queue, fileKey));
 159                         break;
 160                     }
 161                 }
 162                 // someone else got there first
 163                 list = prev;
 164             }
 165 
 166             // There is already a key. It is possible that some other thread
 167             // is removing it so we re-fetch the value from the map. If it
 168             // hasn't changed then we check the list for overlapping locks
 169             // and add the new lock to the list.
 170             synchronized (list) {
 171                 List<FileLockReference> current = lockMap.get(fileKey);
 172                 if (list == current) {
 173                     checkList(list, fl.position(), fl.size());
 174                     list.add(new FileLockReference(fl, queue, fileKey));
 175                     break;
 176                 }
 177                 list = current;
 178             }
 179 
 180         }
 181 
 182         // process any stale entries pending in the reference queue
 183         removeStaleEntries();
 184     }
 185 
 186     private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) {
 187         assert Thread.holdsLock(list);
 188         assert lockMap.get(fk) == list;
 189         if (list.isEmpty()) {
 190             lockMap.remove(fk);
 191         }
 192     }
 193 
 194     @Override
 195     public void remove(FileLock fl) {
 196         assert fl != null;
 197 
 198         // the lock must exist so the list of locks must be present
 199         List<FileLockReference> list = lockMap.get(fileKey);
 200         if (list == null) return;
 201 
 202         synchronized (list) {
 203             int index = 0;
 204             while (index < list.size()) {
 205                 FileLockReference ref = list.get(index);
 206                 FileLock lock = ref.get();
 207                 if (lock == fl) {
 208                     assert (lock != null) && (lock.acquiredBy() == channel);
 209                     ref.clear();
 210                     ref.invalidate();
 211                     list.remove(index);
 212                     break;
 213                 }
 214                 index++;
 215             }
 216         }
 217     }
 218 
 219     @Override
 220     public List<FileLock> removeAll() {
 221         List<FileLock> result = new ArrayList<FileLock>();
 222         List<FileLockReference> list = lockMap.get(fileKey);
 223         if (list != null) {
 224             synchronized (list) {
 225                 int index = 0;
 226                 while (index < list.size()) {
 227                     FileLockReference ref = list.get(index);
 228                     FileLock lock = ref.get();
 229 
 230                     // remove locks obtained by this channel
 231                     if (lock != null && lock.acquiredBy() == channel) {
 232                         // remove the lock from the list
 233                         ref.clear();
 234                         ref.invalidate();
 235                         list.remove(index);
 236 
 237                         // add to result
 238                         result.add(lock);
 239                     } else {
 240                         index++;
 241                     }
 242                 }
 243 
 244                 // once the lock list is empty we remove it from the map
 245                 removeKeyIfEmpty(fileKey, list);
 246             }
 247         }
 248         return result;
 249     }
 250 
 251     @Override
 252     public void replace(FileLock fromLock, FileLock toLock) {
 253         // the lock must exist so there must be a list
 254         List<FileLockReference> list = lockMap.get(fileKey);
 255         assert list != null;
 256 
 257         synchronized (list) {
 258             for (int index=0; index<list.size(); index++) {
 259                 FileLockReference ref = list.get(index);
 260                 FileLock lock = ref.get();
 261                 if (lock == fromLock) {
 262                     ref.clear();
 263                     list.set(index, new FileLockReference(toLock, queue, fileKey));
 264                     break;
 265                 }
 266             }
 267         }
 268     }
 269 
 270     // Check for overlapping file locks
 271     private void checkList(List<FileLockReference> list, long position, long size)
 272         throws OverlappingFileLockException
 273     {
 274         assert Thread.holdsLock(list);
 275         for (FileLockReference ref: list) {
 276             FileLock fl = ref.get();
 277             if ((fl != null || ref.isValid()) && ref.overlaps(position, size))
 278                 throw new OverlappingFileLockException();
 279         }
 280     }
 281 
 282     // Process the reference queue
 283     private void removeStaleEntries() {
 284         FileLockReference ref;
 285         while ((ref = (FileLockReference)queue.poll()) != null) {
 286             FileKey fk = ref.fileKey();
 287             List<FileLockReference> list = lockMap.get(fk);
 288             if (list != null) {
 289                 synchronized (list) {
 290                     if (!ref.isValid()) {
 291                         list.remove(ref);
 292                     }
 293                     removeKeyIfEmpty(fk, list);
 294                 }
 295             }
 296         }
 297     }
 298 }