From bf531728faa1db62566916d0437527463dd555d3 Mon Sep 17 00:00:00 2001 From: pmarguerite Date: Tue, 12 Jun 2007 10:40:53 +0000 Subject: [PATCH] Added to mechanism to determine if a client closing is the last one of the session. Each application creates a file with a lock on it in a subdirectory of the session. When a client is closing, it checks if there is no other locked file. If no file, it is the last one and closes the sessions. git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@403 be28352e-c001-0410-b1a7-c7978e42abec --- .../ClientSessionFileWatcherElement.java | 3 +- .../vamsas/client/simpleclient/SimpleClient.java | 113 +++++++++++- .../client/simpleclient/SimpleClientFactory.java | 4 +- .../vamsas/client/simpleclient/VamsasSession.java | 181 +++++++++++++++++--- 4 files changed, 272 insertions(+), 29 deletions(-) diff --git a/src/uk/ac/vamsas/client/simpleclient/ClientSessionFileWatcherElement.java b/src/uk/ac/vamsas/client/simpleclient/ClientSessionFileWatcherElement.java index 9d2b2d9..f8e4f12 100644 --- a/src/uk/ac/vamsas/client/simpleclient/ClientSessionFileWatcherElement.java +++ b/src/uk/ac/vamsas/client/simpleclient/ClientSessionFileWatcherElement.java @@ -53,6 +53,7 @@ public class ClientSessionFileWatcherElement extends SessionFileWatcherElement { catch (Exception e) { log.error("Whilst watching "+watcher.getSubject(), e); } + // log.debug("got lock watcher"); if (doclock==null) {//no change detected this.cycleCountSinceModif ++; @@ -67,7 +68,7 @@ public class ClientSessionFileWatcherElement extends SessionFileWatcherElement { //log.debug("no modification"); return false; } - this.cycleCountSinceModif =0; //change detected + if (this.isTimeOutEnable) this.cycleCountSinceModif =0; //change detected if(this.handler != null ) synchronized (this.handler) { diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java index 455a0f2..ca68e54 100644 --- a/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java @@ -10,6 +10,10 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + +import java.nio.channels.OverlappingFileLockException; import java.util.Hashtable; import java.util.Vector; @@ -43,6 +47,15 @@ public class SimpleClient implements IClient { protected ClientHandle client = null; protected EventGeneratorThread evgen = null; protected ClientDocument cdocument = null; + + + + + private java.nio.channels.FileLock activeClientFilelock = null; + + private FileChannel activeClientFileChannel = null; + private File clientlockFile = null; + /** * object hash table that persists in each client holding vorbaIds and hash values after a document write */ @@ -445,7 +458,7 @@ public class SimpleClient implements IClient { // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead. log.error("importDocument is not yet implemented for a SimpleClient Session."); - /* try { + /*try { this._session.setVamsasDocument(location); } catch (IOException e) { log.error("importDocument failed."); @@ -495,4 +508,102 @@ public class SimpleClient implements IClient { pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager()); } } + + + protected void releaseActiveClientFile() throws IOException + { + + log.debug("Releasing active client file"); + if( activeClientFilelock != null) + // Release the lock + activeClientFilelock.release(); + + if (activeClientFileChannel != null) + // Close the file + activeClientFileChannel.close(); + if (this.clientlockFile != null) + { + this.clientlockFile.delete(); + log.debug("deleted active client lock file"); + } + + } + + protected void createActiveClientFile() throws IOException + { + if(this.clientlockFile != null )return; + log.debug("createActiveClientFile"); + //create, if need, subdirectory to contain client files + File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory); + if( !clientlockFileDir.exists()) + {//the directory does not exist, create it + if (! clientlockFileDir.mkdirs()) + { + throw new IOException("Failed to create sub directory to session directory for client lock files'"+clientlockFileDir.getAbsolutePath()+"'"); + } + } + else + { + if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite())) + { + throw new IOException("Directory for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'"); + } + } + this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll(File.separator, "").replaceAll(":", "").replaceAll(";", "")); + + log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath()); + if (clientlockFile.exists()) + {//should not happen, file should be deleted when the application closes or when a crashed application has been detected + log.error("client lock file already exits"); + } + try { + //create the empty file + if(! clientlockFile.createNewFile()) + { + log.error("Unable to create active client lock file"); + + return; + } + else + log.debug("file created"); + // Get a file channel for the file + FileChannel channel = new RandomAccessFile(clientlockFile, "rw").getChannel(); + + // Use the file channel to create a lock on the file. + // This method blocks until it can retrieve the lock. + java.nio.channels.FileLock activeClientFilelock ;// = channel.lock(); + + // Try acquiring the lock without blocking. This method returns + // null or throws an exception if the file is already locked. + try + { + activeClientFilelock = channel.tryLock(); + log.debug("Got lock"); + } + catch (OverlappingFileLockException e) + { + // File is already locked in this thread or virtual machine + log.error("Oups the file is already locked",e); + + } + + // Release the lock + /* lock.release(); + + // Close the file + channel.close();*/ + } catch (Exception e) { + log.error("Error during lock file creation",e); + } + + + + } + + /** + * @return the clientlockFile + */ + protected File getClientlockFile() { + return clientlockFile; + } } diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java index b4a74d3..74650b0 100644 --- a/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java @@ -239,7 +239,7 @@ public class SimpleClientFactory implements IClientFactory { //create simple client client = new SimpleClient(userId, clientHandle, vamsasSession); - vamsasSession.addClient(client); + vamsasSession.addClient((SimpleClient) client); vamsasSession.setSessionManager(this.getSessionManager()); return client; } @@ -294,6 +294,8 @@ public class SimpleClientFactory implements IClientFactory { //only one session available, open it. return this.getIClient(clientHandle, availableSessions[0]); } + else + log.debug("No active session found"); } //no session available - create a new one diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java index 61cf0fb..58f442a 100644 --- a/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java @@ -1,8 +1,11 @@ package uk.ac.vamsas.client.simpleclient; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.OverlappingFileLockException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -13,6 +16,7 @@ import org.apache.log4j.PatternLayout; import uk.ac.vamsas.client.ClientHandle; import uk.ac.vamsas.client.Events; import uk.ac.vamsas.client.IClient; +import uk.ac.vamsas.client.SessionHandle; import uk.ac.vamsas.client.UserHandle; /** * Does all the IO operations for a SimpleClient instance accessing @@ -83,14 +87,14 @@ public class VamsasSession { /** * Count of cycles before considering the current client as the last one of the session (if no other client registered as active ) */ - private final int watchCycleCountBeforeLastClient = 1100 ; + private final int watchCycleCountBeforeLastClient = 1220 ; /** * time between checking */ public int WATCH_SLEEP=30; - //protected String clientFileDirectory = "clients"; + protected String clientFileDirectory = "clients"; /** * called to clear update flag after a successful offline storage event @@ -361,7 +365,7 @@ public class VamsasSession { * add the client to the client list file * @param client client to add to the session */ - protected void addClient(IClient client) + protected void addClient(SimpleClient client) { if (client == null) slog.error("Try to add a null client to the session "); @@ -372,14 +376,15 @@ public class VamsasSession { log.debug("Added."); log.debug("Register Client as Active."); - /* try { + try { client.createActiveClientFile(); } catch (IOException e) { log.debug("Error during active client file creation."); - }*/ + } //tracks modification to the client list and readds client to the list getClientWatcherElement().setHandler(new AddClientWatchCallBack(client)); getClientWatcherElement().enableWatch(); + } } @@ -391,13 +396,13 @@ public class VamsasSession { private class AddClientWatchCallBack implements WatcherCallBack { - private IClient client ; + private SimpleClient client ; /** *Inits the handler with the client to check in the list * @param client client to monitor in the client list */ - protected AddClientWatchCallBack (IClient client) + protected AddClientWatchCallBack (SimpleClient client) { this.client = client; } @@ -411,7 +416,7 @@ public class VamsasSession { boolean isWatchEnable = watcher.isWatchEnabled(); if (lock== null)//no update on the list return isWatchEnable; - // log.debug("change on the client list "); + log.debug("change on the client list "); if (client != null) { @@ -436,7 +441,7 @@ public class VamsasSession { log.debug("client is in the list"); } - //log.debug("isWatchEnable "+isWatchEnable); + log.debug("isWatchEnable "+isWatchEnable); return isWatchEnable; } } @@ -468,21 +473,38 @@ public class VamsasSession { //Wait for several watchers cycle to see if the current client was the last client active in the session. //if yes, close the session - getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client)); + // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client)); getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient); log.info("remove client from list"); clist.clearList(); - log.info("client list cleared"); + log.info("list cleared"); if (cwe!=null) { cwe.enableWatch(); - /* try { - log.debug("Releasing active client file"); - client.releaseActiveClientFile(); - } catch (IOException e) { - log.error("error during active file client release"); - }*/ + log.debug("Stopping EventGenerator.."); + client.evgen.stopWatching(); + cwe.setHandler(null); + +// ask to the client to copy application data into the document + client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null); + + if ( this.isLastActiveClient(client)) + { + log.debug("Last active client: closing session"); + log.info("Closing session"); + getSessionManager().removeSession(client.getSessionHandle()); + } + + try + { + log.debug("Releasing active client file"); + client.releaseActiveClientFile(); + } + catch (IOException e) + { + log.error("error during active file client release"); + } } @@ -503,6 +525,98 @@ public class VamsasSession { } + + private boolean isLastActiveClient(SimpleClient client) + { + log.debug("Testing if current client is the last one."); + boolean noOtherActiveClient = true; + //create, if need, subdirectory to contain client files + File clientlockFileDir = new File (this.sessionDir, clientFileDirectory); + if( !clientlockFileDir.exists()) + { + log.error("Something wrong the active client file does not exits... should not happen"); + return false; + } + + try { + + //no check every file in the directory and try to get lock on it. + File [] clientFiles = clientlockFileDir.listFiles(); + if(clientFiles == null || clientFiles.length==0) + {//there is not file on the directory. the current client should be the last one. + return true; + } + + for (int i = clientFiles.length - 1; i>-1&& noOtherActiveClient ;i--) + { + File clientFile = clientFiles[i]; + log.debug("testing file for lock: "+clientFile.getAbsolutePath()); + if(client.getClientlockFile().equals(clientFile)) + { + log.debug("current client file found"); + continue; + } + if (clientFile != null && clientFile.exists() ) + { + try + { + log.debug("Try to acquire a lock on the file"); + // Get a file channel for the file + FileChannel channel = new RandomAccessFile(clientFile, "rw").getChannel(); + + // Use the file channel to create a lock on the file. + // This method blocks until it can retrieve the lock. + // java.nio.channels.FileLock activeClientFilelock = channel.lock(); + + // Try acquiring the lock without blocking. This method returns + // null or throws an exception if the file is already locked. + try + { + java.nio.channels.FileLock activeClientFilelock = channel.tryLock(); + + //the lock has been acquired. + //the file was not lock and so the corresponding application seems to have die + if(activeClientFilelock != null) + { + log.debug("lock obtained : file must be from a crashed application"); + + + activeClientFilelock.release(); + log.debug("lock released"); + + channel.close(); + log.debug("channel closed"); + + //delete file + clientFile.delete(); + log.debug("crashed application file deleted"); + + } + else + { + noOtherActiveClient = false; + log.debug("lock not obtained : another application is active"); + } + } + catch (OverlappingFileLockException e) + { + // File is already locked in this thread or virtual machine + //that the expected behaviour + log.debug("lock not accessible ",e); + } + } + catch (Exception e) + { + log.debug("error during lock testing ",e); + } + } + } + + } catch (Exception e) { + log.error("error during counting active clients"); + } + return noOtherActiveClient; + } /** * Handler for the client watcher. after a client have been removed * @@ -540,8 +654,7 @@ public class VamsasSession { //checks if the client is not already in the lists // ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList(); -// ask to the client to cpoy application data into the document - client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null); + boolean islastClient = true; if (manualCheckOfClientCount) { @@ -564,13 +677,7 @@ public class VamsasSession { { //the client is the last one, so close current session log.info("last client removed: closing session"); - -// close document - client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null); - log.debug("close document request done"); - - getSessionManager().removeSession(client.getSessionHandle()); - log.debug("Session removed"); + closeSession(client); } } else @@ -592,6 +699,28 @@ public class VamsasSession { } } + /** + * closes the current session, + * and send an event to the last client to close the document + * @param client the last client of the client + */ + private void closeSession(SimpleClient client) + { +// close document + client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null); + log.debug("close document request done"); + this.closeSession(client.getSessionHandle()); + } + + /** + * CLoses the current session + * @param sessionHandle sessionHandle of the session to remove + */ + private void closeSession(SessionHandle sessionHandle) + { + getSessionManager().removeSession(sessionHandle); + log.debug("Session removed"); + } /** * @return the sessionManager */ -- 1.7.10.2