Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to locate lambda when exception happens?

Say I have a ComparatorFactory, it has many comparators composed by a lambda:

   public static Comparator<SomeClass> getXCmp() {
      return (o1, o2) -> {
         Double d1 = Double.parseDouble(o1.getX());
         Double d2 = Double.parseDouble(o2.getX());
         return d1.compareTo(d2);
      };
   }

I used those comparators to sort and filter data. Unfortunately, I used the wrong comparator at some place and it caused a ClassCastException as shown:

java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
at businesslogic.utility.ComparatorFactory$$Lambda$24/115669291.compare(Unknown Source)
at javax.swing.DefaultRowSorter.compare(DefaultRowSorter.java:968)
...
...

As you can see, it shows (Unknown Source) which makes it hard for me to find which comparator is wrong. I also tried to add a breakpoint before the compare happens(ie, in the upper example, at DefaulRowSorter.java:968), but next step also can not locate which lambda it is(it jumps to the wrong comparator which has nothing to do with double and string and when I finally found the bug it was not the correct one).

After I found the bug(by trying to understanding the whole project and a lot of time), I tried an anonymous class.The backtrace of stack explicitly told me where it is.

Q:

If I want the lambda to provide concise code, is there any good way to find where the lambda source is or any good practice to help me when an exception happens?

A simple example to re-produce similar problem.

like image 485
Tony Avatar asked May 24 '15 14:05

Tony


People also ask

What happens when AWS Lambda throws exception?

If Lambda encounters an error, it returns an exception type, message, and HTTP status code that indicates the cause of the error. The client or service that invoked the Lambda function can handle the error programmatically, or pass it along to an end user.

How do you find the Lambda error?

To troubleshoot Lambda code errors You can use CloudWatch to view all logs generated by your function's code and identify potential issues. For more information, see Accessing Amazon CloudWatch Logs for AWS Lambda.

Where are AWS Lambda errors?

In CloudWatch, go to the Metrics page, then go to the Graph Metrics tab, then navigate to the dropdown menu item “Math expression > Search > Lambda Throttles or Errors.” This will give you error counts per lambda in a graph, mouse over to get the name of the offending lambda.

Does Lambda retry on exception?

If your function throws an error, the Lambda service retries your function. Since the same event may be received more than once, functions should be designed to be idempotent . This means that receiving the same event multiple times does not change the result beyond the first time the event was received.


2 Answers

Make sure you include this option for javac when compiling your classes:

-g:lines,source,vars

The "-g" compiler option can be used to control how much debugging information should be generated into the class files (see documentation)

Here is a simple example with lambdas:

package test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestLambda {

    public static Comparator<String> comparator1() {
        return (o1, o2) -> {
            return o1.compareTo(o2);
        };
    }

    public static Comparator<String> comparator2() {
        return (o1, o2) -> {
            System.out.println("test");
            if (true) {
                throw new RuntimeException("Exception"); // line 20: stacktrace points to this line
            }
            return o1.compareTo(o2);
        };
    }

    public static void main(String[] args) {
        List<String> strings = Arrays.asList("string1", "string2", "string3");

        Collections.sort(strings, comparator2());
    }
}

Here is the stacktrace:

Exception in thread "main" java.lang.RuntimeException: Exception
    at test.TestLambda.lambda$comparator2$1(TestLambda.java:20)
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(TestLambda.java:29)

As you can see the stacktrace at test.TestLambda.lambda$comparator2$1(TestLambda.java:20) points to the exact line of the source code.

Your IDE should be able to parse the stacktrace and decorate it with links clicking on which should bring you to the exact line in your sources (at least that's what IntelliJ IDEA does).

If you compile with -g:none the stacktrace will be different:

Exception in thread "main" java.lang.RuntimeException: Exception
    at test.TestLambda.lambda$comparator2$1(Unknown Source)
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(Unknown Source)

Update:

Below is another example that is closer to the one provided in the question:

package test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestLambda {

    public static Comparator<String> comparator1() {
        return (o1, o2) -> {
            return o1.compareTo(o2);
        };
    }

    public static Comparator<String> comparator2() {
        return (o1, o2) -> {
            System.out.println("test");
            if (true) {
                throw new RuntimeException("Exception");
            }
            return o1.compareTo(o2);
        };
    }

    public static void main(String[] args) {
        List strings = Arrays.asList(1, 2, 3);

        Collections.sort(strings, comparator2());
    }
}

The only difference is that it uses raw type for List thus making possible using String comparator for list of Integers. The stacktrace indeed doesn't contain the line number since the exception happened during casting and not in our source code:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(TestLambda.java:29)

A rule of thumb here is not to use raw types which in this case will make the debugging process easier (What is a raw type and why shouldn't we use it?). The compiler can help you here too: include this option for javac:

-Xlint:all

The compiler will warn you about raw types a lots of other things. Add another option:

-Werror

and the compiler will produce an error instead of warning (useful when using with CI servers to ensure high quality of the source code)

like image 79
medvedev1088 Avatar answered Oct 19 '22 17:10

medvedev1088


As far as I have tried and searched, you can't locate where is the lambda in Java 8.

The lambda here is a replacement of anonymous class but the replacement is invisible for JVM and that is why JVM can't locate the lambda.

Take Two simple comparators as examples:

    public static Comparator<String> comparator1() {
    return (o1, o2) -> {
        Double d1 = Double.parseDouble(o1);
        Double d2 = Double.parseDouble(o2);
        return d1.compareTo(d2);
    };
    }

    public static Comparator<String> comparator2() {
    return new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            Double d1 = Double.parseDouble(o1);
            Double d2 = Double.parseDouble(o2);
            return d1.compareTo(d2);
        }
    };
    }

Compiled from upper code example (remove some redundant lines):

public static comparator1()Ljava/util/Comparator;
    INVOKEDYNAMIC compare()Ljava/util/Comparator; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Ljava/lang/Object;Ljava/lang/Object;)I, 
      // handle kind 0x6 : INVOKESTATIC
      lambda/ComparatorFa.lambda$comparator1$0(Ljava/lang/String;Ljava/lang/String;)I, 
      (Ljava/lang/String;Ljava/lang/String;)I
    ]

public static comparator2()Ljava/util/Comparator;
    NEW lambda/ComparatorFa$1
    DUP
    INVOKESPECIAL lambda/ComparatorFa$1.<init> ()V

An very important difference is that the second comparator has a class and NEW an instance, but the one with lambda just become a method which is INVOKEDYNAMIC.

And we find that compiler just compile a synthetic method to let JVM to invoke:

  private static synthetic lambda$comparator1$0(Ljava/lang/String;Ljava/lang/String;)I
    ALOAD 0
    INVOKESTATIC java/lang/Double.parseDouble (Ljava/lang/String;)D
    INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
    ASTORE 2

    ALOAD 1
    INVOKESTATIC java/lang/Double.parseDouble (Ljava/lang/String;)D
    INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
    ASTORE 3

    ALOAD 2
    ALOAD 3
    INVOKEVIRTUAL java/lang/Double.compareTo (Ljava/lang/Double;)I
    IRETURN

So JVM is totally unaware of the existence of lambda. It just invoke a method when necessary and it obviously can't locate where is that lambda, so it has to show Unknown source.

like image 2
Tony Avatar answered Oct 19 '22 17:10

Tony