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.io.FileDescriptor;
  29 import java.io.IOException;
  30 import java.lang.ref.ReferenceQueue;
  31 import java.lang.ref.WeakReference;
  32 import java.nio.channels.Channel;
  33 import java.nio.channels.FileLock;
  34 import java.nio.channels.OverlappingFileLockException;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import java.util.concurrent.ConcurrentHashMap;
  38 
  39 abstract class FileLockTable {
  40     protected FileLockTable() {
  41     }
  42 
  43     /**
  44      * Creates and returns a file lock table for a channel that is connected to
  45      * the a system-wide map of all file locks for the Java virtual machine.
  46      */
  47     public static FileLockTable newSharedFileLockTable(Channel channel,
  48                                                        FileDescriptor fd)
  49         throws IOException
  50     {
  51         return new SharedFileLockTable(channel, fd);
  52     }
  53 
  54     /**
  55      * Adds a file lock to the table.
  56      *
  57      * @throws OverlappingFileLockException if the file lock overlaps
  58      *         with an existing file lock in the table
  59      */
  60     public abstract void add(FileLock fl) throws OverlappingFileLockException;
  61 
  62     /**
  63      * Remove an existing file lock from the table.
  64      */
  65     public abstract void remove(FileLock fl);
  66 
  67     /**
  68      * Removes all file locks from the table.
  69      *
  70      * @return  The list of file locks removed
  71      */
  72     public abstract List<FileLock> removeAll();
  73 
  74     /**
  75      * Replaces an existing file lock in the table.
  76      */
  77     public abstract void replace(FileLock fl1, FileLock fl2);
  78 }
  79 
  80 
  81 /**
  82  * A file lock table that is over a system-wide map of all file locks.
  83  */
  84 class SharedFileLockTable extends FileLockTable {
  85 
  86     /**
  87      * A weak reference to a FileLock.
  88      * <p>
  89      * SharedFileLockTable uses a list of file lock references to avoid keeping the
  90      * FileLock (and FileChannel) alive.
  91      */
  92     private static class FileLockReference extends WeakReference<FileLock>
  93         implements FileLockListener {
  94         private FileKey fileKey;
  95 
  96         // Mirror of FileLock state for use when the lock object is no longer
  97         // weakly reachable but the native lock has not yet been released.
  98         private final long position;
  99         private final long size;
 100         private boolean isLockReleased;
 101 
 102         FileLockReference(FileLock referent,
 103                           ReferenceQueue<FileLock> queue,
 104                           FileKey key) {
 105             super(referent, queue);
 106             this.fileKey = key;
 107             this.position = referent.position();
 108             this.size = referent.size();
 109             this.isLockReleased = !referent.isValid();
 110             if (referent instanceof FileLockImpl) {
 111                 ((FileLockImpl)referent).setFileLockListener(this);
 112             }
 113         }
 114 
 115         FileKey fileKey() {
 116             return fileKey;
 117         }
 118 
 119         // FileLockListener implementation
 120         public void invalidate() {
 121             this.isLockReleased = true;
 122         }
 123 
 124         boolean isLockReleased() {
 125             return this.isLockReleased;
 126         }
 127 
 128         // Copy of FileLock.overlaps(long,long)
 129         boolean overlaps(long position, long size) {
 130             if (position + size <= this.position)
 131                 return false;               // That is below this
 132             if (this.position + this.size <= position)
 133                 return false;               // This is below that
 134             return true;
 135         }
 136     }
 137 
 138     // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
 139     // The map value is a list of file locks represented by FileLockReferences.
 140     // All access to the list must be synchronized on the list.
 141     private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap =
 142         new ConcurrentHashMap<FileKey, List<FileLockReference>>();
 143 
 144     // reference queue for cleared refs
 145     private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>();
 146 
 147     // The connection to which this table is connected
 148     private final Channel channel;
 149 
 150     // File key for the file that this channel is connected to
 151     private final FileKey fileKey;
 152 
 153     SharedFileLockTable(Channel channel, FileDescriptor fd) throws IOException {
 154         this.channel = channel;
 155         this.fileKey = FileKey.create(fd);
 156     }
 157 
 158     @Override
 159     public void add(FileLock fl) throws OverlappingFileLockException {
 160         List<FileLockReference> list = lockMap.get(fileKey);
 161 
 162         for (;;) {
 163 
 164             // The key isn't in the map so we try to create it atomically
 165             if (list == null) {
 166                 list = new ArrayList<FileLockReference>(2);
 167                 List<FileLockReference> prev;
 168                 synchronized (list) {
 169                     prev = lockMap.putIfAbsent(fileKey, list);
 170                     if (prev == null) {
 171                         // we successfully created the key so we add the file lock
 172                         list.add(new FileLockReference(fl, queue, fileKey));
 173                         break;
 174                     }
 175                 }
 176                 // someone else got there first
 177                 list = prev;
 178             }
 179 
 180             // There is already a key. It is possible that some other thread
 181             // is removing it so we re-fetch the value from the map. If it
 182             // hasn't changed then we check the list for overlapping locks
 183             // and add the new lock to the list.
 184             synchronized (list) {
 185                 List<FileLockReference> current = lockMap.get(fileKey);
 186                 if (list == current) {
 187                     checkList(list, fl.position(), fl.size());
 188                     list.add(new FileLockReference(fl, queue, fileKey));
 189                     break;
 190                 }
 191                 list = current;
 192             }
 193 
 194         }
 195 
 196         // process any stale entries pending in the reference queue
 197         removeStaleEntries();
 198     }
 199 
 200     private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) {
 201         assert Thread.holdsLock(list);
 202         assert lockMap.get(fk) == list;
 203         if (list.isEmpty()) {
 204             lockMap.remove(fk);
 205         }
 206     }
 207 
 208     @Override
 209     public void remove(FileLock fl) {
 210         assert fl != null;
 211 
 212         // the lock must exist so the list of locks must be present
 213         List<FileLockReference> list = lockMap.get(fileKey);
 214         if (list == null) return;
 215 
 216         synchronized (list) {
 217             int index = 0;
 218             while (index < list.size()) {
 219                 FileLockReference ref = list.get(index);
 220                 FileLock lock = ref.get();
 221                 if (lock == fl) {
 222                     assert (lock != null) && (lock.acquiredBy() == channel);
 223                     ref.clear();
 224                     list.remove(index);
 225                     break;
 226                 }
 227                 index++;
 228             }
 229         }
 230     }
 231 
 232     @Override
 233     public List<FileLock> removeAll() {
 234         List<FileLock> result = new ArrayList<FileLock>();
 235         List<FileLockReference> list = lockMap.get(fileKey);
 236         if (list != null) {
 237             synchronized (list) {
 238                 int index = 0;
 239                 while (index < list.size()) {
 240                     FileLockReference ref = list.get(index);
 241                     FileLock lock = ref.get();
 242 
 243                     // remove locks obtained by this channel
 244                     if (lock != null && lock.acquiredBy() == channel) {
 245                         // remove the lock from the list
 246                         ref.clear();
 247                         list.remove(index);
 248 
 249                         // add to result
 250                         result.add(lock);
 251                     } else if (lock == null && !ref.isLockReleased()) {
 252                         ref.clear();
 253                         list.remove(index);
 254                     } else {
 255                         index++;
 256                     }
 257                 }
 258 
 259                 // once the lock list is empty we remove it from the map
 260                 removeKeyIfEmpty(fileKey, list);
 261             }
 262         }
 263         return result;
 264     }
 265 
 266     @Override
 267     public void replace(FileLock fromLock, FileLock toLock) {
 268         // the lock must exist so there must be a list
 269         List<FileLockReference> list = lockMap.get(fileKey);
 270         assert list != null;
 271 
 272         synchronized (list) {
 273             for (int index=0; index<list.size(); index++) {
 274                 FileLockReference ref = list.get(index);
 275                 FileLock lock = ref.get();
 276                 if (lock == fromLock) {
 277                     ref.clear();
 278                     list.set(index, new FileLockReference(toLock, queue, fileKey));
 279                     break;
 280                 }
 281             }
 282         }
 283     }
 284 
 285     // Check for overlapping file locks
 286     private void checkList(List<FileLockReference> list, long position, long size)
 287         throws OverlappingFileLockException
 288     {
 289         assert Thread.holdsLock(list);
 290         for (FileLockReference ref: list) {
 291             FileLock fl = ref.get();
 292             // Check for overlap if the FileLock instance has not been collected
 293             // or the underlying lock has not been released to the file system.
 294             if ((fl != null || !ref.isLockReleased())
 295                 && ref.overlaps(position, size)) {
 296                 throw new OverlappingFileLockException();
 297             }
 298         }
 299     }
 300 
 301     // Process the reference queue
 302     private void removeStaleEntries() {
 303         FileLockReference ref;
 304         while ((ref = (FileLockReference)queue.poll()) != null) {
 305             FileKey fk = ref.fileKey();
 306             List<FileLockReference> list = lockMap.get(fk);
 307             if (list != null) {
 308                 synchronized (list) {
 309                     // Retain the reference in the list if it refers to a
 310                     // FileLock which was collected without being released.
 311                     if (ref.isLockReleased()) {
 312                         list.remove(ref);
 313                     }
 314                     removeKeyIfEmpty(fk, list);
 315                 }
 316             }
 317         }
 318     }
 319 }