KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > h2 > store > FileLock


1 /*
2  * Copyright 2004-2006 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
3  * Initial Developer: H2 Group
4  */

5 package org.h2.store;
6
7 import java.io.File JavaDoc;
8 import java.io.FileOutputStream JavaDoc;
9 import java.io.IOException JavaDoc;
10 import java.net.BindException JavaDoc;
11 import java.net.ConnectException JavaDoc;
12 import java.net.InetAddress JavaDoc;
13 import java.net.ServerSocket JavaDoc;
14 import java.net.Socket JavaDoc;
15 import java.net.UnknownHostException JavaDoc;
16 import java.sql.SQLException JavaDoc;
17 import java.util.Properties JavaDoc;
18
19 import org.h2.engine.Constants;
20 import org.h2.jdbc.JdbcSQLException;
21 import org.h2.message.Message;
22 import org.h2.message.Trace;
23 import org.h2.message.TraceSystem;
24 import org.h2.util.ByteUtils;
25 import org.h2.util.FileUtils;
26 import org.h2.util.RandomUtils;
27
28 /**
29  * @author Thomas
30  */

31 public class FileLock {
32
33     public static final int LOCK_NO = 0, LOCK_FILE = 1, LOCK_SOCKET = 2;
34
35     // TODO lock: maybe not so secure! what if tread does not have chance to run?
36
// TODO lock: implement locking method using java 1.4 filelock
37
private static final String JavaDoc MAGIC = "FileLock";
38     private static final String JavaDoc FILE = "file", SOCKET = "socket";
39     private static final int RANDOM_BYTES = 16;
40     private static final int SLEEP_GAP = 20;
41     private static final int TIME_GRANULARITY = 2000;
42
43     private String JavaDoc method, ipAddress;
44     private int sleep;
45     private long lastWrite;
46     private Properties JavaDoc properties;
47     private volatile String JavaDoc fileName;
48     private volatile ServerSocket JavaDoc socket;
49     private boolean locked;
50     private Trace trace;
51
52     public FileLock(TraceSystem traceSystem, int sleep) {
53         this.trace = traceSystem.getTrace(Trace.FILE_LOCK);
54         this.sleep = sleep;
55     }
56
57     public synchronized void lock(String JavaDoc fileName, boolean allowSocket) throws SQLException JavaDoc {
58         this.fileName = fileName;
59         if (locked) {
60             throw Message.getInternalError("already locked");
61         }
62         if (allowSocket) {
63             lockSocket();
64         } else {
65             lockFile();
66         }
67         locked = true;
68     }
69
70     protected void finalize() {
71         if (!Constants.RUN_FINALIZERS) {
72             return;
73         }
74         if(locked) {
75             unlock();
76         }
77     }
78
79 // void kill() {
80
// socket = null;
81
// file = null;
82
// locked = false;
83
// trace("killed", null);
84
// }
85

86     // TODO log / messages: use translatable messages!
87
public synchronized void unlock() {
88         if(!locked) {
89             return;
90         }
91         try {
92             if (fileName != null) {
93                 if (load().equals(properties)) {
94                     FileUtils.delete(fileName);
95                 }
96             }
97             if (socket != null) {
98                 socket.close();
99             }
100         } catch (Exception JavaDoc e) {
101             trace.debug("unlock", e);
102         }
103         fileName = null;
104         socket = null;
105         locked = false;
106     }
107
108     void save() throws SQLException JavaDoc {
109         try {
110             File JavaDoc file = new File JavaDoc(fileName);
111             // TODO file: delegate to FileUtils
112
FileOutputStream JavaDoc out = FileUtils.openFileOutputStream(file);
113             try {
114                 properties.setProperty("method", String.valueOf(method));
115                 properties.store(out, MAGIC);
116             } finally {
117                 out.close();
118             }
119             lastWrite = file.lastModified();
120             trace.debug("save " + properties);
121         } catch(IOException JavaDoc e) {
122             throw getException(e);
123         }
124     }
125
126     private Properties JavaDoc load() throws SQLException JavaDoc {
127         try {
128             Properties JavaDoc p2 = FileUtils.loadProperties(new File JavaDoc(fileName));
129             trace.debug("load " + p2);
130             return p2;
131         } catch(IOException JavaDoc e) {
132             throw getException(e);
133         }
134     }
135
136     private void waitUntilOld() throws SQLException JavaDoc {
137         File JavaDoc file = new File JavaDoc(fileName);
138         for(int i=0; i<10; i++) {
139             long last = file.lastModified();
140             long dist = System.currentTimeMillis() - last;
141             if(dist < -TIME_GRANULARITY) {
142                 throw error("Lock file modified in the future: dist=" + dist);
143             }
144             if(dist < SLEEP_GAP) {
145                 try {
146                     Thread.sleep(dist+1);
147                 } catch (Exception JavaDoc e) {
148                     trace.debug("sleep", e);
149                 }
150             } else {
151                 return;
152             }
153         }
154         throw error("Lock file recently modified");
155     }
156
157     private void lockFile() throws SQLException JavaDoc {
158         method = FILE;
159         properties = new Properties JavaDoc();
160         String JavaDoc random = ByteUtils.convertBytesToString(RandomUtils.getSecureBytes(RANDOM_BYTES));
161         properties.setProperty("id", Long.toHexString(System.currentTimeMillis())+random);
162         if (!FileUtils.createNewFile(fileName)) {
163             waitUntilOld();
164             String JavaDoc m2 = load().getProperty("method", FILE);
165             if (!m2.equals(FILE)) {
166                 throw error("Unsupported lock method " + m2);
167             }
168             save();
169             sleep(2 * sleep);
170             if (!load().equals(properties)) {
171                 throw error("Locked by another process");
172             }
173             FileUtils.delete(fileName);
174             if (!FileUtils.createNewFile(fileName)) {
175                 throw error("Another process was faster");
176             }
177         }
178         save();
179         sleep(SLEEP_GAP);
180         if (!load().equals(properties)) {
181             fileName = null;
182             throw error("Concurrent update");
183         }
184         Thread JavaDoc watchdog = new Thread JavaDoc(new Runnable JavaDoc() {
185             public void run() {
186                 try {
187                     File JavaDoc file = new File JavaDoc(fileName);
188                     while (fileName != null) {
189                         // trace.debug("watchdog check");
190
try {
191                             if (!file.exists() || file.lastModified() != lastWrite) {
192                                 save();
193                             }
194                             Thread.sleep(sleep);
195                         } catch (Exception JavaDoc e) {
196                             trace.debug("watchdog", e);
197                         }
198                     }
199                 } catch(Exception JavaDoc e) {
200                     trace.debug("watchdog", e);
201                 }
202                 trace.debug("watchdog end");
203             }
204         });
205         watchdog.setName("H2 File Lock Watchdog " + fileName);
206         watchdog.setDaemon(true);
207         watchdog.setPriority(Thread.MAX_PRIORITY-1);
208         watchdog.start();
209     }
210
211     private void lockSocket() throws SQLException JavaDoc {
212         method = SOCKET;
213         properties = new Properties JavaDoc();
214         try {
215             // TODO documentation: if this returns 127.0.0.1, the computer is probably not networked
216
ipAddress = InetAddress.getLocalHost().getHostAddress();
217         } catch (UnknownHostException JavaDoc e) {
218             throw getException(e);
219         }
220         if (!FileUtils.createNewFile(fileName)) {
221             waitUntilOld();
222             File JavaDoc file = new File JavaDoc(fileName);
223             long read = file.lastModified();
224             Properties JavaDoc p2 = load();
225             String JavaDoc m2 = p2.getProperty("method", SOCKET);
226             if (m2.equals(FILE)) {
227                 lockFile();
228                 return;
229             } else if (!m2.equals(SOCKET)) {
230                 throw error("Unsupported lock method " + m2);
231             }
232             String JavaDoc ip = p2.getProperty("ipAddress", ipAddress);
233             if (!ipAddress.equals(ip)) {
234                 throw error("Locked by another computer: " + ip);
235             }
236             String JavaDoc port = p2.getProperty("port", "0");
237             int portId = Integer.parseInt(port);
238             InetAddress JavaDoc address;
239             try {
240                 address = InetAddress.getByName(ip);
241             } catch (UnknownHostException JavaDoc e) {
242                 throw getException(e);
243             }
244             for (int i = 0; i < 3; i++) {
245                 try {
246                     Socket JavaDoc s = new Socket JavaDoc(address, portId);
247                     s.close();
248                     throw error("Locked by another process");
249                 } catch (BindException JavaDoc e) {
250                     throw error("Bind Exception");
251                 } catch (ConnectException JavaDoc e) {
252                     trace.debug("lockSocket not connected " + port, e);
253                 } catch (IOException JavaDoc e) {
254                     throw error("IOException");
255                 }
256             }
257             if (read != file.lastModified()) {
258                 throw error("Concurrent update");
259             }
260             FileUtils.delete(fileName);
261             if (!FileUtils.createNewFile(fileName)) {
262                 throw error("Another process was faster");
263             }
264         }
265         try {
266             // 0 to use any free port
267
socket = new ServerSocket JavaDoc(0);
268             int port = socket.getLocalPort();
269             properties.setProperty("ipAddress", ipAddress);
270             properties.setProperty("port", String.valueOf(port));
271         } catch (Exception JavaDoc e) {
272             trace.debug("lock", e);
273             socket = null;
274             lockFile();
275             return;
276         }
277         save();
278         Thread JavaDoc watchdog = new Thread JavaDoc(new Runnable JavaDoc() {
279             public void run() {
280                 while (socket != null) {
281                     try {
282                         trace.debug("watchdog accept");
283                         Socket JavaDoc s = socket.accept();
284                         s.close();
285                     } catch (Exception JavaDoc e) {
286                         trace.debug("watchdog", e);
287                     }
288                 }
289                 trace.debug("watchdog end");
290             }
291         });
292         watchdog.setDaemon(true);
293         watchdog.setName("H2 File Lock Watchdog (Socket) " + fileName);
294         watchdog.start();
295     }
296
297     private void sleep(int time) throws SQLException JavaDoc {
298         try {
299             Thread.sleep(time);
300         } catch(InterruptedException JavaDoc e) {
301             throw getException(e);
302         }
303     }
304
305     private SQLException JavaDoc getException(Throwable JavaDoc t) {
306         return Message.getSQLException(Message.ERROR_OPENING_DATABASE, null, t);
307     }
308
309     private SQLException JavaDoc error(String JavaDoc reason) {
310         return Message.getSQLException(Message.DATABASE_ALREADY_OPEN_1, reason);
311     }
312     
313     public static int getFileLockMethod(String JavaDoc method) throws JdbcSQLException {
314         if(method == null || method.equalsIgnoreCase("FILE")) {
315             return FileLock.LOCK_FILE;
316         } else if(method.equalsIgnoreCase("NO")) {
317             return FileLock.LOCK_NO;
318         } else if(method.equalsIgnoreCase("SOCKET")) {
319             return FileLock.LOCK_SOCKET;
320         } else {
321             throw Message.getSQLException(Message.UNSUPPORTED_LOCK_METHOD_1, method);
322         }
323     }
324
325 }
326
Popular Tags