35407   Standalone help may hang if started/shutdown randomly
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/HelpApplication.java b/org.eclipse.help/src/org/eclipse/help/internal/HelpApplication.java
index ed3fec7..60054ea 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/HelpApplication.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/HelpApplication.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.help.internal;
 import java.io.*;
+import java.nio.channels.*;
 import java.util.*;
 
 import org.eclipse.core.boot.IPlatformRunnable;
@@ -26,10 +27,13 @@
  */
 public class HelpApplication
 	implements IPlatformRunnable, IExecutableExtension {
+	private static final String APPLICATION_LOCK_FILE = ".applicationlock";
 	private static final int STATUS_EXITTING = 0;
 	private static final int STATUS_RESTARTING = 2;
 	private static final int STATUS_RUNNING = 1;
 	private static int status = STATUS_RUNNING;
+	private File metadata;
+	private FileLock lock;
 	/**
 	 * Causes help service to stop and exit
 	 */
@@ -48,21 +52,33 @@
 	 * Runs help service application.
 	 */
 	public Object run(Object args) throws Exception {
+		if (status == STATUS_RESTARTING) {
+			return EXIT_RESTART;
+		}
+
+		metadata = new File(Platform.getLocation().toFile(), ".metadata/");
 		if (!HelpSystem.ensureWebappRunning()) {
 			System.out.println(
 				"Help web application could not start.  Check log file for details.");
 			return EXIT_OK;
 		}
+
+		if (status == STATUS_RESTARTING) {
+			return EXIT_RESTART;
+
+		}
 		writeHostAndPort();
+		obtainLock();
+
 		// main program loop
 		while (status == STATUS_RUNNING) {
 			try {
-				Thread.sleep(250);
+				Thread.sleep(100);
 			} catch (InterruptedException ie) {
 				break;
 			}
 		}
-
+		releaseLock();
 		if (status == STATUS_RESTARTING) {
 			return EXIT_RESTART;
 		} else {
@@ -88,8 +104,7 @@
 		p.put("host", WebappManager.getHost());
 		p.put("port", "" + WebappManager.getPort());
 
-		File workspace = Platform.getLocation().toFile();
-		File hostPortFile = new File(workspace, ".metadata/.connection");
+		File hostPortFile = new File(metadata, ".connection");
 		hostPortFile.deleteOnExit();
 		FileOutputStream out = null;
 		try {
@@ -105,4 +120,21 @@
 		}
 
 	}
+	private void obtainLock() {
+		File lockFile = new File(metadata, APPLICATION_LOCK_FILE);
+		try {
+			RandomAccessFile raf = new RandomAccessFile(lockFile, "rw");
+			lock = raf.getChannel().lock();
+		} catch (IOException ioe) {
+			lock = null;
+		}
+	}
+	private void releaseLock() {
+		if (lock != null) {
+			try {
+				lock.channel().close();
+			} catch (IOException ioe) {
+			}
+		}
+	}
 }
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/Eclipse.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/Eclipse.java
index 474294f..967eccb 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/standalone/Eclipse.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/Eclipse.java
@@ -27,14 +27,16 @@
 
 	File dir;
 	String[] cmdarray;
-	private int status;
-	private Exception exception = new Exception("Unknown exception.");
+	private int status = STATUS_INIT;
+	private Exception exception;
 	Process pr;
+	private EclipseLifeCycleListener lifeCycleListener;
 	/**
 	 * Constructor
 	 */
-	public Eclipse() {
+	public Eclipse(EclipseLifeCycleListener listener) {
 		super();
+		this.lifeCycleListener = listener;
 		this.setName("Eclipse");
 		this.dir = Options.getEclipseHome();
 	}
@@ -88,24 +90,18 @@
 			if (Options.isDebug()) {
 				printCommand();
 			}
-			launchProcess();
-		} catch (Exception exc) {
-			exception = exc;
-			status = STATUS_ERROR;
-		} finally {
-			if (status == STATUS_INIT) {
-				status = STATUS_ERROR;
-			}
-		}
-	}
-	private void launchProcess() throws IOException {
-		try {
 			do {
 				pr = Runtime.getRuntime().exec(cmdarray, (String[]) null, dir);
 				(new StreamConsumer(pr.getInputStream())).start();
 				(new StreamConsumer(pr.getErrorStream())).start();
-				status = STATUS_STARTED;
-				pr.waitFor();
+				if (status == STATUS_INIT) {
+					// started first time
+					status = STATUS_STARTED;
+				}
+				try {
+					pr.waitFor();
+				} catch (InterruptedException e) {
+				}
 				if (Options.isDebug()) {
 					System.out.println(
 						"Eclipse exited with status code " + pr.exitValue());
@@ -115,7 +111,17 @@
 					}
 				}
 			} while (pr.exitValue() == NEEDS_RESTART);
-		} catch (InterruptedException e) {
+		} catch (Exception exc) {
+			exception = exc;
+			status = STATUS_ERROR;
+		} finally {
+			if (status == STATUS_INIT) {
+				status = STATUS_ERROR;
+			}
+			if (status == STATUS_ERROR) {
+				exception = new Exception("Unknown exception.");
+			}
+			lifeCycleListener.eclipseEnded();
 		}
 	}
 	/**
@@ -202,7 +208,7 @@
 
 	}
 	/**
-	 * Used in unit testing.
+	 * Forcibly kill Eclipse process.
 	 */
 	public void killProcess() {
 		if (pr != null) {
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseConnection.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseConnection.java
index 59bf71b..5427660 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseConnection.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseConnection.java
@@ -20,30 +20,12 @@
  * It should be launched from command line.
  */
 public class EclipseConnection {
-	// timout for .hostport file to apper since starting eclipse [ms]
-	// 0 if no waiting for file should occur
-	int startupTimeout;
-	// number of retries to connectect to webapp
-	int connectionRetries;
-	// time between retries to connectect to webapp [ms]
-	int connectionRetryInterval;
 	// help server host
 	private String host;
 	// help server port
 	private String port;
 
 	public EclipseConnection() {
-		this(0, 0, 5 * 1000);
-	}
-
-	public EclipseConnection(
-		int startupTimeout,
-		int connectionRetries,
-		int connectionRetryInterval) {
-
-		this.startupTimeout = startupTimeout;
-		this.connectionRetries = connectionRetries;
-		this.connectionRetryInterval = connectionRetryInterval;
 	}
 
 	public String getPort() {
@@ -64,34 +46,28 @@
 	}
 
 	public void connect(URL url) throws InterruptedException, Exception {
-		for (int i = 0; i <= connectionRetries; i++) {
-			try {
-				HttpURLConnection connection =
-					(HttpURLConnection) url.openConnection();
-				if (Options.isDebug()) {
-					System.out.println(
-						"Connection  to control servlet created.");
-				}
-				connection.connect();
-				if (Options.isDebug()) {
-					System.out.println(
-						"Connection  to control servlet connected.");
-				}
-				int code = connection.getResponseCode();
-				if (Options.isDebug()) {
-					System.out.println(
-						"Response code from control servlet=" + code);
-				}
-				connection.disconnect();
-				return;
-			} catch (IOException ioe) {
-				if (Options.isDebug()) {
-					ioe.printStackTrace();
-				}
+		try {
+			HttpURLConnection connection =
+				(HttpURLConnection) url.openConnection();
+			if (Options.isDebug()) {
+				System.out.println("Connection  to control servlet created.");
 			}
-			Thread.sleep(connectionRetryInterval);
+			connection.connect();
+			if (Options.isDebug()) {
+				System.out.println("Connection  to control servlet connected.");
+			}
+			int code = connection.getResponseCode();
+			if (Options.isDebug()) {
+				System.out.println(
+					"Response code from control servlet=" + code);
+			}
+			connection.disconnect();
+			return;
+		} catch (IOException ioe) {
+			if (Options.isDebug()) {
+				ioe.printStackTrace();
+			}
 		}
-		throw new Exception("Connection to Help System timed out.");
 	}
 
 	/**
@@ -100,28 +76,6 @@
 	 * and help might be starting up.
 	 */
 	public void renew() throws Exception {
-		long time1 = System.currentTimeMillis();
-		while (!Options.getConnectionFile().exists()) {
-			// wait for .hostport file to appear
-			if (Options.isDebug()) {
-				System.out.println(
-					"File "
-						+ Options.getConnectionFile()
-						+ " does not exist, at the moment.");
-			}
-			// timeout
-			if (System.currentTimeMillis() - time1 >= startupTimeout) {
-				if (Options.isDebug()) {
-					System.out.println(
-						"Timeout waiting for file "
-							+ Options.getConnectionFile()+"\nEclipse is not running.");
-				}
-				throw new Exception(
-					"Timeout waiting for file " + Options.getConnectionFile()+"\nEclipse is not running.");
-			}
-			// wait more
-			Thread.sleep(2000);
-		}
 		Properties p = new Properties();
 		FileInputStream is = null;
 		try {
@@ -129,7 +83,7 @@
 			p.load(is);
 			is.close();
 		} catch (IOException ioe) {
-			// it ok, eclipse might have just exited
+			// it is ok, eclipse might have just exited
 			throw ioe;
 		} finally {
 			if (is != null) {
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseController.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseController.java
index fa96b67..8c284f9 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseController.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseController.java
@@ -10,14 +10,16 @@
  *******************************************************************************/
 package org.eclipse.help.internal.standalone;
 
+import java.io.*;
 import java.net.*;
+import java.nio.channels.*;
 
 /**
  * This program is used to start or stop Eclipse
  * Infocenter application.
  * It should be launched from command line.
  */
-public class EclipseController {
+public class EclipseController implements EclipseLifeCycleListener {
 
 	// control servlet path
 	private static final String CONTROL_SERVLET_PATH =
@@ -29,7 +31,10 @@
 	// Eclipse connection params
 	protected EclipseConnection connection;
 
-	private Eclipse eclipse = null;
+	public Eclipse eclipse = null;
+	// Inter process lock
+	private FileLock lock;
+	private boolean eclipseEnded = false;
 	/**
 	 * Constructs help system
 	 * @param applicationID ID of Eclipse help application
@@ -44,36 +49,35 @@
 
 		this.applicationId = applicationId;
 		Options.init(applicationId, args);
-		connection = initConnection();
-	}
-
-	/**
-	 * Creates a connection to Eclipse. May need to override this to pass retry parameters.
-	 */
-	protected EclipseConnection initConnection() {
-		return new EclipseConnection();
+		connection = new EclipseConnection();
 	}
 
 	/**
 	 * @see org.eclipse.help.standalone.Help#shutdown()
 	 */
-	public void shutdown() throws Exception {
+	public final synchronized void shutdown() throws Exception {
 		try {
-			sendHelpCommand("shutdown", new String[0]);
+			obtainLock();
+			sendHelpCommandInternal("shutdown", new String[0]);
 		} catch (MalformedURLException mue) {
 			mue.printStackTrace();
 		} catch (InterruptedException ie) {
+		} finally {
+			releaseLock();
 		}
-
-		connection.reset();
 	}
 
 	/**
 	 * @see org.eclipse.help.standalone.Help#start()
 	 */
-	public void start() throws Exception {
-		connection.reset();
-		startEclipse();
+	public final synchronized void start() throws Exception {
+		try {
+			obtainLock();
+			startEclipse();
+		} finally {
+			releaseLock();
+		}
+
 	}
 
 	/**
@@ -82,20 +86,91 @@
 	 * If connection fails, retries several times,
 	 * in case webapp is starting up.
 	 */
-	protected void sendHelpCommand(String command, String[] parameters)
+	protected final synchronized void sendHelpCommand(
+		String command,
+		String[] parameters)
+		throws Exception {
+		try {
+			obtainLock();
+			sendHelpCommandInternal(command, parameters);
+		} finally {
+			releaseLock();
+		}
+
+	}
+
+	/**
+	 * Starts Eclipse if not yet running.
+	 */
+	private void startEclipse() throws Exception {
+		boolean fullyRunning = isApplicationRunning();
+		if (fullyRunning) {
+			return;
+		}
+		if (Options.isDebug()) {
+			System.out.println(
+				"Using workspace " + Options.getWorkspace().getAbsolutePath());
+		}
+		// delete old connection file
+		Options.getConnectionFile().delete();
+		connection.reset();
+
+		if (Options.isDebug()) {
+			System.out.println(
+				"Ensured old .connection file is deleted.  Launching Eclipse.");
+		}
+		eclipseEnded = false;
+		eclipse = new Eclipse(this);
+		eclipse.start();
+		fullyRunning = isApplicationRunning();
+		while (!eclipseEnded && !fullyRunning) {
+			try {
+				Thread.sleep(250);
+			} catch (InterruptedException ie) {
+			}
+			fullyRunning = isApplicationRunning();
+		}
+		if (eclipseEnded) {
+			if (eclipse.getStatus() == Eclipse.STATUS_ERROR) {
+				throw eclipse.getException();
+			}
+			return;
+		}
+		if (Options.isDebug()) {
+			System.out.println("Eclipse launched");
+		}
+		// in case controller is killed
+		Runtime.getRuntime().addShutdownHook(new EclipseCleaner());
+	}
+	private void sendHelpCommandInternal(String command, String[] parameters)
 		throws Exception {
 		if (!"shutdown".equalsIgnoreCase(command)) {
-			if (eclipse == null || !eclipse.isAlive()) {
-				startEclipse();
-			}
+			startEclipse();
+		}
+		if (!isApplicationRunning()) {
+			return;
 		}
 		if (!connection.isValid()) {
 			connection.renew();
 		}
-
 		try {
 			URL url = createCommandURL(command, parameters);
-			connection.connect(url);
+			if ("shutdown".equalsIgnoreCase(command)
+				&& Options.getConnectionFile().exists()) {
+				connection.connect(url);
+				long timeLimit = System.currentTimeMillis() + 60 * 1000;
+				while (Options.getConnectionFile().exists()) {
+					Thread.sleep(200);
+					if (System.currentTimeMillis() > timeLimit) {
+						System.out.println(
+							"Shutting down is taking too long.  Will not wait.");
+						break;
+					}
+				}
+
+			} else {
+				connection.connect(url);
+			}
 		} catch (MalformedURLException mue) {
 			mue.printStackTrace();
 		} catch (InterruptedException ie) {
@@ -128,92 +203,72 @@
 		return new URL(urlStr.toString());
 	}
 
-	/**
-	 * Starts Eclipse if not yet running.
-	 */
-	private void startEclipse() throws Exception {
-		if (Options.isDebug()) {
-			System.out.println(
-				"Using workspace " + Options.getWorkspace().getAbsolutePath());
-			System.out.println(
-				"Checking if file " + Options.getLockFile() + " exists.");
-		}
-		if (isAnotherRunning()) {
+	public void eclipseEnded() {
+		connection.reset();
+	}
+	private void obtainLock() throws IOException {
+		if (lock != null) {
+			// we already have lock
 			return;
 		}
-		// delete old connection file
-		Options.getConnectionFile().delete();
-
-		if (Options.isDebug()) {
-			System.out.println(
-				"Ensured old .connection file is deleted.  Launching Eclipse.");
-		}
-		eclipse = new Eclipse();
-		eclipse.start();
-		while (eclipse.getStatus() == Eclipse.STATUS_INIT) {
-			try {
-				Thread.sleep(50);
-			} catch (InterruptedException ie) {
-			}
-		}
-		if (eclipse.getStatus() == Eclipse.STATUS_ERROR) {
-			throw eclipse.getException();
-		}
-		if (Options.isDebug()) {
-			System.out.println("Eclipse launched");
-		}
-	}
-
-	/**
-	 * @return true if eclipse is already running in another process
-	 */
-	private boolean isAnotherRunning() {
 		if (!Options.getLockFile().exists()) {
-			if (Options.isDebug()) {
-				System.out.println(
-					"File "
-						+ Options.getLockFile()
-						+ " does not exist.  Eclipse needs to be started.");
-			}
-			return false;
+			Options.getLockFile().getParentFile().mkdirs();
 		}
-
-		if (System.getProperty("os.name").startsWith("Win")) {
-			// if file cannot be deleted, Eclipse is running
-			if (!Options.getLockFile().delete()) {
-				if (Options.isDebug()) {
-					System.out.println(
-						"File "
-							+ Options.getLockFile()
-							+ " is locked.  Eclipse is already running.");
-				}
-				return true;
-			} else {
-				return false;
-			}
-		} else {
-			// if connection to control servlet can be made, Eclipse is running
-			try {
-				connection.renew();
-				if (connection.getHost() != null
-					&& connection.getPort() != null) {
-					URL url = createCommandURL("test", new String[0]);
-					connection.connect(url);
-					if (Options.isDebug()) {
-						System.out.println(
-							"Test connection to Eclipse established.  No need to start new Eclipse instance.");
-					}
-					return true;
-				}
-			} catch (Exception e) {
-			}
-			if (Options.isDebug()) {
-				System.out.println(
-					"Test connection to Eclipse could not be established.  Eclipse instance needs to be started.");
-			}
-			connection.reset();
-			return false;
+		RandomAccessFile raf =
+			new RandomAccessFile(Options.getLockFile(), "rw");
+		lock = raf.getChannel().lock();
+		if (Options.isDebug()) {
+			System.out.println("Lock obtained.");
 		}
 	}
-
+	private void releaseLock() {
+		if (lock != null) {
+			try {
+				lock.channel().close();
+				if (Options.isDebug()) {
+					System.out.println("Lock released.");
+				}
+				lock = null;
+			} catch (IOException ioe) {
+			}
+		}
+	}
+	/** Tests whether HelpApplication is running
+	 * by testing if .applicationlock is locked
+	 */
+	private boolean isApplicationRunning() {
+		File applicationLockFile =
+			new File(Options.getLockFile().getParentFile(), ".applicationlock");
+		RandomAccessFile randomAccessFile = null;
+		FileLock applicationLock = null;
+		try {
+			randomAccessFile = new RandomAccessFile(applicationLockFile, "rw");
+			applicationLock = randomAccessFile.getChannel().tryLock();
+		} finally {
+			if (applicationLock != null) {
+				try {
+					applicationLock.release();
+				} catch (IOException ioe) {
+				}
+			}
+			if (randomAccessFile != null) {
+				try {
+					randomAccessFile.close();
+				} catch (IOException ioe) {
+				}
+			}
+			if (Options.isDebug()) {
+				System.out.println(
+					"isApplicationRunning? " + (applicationLock == null));
+			}
+			return applicationLock == null;
+		}
+	}
+	public class EclipseCleaner extends Thread {
+		public void run() {
+			if (eclipse != null) {
+				eclipse.killProcess();
+			}
+		}
+	}
 }
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseLifeCycleListener.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseLifeCycleListener.java
new file mode 100644
index 0000000..cb91e1f
--- /dev/null
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/EclipseLifeCycleListener.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.help.internal.standalone;
+/**
+ * Notified when Eclipse process exits.
+ */
+public interface EclipseLifeCycleListener {
+	public void eclipseEnded();
+}
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/Options.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/Options.java
index 372e738..6cd9cf2 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/standalone/Options.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/Options.java
@@ -80,8 +80,8 @@
 
 		// consume -command option
 		helpCommand = extractOption(eclipseArgs, "-command");
-		if(helpCommand==null){
-			helpCommand=new ArrayList(0);
+		if (helpCommand == null) {
+			helpCommand = new ArrayList(0);
 		}
 
 		// read -debug option
@@ -95,10 +95,10 @@
 		}
 		// consume -eclipsehome (accept eclipse_home too) option
 		List homes = extractOption(eclipseArgs, "-eclipseHome");
-		if (homes==null || homes.isEmpty()) {
+		if (homes == null || homes.isEmpty()) {
 			homes = extractOption(eclipseArgs, "-eclipse_Home");
 		}
-		if (homes!=null && !homes.isEmpty()) {
+		if (homes != null && !homes.isEmpty()) {
 			eclipseHome = new File((String) homes.get(0));
 		} else {
 			eclipseHome = new File(System.getProperty("user.dir"));
@@ -111,7 +111,7 @@
 		} else {
 			workspace = new File(eclipseHome, "workspace");
 		}
-		lockFile = new File(workspace, "/.metadata/.lock");
+		lockFile = new File(workspace, "/.metadata/.helplock");
 		hostPortFile = new File(workspace, "/.metadata/.connection");
 
 		// consume -host option
@@ -158,10 +158,10 @@
 		}
 
 		// consume -vmargs option
-		vmArgs=new ArrayList(0);
+		vmArgs = new ArrayList(0);
 		List passedVmArgs = extractOption(eclipseArgs, "-vmargs");
-		if (passedVmArgs!=null && passedVmArgs.size() > 0) {
-			vmArgs=passedVmArgs;
+		if (passedVmArgs != null && passedVmArgs.size() > 0) {
+			vmArgs = passedVmArgs;
 		}
 
 		// modify the options for passing them to eclipse
diff --git a/org.eclipse.help/src/org/eclipse/help/internal/standalone/StandaloneHelp.java b/org.eclipse.help/src/org/eclipse/help/internal/standalone/StandaloneHelp.java
index 01926df..3fb01fa 100644
--- a/org.eclipse.help/src/org/eclipse/help/internal/standalone/StandaloneHelp.java
+++ b/org.eclipse.help/src/org/eclipse/help/internal/standalone/StandaloneHelp.java
@@ -31,15 +31,9 @@
  * </ul>
  */
 public class StandaloneHelp extends EclipseController {
-	// timout for .hostport file to apper since starting eclipse [ms]
-	private static final int STARTUP_TIMEOUT = 40 * 1000;
-	// number of retries to connectect to webapp
-	private static final int CONNECTION_RETRIES = 3;
-	// time between retries to connectect to webapp [ms]
-	private static final int CONNECTION_RETRY_INTERVAL = 5 * 1000;
 	// ID of the application to run
 	private static final String HELP_APPLICATION_ID =
-		HelpPlugin.PLUGIN_ID+".helpApplication";
+		HelpPlugin.PLUGIN_ID + ".helpApplication";
 
 	/**
 	 * Constructs help system
@@ -75,19 +69,6 @@
 	}
 
 	/**
-	 * Overrides the initialization of the connection to pass retry parameters.
-	 */
-	protected EclipseConnection initConnection() {
-		int timeout = STARTUP_TIMEOUT;
-		if (Options.getServerTimeout() > 0) {
-			timeout = 1000 * Options.getServerTimeout();
-		}
-		return new EclipseConnection(
-			timeout,
-			CONNECTION_RETRIES,
-			CONNECTION_RETRY_INTERVAL);
-	}
-	/**
 	 * @see org.eclipse.help.standalone.Help#displayContext(java.lang.String,int,int)
 	 */
 	public void displayContext(String contextId, int x, int y) {