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 }