File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\store\FileLock.java.html

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