/* $Id: LockObject.java 988245 2010-08-23 18:39:35Z kwright $ */ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.manifoldcf.core.lockmanager; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.core.system.ManifoldCF; import org.apache.manifoldcf.core.system.Logging; import java.io.*; import java.nio.charset.StandardCharsets; /** One instance of this object exists for each lock on each JVM! * This is the file-system version of the lock. */ public class FileLockObject extends LockObject { public static final String _rcsid = "@(#)$Id: LockObject.java 988245 2010-08-23 18:39:35Z kwright $"; private final static int STATUS_WRITELOCKED = -1; private File lockDirectoryName = null; private File lockFileName = null; private boolean isSync; // True if we need to be synchronizing across JVM's private final static String DOTLOCK = ".lock"; private final static String DOTFILE = ".file"; private final static String SLASH = "/"; public FileLockObject(LockPool lockPool, Object lockKey, File synchDir) { super(lockPool,lockKey); this.isSync = (synchDir != null); if (isSync) { // Hash the filename int hashcode = lockKey.hashCode(); int outerDirNumber = (hashcode & (1023)); int innerDirNumber = ((hashcode >> 10) & (1023)); String fullDir = synchDir.toString(); if (fullDir.length() == 0 || !fullDir.endsWith(SLASH)) fullDir = fullDir + SLASH; fullDir = fullDir + Integer.toString(outerDirNumber)+SLASH+Integer.toString(innerDirNumber); (new File(fullDir)).mkdirs(); String filename = createFileName(lockKey); lockDirectoryName = new File(fullDir,filename+DOTLOCK); lockFileName = new File(fullDir,filename+DOTFILE); } } private static String createFileName(Object lockKey) { return "lock-"+ManifoldCF.safeFileName(lockKey.toString()); } @Override protected void obtainGlobalWriteLockNoWait() throws ManifoldCFException, LockException, InterruptedException { if (isSync) { grabFileLock(); try { int status = readFile(); if (status != 0) { throw new LockException(LOCKEDANOTHERJVM); } writeFile(STATUS_WRITELOCKED); } finally { releaseFileLock(); } } } @Override protected void clearGlobalWriteLockNoWait() throws ManifoldCFException, LockException, InterruptedException { if (isSync) { grabFileLock(); try { writeFile(0); } finally { releaseFileLock(); } } } @Override protected void obtainGlobalNonExWriteLockNoWait() throws ManifoldCFException, LockException, InterruptedException { // Attempt to obtain a global write lock if (isSync) { grabFileLock(); try { int status = readFile(); if (status == STATUS_WRITELOCKED || status > 0) { throw new LockException(LOCKEDANOTHERJVM); } if (status == 0) status = STATUS_WRITELOCKED; status--; writeFile(status); } finally { releaseFileLock(); } } } @Override protected void clearGlobalNonExWriteLockNoWait() throws ManifoldCFException, LockException, InterruptedException { if (isSync) { grabFileLock(); try { int status = readFile(); if (status >= STATUS_WRITELOCKED) throw new RuntimeException("JVM error: File lock is not in expected state for object "+this.toString()); status++; if (status == STATUS_WRITELOCKED) status = 0; writeFile(status); } finally { releaseFileLock(); } } } @Override protected void obtainGlobalReadLockNoWait() throws ManifoldCFException, LockException, InterruptedException { // Attempt to obtain a global read lock if (isSync) { grabFileLock(); try { int status = readFile(); if (status <= STATUS_WRITELOCKED) { throw new LockException(LOCKEDANOTHERJVM); } status++; writeFile(status); } finally { releaseFileLock(); } } } @Override protected void clearGlobalReadLockNoWait() throws ManifoldCFException, LockException, InterruptedException { if (isSync) { grabFileLock(); try { int status = readFile(); // System.out.println(" Read status = "+Integer.toString(status)); if (status == 0) throw new RuntimeException("JVM error: File lock is not in expected state for object "+this.toString()); status--; writeFile(status); // System.out.println(" Wrote status = "+Integer.toString(status)); } finally { releaseFileLock(); } } } private final static String FILELOCKED = "File locked"; private synchronized void grabFileLock() throws LockException, InterruptedException { while (true) { // Try to create the lock file try { if (lockDirectoryName.createNewFile() == false) throw new LockException(FILELOCKED); break; } catch (InterruptedIOException e) { throw new InterruptedException("Interrupted IO: "+e.getMessage()); } catch (IOException e) { // Log this if possible try { Logging.lock.warn("Attempt to set file lock '"+lockDirectoryName.toString()+"' failed: "+e.getMessage(),e); } catch (Throwable e2) { e.printStackTrace(); } // Winnt sometimes throws an exception when you can't do the lock ManifoldCF.sleep(100); continue; } } } private synchronized void releaseFileLock() throws InterruptedException { Throwable ie = null; while (true) { try { if (lockDirectoryName.delete()) break; try { Logging.lock.fatal("Failure deleting file lock '"+lockDirectoryName.toString()+"'"); } catch (Throwable e2) { System.out.println("Failure deleting file lock '"+lockDirectoryName.toString()+"'"); } // Fail hard System.exit(-100); } catch (Error e) { // An error - must try again to delete // Attempting to log this to the log may not work due to disk being full, but try anyway. String message = "Error deleting file lock '"+lockDirectoryName.toString()+"': "+e.getMessage(); try { Logging.lock.error(message,e); } catch (Throwable e2) { // Ok, we failed, send it to standard out System.out.println(message); e.printStackTrace(); } ie = e; ManifoldCF.sleep(100); continue; } catch (RuntimeException e) { // A runtime exception - try again to delete // Attempting to log this to the log may not work due to disk being full, but try anyway. String message = "Error deleting file lock '"+lockDirectoryName.toString()+"': "+e.getMessage(); try { Logging.lock.error(message,e); } catch (Throwable e2) { // Ok, we failed, send it to standard out System.out.println(message); e.printStackTrace(); } ie = e; ManifoldCF.sleep(100); continue; } } // Succeeded finally - but we need to rethrow any exceptions we got if (ie != null) { if (ie instanceof InterruptedException) throw (InterruptedException)ie; if (ie instanceof Error) throw (Error)ie; if (ie instanceof RuntimeException) throw (RuntimeException)ie; } } private synchronized int readFile() throws InterruptedException { try { InputStreamReader isr = new InputStreamReader(new FileInputStream(lockFileName), StandardCharsets.UTF_8); try { BufferedReader x = new BufferedReader(isr); try { StringBuilder sb = new StringBuilder(); while (true) { int rval = x.read(); if (rval == -1) break; sb.append((char)rval); } try { return Integer.parseInt(sb.toString()); } catch (NumberFormatException e) { // We should never be in a situation where we can't parse a number we have supposedly written. // But, print a stack trace and throw IOException, so we recover. throw new IOException("Lock number read was not valid: "+e.getMessage()); } } finally { x.close(); } } catch (InterruptedIOException e) { throw new InterruptedException("Interrupted IO: "+e.getMessage()); } catch (IOException e) { String message = "Could not read from lock file: '"+lockFileName.toString()+"'"; try { Logging.lock.error(message,e); } catch (Throwable e2) { System.out.println(message); e.printStackTrace(); } // Don't fail hard or there is no way to recover throw e; } finally { isr.close(); } } catch (InterruptedIOException e) { throw new InterruptedException("Interrupted IO: "+e.getMessage()); } catch (IOException e) { return 0; } } private synchronized void writeFile(int value) throws InterruptedException { try { if (value == 0) { if (lockFileName.delete() == false) throw new IOException("Could not delete file '"+lockFileName.toString()+"'"); } else { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(lockFileName), StandardCharsets.UTF_8); try { BufferedWriter x = new BufferedWriter(osw); try { x.write(Integer.toString(value)); } finally { x.close(); } } finally { osw.close(); } } } catch (Error e) { // Couldn't write for some reason! Write to BOTH stdout and the log, since we // can't be sure we will succeed at the latter. String message = "Couldn't write to lock file; hard error occurred. Shutting down process; locks may be left dangling. You must cleanup before restarting."; try { Logging.lock.error(message,e); } catch (Throwable e2) { System.out.println(message); e.printStackTrace(); } System.exit(-100); } catch (RuntimeException e) { // Couldn't write for some reason! Write to BOTH stdout and the log, since we // can't be sure we will succeed at the latter. String message = "Couldn't write to lock file; JVM error. Shutting down process; locks may be left dangling. You must cleanup before restarting."; try { Logging.lock.error(message,e); } catch (Throwable e2) { System.out.println(message); e.printStackTrace(); } System.exit(-100); } catch (InterruptedIOException e) { throw new InterruptedException("Interrupted IO: "+e.getMessage()); } catch (IOException e) { // Couldn't write for some reason! Write to BOTH stdout and the log, since we // can't be sure we will succeed at the latter. String message = "Couldn't write to lock file; disk may be full. Shutting down process; locks may be left dangling. You must cleanup before restarting."; try { Logging.lock.error(message,e); } catch (Throwable e2) { System.out.println(message); e.printStackTrace(); } System.exit(-100); // Hard failure is called for // throw new Error("Lock management system failure",e); } } }