It can be useful to slowly step through the code using a debugger to find runtime problems, but this can be confusing with multiple threads are involved.

Debuggers are not always available, and a developer who uses the debugger regularly can become dependent on it to solve problems rather than using their own powers of deduction.  An alternative debugging option can be to add debug messages to identify the sequence of events that caused the problem.  If the time between retests is long (perhaps the testing server is on another machine), try and dump as much information as possible the first time.

Real example:

An external executable, ffmpeg.exe was required to be called from within Java to translate video from one format to another.  It worked well on a Windows platform, but threw an IOException on a RedHat platform.  Even though the retest time took ten minutes per try (to bundle up and deploy a .war file to the customer server) Fred tried changing only a few lines of code at each test.  One day later he was no closer to solving the problem with this function:

private void test()
{
    String command = ffmpegPath + " -i"
                   + " " + inputFilePath + inputFilename
                   + " " + vcodec1 + " " + vcodec2
                   + " " + audioCodec1
                   + " " + audioCodec2
                   + " " + videoFrameRate1
                   + " " + videoFrameRate2
                   + " " + audioFrameRate1
                   + " " + audioFrameRate2
                   + " " + size1
                   + " " + size2
                   + " " + outputFilePath + outputFilename;
    try
    {
        logger.error("****** TEST 1 ******");
        logger.error(command);
        Runtime.getRuntime().exec(command);
    }
    catch(Exception e)
    {
        e.printStackTrace();
        logger.error("Command failed:” + command);
        logger.error(e.getMessage(),e);
    }
    :

Another developer, Jan took a look at the problem.  She noted a few things:

  1. Some of the messages were missing from the logfiles.  The server had been misconfigured to throw away half the log messages and had to be fixed.

  2. There were five different ways to run an external exectuable from within Java.  She would write some code to try them one after the other, so every possibility for success was covered.

  3. Since the exception was an IOException, she would test that all files involved could be read or written to, even that they could be fully copied.  Unix can have file access permission problems because it is generally much better secured.  

  4. Exceptions were not being caught correctly, and when they were, some of the original exception information was being lost.  She fixed the exception handling and logging, so if any kind of exception was thrown, it was logged correctly.  She spent time adding many messages to display what was happening.

All of these changes took an hour.

The problem was found straight away by adding the following code:

List<String> commands = new ArrayList<String>();
try
{
    logger.error("****** TEST 2 ******");
    commands.add(command);
    ProcessBuilder pb  
     = new ProcessBuilder(commands.toArray(new String[commands.size()]));
    Process process = pb.start();
    readProcessOutputUntilComplete(process);
}
catch(Exception e)
{
    logger.error("Unable to process file ("  
         + inputFilePath + inputFilename  
         + ") with command parameters (" + commands.toString() + ")");
    logger.error(e.getMessage(),e);
}

commands = new ArrayList<String>();
try
{
    logger.error("****** TEST 3 ******");
    commands.add(ffmpegPath);
    commands.add("-i");
    commands.add(inputFilePath + inputFilename);
    commands.add(vcodec1);
    commands.add(vcodec2);
    commands.add(audioCodec1);
    commands.add(audioCodec2);
    commands.add(videoFrameRate1);
    commands.add(videoFrameRate2);
    commands.add(audioFrameRate1);
    commands.add(audioFrameRate2);
    commands.add(size1);
    commands.add(size2);
    commands.add(outputFilePath+outputFilename);
    ProcessBuilder processBuilder  
     = new ProcessBuilder(commands.toArray(new String[commands.size()]));
    Process process = processBuilder.start();
    readProcessOutputUntilComplete(process);
}
catch(Exception e)
{
    logger.error("Unable to process file (" + inputFilePath  
               + inputFilename + ") with command parameters("  
               + commands.toString() + ")");
    logger.error(e.getMessage(),e);
}

commands = new ArrayList<String>();
try
{
    logger.error("****** TEST 4 ******");
    commands.add(ffmpegPath);
    ProcessBuilder processBuilder  
     = new ProcessBuilder(commands.toArray(new String[commands.size()]));
    Process process = processBuilder.start();
    readProcessOutputUntilComplete(process);
}
catch(Exception e)
{
    logger.error("Unable to process file (" + inputFilePath  
               + inputFilename + ") with command parameters("  
               + commands.toString() + ")");
    logger.error(e.getMessage(),e);
}
  
try
{
    logger.error("****** TEST 5 ******");
    StreamUtils.copyStream(new FileInputStream(inputFilePath + inputFilename), new FileOutputStream(outputFilePath+outputFilename));
}
catch(Exception e)
{
    logger.error(e.getMessage(),e);
}     

:
blog comments powered by Disqus