You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@labs.apache.org by ka...@apache.org on 2009/03/13 08:48:36 UTC

svn commit: r753152 - in /labs/bananadb/trunk/src: main/java/org/apache/labs/bananadb/hashtable/ main/java/org/apache/labs/bananadb/hashtable/lock/ test/java/org/apache/labs/bananadb/hashtable/

Author: kalle
Date: Fri Mar 13 07:48:35 2009
New Revision: 753152

URL: http://svn.apache.org/viewvc?rev=753152&view=rev
Log:
BananaDB

Write locks (clone of Lucene store locks)

Added:
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/Lock.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java   (with props)
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockObtainFailedException.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockReleaseFailedException.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockStressTest.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockVerifyServer.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java   (with props)
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java   (with props)
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoSuchDirectoryException.java
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/README.txt
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java   (with props)
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java   (with props)
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/VerifyingLockFactory.java
Modified:
    labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/Hashtable.java
    labs/bananadb/trunk/src/test/java/org/apache/labs/bananadb/hashtable/TestHashtable.java

Modified: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/Hashtable.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/Hashtable.java?rev=753152&r1=753151&r2=753152&view=diff
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/Hashtable.java (original)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/Hashtable.java Fri Mar 13 07:48:35 2009
@@ -20,6 +20,9 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.labs.bananadb.hashtable.lock.Lock;
+import org.apache.labs.bananadb.hashtable.lock.NativeFSLockFactory;
+import org.apache.labs.bananadb.hashtable.lock.LockFactory;
 
 import java.io.*;
 import java.util.*;
@@ -71,6 +74,9 @@
   private int entityCount;
   private int nextKeyPostingOffset;
 
+  private long lockWaitTimeoutMilliseconds = 10000;
+  private Lock writeLock;
+  private LockFactory lockFactory;
 
   private void setNextKeyPostingOffset(int nextKeyPostingOffset) {
     this.nextKeyPostingOffset = nextKeyPostingOffset;
@@ -98,6 +104,12 @@
     return accessor;
   }
 
+  public Hashtable(File path, int capacity, KeyClassHandler<K> keyClassHandler, ValueClassHandler<V> valueClassHandler) throws IOException {
+    this(path, capacity, keyClassHandler, valueClassHandler, new NativeFSLockFactory(path));
+  }
+
+
+
   /**
    * Open or create a hashtable.
    *
@@ -114,7 +126,9 @@
    * @param valueClassHandler
    * @throws IOException
    */
-  public Hashtable(File path, int capacity, KeyClassHandler<K> keyClassHandler, ValueClassHandler<V> valueClassHandler) throws IOException {
+  public Hashtable(File path, int capacity, KeyClassHandler<K> keyClassHandler, ValueClassHandler<V> valueClassHandler, LockFactory lockFactory) throws IOException {
+
+    writeLock = lockFactory.makeLock("lock");
 
     this.keyClassHandler = keyClassHandler;
     this.valueClassHandler = valueClassHandler;
@@ -203,7 +217,24 @@
    * @return Previous value of the parameter key, or null if a new key.
    * @throws IOException
    */
-  public synchronized V put(HashtableAccessor accessor, K key, V value) throws IOException {
+  public V put(final HashtableAccessor accessor, final K key, final V value) throws IOException {
+    Lock.With<V> with = new Lock.With<V>(writeLock, lockWaitTimeoutMilliseconds) {
+      protected V doBody() throws IOException {
+        return doPut(accessor, key, value);
+      }
+    };
+    return with.run();
+  }
+
+  /**
+   *
+   * @param accessor
+   * @param key
+   * @param value
+   * @return Previous value of the parameter key, or null if a new key.
+   * @throws IOException
+   */
+  private V doPut(HashtableAccessor accessor, K key, V value) throws IOException {
 
     // write key to buf
     ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
@@ -220,6 +251,7 @@
       baos.close();
     }
 
+
     V oldValue = null;
 
     int keyHash = key.hashCode();
@@ -312,7 +344,7 @@
 
   }
 
-  private synchronized void writePostings(HashtableAccessor accessor, int nextKeyPostingOffset, byte[] keyBuf, byte[] valueBuf, int keyHashCode) throws IOException {
+  private void writePostings(HashtableAccessor accessor, int nextKeyPostingOffset, byte[] keyBuf, byte[] valueBuf, int keyHashCode) throws IOException {
 
     int partitionNumber = -1;
     long offset = -1;
@@ -566,7 +598,7 @@
       return append(accessor, bytes, bytes.length);
     }
 
-    public synchronized long append(HashtableAccessor accessor, byte[] bytes, int length) throws IOException {
+    public long append(HashtableAccessor accessor, byte[] bytes, int length) throws IOException {
 
       bytesLeft -= length;
 
@@ -846,6 +878,13 @@
     return partitions;
   }
 
+  public long getLockWaitTimeoutMilliseconds() {
+    return lockWaitTimeoutMilliseconds;
+  }
+
+  public void setLockWaitTimeoutMilliseconds(long lockWaitTimeoutMilliseconds) {
+    this.lockWaitTimeoutMilliseconds = lockWaitTimeoutMilliseconds;
+  }
 }
 
 

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/Lock.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/Lock.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/Lock.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/Lock.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,140 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+
+/** An interprocess mutex lock.
+ * <p>Typical use might look like:<pre>
+ * new Lock.With(directory.makeLock("my.lock")) {
+ *     public Object doBody() {
+ *       <i>... code to execute while locked ...</i>
+ *     }
+ *   }.run();
+ * </pre>
+ */
+public abstract class Lock {
+
+  /** How long {@link #obtain(long)} waits, in milliseconds,
+   *  in between attempts to acquire the lock. */
+  public static long LOCK_POLL_INTERVAL = 1000;
+
+  /** Pass this value to {@link #obtain(long)} to try
+   *  forever to obtain the lock. */
+  public static final long LOCK_OBTAIN_WAIT_FOREVER = -1;
+
+  /** Attempts to obtain exclusive access and immediately return
+   *  upon success or failure.
+   * @return true iff exclusive access is obtained
+   */
+  public abstract boolean obtain() throws IOException;
+
+  /**
+   * If a lock obtain called, this failureReason may be set
+   * with the "root cause" Exception as to why the lock was
+   * not obtained.
+   */
+  protected Throwable failureReason;
+
+  /** Attempts to obtain an exclusive lock within amount of
+   *  time given. Polls once per {@link #LOCK_POLL_INTERVAL}
+   *  (currently 1000) milliseconds until lockWaitTimeout is
+   *  passed.
+   * @param lockWaitTimeout length of time to wait in
+   *        milliseconds or {@link
+   *        #LOCK_OBTAIN_WAIT_FOREVER} to retry forever
+   * @return true if lock was obtained
+   * @throws LockObtainFailedException if lock wait times out
+   * @throws IllegalArgumentException if lockWaitTimeout is
+   *         out of bounds
+   * @throws IOException if obtain() throws IOException
+   */
+  public boolean obtain(long lockWaitTimeout) throws LockObtainFailedException, IOException {
+    failureReason = null;
+    boolean locked = obtain();
+    if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER)
+      throw new IllegalArgumentException("lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got " + lockWaitTimeout + ")");
+
+    long maxSleepCount = lockWaitTimeout / LOCK_POLL_INTERVAL;
+    long sleepCount = 0;
+    while (!locked) {
+      if (lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER && sleepCount++ >= maxSleepCount) {
+        String reason = "Lock obtain timed out: " + this.toString();
+        if (failureReason != null) {
+          reason += ": " + failureReason;
+        }
+        LockObtainFailedException e = new LockObtainFailedException(reason);
+        if (failureReason != null) {
+          e.initCause(failureReason);
+        }
+        throw e;
+      }
+      try {
+        Thread.sleep(LOCK_POLL_INTERVAL);
+      } catch (InterruptedException e) {
+        throw new IOException(e.toString());
+      }
+      locked = obtain();
+    }
+    return locked;
+  }
+
+  /** Releases exclusive access. */
+  public abstract void release() throws IOException;
+
+  /** Returns true if the resource is currently locked.  Note that one must
+   * still call {@link #obtain()} before using the resource. */
+  public abstract boolean isLocked();
+
+
+  /** Utility class for executing code with exclusive access. */
+  public abstract static class With<T> {
+    private Lock lock;
+    private long lockWaitTimeout;
+
+
+    /** Constructs an executor that will grab the named lock. */
+    public With(Lock lock, long lockWaitTimeout) {
+      this.lock = lock;
+      this.lockWaitTimeout = lockWaitTimeout;
+    }
+
+    /** Code to execute with exclusive access. */
+    protected abstract T doBody() throws IOException;
+
+    /** Calls {@link #doBody} while <i>lock</i> is obtained.  Blocks if lock
+     * cannot be obtained immediately.  Retries to obtain lock once per second
+     * until it is obtained, or until it has tried ten times. Lock is released when
+     * {@link #doBody} exits.
+     * @throws LockObtainFailedException if lock could not
+     * be obtained
+     * @throws IOException if {@link Lock#obtain} throws IOException
+     */
+    public T run() throws LockObtainFailedException, IOException {
+      boolean locked = false;
+      try {
+         locked = lock.obtain(lockWaitTimeout);
+         return doBody();
+      } finally {
+        if (locked)
+	      lock.release();
+      }
+    }
+  }
+
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,74 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+
+/**
+ * <p>Base class for Locking implementation.  {@link Directory} uses
+ * instances of this class to implement locking.</p>
+ *
+ * <p>Note that there are some useful tools to verify that
+ * your LockFactory is working correctly: {@link
+ * VerifyingLockFactory}, {@link LockStressTest}, {@link
+ * LockVerifyServer}.</p>
+ *
+ * @see LockVerifyServer
+ * @see LockStressTest
+ * @see VerifyingLockFactory
+ */
+
+public abstract class LockFactory {
+
+  protected String lockPrefix = "";
+
+  /**
+   * Set the prefix in use for all locks created in this
+   * LockFactory.  This is normally called once, when a
+   * Directory gets this LockFactory instance.  However, you
+   * can also call this (after this instance is assigned to
+   * a Directory) to override the prefix in use.  This
+   * is helpful if you're running Lucene on machines that
+   * have different mount points for the same shared
+   * directory.
+   */
+  public void setLockPrefix(String lockPrefix) {
+    this.lockPrefix = lockPrefix;
+  }
+
+  /**
+   * Get the prefix in use for all locks created in this LockFactory.
+   */
+  public String getLockPrefix() {
+    return this.lockPrefix;
+  }
+
+  /**
+   * Return a new Lock instance identified by lockName.
+   * @param lockName name of the lock to be created.
+   */
+  public abstract Lock makeLock(String lockName);
+
+  /**
+   * Attempt to clear (forcefully unlock and remove) the
+   * specified lock.  Only call this at a time when you are
+   * certain this lock is no longer in use.
+   * @param lockName name of the lock to be cleared.
+   */
+  abstract public void clearLock(String lockName) throws IOException;
+}

Propchange: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockFactory.java
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockObtainFailedException.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockObtainFailedException.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockObtainFailedException.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockObtainFailedException.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,33 @@
+/**
+ * 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.labs.bananadb.hashtable.lock;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown when the <code>write.lock</code>
+ * could not be acquired.  This
+ * happens when a writer tries to open an index
+ * that another writer already has open.
+ * @see Lock#obtain(long).
+ */
+public class LockObtainFailedException extends IOException {
+  public LockObtainFailedException(String message) {
+    super(message);
+  }
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockReleaseFailedException.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockReleaseFailedException.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockReleaseFailedException.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockReleaseFailedException.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,31 @@
+/**
+ * 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.labs.bananadb.hashtable.lock;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown when the <code>write.lock</code>
+ * could not be released.
+ * @see Lock#release().
+ */
+public class LockReleaseFailedException extends IOException {
+  public LockReleaseFailedException(String message) {
+    super(message);
+  }
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockStressTest.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockStressTest.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockStressTest.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockStressTest.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,117 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * Simple standalone tool that forever acquires & releases a
+ * lock using a specific LockFactory.  Run without any args
+ * to see usage.
+ *
+ * @see VerifyingLockFactory
+ * @see LockVerifyServer
+ */ 
+
+public class LockStressTest {
+
+  public static void main(String[] args) throws Exception {
+
+    if (args.length != 6) {
+      System.out.println("\nUsage: java org.apache.labs.bananadb.hashtable.lock.LockStressTest myID verifierHostOrIP verifierPort lockFactoryClassName lockDirName sleepTime\n" +
+                         "\n" +
+                         "  myID = int from 0 .. 255 (should be unique for test process)\n" +
+                         "  verifierHostOrIP = host name or IP address where LockVerifyServer is running\n" +
+                         "  verifierPort = port that LockVerifyServer is listening on\n" +
+                         "  lockFactoryClassName = primary LockFactory class that we will use\n" +
+                         "  lockDirName = path to the lock directory (only set for Simple/NativeFSLockFactory\n" +
+                         "  sleepTimeMS = milliseconds to pause betweeen each lock obtain/release\n" +
+                         "\n" +
+                         "You should run multiple instances of this process, each with its own\n" +
+                         "unique ID, and each pointing to the same lock directory, to verify\n" +
+                         "that locking is working correctly.\n" +
+                         "\n" +
+                         "Make sure you are first running LockVerifyServer.\n" + 
+                         "\n");
+      System.exit(1);
+    }
+
+    final int myID = Integer.parseInt(args[0]);
+
+    if (myID < 0 || myID > 255) {
+      System.out.println("myID must be a unique int 0..255");
+      System.exit(1);
+    }
+
+    final String verifierHost = args[1];
+    final int verifierPort = Integer.parseInt(args[2]);
+    final String lockFactoryClassName = args[3];
+    final String lockDirName = args[4];
+    final int sleepTimeMS = Integer.parseInt(args[5]);
+
+    Class c;
+    try {
+      c = Class.forName(lockFactoryClassName);
+    } catch (ClassNotFoundException e) {
+      throw new IOException("unable to find LockClass " + lockFactoryClassName);
+    }
+
+    LockFactory lockFactory;
+    try {
+      lockFactory = (LockFactory) c.newInstance();          
+    } catch (IllegalAccessException e) {
+      throw new IOException("IllegalAccessException when instantiating LockClass " + lockFactoryClassName);
+    } catch (InstantiationException e) {
+      throw new IOException("InstantiationException when instantiating LockClass " + lockFactoryClassName);
+    } catch (ClassCastException e) {
+      throw new IOException("unable to cast LockClass " + lockFactoryClassName + " instance to a LockFactory");
+    }
+
+    File lockDir = new File(lockDirName);
+
+    if (lockFactory instanceof NativeFSLockFactory) {
+      ((NativeFSLockFactory) lockFactory).setLockDir(lockDir);
+    } else if (lockFactory instanceof SimpleFSLockFactory) {
+      ((SimpleFSLockFactory) lockFactory).setLockDir(lockDir);
+    }
+
+    lockFactory.setLockPrefix("test");
+    
+    LockFactory verifyLF = new VerifyingLockFactory((byte) myID, lockFactory, verifierHost, verifierPort);
+
+    Lock l = verifyLF.makeLock("test.lock");
+
+    while(true) {
+
+      boolean obtained = false;
+
+      try {
+        obtained = l.obtain(10);
+      } catch (LockObtainFailedException e) {
+        System.out.print("x");
+      }
+
+      if (obtained) {
+        System.out.print("l");
+        l.release();
+      }
+      Thread.sleep(sleepTimeMS);
+    }
+  }
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockVerifyServer.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockVerifyServer.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockVerifyServer.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/LockVerifyServer.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,96 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Simple standalone server that must be running when you
+ * use {@link VerifyingLockFactory}.  This server simply
+ * verifies at most one process holds the lock at a time.
+ * Run without any args to see usage.
+ *
+ * @see VerifyingLockFactory
+ * @see LockStressTest
+ */
+
+public class LockVerifyServer {
+
+  private static String getTime(long startTime) {
+    return "[" + ((System.currentTimeMillis()-startTime)/1000) + "s] ";
+  }
+
+  public static void main(String[] args) throws IOException {
+
+    if (args.length != 1) {
+      System.out.println("\nUsage: java org.apache.labs.bananadb.hashtable.lock.LockVerifyServer port\n");
+      System.exit(1);
+    }
+
+    final int port = Integer.parseInt(args[0]);
+
+    ServerSocket s = new ServerSocket(port);
+    s.setReuseAddress(true);
+    System.out.println("\nReady on port " + port + "...");
+
+    int lockedID = 0;
+    long startTime = System.currentTimeMillis();
+
+    while(true) {
+      Socket cs = s.accept();
+      OutputStream out = cs.getOutputStream();
+      InputStream in = cs.getInputStream();
+
+      int id = in.read();
+      int command = in.read();
+
+      boolean err = false;
+
+      if (command == 1) {
+        // Locked
+        if (lockedID != 0) {
+          err = true;
+          System.out.println(getTime(startTime) + " ERROR: id " + id + " got lock, but " + lockedID + " already holds the lock");
+        }
+        lockedID = id;
+      } else if (command == 0) {
+        if (lockedID != id) {
+          err = true;
+          System.out.println(getTime(startTime) + " ERROR: id " + id + " released the lock, but " + lockedID + " is the one holding the lock");
+        }
+        lockedID = 0;
+      } else
+        throw new RuntimeException("unrecognized command " + command);
+
+      System.out.print(".");
+
+      if (err)
+        out.write(1);
+      else
+        out.write(0);
+
+      out.close();
+      in.close();
+      cs.close();
+    }
+  }
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,332 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Random;
+
+/**
+ * <p>Implements {@link LockFactory} using native OS file
+ * locks.  Note that because this LockFactory relies on
+ * java.nio.* APIs for locking, any problems with those APIs
+ * will cause locking to fail.  Specifically, on certain NFS
+ * environments the java.nio.* locks will fail (the lock can
+ * incorrectly be double acquired) whereas {@link
+ * SimpleFSLockFactory} worked perfectly in those same
+ * environments.  For NFS based access to an index, it's
+ * recommended that you try {@link SimpleFSLockFactory}
+ * first and work around the one limitation that a lock file
+ * could be left when the JVM exits abnormally.</p>
+ *
+ * <p>The primary benefit of {@link NativeFSLockFactory} is
+ * that lock files will be properly removed (by the OS) if
+ * the JVM has an abnormal exit.</p>
+ * 
+ * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
+ * leftover lock files in the filesystem on exiting the JVM
+ * is fine because the OS will free the locks held against
+ * these files even though the files still remain.</p>
+ *
+ * <p>If you suspect that this or any other LockFactory is
+ * not working properly in your environment, you can easily
+ * test it by using {@link VerifyingLockFactory}, {@link
+ * LockVerifyServer} and {@link LockStressTest}.</p>
+ *
+ * @see LockFactory
+ */
+
+public class NativeFSLockFactory extends LockFactory {
+
+  /**
+   * Directory specified by <code>org.apache.lucene.lockDir</code>
+   * system property.  If that is not set, then <code>java.io.tmpdir</code>
+   * system property is used.
+   */
+
+  private File lockDir;
+
+  // Simple test to verify locking system is "working".  On
+  // NFS, if it's misconfigured, you can hit long (35
+  // second) timeouts which cause Lock.obtain to take far
+  // too long (it assumes the obtain() call takes zero
+  // time).  Since it's a configuration problem, we test up
+  // front once on creating the LockFactory:
+  private void acquireTestLock() throws IOException {
+    String randomLockName = "lucene-" + Long.toString(new Random().nextInt(), Character.MAX_RADIX) + "-test.lock";
+    
+    Lock l = makeLock(randomLockName);
+    try {
+      l.obtain();
+    } catch (IOException e) {
+      IOException e2 = new IOException("Failed to acquire random test lock; please verify filesystem for lock directory '" + lockDir + "' supports locking");
+      e2.initCause(e);
+      throw e2;
+    }
+
+    l.release();
+  }
+
+  /**
+   * Create a NativeFSLockFactory instance, with null (unset)
+   * lock directory.  This is package-private and is only
+   * used by FSDirectory when creating this LockFactory via
+   * the System property
+   * org.apache.lucene.store.FSDirectoryLockFactoryClass.
+   */
+  NativeFSLockFactory() throws IOException {
+    this((File) null);
+  }
+
+  /**
+   * Create a NativeFSLockFactory instance, storing lock
+   * files into the specified lockDirName:
+   *
+   * @param lockDirName where lock files are created.
+   */
+  public NativeFSLockFactory(String lockDirName) throws IOException {
+    this(new File(lockDirName));
+  }
+
+  /**
+   * Create a NativeFSLockFactory instance, storing lock
+   * files into the specified lockDir:
+   * 
+   * @param lockDir where lock files are created.
+   */
+  public NativeFSLockFactory(File lockDir) throws IOException {
+    setLockDir(lockDir);
+  }
+
+  /**
+   * Set the lock directory.  This is package-private and is
+   * only used externally by FSDirectory when creating this
+   * LockFactory via the System property
+   * org.apache.lucene.store.FSDirectoryLockFactoryClass.
+   */
+  void setLockDir(File lockDir) throws IOException {
+    this.lockDir = lockDir;
+    if (lockDir != null) {
+      // Ensure that lockDir exists and is a directory.
+      if (!lockDir.exists()) {
+        if (!lockDir.mkdirs())
+          throw new IOException("Cannot create directory: " +
+                                lockDir.getAbsolutePath());
+      } else if (!lockDir.isDirectory()) {
+        throw new IOException("Found regular file where directory expected: " + 
+                              lockDir.getAbsolutePath());
+      }
+
+      acquireTestLock();
+    }
+  }
+
+  public synchronized Lock makeLock(String lockName) {
+    if (lockPrefix != null)
+      lockName = lockPrefix + "-n-" + lockName;
+    return new NativeFSLock(lockDir, lockName);
+  }
+
+  public void clearLock(String lockName) throws IOException {
+    // Note that this isn't strictly required anymore
+    // because the existence of these files does not mean
+    // they are locked, but, still do this in case people
+    // really want to see the files go away:
+    if (lockDir.exists()) {
+      if (lockPrefix != null) {
+        lockName = lockPrefix + "-n-" + lockName;
+      }
+      File lockFile = new File(lockDir, lockName);
+      if (lockFile.exists() && !lockFile.delete()) {
+        throw new IOException("Cannot delete " + lockFile);
+      }
+    }
+  }
+};
+
+class NativeFSLock extends Lock {
+
+  private RandomAccessFile f;
+  private FileChannel channel;
+  private FileLock lock;
+  private File path;
+  private File lockDir;
+
+  /*
+   * The javadocs for FileChannel state that you should have
+   * a single instance of a FileChannel (per JVM) for all
+   * locking against a given file.  To ensure this, we have
+   * a single (static) HashSet that contains the file paths
+   * of all currently locked locks.  This protects against
+   * possible cases where different Directory instances in
+   * one JVM (each with their own NativeFSLockFactory
+   * instance) have set the same lock dir and lock prefix.
+   */
+  private static HashSet LOCK_HELD = new HashSet();
+
+  public NativeFSLock(File lockDir, String lockFileName) {
+    this.lockDir = lockDir;
+    path = new File(lockDir, lockFileName);
+  }
+
+  public synchronized boolean obtain() throws IOException {
+
+    if (isLocked()) {
+      // Our instance is already locked:
+      return false;
+    }
+
+    // Ensure that lockDir exists and is a directory.
+    if (!lockDir.exists()) {
+      if (!lockDir.mkdirs())
+        throw new IOException("Cannot create directory: " +
+                              lockDir.getAbsolutePath());
+    } else if (!lockDir.isDirectory()) {
+      throw new IOException("Found regular file where directory expected: " + 
+                            lockDir.getAbsolutePath());
+    }
+
+    String canonicalPath = path.getCanonicalPath();
+
+    boolean markedHeld = false;
+
+    try {
+
+      // Make sure nobody else in-process has this lock held
+      // already, and, mark it held if not:
+
+      synchronized(LOCK_HELD) {
+        if (LOCK_HELD.contains(canonicalPath)) {
+          // Someone else in this JVM already has the lock:
+          return false;
+        } else {
+          // This "reserves" the fact that we are the one
+          // thread trying to obtain this lock, so we own
+          // the only instance of a channel against this
+          // file:
+          LOCK_HELD.add(canonicalPath);
+          markedHeld = true;
+        }
+      }
+
+      try {
+        f = new RandomAccessFile(path, "rw");
+      } catch (IOException e) {
+        // On Windows, we can get intermittant "Access
+        // Denied" here.  So, we treat this as failure to
+        // acquire the lock, but, lock the reason in case
+        // there is in fact a real error case.
+        failureReason = e;
+        f = null;
+      }
+
+      if (f != null) {
+        try {
+          channel = f.getChannel();
+          try {
+            lock = channel.tryLock();
+          } catch (IOException e) {
+            // At least on OS X, we will sometimes get an
+            // intermittant "Permission Denied" IOException,
+            // which seems to simply mean "you failed to get
+            // the lock".  But other IOExceptions could be
+            // "permanent" (eg, locking is not supported via
+            // the filesystem).  So, we record the failure
+            // reason here; the timeout obtain (usually the
+            // one calling us) will use this as "root cause"
+            // if it fails to get the lock.
+            failureReason = e;
+          } finally {
+            if (lock == null) {
+              try {
+                channel.close();
+              } finally {
+                channel = null;
+              }
+            }
+          }
+        } finally {
+          if (channel == null) {
+            try {
+              f.close();
+            } finally {
+              f = null;
+            }
+          }
+        }
+      }
+
+    } finally {
+      if (markedHeld && !isLocked()) {
+        synchronized(LOCK_HELD) {
+          if (LOCK_HELD.contains(canonicalPath)) {
+            LOCK_HELD.remove(canonicalPath);
+          }
+        }
+      }
+    }
+    return isLocked();
+  }
+
+  public synchronized void release() throws IOException {
+    if (isLocked()) {
+      try {
+        lock.release();
+      } finally {
+        lock = null;
+        try {
+          channel.close();
+        } finally {
+          channel = null;
+          try {
+            f.close();
+          } finally {
+            f = null;
+            synchronized(LOCK_HELD) {
+              LOCK_HELD.remove(path.getCanonicalPath());
+            }
+          }
+        }
+      }
+      if (!path.delete())
+        throw new LockReleaseFailedException("failed to delete " + path);
+    }
+  }
+
+  public synchronized boolean isLocked() {
+    return lock != null;
+  }
+
+  public String toString() {
+    return "NativeFSLock@" + path;
+  }
+
+  public void finalize() throws Throwable {
+    try {
+      if (isLocked()) {
+        release();
+      }
+    } finally {
+      super.finalize();
+    }
+  }
+}

Propchange: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NativeFSLockFactory.java
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,63 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+
+/**
+ * Use this {@link LockFactory} to disable locking entirely.
+ * This LockFactory is used when you call {@link FSDirectory#setDisableLocks}.
+ * Only one instance of this lock is created.  You should call {@link
+ * #getNoLockFactory()} to get the instance.
+ *
+ * @see LockFactory
+ */
+
+public class NoLockFactory extends LockFactory {
+
+  // Single instance returned whenever makeLock is called.
+  private static NoLock singletonLock = new NoLock();
+  private static NoLockFactory singleton = new NoLockFactory();
+
+  public static NoLockFactory getNoLockFactory() {
+    return singleton;
+  }
+
+  public Lock makeLock(String lockName) {
+    return singletonLock;
+  }
+
+  public void clearLock(String lockName) {};
+};
+
+class NoLock extends Lock {
+  public boolean obtain() throws IOException {
+    return true;
+  }
+
+  public void release() {
+  }
+
+  public boolean isLocked() {
+    return false;
+  }
+
+  public String toString() {
+    return "NoLock";
+  }
+}

Propchange: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoLockFactory.java
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoSuchDirectoryException.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoSuchDirectoryException.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoSuchDirectoryException.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/NoSuchDirectoryException.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,31 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.FileNotFoundException;
+
+/**
+ * This exception is thrown when you try to list a
+ * non-existent directory.
+ */
+
+public class NoSuchDirectoryException extends FileNotFoundException {
+  public NoSuchDirectoryException(String message) {
+    super(message);
+  }
+}

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/README.txt
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/README.txt?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/README.txt (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/README.txt Fri Mar 13 07:48:35 2009
@@ -0,0 +1,3 @@
+This package is a copy of the Lucene lock mechanism as of SVN revision 745917.
+
+https://svn.apache.org/repos/asf/lucene/java/trunk/src/java/org/apache/lucene/store

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,159 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * <p>Implements {@link LockFactory} using {@link
+ * File#createNewFile()}.  This is the default LockFactory
+ * for {@link FSDirectory}.</p>
+ *
+ * <p><b>NOTE:</b> the <a target="_top"
+ * href="http://java.sun.com/j2se/1.4.2/docs/api/java/io/File.html#createNewFile()">javadocs
+ * for <code>File.createNewFile</code></a> contain a vague
+ * yet spooky warning about not using the API for file
+ * locking.  This warning was added due to <a target="_top"
+ * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4676183">this
+ * bug</a>, and in fact the only known problem with using
+ * this API for locking is that the Lucene write lock may
+ * not be released when the JVM exits abnormally.</p>
+
+ * <p>When this happens, a {@link LockObtainFailedException}
+ * is hit when trying to create a writer, in which case you
+ * need to explicitly clear the lock file first.  You can
+ * either manually remove the file, or use the {@link
+ * org.apache.lucene.index.IndexReader#unlock(Directory)}
+ * API.  But, first be certain that no writer is in fact
+ * writing to the index otherwise you can easily corrupt
+ * your index.</p>
+ *
+ * <p>If you suspect that this or any other LockFactory is
+ * not working properly in your environment, you can easily
+ * test it by using {@link VerifyingLockFactory}, {@link
+ * LockVerifyServer} and {@link LockStressTest}.</p>
+ *
+ * @see LockFactory
+ */
+
+public class SimpleFSLockFactory extends LockFactory {
+
+  /**
+   * Directory specified by <code>org.apache.lucene.lockDir</code>
+   * system property.  If that is not set, then <code>java.io.tmpdir</code>
+   * system property is used.
+   */
+
+  private File lockDir;
+
+  /**
+   * Create a SimpleFSLockFactory instance, with null (unset)
+   * lock directory.  This is package-private and is only
+   * used by FSDirectory when creating this LockFactory via
+   * the System property
+   * org.apache.lucene.store.FSDirectoryLockFactoryClass.
+   */
+  SimpleFSLockFactory() throws IOException {
+    this((File) null);
+  }
+
+  /**
+   * Instantiate using the provided directory (as a File instance).
+   * @param lockDir where lock files should be created.
+   */
+  public SimpleFSLockFactory(File lockDir) throws IOException {
+    setLockDir(lockDir);
+  }
+
+  /**
+   * Instantiate using the provided directory name (String).
+   * @param lockDirName where lock files should be created.
+   */
+  public SimpleFSLockFactory(String lockDirName) throws IOException {
+    lockDir = new File(lockDirName);
+    setLockDir(lockDir);
+  }
+
+  /**
+   * Set the lock directory.  This is package-private and is
+   * only used externally by FSDirectory when creating this
+   * LockFactory via the System property
+   * org.apache.lucene.store.FSDirectoryLockFactoryClass.
+   */
+  void setLockDir(File lockDir) throws IOException {
+    this.lockDir = lockDir;
+  }
+
+  public Lock makeLock(String lockName) {
+    if (lockPrefix != null) {
+      lockName = lockPrefix + "-" + lockName;
+    }
+    return new SimpleFSLock(lockDir, lockName);
+  }
+
+  public void clearLock(String lockName) throws IOException {
+    if (lockDir.exists()) {
+      if (lockPrefix != null) {
+        lockName = lockPrefix + "-" + lockName;
+      }
+      File lockFile = new File(lockDir, lockName);
+      if (lockFile.exists() && !lockFile.delete()) {
+        throw new IOException("Cannot delete " + lockFile);
+      }
+    }
+  }
+};
+
+class SimpleFSLock extends Lock {
+
+  File lockFile;
+  File lockDir;
+
+  public SimpleFSLock(File lockDir, String lockFileName) {
+    this.lockDir = lockDir;
+    lockFile = new File(lockDir, lockFileName);
+  }
+
+  public boolean obtain() throws IOException {
+
+    // Ensure that lockDir exists and is a directory:
+    if (!lockDir.exists()) {
+      if (!lockDir.mkdirs())
+        throw new IOException("Cannot create directory: " +
+                              lockDir.getAbsolutePath());
+    } else if (!lockDir.isDirectory()) {
+      throw new IOException("Found regular file where directory expected: " + 
+                            lockDir.getAbsolutePath());
+    }
+    return lockFile.createNewFile();
+  }
+
+  public void release() throws LockReleaseFailedException {
+    if (lockFile.exists() && !lockFile.delete())
+      throw new LockReleaseFailedException("failed to delete " + lockFile);
+  }
+
+  public boolean isLocked() {
+    return lockFile.exists();
+  }
+
+  public String toString() {
+    return "SimpleFSLock@" + lockFile;
+  }
+}

Propchange: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SimpleFSLockFactory.java
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,85 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+import java.util.HashSet;
+
+/**
+ * Implements {@link LockFactory} for a single in-process instance,
+ * meaning all locking will take place through this one instance.
+ * Only use this {@link LockFactory} when you are certain all
+ * IndexReaders and IndexWriters for a given index are running
+ * against a single shared in-process Directory instance.  This is
+ * currently the default locking for RAMDirectory.
+ *
+ * @see LockFactory
+ */
+
+public class SingleInstanceLockFactory extends LockFactory {
+
+  private HashSet locks = new HashSet();
+
+  public Lock makeLock(String lockName) {
+    // We do not use the LockPrefix at all, because the private
+    // HashSet instance effectively scopes the locking to this
+    // single Directory instance.
+    return new SingleInstanceLock(locks, lockName);
+  }
+
+  public void clearLock(String lockName) throws IOException {
+    synchronized(locks) {
+      if (locks.contains(lockName)) {
+        locks.remove(lockName);
+      }
+    }
+  }
+};
+
+class SingleInstanceLock extends Lock {
+
+  String lockName;
+  private HashSet locks;
+
+  public SingleInstanceLock(HashSet locks, String lockName) {
+    this.locks = locks;
+    this.lockName = lockName;
+  }
+
+  public boolean obtain() throws IOException {
+    synchronized(locks) {
+      return locks.add(lockName);
+    }
+  }
+
+  public void release() {
+    synchronized(locks) {
+      locks.remove(lockName);
+    }
+  }
+
+  public boolean isLocked() {
+    synchronized(locks) {
+      return locks.contains(lockName);
+    }
+  }
+
+  public String toString() {
+    return super.toString() + ": " + lockName;
+  }
+}

Propchange: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/SingleInstanceLockFactory.java
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/VerifyingLockFactory.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/VerifyingLockFactory.java?rev=753152&view=auto
==============================================================================
--- labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/VerifyingLockFactory.java (added)
+++ labs/bananadb/trunk/src/main/java/org/apache/labs/bananadb/hashtable/lock/VerifyingLockFactory.java Fri Mar 13 07:48:35 2009
@@ -0,0 +1,119 @@
+package org.apache.labs.bananadb.hashtable.lock;
+
+/**
+ * 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.
+ */
+
+import java.net.Socket;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@link LockFactory} that wraps another {@link
+ * LockFactory} and verifies that each lock obtain/release
+ * is "correct" (never results in two processes holding the
+ * lock at the same time).  It does this by contacting an
+ * external server ({@link LockVerifyServer}) to assert that
+ * at most one process holds the lock at a time.  To use
+ * this, you should also run {@link LockVerifyServer} on the
+ * host & port matching what you pass to the constructor.
+ *
+ * @see LockVerifyServer
+ * @see LockStressTest
+ */
+
+public class VerifyingLockFactory extends LockFactory {
+
+  LockFactory lf;
+  byte id;
+  String host;
+  int port;
+
+  private class CheckedLock extends Lock {
+    private Lock lock;
+
+    public CheckedLock(Lock lock) {
+      this.lock = lock;
+    }
+
+    private void verify(byte message) {
+      try {
+        Socket s = new Socket(host, port);
+        OutputStream out = s.getOutputStream();
+        out.write(id);
+        out.write(message);
+        InputStream in = s.getInputStream();
+        int result = in.read();
+        in.close();
+        out.close();
+        s.close();
+        if (result != 0)
+          throw new RuntimeException("lock was double acquired");
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public synchronized boolean obtain(long lockWaitTimeout)
+      throws LockObtainFailedException, IOException {
+      boolean obtained = lock.obtain(lockWaitTimeout);
+      if (obtained)
+        verify((byte) 1);
+      return obtained;
+    }
+
+    public synchronized boolean obtain()
+      throws LockObtainFailedException, IOException {
+      return lock.obtain();
+    }
+
+    public synchronized boolean isLocked() {
+      return lock.isLocked();
+    }
+
+    public synchronized void release() throws IOException {
+      if (isLocked()) {
+        verify((byte) 0);
+        lock.release();
+      }
+    }
+  }
+
+  /**
+   * @param id should be a unique id across all clients
+   * @param lf the LockFactory that we are testing
+   * @param host host or IP where {@link LockVerifyServer}
+            is running
+   * @param port the port {@link LockVerifyServer} is
+            listening on
+  */
+  public VerifyingLockFactory(byte id, LockFactory lf, String host, int port) throws IOException {
+    this.id = id;
+    this.lf = lf;
+    this.host = host;
+    this.port = port;
+  }
+
+  public synchronized Lock makeLock(String lockName) {
+    return new CheckedLock(lf.makeLock(lockName));
+  }
+
+  public synchronized void clearLock(String lockName)
+    throws IOException {
+    lf.clearLock(lockName);
+  }
+}

Modified: labs/bananadb/trunk/src/test/java/org/apache/labs/bananadb/hashtable/TestHashtable.java
URL: http://svn.apache.org/viewvc/labs/bananadb/trunk/src/test/java/org/apache/labs/bananadb/hashtable/TestHashtable.java?rev=753152&r1=753151&r2=753152&view=diff
==============================================================================
--- labs/bananadb/trunk/src/test/java/org/apache/labs/bananadb/hashtable/TestHashtable.java (original)
+++ labs/bananadb/trunk/src/test/java/org/apache/labs/bananadb/hashtable/TestHashtable.java Fri Mar 13 07:48:35 2009
@@ -20,10 +20,15 @@
 import junit.framework.TestCase;
 import org.apache.labs.bananadb.hashtable.handlers.IntegerHandler;
 import org.apache.labs.bananadb.hashtable.handlers.StringValueHandler;
+import org.apache.labs.bananadb.hashtable.lock.NoLockFactory;
+import org.apache.labs.bananadb.hashtable.lock.SingleInstanceLockFactory;
 import org.junit.Test;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * User: kalle
@@ -41,6 +46,72 @@
     }
   }
 
+
+  private class TestThreadedRunnable implements Runnable {
+    private Hashtable<Integer, String> map;
+    private HashtableAccessor accessor;
+    private Exception exception;
+    private AtomicInteger pk;
+    private int id;
+
+    private TestThreadedRunnable(Hashtable<Integer, String> map, AtomicInteger pk) throws IOException {
+      this.map = map;
+      this.pk = pk;
+      accessor = map.createAccessor(false);
+    }
+
+    public void run() {
+      try {
+        for (int i = 0; i < 1000; i++) {
+          map.put(accessor, pk.getAndIncrement(), "foobar");
+          System.out.println(id + "\t" + i);
+        }
+      } catch (Exception exception) {
+        this.exception = exception;
+        throw new RuntimeException(exception);
+      }
+    }
+  }
+
+  @Test
+  public void testThreaded() throws Exception {
+    File path = new File(this.path, "testThreaded");
+
+    Hashtable<Integer, String> map = new Hashtable<Integer, String>(path, 40000, new IntegerHandler(), new StringValueHandler());
+    map.setLockWaitTimeoutMilliseconds(60000);
+    AtomicInteger pk = new AtomicInteger(0);
+
+    List<Thread> threads = new ArrayList<Thread>();
+    List<TestThreadedRunnable> runnables = new ArrayList<TestThreadedRunnable>();
+
+    for (int i = 0; i < 10; i++) {
+      TestThreadedRunnable runnable = new TestThreadedRunnable(map, pk);
+      runnable.id = i;
+      runnables.add(runnable);
+      Thread thread = new Thread(runnable);
+      thread.setName("TestThread " + String.valueOf(i));
+      threads.add(thread);
+    }
+    for (Thread thread : threads) {
+      thread.start();
+    }
+    for (Thread thread : threads) {
+      thread.join();
+    }
+
+    for (TestThreadedRunnable runnable : runnables) {
+      if (runnable.exception != null) {
+        throw new RuntimeException(runnable.exception);
+      }
+      runnable.accessor.close();
+    }
+
+    assertEquals(10000, map.size());
+
+    
+
+  }
+
   @Test
   public void testNull() throws Exception {
 
@@ -147,8 +218,6 @@
     accessor.close();
 
 
-
-
   }
 
 



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@labs.apache.org
For additional commands, e-mail: commits-help@labs.apache.org