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