Hello, everyone. As a bug writer and repairman, TODAY I bring you an accident that I just fixed a few days ago. I have to admit, there are always so many bugs where I’m here.

The cause of

The beginning of the story happened a few days ago, there is a not very commonly used export function, by the user feedback, no matter what the condition is, the export data is only one, but in fact according to the condition query is a lot of data, and the page also query a lot of data. (The issue has since been fixed, so the Kibana logs are not available at the time.) I dropped what I was doing and dove in.

Analysis of the

From the analysis of the problem description, the following situations can only occur:

  1. Only one record is displayed based on the search criteria.
  2. The relevant business processing of the queried data results in only one final result.
  3. File export component logic processing, resulting in only one result.

As an aside, I suddenly thought of a classic interview question, MQ message loss scenario cause analysis. Ha, ha, ha, ha, ha, ha, ha. (Opportunity to write MQ article) digression

So one by one to analyze:

  1. Find relevant business SQL and corresponding parameters, query can be obtained, more than one data, so the first case can be excluded.
  2. Intermediate services involve related permissions and data sensitivity, etc. After all these are released, there is still only one piece of data.
  3. When the file export component receives data, only one log is printed, which indicates that there must be a problem with the logic of the related services in the middle.

Because this code is written in a whole method, Arthas is difficult to check, so we have to set up a log step by step to check. (So, if it is a long piece of logic, the suggestion is to break it up into duoge sub-methods. First, I have a clear idea when writing, and I have a modular concept. As for method reuse, I will not mention the basic operations. Two is a problem, but the investigation will also be convenient, experience). You end up inside a for loop.

code

So without further ado, let’s get right to the code. I’m known for being very protective of company code, so I’m going to have to do a little simulation here. In the case of the problem, it is the exported object record that is empty

import com.google.common.collect.Lists;
import java.util.List;

public class Test {

    public static void main(String[] args) {

        // Get Customer data
        List<Customer> customerList = Lists.newArrayList(new Customer("Java"), new Customer("Showyool"), new Customer("Soga"));
        int index = 0;
        String[][] exportData = new String[customerList.size()][2];
        for (Customer customer : customerList) {
            exportData[index][0] = String.valueOf(index);
            exportData[index][1] = customer.getName(); index = index++; } System.out.println(JSON.toJSONString(exportData)); }}class Customer {

    public Customer(String name) {
        this.name = name;
    }

    private String name;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name; }}Copy the code

This code doesn’t look like much, just converting the Customer collection into a two-dimensional array of strings. But the output shows:So that’s what we said, there are multiple queries, but only one output.

After careful observation, we can find that the output data is always the last item, that is to say, when the set Customer is traversed, the latter always overwrites the former. In other words, the subscript of this index has not changed, it is always 0.

modeling

So we have a problem with this increment, so let’s write a simple model

public class Test2 {

    public static void main(String[] args) {
        int index = 3; index = index++; System.out.println(index); }}Copy the code

If we simplify the business logic above to such a model, the result is, unsurprisingly, 3.

explain

So let’s execute Javap to see how the JVM bytecode is interpreted:

javap -c Test2

Compiled from "Test2.java"
public class com.showyool.blog_4.Test2 {
  public com.showyool.blog_4.Test2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: istore_1
       2: iload_1
       3: iinc          1.1
       6: istore_1
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: return
}

Copy the code

First of all, we need to know that there are two concepts, operand stack and local variable table, which are data structures that exist in the stack frame of the virtual machine, as shown below:









Extend the

In this case, we see that the problem is actually in the last step, after performing the operation, we reassign the value of the original stack to the variable, overwriting it if we write:

public class Test2 {

    public static void main(String[] args) {
        int index = 3;
        index++;
        System.out.println(index);
    }

}

Compiled from "Test2.java"
public class com.showyool.blog_4.Test2 {
  public com.showyool.blog_4.Test2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: istore_1
       2: iinc          1.1
       5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iload_1
       9: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      12: return
}
Copy the code

As you can see, there is no istore_1 of the last step, so after iinc, the index value becomes the expected 4. Here’s another case, let’s see:

public class Test2 {

    public static void main(String[] args) {
        int index = 3;
        index = index + 2;
        System.out.println(index);
    }

}

Compiled from "Test2.java"
public class com.showyool.blog_4.Test2 {
  public com.showyool.blog_4.Test2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: istore_1
       2: iload_1
       3: iconst_2
       4: iadd
       5: istore_1
       6: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: iload_1
      10: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      13: return
}

Copy the code

0: iconST_3 (pushing constant 3 first) 1: istore_1 (pushing the value of the first parameter, that is, assigning 3 to index) 2: iloAD_1 (pushing the value of the first parameter, that is, pushing 3, at which point the top value of the stack is 3) 3: Iconst_2 (pushes constant 2 onto the stack, where the top value is 2 and 2 is above 3) 4: iadd (adds the two numbers at the top of the stack and pushes the result onto the stack. 2+3=5, the top of the stack is 5) 5: istore_1

Why does iAdd add affect the data in the stack, while IInc does not operate in the stack? Well, we can take a look at the JVM specification, which says:

The iinc instruction, which increments a given local variable, is one of the few instructions that executes without modifying the operand stack at all. It receives two operands:

The position of the first local variable scale, the sum of the second place. The common i++, for example, produces this instruction

So if we look at this, we know that copying is fine for normal addition, but if we use i++, then the number at the top of the stack is the same as it was before, and if we do an assignment at this point it’s going to go back to the old value, because it’s not changing anything on the stack. So the previous bug, just need to increment without assigning can be fixed.

The last

Thank you for being able to see here, that’s all I did to deal with this bug. Although this is only a small bug, but this small bug is worth learning and thinking. In the future, I will continue to share the bugs I found and knowledge points, if my article is helpful to you, but also hope you big boy dot attention \color{red}{dot concern} dot concern dot like \color{red}{dot like} dot like, thank you again for your support! Also attached here is my Github address :github.com/showyool/ju…