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