EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.store]

COVERAGE SUMMARY FOR SOURCE FILE [FileLock.java]

nameclass, %method, %block, %line, %
FileLock.java100% (1/1)89%  (16/18)61%  (604/986)64%  (166/261)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FileLock100% (1/1)89%  (16/18)61%  (604/986)64%  (166/261)
getExceptionFatal (String, Throwable): DbException 0%   (0/1)0%   (0/10)0%   (0/1)
lockSerialized (): void 0%   (0/1)0%   (0/38)0%   (0/12)
lockSocket (): void 100% (1/1)33%  (79/237)30%  (17/56)
sleep (int): void 100% (1/1)50%  (5/10)60%  (3/5)
getExceptionAlreadyInUse (String): DbException 100% (1/1)51%  (19/37)70%  (7/10)
unlock (): void 100% (1/1)53%  (50/94)61%  (15.3/25)
setProperty (String, String): void 100% (1/1)60%  (9/15)75%  (3/4)
load (): Properties 100% (1/1)62%  (28/45)62%  (5.6/9)
save (): Properties 100% (1/1)67%  (37/55)77%  (7.7/10)
waitUntilOld (): void 100% (1/1)69%  (35/51)69%  (11/16)
lock (int): void 100% (1/1)74%  (17/23)75%  (9/12)
run (): void 100% (1/1)76%  (54/71)70%  (15.4/22)
lockFile (): void 100% (1/1)83%  (104/126)86%  (24/28)
getFileLockMethod (String): int 100% (1/1)83%  (30/36)82%  (9/11)
checkServer (): void 100% (1/1)99%  (91/92)97%  (28/29)
FileLock (TraceSystem, String, int): void 100% (1/1)100% (18/18)100% (5/5)
getUniqueId (): String 100% (1/1)100% (3/3)100% (1/1)
setUniqueId (): void 100% (1/1)100% (25/25)100% (5/5)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.store;
7 
8import java.io.IOException;
9import java.io.OutputStream;
10import java.net.BindException;
11import java.net.ConnectException;
12import java.net.InetAddress;
13import java.net.ServerSocket;
14import java.net.Socket;
15import java.net.UnknownHostException;
16import java.util.Properties;
17import org.h2.Driver;
18import org.h2.api.ErrorCode;
19import org.h2.engine.Constants;
20import org.h2.engine.SessionRemote;
21import org.h2.message.DbException;
22import org.h2.message.Trace;
23import org.h2.message.TraceSystem;
24import org.h2.store.fs.FileUtils;
25import org.h2.util.MathUtils;
26import org.h2.util.NetUtils;
27import org.h2.util.SortedProperties;
28import org.h2.util.StringUtils;
29import org.h2.value.Transfer;
30 
31/**
32 * The file lock is used to lock a database so that only one process can write
33 * to it. It uses a cooperative locking protocol. Usually a .lock.db file is
34 * used, but locking by creating a socket is supported as well.
35 */
36public class FileLock implements Runnable {
37 
38    /**
39     * This locking method means no locking is used at all.
40     */
41    public static final int LOCK_NO = 0;
42 
43    /**
44     * This locking method means the cooperative file locking protocol should be
45     * used.
46     */
47    public static final int LOCK_FILE = 1;
48 
49    /**
50     * This locking method means a socket is created on the given machine.
51     */
52    public static final int LOCK_SOCKET = 2;
53 
54    /**
55     * This locking method means multiple writers are allowed, and they
56     * synchronize themselves.
57     */
58    public static final int LOCK_SERIALIZED = 3;
59 
60    /**
61     * Use the file system to lock the file; don't use a separate lock file.
62     */
63    public static final int LOCK_FS = 4;
64 
65    private static final String MAGIC = "FileLock";
66    private static final String FILE = "file";
67    private static final String SOCKET = "socket";
68    private static final String SERIALIZED = "serialized";
69    private static final int RANDOM_BYTES = 16;
70    private static final int SLEEP_GAP = 25;
71    private static final int TIME_GRANULARITY = 2000;
72 
73    /**
74     * The lock file name.
75     */
76    private volatile String fileName;
77 
78    /**
79     * The server socket (only used when using the SOCKET mode).
80     */
81    private volatile ServerSocket serverSocket;
82 
83    /**
84     * Whether the file is locked.
85     */
86    private volatile boolean locked;
87 
88    /**
89     * The number of milliseconds to sleep after checking a file.
90     */
91    private final int sleep;
92 
93    /**
94     * The trace object.
95     */
96    private final Trace trace;
97 
98    /**
99     * The last time the lock file was written.
100     */
101    private long lastWrite;
102 
103    private String method, ipAddress;
104    private Properties properties;
105    private String uniqueId;
106    private Thread watchdog;
107 
108    /**
109     * Create a new file locking object.
110     *
111     * @param traceSystem the trace system to use
112     * @param fileName the file name
113     * @param sleep the number of milliseconds to sleep
114     */
115    public FileLock(TraceSystem traceSystem, String fileName, int sleep) {
116        this.trace = traceSystem == null ?
117                null : traceSystem.getTrace(Trace.FILE_LOCK);
118        this.fileName = fileName;
119        this.sleep = sleep;
120    }
121 
122    /**
123     * Lock the file if possible. A file may only be locked once.
124     *
125     * @param fileLockMethod the file locking method to use
126     * @throws DbException if locking was not successful
127     */
128    public synchronized void lock(int fileLockMethod) {
129        checkServer();
130        if (locked) {
131            DbException.throwInternalError("already locked");
132        }
133        switch (fileLockMethod) {
134        case LOCK_FILE:
135            lockFile();
136            break;
137        case LOCK_SOCKET:
138            lockSocket();
139            break;
140        case LOCK_SERIALIZED:
141            lockSerialized();
142            break;
143        case LOCK_FS:
144            break;
145        }
146        locked = true;
147    }
148 
149    /**
150     * Unlock the file. The watchdog thread is stopped. This method does nothing
151     * if the file is already unlocked.
152     */
153    public synchronized void unlock() {
154        if (!locked) {
155            return;
156        }
157        locked = false;
158        try {
159            if (watchdog != null) {
160                watchdog.interrupt();
161            }
162        } catch (Exception e) {
163            trace.debug(e, "unlock");
164        }
165        try {
166            if (fileName != null) {
167                if (load().equals(properties)) {
168                    FileUtils.delete(fileName);
169                }
170            }
171            if (serverSocket != null) {
172                serverSocket.close();
173            }
174        } catch (Exception e) {
175            trace.debug(e, "unlock");
176        } finally {
177            fileName = null;
178            serverSocket = null;
179        }
180        try {
181            if (watchdog != null) {
182                watchdog.join();
183            }
184        } catch (Exception e) {
185            trace.debug(e, "unlock");
186        } finally {
187            watchdog = null;
188        }
189    }
190 
191    /**
192     * Add or change a setting to the properties. This call does not save the
193     * file.
194     *
195     * @param key the key
196     * @param value the value
197     */
198    public void setProperty(String key, String value) {
199        if (value == null) {
200            properties.remove(key);
201        } else {
202            properties.put(key, value);
203        }
204    }
205 
206    /**
207     * Save the lock file.
208     *
209     * @return the saved properties
210     */
211    public Properties save() {
212        try {
213            OutputStream out = FileUtils.newOutputStream(fileName, false);
214            try {
215                properties.store(out, MAGIC);
216            } finally {
217                out.close();
218            }
219            lastWrite = FileUtils.lastModified(fileName);
220            if (trace.isDebugEnabled()) {
221                trace.debug("save " + properties);
222            }
223            return properties;
224        } catch (IOException e) {
225            throw getExceptionFatal("Could not save properties " + fileName, e);
226        }
227    }
228 
229    private void checkServer() {
230        Properties prop = load();
231        String server = prop.getProperty("server");
232        if (server == null) {
233            return;
234        }
235        boolean running = false;
236        String id = prop.getProperty("id");
237        try {
238            Socket socket = NetUtils.createSocket(server,
239                    Constants.DEFAULT_TCP_PORT, false);
240            Transfer transfer = new Transfer(null);
241            transfer.setSocket(socket);
242            transfer.init();
243            transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
244            transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_15);
245            transfer.writeString(null);
246            transfer.writeString(null);
247            transfer.writeString(id);
248            transfer.writeInt(SessionRemote.SESSION_CHECK_KEY);
249            transfer.flush();
250            int state = transfer.readInt();
251            if (state == SessionRemote.STATUS_OK) {
252                running = true;
253            }
254            transfer.close();
255            socket.close();
256        } catch (IOException e) {
257            return;
258        }
259        if (running) {
260            DbException e = DbException.get(
261                    ErrorCode.DATABASE_ALREADY_OPEN_1, "Server is running");
262            throw e.addSQL(server + "/" + id);
263        }
264    }
265 
266    /**
267     * Load the properties file.
268     *
269     * @return the properties
270     */
271    public Properties load() {
272        IOException lastException = null;
273        for (int i = 0; i < 5; i++) {
274            try {
275                Properties p2 = SortedProperties.loadProperties(fileName);
276                if (trace.isDebugEnabled()) {
277                    trace.debug("load " + p2);
278                }
279                return p2;
280            } catch (IOException e) {
281                lastException = e;
282            }
283        }
284        throw getExceptionFatal(
285                "Could not load properties " + fileName, lastException);
286    }
287 
288    private void waitUntilOld() {
289        for (int i = 0; i < 2 * TIME_GRANULARITY / SLEEP_GAP; i++) {
290            long last = FileUtils.lastModified(fileName);
291            long dist = System.currentTimeMillis() - last;
292            if (dist < -TIME_GRANULARITY) {
293                // lock file modified in the future -
294                // wait for a bit longer than usual
295                try {
296                    Thread.sleep(2 * (long) sleep);
297                } catch (Exception e) {
298                    trace.debug(e, "sleep");
299                }
300                return;
301            } else if (dist > TIME_GRANULARITY) {
302                return;
303            }
304            try {
305                Thread.sleep(SLEEP_GAP);
306            } catch (Exception e) {
307                trace.debug(e, "sleep");
308            }
309        }
310        throw getExceptionFatal("Lock file recently modified", null);
311    }
312 
313    private void setUniqueId() {
314        byte[] bytes = MathUtils.secureRandomBytes(RANDOM_BYTES);
315        String random = StringUtils.convertBytesToHex(bytes);
316        uniqueId = Long.toHexString(System.currentTimeMillis()) + random;
317        properties.setProperty("id", uniqueId);
318    }
319 
320    private void lockSerialized() {
321        method = SERIALIZED;
322        FileUtils.createDirectories(FileUtils.getParent(fileName));
323        if (FileUtils.createFile(fileName)) {
324            properties = new SortedProperties();
325            properties.setProperty("method", String.valueOf(method));
326            setUniqueId();
327            save();
328        } else {
329            while (true) {
330                try {
331                    properties = load();
332                } catch (DbException e) {
333                    // ignore
334                }
335                return;
336            }
337        }
338    }
339 
340    private void lockFile() {
341        method = FILE;
342        properties = new SortedProperties();
343        properties.setProperty("method", String.valueOf(method));
344        setUniqueId();
345        FileUtils.createDirectories(FileUtils.getParent(fileName));
346        if (!FileUtils.createFile(fileName)) {
347            waitUntilOld();
348            String m2 = load().getProperty("method", FILE);
349            if (!m2.equals(FILE)) {
350                throw getExceptionFatal("Unsupported lock method " + m2, null);
351            }
352            save();
353            sleep(2 * sleep);
354            if (!load().equals(properties)) {
355                throw getExceptionAlreadyInUse("Locked by another process");
356            }
357            FileUtils.delete(fileName);
358            if (!FileUtils.createFile(fileName)) {
359                throw getExceptionFatal("Another process was faster", null);
360            }
361        }
362        save();
363        sleep(SLEEP_GAP);
364        if (!load().equals(properties)) {
365            fileName = null;
366            throw getExceptionFatal("Concurrent update", null);
367        }
368        watchdog = new Thread(this, "H2 File Lock Watchdog " + fileName);
369        Driver.setThreadContextClassLoader(watchdog);
370        watchdog.setDaemon(true);
371        watchdog.setPriority(Thread.MAX_PRIORITY - 1);
372        watchdog.start();
373    }
374 
375    private void lockSocket() {
376        method = SOCKET;
377        properties = new SortedProperties();
378        properties.setProperty("method", String.valueOf(method));
379        setUniqueId();
380        // if this returns 127.0.0.1,
381        // the computer is probably not networked
382        ipAddress = NetUtils.getLocalAddress();
383        FileUtils.createDirectories(FileUtils.getParent(fileName));
384        if (!FileUtils.createFile(fileName)) {
385            waitUntilOld();
386            long read = FileUtils.lastModified(fileName);
387            Properties p2 = load();
388            String m2 = p2.getProperty("method", SOCKET);
389            if (m2.equals(FILE)) {
390                lockFile();
391                return;
392            } else if (!m2.equals(SOCKET)) {
393                throw getExceptionFatal("Unsupported lock method " + m2, null);
394            }
395            String ip = p2.getProperty("ipAddress", ipAddress);
396            if (!ipAddress.equals(ip)) {
397                throw getExceptionAlreadyInUse("Locked by another computer: " + ip);
398            }
399            String port = p2.getProperty("port", "0");
400            int portId = Integer.parseInt(port);
401            InetAddress address;
402            try {
403                address = InetAddress.getByName(ip);
404            } catch (UnknownHostException e) {
405                throw getExceptionFatal("Unknown host " + ip, e);
406            }
407            for (int i = 0; i < 3; i++) {
408                try {
409                    Socket s = new Socket(address, portId);
410                    s.close();
411                    throw getExceptionAlreadyInUse("Locked by another process");
412                } catch (BindException e) {
413                    throw getExceptionFatal("Bind Exception", null);
414                } catch (ConnectException e) {
415                    trace.debug(e, "socket not connected to port " + port);
416                } catch (IOException e) {
417                    throw getExceptionFatal("IOException", null);
418                }
419            }
420            if (read != FileUtils.lastModified(fileName)) {
421                throw getExceptionFatal("Concurrent update", null);
422            }
423            FileUtils.delete(fileName);
424            if (!FileUtils.createFile(fileName)) {
425                throw getExceptionFatal("Another process was faster", null);
426            }
427        }
428        try {
429            // 0 to use any free port
430            serverSocket = NetUtils.createServerSocket(0, false);
431            int port = serverSocket.getLocalPort();
432            properties.setProperty("ipAddress", ipAddress);
433            properties.setProperty("port", String.valueOf(port));
434        } catch (Exception e) {
435            trace.debug(e, "lock");
436            serverSocket = null;
437            lockFile();
438            return;
439        }
440        save();
441        watchdog = new Thread(this,
442                "H2 File Lock Watchdog (Socket) " + fileName);
443        watchdog.setDaemon(true);
444        watchdog.start();
445    }
446 
447    private static void sleep(int time) {
448        try {
449            Thread.sleep(time);
450        } catch (InterruptedException e) {
451            throw getExceptionFatal("Sleep interrupted", e);
452        }
453    }
454 
455    private static DbException getExceptionFatal(String reason, Throwable t) {
456        return DbException.get(
457                ErrorCode.ERROR_OPENING_DATABASE_1, t, reason);
458    }
459 
460    private DbException getExceptionAlreadyInUse(String reason) {
461        DbException e = DbException.get(
462                ErrorCode.DATABASE_ALREADY_OPEN_1, reason);
463        if (fileName != null) {
464            try {
465                Properties prop = load();
466                String server = prop.getProperty("server");
467                if (server != null) {
468                    String serverId = server + "/" + prop.getProperty("id");
469                    e = e.addSQL(serverId);
470                }
471            } catch (DbException e2) {
472                // ignore
473            }
474        }
475        return e;
476    }
477 
478    /**
479     * Get the file locking method type given a method name.
480     *
481     * @param method the method name
482     * @return the method type
483     * @throws DbException if the method name is unknown
484     */
485    public static int getFileLockMethod(String method) {
486        if (method == null || method.equalsIgnoreCase("FILE")) {
487            return FileLock.LOCK_FILE;
488        } else if (method.equalsIgnoreCase("NO")) {
489            return FileLock.LOCK_NO;
490        } else if (method.equalsIgnoreCase("SOCKET")) {
491            return FileLock.LOCK_SOCKET;
492        } else if (method.equalsIgnoreCase("SERIALIZED")) {
493            return FileLock.LOCK_SERIALIZED;
494        } else if (method.equalsIgnoreCase("FS")) {
495            return FileLock.LOCK_FS;
496        } else {
497            throw DbException.get(
498                    ErrorCode.UNSUPPORTED_LOCK_METHOD_1, method);
499        }
500    }
501 
502    public String getUniqueId() {
503        return uniqueId;
504    }
505 
506    @Override
507    public void run() {
508        try {
509            while (locked && fileName != null) {
510                // trace.debug("watchdog check");
511                try {
512                    if (!FileUtils.exists(fileName) ||
513                            FileUtils.lastModified(fileName) != lastWrite) {
514                        save();
515                    }
516                    Thread.sleep(sleep);
517                } catch (OutOfMemoryError e) {
518                    // ignore
519                } catch (InterruptedException e) {
520                    // ignore
521                } catch (NullPointerException e) {
522                    // ignore
523                } catch (Exception e) {
524                    trace.debug(e, "watchdog");
525                }
526            }
527            while (serverSocket != null) {
528                try {
529                    trace.debug("watchdog accept");
530                    Socket s = serverSocket.accept();
531                    s.close();
532                } catch (Exception e) {
533                    trace.debug(e, "watchdog");
534                }
535            }
536        } catch (Exception e) {
537            trace.debug(e, "watchdog");
538        }
539        trace.debug("watchdog end");
540    }
541 
542}

[all classes][org.h2.store]
EMMA 2.0.5312 (C) Vladimir Roubtsov