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