Saturday, August 29, 2009

Speed up Eclipse

I've just noticed a big performance improvement in Eclipse 3.5 (Galileo), just by launching it with the latest JRE (1.6.0_16) and unlocking the new G1 garbage collector. My startup time has reduced from about 30 secs to 4-5 secs and the interface is snappier.

Here is my eclipse.ini:

-startup
plugins/org.eclipse.equinox.launcher_1.0.200.v20090520.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.0.200.v20090519
-product
org.eclipse.epp.package.java.product
-data
c:\eclipse
-showlocation
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
384m
-vm
C:\program files\Java\jdk1.6.0_16\jre\bin\client\jvm.dll
-vmargs
-Dosgi.requiredJavaVersion=1.5
-Xms128m
-Xmx384m
-Xss4m
-XX:PermSize=128m
-XX:MaxPermSize=128m
-XX:CompileThreshold=5
-XX:MaxGCPauseMillis=10
-XX:MaxHeapFreeRatio=70
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
-XX:+UseFastAccessorMethods
-XX:+AggressiveOpts
-Dcom.sun.management.jmxremote
Let me know if there are any other improvements I can make!

Tuesday, August 18, 2009

Cron Jobs Not Launching

We had an issue recently with cron jobs not firing on our Solaris 10 server. We finally tracked it down to cron not being initialised when the server rebooted. The error was in a file called /var/svc/log/system-cron:default.log and indicated that it couldn't find the user. Possibly due to NFS not being mounted.
sharfah@starship:~> tail -10 /var/svc/log/system-cron:default.log
[ Aug 16 00:01:25 Stopping because service disabled. ]
[ Aug 16 00:01:25 Executing stop method (:kill) ]
[ Aug 16 00:07:27 Enabled. ]
[ Aug 16 00:07:28 Executing start method ("/lib/svc/method/svc-cron") ]
[ Aug 16 00:07:28 Method "start" exited with status 0 ]
! No such user as sharfah - cron entries not created Sun Aug 16 00:07:28 2009
We then got the Unix SAs to restart svc:/system/cron:default:
sharfah@starship:~> tail -10 /var/svc/log/system-cron:default.log
[ Aug 18 17:25:19 Stopping because service restarting. ]
[ Aug 18 17:25:19 Executing stop method (:kill) ]
[ Aug 18 17:25:20 Executing start method ("/lib/svc/method/svc-cron") ]
[ Aug 18 17:25:20 Method "start" exited with status 0 ]
Another way to fix this issue, is to edit and resave crontab.

For debugging cron issues, a useful tip is to look at /var/mail/user which will contain any stdout or errors from your cron jobs.

Related post:
Percent Sign in Crontab

Monday, August 17, 2009

Retrying Operations in Java

There are many cases in which you may wish to retry an operation a certain number of times. Examples are database failures, network communication failures or file IO problems.

Approach 1
This is the traditional approach and involves a counter and a loop.

final int numberOfRetries = 5 ;
final long timeToWait = 1000 ;

for (int i=0; i<numberOfRetries; i++) {
 //perform the operation
 try {
  Naming.lookup("rmi://localhost:2106/MyApp");
  break;
 }
 catch (Exception e) {
  logger.error("Retrying...",e);
  try {
   Thread.sleep(timeToWait);
  }
  catch (InterruptedException i) {
  }
 }
}
Approach 2
In this approach, we hide the retry counter in a separate class called RetryStrategy and call it like this:
public class RetryStrategy
{
 public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
 public static final long DEFAULT_WAIT_TIME = 1000;

 private int numberOfRetries; //total number of tries
 private int numberOfTriesLeft; //number left
 private long timeToWait; //wait interval

 public RetryStrategy()
 {
  this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME);
 }

 public RetryStrategy(int numberOfRetries, long timeToWait)
 {
  this.numberOfRetries = numberOfRetries;
  numberOfTriesLeft = numberOfRetries;
  this.timeToWait = timeToWait;
 }

 /**
  * @return true if there are tries left
  */
 public boolean shouldRetry()
 {
  return numberOfTriesLeft > 0;
 }

 /**
  * This method should be called if a try fails.
  *
  * @throws RetryException if there are no more tries left
  */
 public void errorOccured() throws RetryException
 {
  numberOfTriesLeft --;
  if (!shouldRetry())
  {
   throw new RetryException(numberOfRetries +
     " attempts to retry failed at " + getTimeToWait() +
     "ms interval");
  }
  waitUntilNextTry();
 }

 /**
  * @return time period between retries
  */
 public long getTimeToWait()
 {
  return timeToWait ;
 }

 /**
  * Sleeps for the duration of the defined interval
  */
 private void waitUntilNextTry()
 {
  try
  {
   Thread.sleep(getTimeToWait());
  }
  catch (InterruptedException ignored) {}
 }

 public static void main(String[] args) {
  RetryStrategy retry = new RetryStrategy();
  while (retry.shouldRetry()) {
   try {
    Naming.lookup("rmi://localhost:2106/MyApp");
    break;
   }
   catch (Exception e) {
    try {
     retry.errorOccured();
    }
    catch (RetryException e1) {
     e.printStackTrace();
    }
   }
  }
 }
}
Approach 3
Approach 2, although cleaner, hasn't really reduced the number of lines of code we have to write. In the next approach, we hide the retry loop and all logic in a separate class called RetriableTask. We make the operation that we are going to retry Callable and wrap it in a RetriableTask which then handles all the retrying for us, behind-the-scenes:
public class RetriableTask<T> implements Callable<T> {

 private Callable<T> task;
 public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
 public static final long DEFAULT_WAIT_TIME = 1000;

 private int numberOfRetries; // total number of tries
 private int numberOfTriesLeft; // number left
 private long timeToWait; // wait interval

 public RetriableTask(Callable<T> task) {
  this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME, task);
 }

 public RetriableTask(int numberOfRetries, long timeToWait,
                      Callable<T> task) {
  this.numberOfRetries = numberOfRetries;
  numberOfTriesLeft = numberOfRetries;
  this.timeToWait = timeToWait;
  this.task = task;
 }

 public T call() throws Exception {
  while (true) {
   try {
    return task.call();
   }
   catch (InterruptedException e) {
    throw e;
   }
   catch (CancellationException e) {
    throw e;
   }
   catch (Exception e) {
    numberOfTriesLeft--;
    if (numberOfTriesLeft == 0) {
     throw new RetryException(numberOfRetries +
     " attempts to retry failed at " + timeToWait +
     "ms interval", e);
    }
    Thread.sleep(timeToWait);
   }
  }
 }

 public static void main(String[] args) {
  Callable<Remote> task = new Callable<Remote>() {
   public Remote call() throws Exception {
    String url="rmi://localhost:2106/MyApp";
    return (Remote) Naming.lookup(url);
   }
  };

  RetriableTask<Remote> r = new RetriableTask<Remote>(task);
  try {
   r.call();
  }
  catch (Exception e) {
   e.printStackTrace();
  }
 }
}
Also see: References:

Tuesday, August 04, 2009

Setting up Tortoise CVS with CollabNet

We recently moved from Sourceforge to CollabNet and had to make changes to our CVS client setup. Sourceforge uses "pserver", whereas Collabnet uses "ext ssh". This is how you can get Tortoise CVS to connect to CollabNet, without prompting you for passwords every time.

1. Create a pair of public/private keys
You can create a pair of keys using PuTTY Key Generator (PUTTYGEN.EXE). Simply press the Generate button and move your mouse to generate randomness. This will create a public key for pasting into an authorized_keys file. Press the Save public key and Save private key buttons to save the keys as files. I have saved mine as:

  • C:\Documents and Settings\[xpid]\ssh\id_dsa (private)
  • C:\Documents and Settings\[xpid]\ssh\id_dsa.pub (public)
Alternatively, you can use the UNIX utility "ssh-keygen", as explained in one of my previous posts here.

2. Store your public key on CollabNet

  • Log onto CollabNet
  • Click on My Workspace
  • Click on Authorization Keys
  • Paste the public key, generated in Step 1, into the Authorized Keys text area and press Update
3. Update Tortoise SSH Settings
  • Open TortoiseCVS Preferences
  • Go to the Tools tab
  • Add your private key to your SSH parameters, so that it becomes:
-i "C:\Documents and Settings\[xpid]\ssh\id_dsa" -l "%u" "%h"
Now, whenever you perform a CVS operation through Tortoise, it will not prompt you for a password.

Your CVSROOT should look something like this:

:ext:fahd_shariff@cvs-collabnet.server.com:/cvsroot/module
References:
Tortoise FAQs
CollabNet Help