javamemory-leaksusb4java

Executing .jar file does not write a file every time... could it be a memory leak?


I am using this application to get a weight from a scale to be read into another application. long story short once the file exists, the other application reads it then deletes the file. If I try and run this multiple times in a row, it does not get the weight. Not like it errors out or throws an exception I just have to wait about 5 Minutes. My suspect is that it is a writer or possible object handle that isn't being let go when the program exits. I presume the JVM has to actually close before I run it for it to run properly. Can someone point me in the right Direction? github.com/cshort2112/dymo_s100_interface

package Model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Init {

    //Vendor ID = 0x0922
    //Product ID = 0x8009

    public static void main(String[] args) {
        try {
            UsbScale scale = UsbScale.findScale();
            assert scale != null;
            scale.open();
            double weight = 0;
            try {
                while (weight == 0) {
                    weight = scale.syncSubmit();
                }
            } finally {
                scale.close();
            }
            weight = (double)Math.round(weight * 10d) / 10d;
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write(String.valueOf(weight));
                } finally {
                    out.close();
                }
            } catch (IOException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

}

and the usbscale.java:

package Model;

import org.usb4java.*;

import javax.usb.*;
import javax.usb.event.UsbPipeDataEvent;
import javax.usb.event.UsbPipeErrorEvent;
import javax.usb.event.UsbPipeListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class UsbScale implements UsbPipeListener {

    private final UsbDevice device;
    private UsbInterface iface;
    private UsbPipe pipe;
    private final byte[] data = new byte[6];
    private double finalWeight;
    private Context context;

    private UsbScale(UsbDevice device) {
        this.device = device;
    }


    public static UsbScale findScale() {
        try {
            UsbServices services = UsbHostManager.getUsbServices();
            UsbHub rootHub = services.getRootUsbHub();
            // Dymo S100 Scale:
            UsbDevice device = findDevice(rootHub, (short) 0x0922, (short) 0x8009);
 //           // Dymo M25 Scale:
 //           if (device == null) {
 //               device = findDevice(rootHub, (short) 0x0922, (short) 0x8004);
 //           }
            if (device == null) {
                return null;
            }
            return new UsbScale(device);
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
            return null;
        }
    }

    private static UsbDevice findDevice(UsbHub hub, short vendorId, short productId) {
        for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices()) {
            UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
            if (desc.idVendor() == vendorId && desc.idProduct() == productId) {
                return device;
            }
            if (device.isUsbHub()) {
                device = findDevice((UsbHub) device, vendorId, productId);
                if (device != null) {
                    return device;
                }
            }
        }
        return null;
    }

    public Device findDevice(short vendorId, short productId)
    {
        int result = LibUsb.init(context);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to initialize libusb.", result);
        // Read the USB device list
        DeviceList list = new DeviceList();
        result = LibUsb.getDeviceList(context, list);
        if (result < 0) throw new LibUsbException("Unable to get device list", result);
        try
        {
            // Iterate over all devices and scan for the right one
            for (Device device: list)
            {
                DeviceDescriptor descriptor = new DeviceDescriptor();
                result = LibUsb.getDeviceDescriptor(device, descriptor);
                if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
                if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) {
                    return device;
                }
            }
        }
        finally
        {
            // Ensure the allocated device list is freed
            LibUsb.freeDeviceList(list, true);
        }

        // Device not found
        return null;
    }

    public void open()  {
        try {
            context = new Context();
            UsbConfiguration configuration = device.getActiveUsbConfiguration();
            iface = configuration.getUsbInterface((byte) 0);
            // this allows us to steal the lock from the kernel
            DeviceHandle handle = new DeviceHandle();
            int result = LibUsb.open( findDevice((short) 0x0922, (short) 0x8009), handle);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);
            result = LibUsb.setConfiguration(handle, 0);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to set Configuration", result);
            iface.claim(usbInterface -> true);
            final List<UsbEndpoint> endpoints = iface.getUsbEndpoints();
            pipe = endpoints.get(0).getUsbPipe(); // there is only 1 endpoint
            pipe.addUsbPipeListener(this);
            pipe.open();
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

    public double syncSubmit() {
        try {
            pipe.syncSubmit(data);
            return finalWeight;
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
            return finalWeight;
        }

    }


    public void close() throws UsbException {
        try {
            pipe.close();
            iface.release();
            LibUsb.exit(context);
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

    @Override
    public void dataEventOccurred(UsbPipeDataEvent upde) {
        if (data[2] == 12) { //This means it is in imperial Mode
            if (data[1] == 4) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = scaleWeight(weight, scalingFactor); //final weight, applies to both metric and imperial
            }else if (data[1] == 5) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = scaleWeight(weight, scalingFactor)*(-1); //final weight, applies to both metric and imperial
            } else if (data[1] == 2) {
                finalWeight = 0;
            }
        } else { //This would mean it is in metric
            if (data[1] == 4) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = (scaleWeight(weight, scalingFactor)*2.20462); //final weight, applies to both metric and imperial
            } else if (data[1] == 5) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = (scaleWeight(weight, scalingFactor)*2.20462)*(-1); //final weight, applies to both metric and imperial
            } else if (data[1] == 2) {
                finalWeight = 0;
            }

        }

    }

    private double scaleWeight(int weight, int scalingFactor) {
        return weight * Math.pow(10, scalingFactor);
    }


    @Override
    public void errorEventOccurred(UsbPipeErrorEvent usbPipeErrorEvent) {
        Logger.getLogger(UsbScale.class.getName()).log(Level.SEVERE, "Scale Error", usbPipeErrorEvent);
        try {
            String home = System.getProperty("user.home");
            File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

            BufferedWriter out = new BufferedWriter(new FileWriter(f));
            try {
                out.write("Error! " + usbPipeErrorEvent);
            } finally {
                out.close();
                System.exit(0);
            }
        } catch (IOException exception) {
            System.out.println("An error occurred.");
            exception.printStackTrace();
        }
    }
}

Thanks for any pointers in advance!


Solution

  • You have poor exception handling which means that the file of interest is written in many parts of the code so it is harder to narrow down exactly what your code is doing.

    Instead, try this refactoring which may not solve your problem but will cut down the number of places you need to check and perhaps the location of the issue with be clearer:

    1. Add single variable for the file to write in Init.java:

      // Use Paths.get in older JVM
      Path f = Path.of(System.getProperty("user.home"), "Documents", "Weight.txt"); 
      
    2. Wipe all catch block exception handlers in UsbScale class and let the exception leave ALL methods. Change return null; or other errors to throw new LibUsbException("some message") to provide specific types of issues such as in UsbScale.findScale and UsbScale.findDevice.

    3. Make USBScale implement AutoCloseable so it can be used in try-with-resources

      public class UsbScale implements UsbPipeListener, AutoCloseable {
      
    4. Add flag isOpened=true in open(), and logic to close so it checks !isOpened

      public void open()  {
         isOpened = true;
         ...
      }
      private boolean isOpened = false;
      public void close() throws UsbException {
         if (!isOpened) return;
         isOpened =false;
         ...
      }
      
    5. Init therefore becomes much shorter and is the only place that writes the output file so debugging is easier. Use Files.writeString(f, message) rather than a verbose (and copied) new FileWriter(f) code block, and handle exceptions in this one method:

      public static void main(String[] args) throws IOException {
         String message = "[UNKNOWN]";
         Path f = Path.of(System.getProperty("user.home"), "Documents", "Weight.txt");
         try (UsbScale scale = UsbScale.findScale()){
             // scale is NEVER NULL AT THIS POINT if exceptions are handled correctly in UsbScale.findScale()
             // assert scale != null;
             scale.open();
             double weight = 0;
             while (weight == 0) {
                 weight = scale.syncSubmit();
             }
             weight = Math.round(weight * 10d) / 10d;
             message = String.valueOf(weight);
         } catch (Exception e) {
             message = "Error! " + e.getMessage();
             e.printStackTrace();
         } finally {
             Files.writeString(f, message, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
         }
      

      }

    6. Finally, with 2 processes accessing same file, you may want to write to temporary file first and rename to target afterwards:

             Path tmp = Files.createTempFile(f.getParent(), "weight","tmp"); 
             Files.writeString(tmp, message, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
             Files.move(tmp, f);