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