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 }