admin@glassfish.java.net

Re: code review: com.sun.enterprise.admin.server.core.channel.RMIClient

From: Lloyd L Chambers <Lloyd.Chambers_at_Sun.COM>
Date: Fri, 02 Feb 2007 13:59:15 -0800

Thanks for the feedback Byron. Who in SQE might be able to test it
for me?

Lloyd

On Feb 2, 2007, at 11:51 AM, Byron Nevins wrote:

> Changing RMIClient carries extreme risk Have you done multi-machine
> tests with plenty of instances and a cluster or two and repeated
> starts and stops of everything? If not, then I recommend doing it,
> asking QE to do it, or not committing the changes.
>
> Lloyd Chambers wrote:
>> TIMEOUT: 17:00 PST Feb 2, 2007
>>
>> https://glassfish.dev.java.net/issues/show_bug.cgi?id=2273
>>
>> Diffs and whole file follow. To make thread safe:
>>
>> - static and instance variables 'final' wherever possible
>>
>> - constructor changed to allow instance variables to be final
>> (prefetch booleans, allowing assignment once to final variable)
>>
>> - 'synchronized' and 'volatile' used for mutable state
>>
>> Lloyd
>>
>>
>> ----------------
>> MB2:/gf/build/glassfish/appserv-core lloyd$ cvs diff -u 10 -w src/
>> java/com/sun/enterprise/admin/server/core/channel/RMIClient.java
>> cvs server: I know nothing about 10
>> cvs server: I know nothing about -w
>> 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 -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 2 Feb 2007 19:18:16 -0000
>> @@ -52,16 +52,15 @@
>> /**
>> * RMI client is used to send admin channel messages over RMI.
>> */
>> -public 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;
>> +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
>> @@ -84,19 +83,19 @@
>> if (this.stubFile.exists()) {
>> stub = readStub();
>> }
>> - if (AdminChannel.getClientAutoRefreshEnabled()) {
>> - startAutoRefreshThread(
>> - AdminChannel.getClientAutoRefreshInterval());
>> - }
>> +
>> + 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 (! isDebug) {
>> + logger.setLevel(java.util.logging.Level.SEVERE);
>> + }
>> if (stubFile == null || seedFile == null) {
>> throw new IllegalArgumentException
>> (CLIENT_NULLARGS_ERRMSG);
>> }
>> @@ -105,24 +104,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 +386,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 +563,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 +584,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$
>>
>> --------------------
>>
>> 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) {
>> 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 {
>> 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) {
>> // 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);
>> }
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: admin-unsubscribe_at_glassfish.dev.java.net
>> For additional commands, e-mail: admin-help_at_glassfish.dev.java.net
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: admin-unsubscribe_at_glassfish.dev.java.net
> For additional commands, e-mail: admin-help_at_glassfish.dev.java.net
>