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
26package sun.nio.ch;
27
28import java.nio.channels.*;
29import java.util.*;
30import java.util.concurrent.ConcurrentHashMap;
31import java.lang.ref.*;
32import java.io.FileDescriptor;
33import java.io.IOException;
34
35abstract class 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     */
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     */
56    public abstract void add(FileLock fl) throws OverlappingFileLockException;
57
58    /**
59     * Remove an existing file lock from the table.
60     */
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     */
68    public abstract List<FileLock> removeAll();
69
70    /**
71     * Replaces an existing file lock in the table.
72     */
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 */
80class 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
91        FileLockReference(FileLock referent,
92                          ReferenceQueue<FileLock> queue,
93                          FileKey key) {
94            super(referent, queue);
95            this.fileKey = key;
96        }
97
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
118    SharedFileLockTable(Channel channel, FileDescriptor fd) throws IOException {
119        this.channel = channel;
120        this.fileKey = FileKey.create(fd);
121    }
122
123    @Override
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
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
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
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
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
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
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