Ticket #16296: 0001-Make-LockFile-more-robust.-Per-ticket-16296.patch

File 0001-Make-LockFile-more-robust.-Per-ticket-16296.patch, 8.5 KB (added by leeroy, 4 years ago)
  • src/main/java/org/torproject/onionoo/util/LockFile.java

    From 16a6663db534ca1461f0c3d00e5c144e550cc033 Mon Sep 17 00:00:00 2001
    From: leeroy <ter.one.leeboi@hush.com>
    Date: Sun, 14 Jun 2015 10:04:22 -0400
    Subject: [PATCH] Make LockFile more robust. Per ticket 16296.
    
    ---
     .../java/org/torproject/onionoo/util/LockFile.java | 226 +++++++++++++++++----
     1 file changed, 188 insertions(+), 38 deletions(-)
    
    diff --git a/src/main/java/org/torproject/onionoo/util/LockFile.java b/src/main/java/org/torproject/onionoo/util/LockFile.java
    index fc8a792..af564e8 100644
    a b  
    1 /* Copyright 2013 The Tor Project
     1/* Copyright 2015 The Tor Project
    22 * See LICENSE for licensing information */
    33package org.torproject.onionoo.util;
    44
    5 import java.io.BufferedWriter;
    6 import java.io.File;
    7 import java.io.FileWriter;
    85import java.io.IOException;
     6import java.lang.SecurityException;
     7import java.net.SocketException;
     8
     9import java.io.File;
     10import java.nio.MappedByteBuffer;
     11import java.nio.channels.FileChannel;
     12import java.nio.file.OpenOption;
     13import java.net.DatagramSocket;
     14import static java.nio.file.StandardOpenOption.CREATE;
     15import static java.nio.file.StandardOpenOption.READ;
     16import static java.nio.file.StandardOpenOption.WRITE;
     17import static java.nio.channels.FileChannel.MapMode.READ_WRITE;
    918
    1019import org.slf4j.Logger;
    1120import org.slf4j.LoggerFactory;
    1221
    1322public class LockFile {
    1423
    15   private final static Logger log = LoggerFactory.getLogger(
    16       LockFile.class);
     24  private final static Logger log =
     25    LoggerFactory.getLogger(LockFile.class);
    1726
    18   private final File lockFile = new File("lock");
     27  /* The locks used to prevent multiple instances */
     28  private File lockFile;            // A file on disk
     29  private MappedByteBuffer mmap;    // lockFile mapped into memory
     30  private DatagramSocket indicator; // Indicates unclean lock removal
     31 
     32  /**
     33   * Default constructor using a lock file named `lock`. 
     34   *
     35   * @return A LockFile object backed by a file named `lock`. 
     36   */
     37  public LockFile() { this("lock"); }
     38 
     39  /**
     40   * Construct a lock file using the filename specified. 
     41   *
     42   * @param file The filename to use when creating a lock file. 
     43   * @return A LockFile object backed by a file named `file`. 
     44   */
     45  public LockFile(String file) { lockFile = new File(file); }
    1946
    2047  /**
    21    * Acquire the lock by writing a lock file with the current time in
    22    * milliseconds and return whether this operation was successful.
     48   * Acquire the lock by opening a lock file and writing a bound port
     49   * to it's contents then return whether this operation was
     50   * successful. 
    2351   *
    24    * @return <code>true</code> if the lock file did not exist and writing
    25    * that file now succeeded, <code>false</code> otherwise.
     52   * @return <code>true</code> if successful, <code>false</code>
     53   * otherwise. 
    2654   */
    2755  public boolean acquireLock() {
    28     Time time = TimeFactory.getTime();
    29     try {
    30       if (this.lockFile.exists()) {
    31         return false;
    32       }
    33       if (this.lockFile.getParentFile() != null) {
    34         this.lockFile.getParentFile().mkdirs();
    35       }
    36     } catch (SecurityException e) {
    37       log.error("Unable to access lock file location", e);
    38       return false;
    39     }
    4056
    41     try (BufferedWriter bw = new BufferedWriter(new FileWriter(
    42         this.lockFile))) {
    43       bw.append(String.valueOf(time.currentTimeMillis()));
    44       bw.newLine();
    45       return true;
    46     } catch (IOException e) {
    47       log.error("Caught exception while trying to acquire lock!", e);
    48       return false;
     57    boolean lockFileExists = openLockFile(); // Open the lock file
     58
     59    if (lockFileExists) {
     60      // Check if the lock file indicates a running instance
     61      if (indicatorIsValid()) return false;  // Found a running instance
     62      else removeIndicator(); // Indicator was not valid
    4963    }
     64
     65    createIndicatorWithPort(0); // Obtain an indicator
     66    writeIndicatorToLockFile(); // Write the lock file
     67    lockFile.deleteOnExit();    // Remove lock file on clean shutdown
     68
     69    return true;
    5070  }
    5171
     72
    5273  /**
    53    * Release the lock by deleting the lock file if it exists and return
    54    * whether the file was successfully deleted.
     74   * Release the lock by deleting the lock file, if it exists, removing
     75   * the indicator, and then return whether this operation was
     76   * successful. 
    5577   *
    56    * @return <code>true</code> if the lock file does not exist anymore
    57    * when returning, <code>false</code> otherwise.
     78   * @return <code>true</code> if the lock was removed,
     79   * <code>false</code> otherwise. 
    5880   */
    5981  public boolean releaseLock() {
    60     if (this.lockFile.exists()) {
    61       this.lockFile.delete();
     82    if (hasAcquiredLock()) {
     83      removeIndicator();
     84      lockFile.delete();
     85    }
     86    return !lockFile.exists();
     87  }
     88 
     89  /**
     90   * Open a lock file for read/write access using mapped memory. 
     91   *
     92   * @return <code>true</code> indicating the lock file already exists,
     93   * <code>false</code> otherwise. 
     94   */
     95  private boolean openLockFile() {
     96   
     97    boolean alreadyExists = false;
     98
     99    try {
     100      // Check if the lock file already exists
     101      alreadyExists = lockFile.exists();
     102     
     103      // Open the lock file
     104      OpenOption[] openOptions = { CREATE, READ, WRITE };
     105      FileChannel fileChannel =
     106        FileChannel.open(lockFile.toPath(), openOptions );
     107     
     108      // Map the lock file into memory
     109      mmap = fileChannel.map(READ_WRITE, 0, 4);
     110
     111    } catch (SecurityException se) {
     112      log.debug("Access denied during lock file setup", se);
     113    } catch (IOException ie) {
     114      log.debug("Read or write failure during lock file setup", ie);
     115    } catch (Exception e) {
     116      log.debug("Unhandled error during lock file setup", e);
     117    }
     118
     119    return alreadyExists;
     120  }
     121
     122  /**
     123   * Create, or check, a port indicating an active instance by attempting
     124   * to bind it.  Providing zero will bind any available port. 
     125   * Specifying an already bound port will fail due to an active
     126   * instance. 
     127   *
     128   * @param port The port to bind, or zero to bind any port available.
     129   * @return <code>true</code> if a port was bound, <code>false</code>
     130   * otherwise.
     131   */
     132  private boolean createIndicatorWithPort(int port) {
     133 
     134    boolean created = false;
     135   
     136    try {
     137      // Bind the port specified, or use any port if zero
     138      indicator =
     139        port == 0 ? new DatagramSocket(): new DatagramSocket(port);
     140      created = indicator.isBound();
     141     
     142    } catch (SocketException e) {
     143      log.debug("While trying to check or create an indicator", e);
     144    }
     145   
     146    return created;
     147  }
     148 
     149  /**
     150   * Write an integer port number to the lock file mapped into memory. 
     151   */
     152  private void writeIndicatorToLockFile() {
     153 
     154    try {
     155      // Write the indicator to the lock file
     156      mmap.putInt(indicator.getLocalPort());
     157     
     158    } catch (Exception e) {
     159      log.debug("While trying to write to the lock file", e);
     160    }
     161  }
     162 
     163  /**
     164   * Check if the lock file indicates an active instance by treating the
     165   * contents as a bound port number. 
     166   *
     167   * @return <code>true</code> if an instance is active,
     168   * <code>false</code> otherwise. 
     169   */
     170  private boolean indicatorIsValid() {
     171 
     172    boolean valid = false;
     173 
     174    try {
     175      // Read the lock file to obtain the port number
     176      int port = mmap.getInt();
     177     
     178      // Check if the port can be bound
     179      boolean created = createIndicatorWithPort(port);
     180      if (created == false) valid = true; // Port is still in use
     181     
     182    } catch (Exception e) {
     183      log.debug("While trying to read from the lock file", e);
     184      valid = true; // Prevent another instance after an exception
    62185    }
    63     return !this.lockFile.exists();
     186   
     187    return valid;
     188  }
     189 
     190  /**
     191   * Remove the indicator used to detect a running instance by closing
     192   * the port and clearing the contents of the lock file. 
     193   */
     194  private void removeIndicator() {
     195 
     196    // Close the indicator port and remove it's reference   
     197    if (indicator.isBound()) indicator.close();
     198    indicator = null;
     199     
     200    // Clear the lock file
     201    mmap.clear();
     202  }
     203 
     204  /**
     205   * Indicate if this LockFile may have acquired a lock by checking if
     206   * the file exists and the indicator has been set. 
     207   *
     208   * @return <code>true</code> if a lock may have been acquired,
     209   * <code>false</code> otherwise. 
     210   */
     211  private boolean hasAcquiredLock() {
     212 
     213    return lockFile.exists() && indicator != null;
    64214  }
    65215}
    66216