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 /** 36 * A file lock table that is over a system-wide map of all file locks. 37 */ 38 class FileLockTable { 39 /** 40 * A weak reference to a FileLock. 41 * <p> 42 * FileLockTable uses a list of file lock references to avoid keeping the 43 * FileLock (and FileChannel) alive. 44 */ 45 private static class FileLockReference extends WeakReference<FileLock> { 46 private FileKey fileKey; 47 48 FileLockReference(FileLock referent, 49 ReferenceQueue<FileLock> queue, 50 FileKey key) { 51 super(referent, queue); 52 this.fileKey = key; 53 } 54 55 FileKey fileKey() { 56 return fileKey; 57 } 58 } 59 60 // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey. 61 // The map value is a list of file locks represented by FileLockReferences. 62 // All access to the list must be synchronized on the list. 63 private static ConcurrentHashMap<FileKey, List<FileLockReference>> lockMap = 64 new ConcurrentHashMap<FileKey, List<FileLockReference>>(); 65 66 // reference queue for cleared refs 67 private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>(); 68 69 // The connection to which this table is connected 70 private final Channel channel; 71 72 // File key for the file that this channel is connected to 73 private final FileKey fileKey; 74 75 // Locks obtained for this channel 76 private final Set<FileLock> locks; 77 78 /** 79 * Creates and returns a file lock table for a channel that is connected to 80 * the a system-wide map of all file locks for the Java virtual machine. 81 */ 82 public static FileLockTable newFileLockTable(Channel channel, 83 FileDescriptor fd) 84 throws IOException 85 { 86 return new FileLockTable(channel, fd); 87 } 88 89 FileLockTable(Channel channel, FileDescriptor fd) throws IOException { 90 this.channel = channel; 91 this.fileKey = FileKey.create(fd); 92 this.locks = ConcurrentHashMap.newKeySet(); 93 } 94 95 public void add(FileLock fl) throws OverlappingFileLockException { 96 List<FileLockReference> list = lockMap.get(fileKey); 97 98 for (;;) { 99 100 // The key isn't in the map so we try to create it atomically 101 if (list == null) { 102 list = new ArrayList<FileLockReference>(2); 103 List<FileLockReference> prev; 104 synchronized (list) { 105 prev = lockMap.putIfAbsent(fileKey, list); 106 if (prev == null) { 107 // we successfully created the key so we add the file lock 108 list.add(new FileLockReference(fl, queue, fileKey)); 109 locks.add(fl); 110 break; 111 } 112 } 113 // someone else got there first 114 list = prev; 115 } 116 117 // There is already a key. It is possible that some other thread 118 // is removing it so we re-fetch the value from the map. If it 119 // hasn't changed then we check the list for overlapping locks 120 // and add the new lock to the list. 121 synchronized (list) { 122 List<FileLockReference> current = lockMap.get(fileKey); 123 if (list == current) { 124 checkList(list, fl.position(), fl.size()); 125 list.add(new FileLockReference(fl, queue, fileKey)); 126 locks.add(fl); 127 break; 128 } 129 list = current; 130 } 131 132 } 133 134 // process any stale entries pending in the reference queue 135 removeStaleEntries(); 136 } 137 138 private void removeKeyIfEmpty(FileKey fk, List<FileLockReference> list) { 139 assert Thread.holdsLock(list); 140 assert lockMap.get(fk) == list; 141 if (list.isEmpty()) { 142 lockMap.remove(fk); 143 } 144 } 145 146 public void remove(FileLock fl) { 147 assert fl != null; 148 149 // the lock must exist so the list of locks must be present 150 List<FileLockReference> list = lockMap.get(fileKey); 151 if (list == null) return; 152 153 synchronized (list) { 154 int index = 0; 155 while (index < list.size()) { 156 FileLockReference ref = list.get(index); 157 FileLock lock = ref.get(); 158 if (lock == fl) { 159 assert (lock != null) && (lock.acquiredBy() == channel); 160 ref.clear(); 161 list.remove(index); 162 locks.remove(fl); 163 break; 164 } 165 index++; 166 } 167 } 168 } 169 170 public List<FileLock> removeAll() { 171 List<FileLock> result = new ArrayList<FileLock>(); 172 List<FileLockReference> list = lockMap.get(fileKey); 173 if (list != null) { 174 synchronized (list) { 175 int index = 0; 176 while (index < list.size()) { 177 FileLockReference ref = list.get(index); 178 FileLock lock = ref.get(); 179 180 // remove locks obtained by this channel 181 if (lock != null && lock.acquiredBy() == channel) { 182 // remove the lock from the list 183 ref.clear(); 184 list.remove(index); 185 locks.remove(lock); 186 187 // add to result 188 result.add(lock); 189 } else { 190 index++; 191 } 192 } 193 194 // once the lock list is empty we remove it from the map 195 removeKeyIfEmpty(fileKey, list); 196 } 197 } 198 return result; 199 } 200 201 public void replace(FileLock fromLock, FileLock toLock) { 202 // the lock must exist so there must be a list 203 List<FileLockReference> list = lockMap.get(fileKey); 204 assert list != null; 205 206 synchronized (list) { 207 for (int index=0; index<list.size(); index++) { 208 FileLockReference ref = list.get(index); 209 FileLock lock = ref.get(); 210 if (lock == fromLock) { 211 ref.clear(); 212 locks.remove(fromLock); 213 list.set(index, new FileLockReference(toLock, queue, fileKey)); 214 locks.add(toLock); 215 break; 216 } 217 } 218 } 219 } 220 221 // Check for overlapping file locks 222 private void checkList(List<FileLockReference> list, long position, long size) 223 throws OverlappingFileLockException 224 { 225 assert Thread.holdsLock(list); 226 for (FileLockReference ref: list) { 227 FileLock fl = ref.get(); 228 if (fl != null && fl.overlaps(position, size)) 229 throw new OverlappingFileLockException(); 230 } 231 } 232 233 // Process the reference queue 234 private void removeStaleEntries() { 235 FileLockReference ref; 236 while ((ref = (FileLockReference)queue.poll()) != null) { 237 FileKey fk = ref.fileKey(); 238 List<FileLockReference> list = lockMap.get(fk); 239 if (list != null) { 240 synchronized (list) { 241 list.remove(ref); 242 removeKeyIfEmpty(fk, list); 243 } 244 } 245 } 246 } 247 }