Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
[GLASSFISH-2273] RMIClient is not thread safe Created: 30/Jan/07 Updated: 17/May/07 Resolved: 17/May/07 Status: Project: Component/s: Affects Version/s: Fix Version/s: Resolved glassfish admin 9.1pe Type: Reporter: Resolution: Labels: Remaining Estimate: Time Spent: Original Estimate: Environment: Bug llc Won't Fix None Not Specified Issuezilla Id: 2,273 9.1pe Priority: Assignee: Votes: Minor llc 0 Not Specified Not Specified Operating System: All Platform: All Description RMIClient is not thread safe. A calling thread creates it, and it spawns a thread. A later thread could try to do various things with it, but there are no synchronization constructs used anywhere. By making most of the variable 'final', this problem can largely be eliminated. The few remaining variables can be made 'volatile'. Suggested diffs: MB2:/gf/build/glassfish/appserv-core lloyd$ cvs diff -u -w src/java/com/sun/enterprise/admin/ server/core/channel/RMIClient.java Index: src/java/com/sun/enterprise/admin/server/core/channel/RMIClient.java =============================================================== ==== RCS file: /cvs/glassfish/appserv-core/src/java/com/sun/enterprise/admin/server/core/channel/ RMIClient.java,v retrieving revision 1.3 diff -u -w -r1.3 RMIClient.java — src/java/com/sun/enterprise/admin/server/core/channel/RMIClient.java 25 Dec 2005 04:14:23 -0000 1.3 +++ src/java/com/sun/enterprise/admin/server/core/channel/RMIClient.java 31 Jan 2007 02:22:23 -0000 @@ -52,16 +52,16 @@ /** RMI client is used to send admin channel messages over RMI. */ -public class RMIClient implements Runnable { +public final class RMIClient implements Runnable { private File stubFile; private File seedFile; private long stubFileTs = 0; private byte[] key; private RemoteAdminChannel stub; private boolean autoRefresh; private long autoRefreshInterval; private Thread autoRefreshThread; + private final File stubFile; + private final File seedFile; + private volatile long stubFileTs = 0; + private volatile byte[] key; + private volatile RemoteAdminChannel stub; + private volatile boolean autoRefresh; + private final long autoRefreshInterval; + private final Thread autoRefreshThread; Create a new RMI client using specified stub file and use the specified @@ -84,10 +84,10 @@ if (this.stubFile.exists()) { stub = readStub(); } - if (AdminChannel.getClientAutoRefreshEnabled()) { - startAutoRefreshThread( AdminChannel.getClientAutoRefreshInterval()); - } + + autoRefresh = AdminChannel.getClientAutoRefreshEnabled(); + autoRefreshInterval = autoRefresh ? AdminChannel.getClientAutoRefreshInterval() : 0; + autoRefreshThread = autoRefresh ? startAutoRefreshThread() : null; } /** /** @@ -105,24 +105,25 @@ if (this.stubFile.exists()) { stub = readStub(); } + + autoRefresh = false; + autoRefreshInterval = 0; + autoRefreshThread = null; } /** Start auto refresh thread. The auto refresh thread refreshes the remote stub if the stub file has changed on the disk. */ void startAutoRefreshThread(long interval) { if (interval <= 0) { + private Thread startAutoRefreshThread() { + if ( autoRefreshInterval <= 0 || ! autoRefresh ) { throw new IllegalArgumentException(INVALID_AUTO_REFRESH_INTERVAL); } autoRefresh = true; autoRefreshInterval = interval; if (autoRefreshThread != null && autoRefreshThread.isAlive()) { - return; - } else { - autoRefreshThread = new Thread(this); - autoRefreshThread.start(); - } + + final Thread theThread = new Thread(this); + theThread.start(); + + return theThread; } /** @@ -386,7 +387,7 @@ /** Check status of server stub file and refresh if needed. */ private boolean checkServerStatus() { + private synchronized boolean checkServerStatus() { boolean gotNew = false; if (stubFile.exists()) { long ts = stubFile.lastModified(); @@ -563,7 +564,7 @@ } // i18n StringManager private static StringManager localStrings = + private static final StringManager localStrings = StringManager.getManager( RMIClient.class ); private static final String CLIENT_NULLARGS_ERRCODE = @@ -584,5 +585,5 @@ static final String FILE_READ_ERROR = "channel.file_read_error"; static final String SEED_FILE_OLDER = "channel.seed_file_older"; static Logger logger = Logger.getLogger(AdminConstants.kLoggerName); + static final Logger logger = Logger.getLogger(AdminConstants.kLoggerName); } MB2:/gf/build/glassfish/appserv-core lloyd$ Comments Comment by gfbugbridge [ 31/Jan/07 ] <BT6519766> Comment by llc [ 02/Feb/07 ] code review pending Comment by llc [ 15/Feb/07 ] Too risky to mess with. Comment by llc [ 15/Feb/07 ] Fixed version; deemed too risky to fix now (not my opinion). /* The contents of this file are subject to the terms of the Common Development and Distribution License (the License). You may not use this file except in compliance with the License. * You can obtain a copy of the license at https://glassfish.dev.java.net/public/CDDLv1.0.html or glassfish/bootstrap/legal/CDDLv1.0.txt. See the License for the specific language governing permissions and limitations under the License. * When distributing Covered Code, include this CDDL Header Notice in each file and include the License file at glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable, add the following below the CDDL Header, with the fields enclosed by brackets [] replaced by you own identifying information: "Portions Copyrighted [year] [name of copyright owner]" * Copyright 2006 Sun Microsystems, Inc. All rights reserved. */ /** PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms. * Copyright 2001-2002 by iPlanet/Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. All rights reserved. */ package com.sun.enterprise.admin.server.core.channel; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.rmi.RemoteException; import java.rmi.ServerException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.Handler; import com.sun.enterprise.admin.common.constant.AdminConstants; import com.sun.enterprise.admin.common.ServerInstanceStatus; import com.sun.enterprise.admin.common.Status; import com.sun.enterprise.admin.event.AdminEvent; import com.sun.enterprise.admin.event.AdminEventResult; //i18n import import com.sun.enterprise.util.i18n.StringManager; /** RMI client is used to send admin channel messages over RMI. */ public final class RMIClient implements Runnable { private final File stubFile; private final File seedFile; private volatile long stubFileTs = 0; private volatile byte[] key; private volatile RemoteAdminChannel stub; private volatile boolean autoRefresh; private final long autoRefreshInterval; private final Thread autoRefreshThread; /** Create a new RMI client using specified stub file and use the specified byte array (seed) for all calls to the remote object. To obtain an instance of RMIClient please use the public method <code>AdminChannel.getRMIClient()</code> which ensure that all calls to a particular server instance go through the same object instance (of RMIClient). @param stubFile name of the RMI stub file @param seedFile name of the shared secret file @throws IllegalArgumentException if either stubFile or seedFile is null. */ RMIClient(String stubFile, String seedFile) { if (stubFile == null || seedFile == null) { warn(CLIENT_NULLARGS_ERRCODE); throw new IllegalArgumentException(CLIENT_NULLARGS_ERRMSG); } this.stubFile = new File(stubFile); this.seedFile = new File(seedFile); if (this.stubFile.exists()) { stub = readStub(); } autoRefresh = AdminChannel.getClientAutoRefreshEnabled(); autoRefreshInterval = autoRefresh ? AdminChannel.getClientAutoRefreshInterval() : 0; autoRefreshThread = autoRefresh ? startAutoRefreshThread() : null; } /** * Constructor for local mode */ public RMIClient(boolean isDebug, String stubFile, String seedFile) { if (! isDebug) { logger.setLevel(java.util.logging.Level.SEVERE); } if (stubFile == null || seedFile == null) { throw new IllegalArgumentException(CLIENT_NULLARGS_ERRMSG); } this.stubFile = new File(stubFile); this.seedFile = new File(seedFile); if (this.stubFile.exists()) { stub = readStub(); } autoRefresh = false; autoRefreshInterval = 0; autoRefreshThread = null; } /** Start auto refresh thread. The auto refresh thread refreshes the remote stub if the stub file has changed on the disk. */ private Thread startAutoRefreshThread() { if ( autoRefreshInterval <= 0 || ! autoRefresh ) { throw new IllegalArgumentException(INVALID_AUTO_REFRESH_INTERVAL); } final Thread theThread = new Thread(this); theThread.start(); return theThread; } /** Stop auto refresh thread. */ void stopAutoRefreshThread() { autoRefresh = false; } Auto refresh this rmi client. */ public void run() { while (autoRefresh) /** Unknown macro: { try { Thread.sleep(autoRefreshInterval); } catch (InterruptedException ie) { warn(AUTO_REFRESH_INTR); autoRefresh = false; } if (autoRefresh) { checkServerStatus(); } } } /** Send specified event notification over admin channel */ public AdminEventResult sendNotification(AdminEvent event) { // Normal handling, send the event boolean doRetry = true; AdminEventResult result = null; if (stub != null) { try { result = stub.sendNotification(key, event); doRetry = false; } catch (ServerException re) { if ((re.detail != null) && (re.detail instanceof java.lang.IllegalArgumentException re.detail instanceof java.lang.SecurityException)) { doRetry = false; warn(EVENT_NOTIFY_ERROR); debug(re.detail); } else Unknown macro: { if (re.detail != null) { debug(re.detail); } debug(re); } } catch (RemoteException re) { if (re.detail != null) { debug(re.detail); } debug(re); } } if (doRetry) { // Normal processing did not work, try to get stub again and then // attempt to send the event boolean gotNew = checkServerStatus(); if (stub != null && gotNew) { try { result = stub.sendNotification(key, event); } catch (RemoteException re) { warn(EVENT_RENOTIFY_ERROR); if (re.detail != null) { debug(re.detail); } debug(re); } } } if (result == null) Unknown macro: { // Still couldn't communicate, set result appropriately result = new AdminEventResult(event.getSequenceNumber()); result.setResultCode(AdminEventResult.TRANSMISSION_ERROR); if (stub == null) { result.addMessage(event.getEffectiveDestination(), "Remote Stub is null"); }} return result; } /** Determines wheter the instance with given name is alive. Really speaking, checks whether the RMI channel is responsive. The method returns immediately with the status without any retries. @return boolean indicating whether the RMIServer Object that <code> this </code> server instance is alive. */ public boolean isAlive() { return isAlive(false); } Determines whether the instance represented by this object is alive. Unless the parameter refreshStub is true, the responsiveness of instance is checked by using cached stub (if any) and if there is no cached stub the method returns false. @param refreshStub if true, refresh remote stub if it has changed @return true if the instance represented by <code>this</code> object is responding, false otherwise. */ public boolean isAlive(boolean refreshStub) { /** if (refreshStub) { boolean gotNew = checkServerStatus(); } boolean isAlive = true; if (stub != null) { try { stub.pingServer(key); } catch(RemoteException re) { debug(re); isAlive = false; } } else { isAlive = false; } return ( isAlive ); } /** * Get server instance status code. If the instance can not be contacted * over rmi channel then the method reports that instance is not running. * The return value of this method is one of the constants <code> * kInstanceStartingCode, kInstanceRunningCode, kInstanceStoppingCode or * kInstanceNotRunningCode</code> from the class <code> * com.sun.enterprise.admin.common.Status</code>. * @return an int denoting the status of server starting, running, stopping * or not running. */ public int getInstanceStatusCode() { boolean gotNew = checkServerStatus(); int statusCode = Status.kInstanceNotRunningCode; if (stub != null) { try { statusCode = stub.getServerStatusCode(key); } catch (RemoteException re) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); if (re.detail != null) { trace(re.detail); } trace(re); } catch (IllegalArgumentException iae) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); trace(iae); // This means that the key did not match. Attempt to read // the key from the disk again to work around the race // condition when the file is read on the client before the // server has finished writing (and hence client has partial // key). Another read updates the shared key on the client. byte[] newKey = null; try { newKey = readSeed(); } catch (IOException ioe) { debug(FILE_READ_ERROR, seedFile.getName()); trace(ioe); } if (newKey != null) { key = newKey; } throw iae; } } return statusCode; } /** * Get the port where the conflict occurs. * * @return A valid port number.0 If there is any error in processing */ public int getConflictedPort() { int conflictedPort = 0; boolean gotNew = checkServerStatus(); if (stub != null) { try { conflictedPort = stub.getConflictedPort(key); } catch (RemoteException re) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); if (re.detail != null) { trace(re.detail); } trace(re); } } return conflictedPort; } /** * Triggers exit of the server VM. Server VM will client * that got the conflicted port number calls this method. */ public void triggerServerExit() { boolean gotNew = checkServerStatus(); if (stub != null) { try { stub.triggerServerExit(key); } catch (RemoteException re) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); if (re.detail != null) { trace(re.detail); } trace(re); } } } /** * Is restart needed on the instance. The status is set by admin server and * instance just keeps track of it across admin server restarts. If the * instance has already restarted since admin server set the status, the * instance does not require restart (as expected). * @return true if the instance requires restart, false otherwise. */ public boolean isRestartNeeded() { boolean restartNeeded = false; boolean gotNew = checkServerStatus(); if (stub != null) { try { restartNeeded = stub.isRestartNeeded(key); } catch (RemoteException re) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); if (re.detail != null) { trace(re.detail); } trace(re); } } return restartNeeded; } /** * Set restart needed status for a server instance. If the instance is not * running or if there is an error in communicating, the instance restart * status will not be changed. The status set by this method is preserved * in the instance across admin server restarts. * @param restartNeeded true if instance restart is needed, false otherwise. */ public void setRestartNeeded(boolean restartNeeded) { boolean gotNew = checkServerStatus(); if (stub != null) { try { stub.setRestartNeeded(key, restartNeeded); } catch (RemoteException re) { debug(CHANNEL_COMM_ERROR, stubFile.getName()); if (re.detail != null) { trace(re.detail); } trace(re); } } } /** * Check status of server stub file and refresh if needed. */ private synchronized boolean checkServerStatus() { boolean gotNew = false; if (stubFile.exists()) { long ts = stubFile.lastModified(); if (ts > stubFileTs) { if (stubFile.canRead()) { RemoteAdminChannel obj = readStub(); if (obj != null) { gotNew = true; stub = obj; } } else { warn(FILE_READ_ERROR, stubFile.getName()); } } } else { if (stub != null) { stub = null; } } return gotNew; } /** * Read stub from stub file */ private RemoteAdminChannel readStub() { RemoteAdminChannel obj = null; FileInputStream fis = null; try { stubFileTs = stubFile.lastModified(); fis = new FileInputStream(stubFile); ObjectInputStream ois = new ObjectInputStream(fis); obj = (RemoteAdminChannel)ois.readObject(); } catch (IOException ioe) { warn(CLIENT_INIT_ERROR); debug(ioe); } catch (ClassNotFoundException cnfe) { warn(CLIENT_INIT_ERROR); debug(cnfe); } finally { if (fis != null) { try { fis.close(); } catch (IOException ioe) { } } } try { key = readSeed(); } catch (IOException ioe) { warn(CLIENT_INIT_ERROR); debug(ioe); obj = null; } if (obj == null) { stubFileTs = 0; } return obj; } /** * Read shared secret from file. */ private byte[] readSeed() throws IOException { byte[] seed = null; if (seedFile.exists() && seedFile.canRead()) { // Seed file is updated after creating stub file and therefore // it should only be read only if it has been modified since stub // file was modified. NOTE: This relies on the fact that the // server always (on every startup) writes seed file after stubfile. long seedFileTs = seedFile.lastModified(); if (seedFileTs >= stubFileTs) { FileInputStream fis = null; try { fis = new FileInputStream(seedFile); seed = new byte[AdminChannel.SEED_LENGTH]; fis.read(seed); } catch (IOException ioe) { warn(AdminChannel.KEY_READ_ERROR); debug(ioe); seed = null; } finally { if (fis != null) { try { fis.close(); } catch (IOException ioe) { } } } } else { debug(SEED_FILE_OLDER, new Long[] {new Long(seedFileTs), new Long(stubFileTs)}); } } else { warn(AdminChannel.KEY_READ_ERROR); debug(FILE_READ_ERROR, seedFile.getName()); } if (seed == null) { String msg = localStrings.getString( "admin.server.core.channel.unable_initializing_key", seedFile ); throw new IOException( msg ); } return seed; } /** * Has the server instance restarted since specified timestamp. If instance * is not running, the method will return false. The return value is * accurate within the timespan as specified in getClientRefreshInterval() * of AdminChannel – meaning that if the time period since instance restart * is less than the specified timespan then the method may return false * instead of true. * @param ts Timestamp to check for * @return true if the instance has restarted since specified timestamp, * false otherwise. */ public boolean hasRestartedSince(long ts) { return hasRestartedSince(ts, false); } /** * Has the server instance restarted since specified timestamp. If instance * is not running, the method will return false. If refreshStub is true * then the method will attempt to read newer stub (if any) and will report * status using that. If refreshStub is false then the stub is not re-read * and return value is dependent on the cached stub (if any). * @param ts Timestamp to check for * @param refreshStub if true, refresh remote stub if it has changed * @return true if the instance has restarted since specified timestamp, * false otherwise. */ public boolean hasRestartedSince(long ts, boolean refreshStub) { if (refreshStub) { boolean gotNew = checkServerStatus(); } boolean restarted = false; if (stubFile != null) { if (stubFileTs > ts) { restarted = true; } } return restarted; } // // logger methods copied from AdminChannel // static void trace(Throwable t) { logger.log(Level.FINEST, t.getMessage(), t); } static void warn(String s) { logger.warning(s); } static void warn(String msgkey, String obj1) { logger.log(Level.WARNING, msgkey, obj1); } static void debug(String s) { logger.fine(s); } static void debug(String msgkey, String obj1) { logger.log(Level.FINE, msgkey, obj1); } static void debug(String msgkey, Object[] objarr) { logger.log(Level.FINE, msgkey, objarr); } static void debug(Throwable t) { logger.log(Level.FINE, t.getMessage(), t); } // i18n StringManager private static final StringManager localStrings = StringManager.getManager( RMIClient.class ); private static final String CLIENT_NULLARGS_ERRCODE = "channel.client_nullargs"; private static final String CLIENT_NULLARGS_ERRMSG = localStrings.getString ( "admin.server.core.channel.attempt_initializing_channel_client_with_null_arguments" ); private static final String CLIENT_INIT_ERROR = "channel.client_init_error"; private static final String EVENT_NOTIFY_ERROR = "channel.event_notify_error"; private static final String EVENT_RENOTIFY_ERROR = "channel.event_renotify_error"; private static final String AUTO_REFRESH_INTR = "channel.auto_refresh_intr"; private static final String CHANNEL_COMM_ERROR = "channel.comm_error"; private static final String INVALID_AUTO_REFRESH_INTERVAL = localStrings.getString( "admin.server.core.channel.invalid_auto_refresh_interval"); static final String FILE_READ_ERROR = "channel.file_read_error"; static final String SEED_FILE_OLDER = "channel.seed_file_older"; static final Logger logger = Logger.getLogger(AdminConstants.kLoggerName); } Comment by llc [ 17/May/07 ] Won't fix. Generated at Sun May 07 12:40:01 UTC 2017 using JIRA 6.2.3#6260sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.