1 5 package org.h2.store; 6 7 import java.io.File ; 8 import java.io.FileOutputStream ; 9 import java.io.IOException ; 10 import java.net.BindException ; 11 import java.net.ConnectException ; 12 import java.net.InetAddress ; 13 import java.net.ServerSocket ; 14 import java.net.Socket ; 15 import java.net.UnknownHostException ; 16 import java.sql.SQLException ; 17 import java.util.Properties ; 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 31 public class FileLock { 32 33 public static final int LOCK_NO = 0, LOCK_FILE = 1, LOCK_SOCKET = 2; 34 35 private static final String MAGIC = "FileLock"; 38 private static final String 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 method, ipAddress; 44 private int sleep; 45 private long lastWrite; 46 private Properties properties; 47 private volatile String fileName; 48 private volatile ServerSocket 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 fileName, boolean allowSocket) throws SQLException { 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 86 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 e) { 101 trace.debug("unlock", e); 102 } 103 fileName = null; 104 socket = null; 105 locked = false; 106 } 107 108 void save() throws SQLException { 109 try { 110 File file = new File (fileName); 111 FileOutputStream 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 e) { 122 throw getException(e); 123 } 124 } 125 126 private Properties load() throws SQLException { 127 try { 128 Properties p2 = FileUtils.loadProperties(new File (fileName)); 129 trace.debug("load " + p2); 130 return p2; 131 } catch(IOException e) { 132 throw getException(e); 133 } 134 } 135 136 private void waitUntilOld() throws SQLException { 137 File file = new File (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 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 { 158 method = FILE; 159 properties = new Properties (); 160 String 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 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 watchdog = new Thread (new Runnable () { 185 public void run() { 186 try { 187 File file = new File (fileName); 188 while (fileName != null) { 189 try { 191 if (!file.exists() || file.lastModified() != lastWrite) { 192 save(); 193 } 194 Thread.sleep(sleep); 195 } catch (Exception e) { 196 trace.debug("watchdog", e); 197 } 198 } 199 } catch(Exception 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 { 212 method = SOCKET; 213 properties = new Properties (); 214 try { 215 ipAddress = InetAddress.getLocalHost().getHostAddress(); 217 } catch (UnknownHostException e) { 218 throw getException(e); 219 } 220 if (!FileUtils.createNewFile(fileName)) { 221 waitUntilOld(); 222 File file = new File (fileName); 223 long read = file.lastModified(); 224 Properties p2 = load(); 225 String 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 ip = p2.getProperty("ipAddress", ipAddress); 233 if (!ipAddress.equals(ip)) { 234 throw error("Locked by another computer: " + ip); 235 } 236 String port = p2.getProperty("port", "0"); 237 int portId = Integer.parseInt(port); 238 InetAddress address; 239 try { 240 address = InetAddress.getByName(ip); 241 } catch (UnknownHostException e) { 242 throw getException(e); 243 } 244 for (int i = 0; i < 3; i++) { 245 try { 246 Socket s = new Socket (address, portId); 247 s.close(); 248 throw error("Locked by another process"); 249 } catch (BindException e) { 250 throw error("Bind Exception"); 251 } catch (ConnectException e) { 252 trace.debug("lockSocket not connected " + port, e); 253 } catch (IOException 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 socket = new ServerSocket (0); 268 int port = socket.getLocalPort(); 269 properties.setProperty("ipAddress", ipAddress); 270 properties.setProperty("port", String.valueOf(port)); 271 } catch (Exception e) { 272 trace.debug("lock", e); 273 socket = null; 274 lockFile(); 275 return; 276 } 277 save(); 278 Thread watchdog = new Thread (new Runnable () { 279 public void run() { 280 while (socket != null) { 281 try { 282 trace.debug("watchdog accept"); 283 Socket s = socket.accept(); 284 s.close(); 285 } catch (Exception 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 { 298 try { 299 Thread.sleep(time); 300 } catch(InterruptedException e) { 301 throw getException(e); 302 } 303 } 304 305 private SQLException getException(Throwable t) { 306 return Message.getSQLException(Message.ERROR_OPENING_DATABASE, null, t); 307 } 308 309 private SQLException error(String reason) { 310 return Message.getSQLException(Message.DATABASE_ALREADY_OPEN_1, reason); 311 } 312 313 public static int getFileLockMethod(String 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 |