1

I developed an application using Java socket. I am exchanging messages with this application with the help of byte arrays. I have a message named M1, 1979 bytes long. My socket buffer length is 512 bytes. I read this message in 4 parts, each with 512 bytes, but the last one is of course 443 bytes. I will name these parts like A, B, C, and D. So ABCD is a valid message of mine respectively.

I have a loop with a thread which is like below.

BlockingQueue<Chunk> queue = new LinkedBlockingQueue<>();
InputStream in = socket.getInputStream()
byte[] buffer = new byte[512];

while(true) {
  int readResult = in.read(buffer);
  if(readResult != -1) {
    byte[] arr = Arrays.copyOf(buffer, readResult);
    Chunk c = new Chunk(arr);
    queue.put(c);
  }
}
    

I'm filling the queue with the code above. When the message sending starts, I see the queue fill up in ABCD form but sometimes I put the data in the queue as a BACD. But I know that this is impossible because the TCP connection guarantees the order.

I looked at the dumps with Wireshark. This message comes correctly with a single tcp package. So there is no problem on the sender side. I am 100% sure that the message has arrived correctly but the read method does not seem to read in the correct order and this situation doesn't always happen. I could not find a valid reason for what caused this.

When I tried the same code on two different computers I noticed that the problem was in only one. The jdk versions on these computers are different. I looked at the version differences between the two jdk versions. When the Jdk version is "JDK 8u202", I am getting the situation where it works incorrectly. When I tried it with jdk 8u271, there was no problem. Maybe it is related to that but I wasn't sure. Because I have no valid evidence.

I am open to all kinds of ideas and suggestions. It's really on its way to being the most interesting problem I've ever encountered. Thank you for your help.

EDIT: I found similar question. Blocking Queue Take out of Order

EDIT: Ok, I have read all the answers given below. Thank you for providing different perspectives for me. I will try to supplement some missing information.

Actually I have 2 threads. Thread 1(SocketReader) is responsible for reading socket. It wraps the information it reads with a Chunk class and puts it on the queue in the other Thread 2. So queue is in Thread 2. Thread 2(MessageDecoder) is consuming the blocking queue. There are no threads other than these. Actually this is a simple example of a "producer consumer design patter".

And yes, other messages are sent, but other messages take up less than 512 bytes. Therefore, I can read in one go. I do not encounter any sort problem.

MessageDecoder.java

public class MessageDecoder implements Runnable{

    private BlockingQueue<Chunk> queue = new LinkedBlockingQueue<>();

    public MessageDecoder() {
    }

    public void run() {
        while(true) {
            Chunk c;
            try {
                c = queue.take();
                System.out.println(c.toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
            decodeMessageChunk(c);
        }
    }

    public void put(Chunk c) {
        try {
            queue.put(c);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

SocketReader.java

public class SocketReader implements Runnable{

    private final MessageDecoder msgDec;
    private final InputStream in;
    byte[] buffer = new byte[512];

    public SocketReader(InputStream in, MessageDecoder msgDec) {
        this.in = in;
        this.msgDec = msgDec;
    }

    public void run() {
        while(true) {
            int readResult = in.read(buffer);
            if(readResult != -1) {
                byte[] arr = Arrays.copyOf(buffer, readResult);
                Chunk c = new Chunk(arr);
                msgDec.put(c);
            }
        }
    }
}
Onur Öçalan
  • 79
  • 1
  • 12
  • 1
    I searched through https://www.oracle.com/java/technologies/javase/8all-relnotes.html, they don't seem to address any issues related to the order of the messages –  Jan 16 '21 at 21:00
  • Do you really need a `BlockingQueue` for this? Are you getting the same effect with a normal/simple `ArrayDeque` or something? Can you show the code where you combine the Chunks? Are you using Oracle's JDKs in both computers? – gthanop Jan 16 '21 at 21:03
  • There are a couple of important things that you haven't told us: 1) Are there multiple threads adding to the queue or removing from the queue? 2) Is the server sending multiple messages one after another? – Stephen C Jan 17 '21 at 05:09
  • I edited my question and answered the relevant parts. But first question is no. Second one is yes. @StephenC – Onur Öçalan Jan 17 '21 at 13:07
  • Yes I am using Oracle's JDK @gthanop – Onur Öçalan Jan 17 '21 at 13:09

1 Answers1

1

Even if it's a FIFO queue, the locking of the LinkedBloquingQueue is unfair, so you can't guarantee the ordering of elements. More info regarding this here

I'd suggest using an ArrayBlockingQueue instead. Like the LinkedBloquingQueue, the order is not guaranteed but offers a slightly different locking mechanism.

This class supports an optional fairness policy for ordering waiting producer and consumer threads. By default, this ordering is not guaranteed. However, a queue constructed with fairness set to true grants threads access in FIFO order. Fairness generally decreases throughput but reduces variability and avoids starvation.

In order to set fairness, you must initialize it using this constructor:

enter image description here

So, for example:

   ArrayBlockingQueue<Chunk> fairQueue = new ArrayBlockingQueue<>(1000, true);
   /*.....*/
   Chunk c = new Chunk(arr);
   fairQueue.add(c);

As the docs state, this should grant thread access in FIFO order, guaranteeing the retrievement of the elements to be consistent while avoiding possible locking robbery happening in LinkedBloquingQueue's lock mechanism.

aran
  • 10,978
  • 5
  • 39
  • 69
  • Are you sure? `BlockingQueue` Javadoc says the queueing methods are all thread-safe, and the OP's code has only one thread queueing blocks, and doesn't specify there is more than one thread dequeueing blocks. What am I missing? – Jim Garrison Jan 17 '21 at 02:04
  • @JimGarrison Even if they are thread safe. the locking mechanism of the linkedqueue is unfair per se. Take a look here, for example: https://stackoverflow.com/a/3339212/2148953 That situation would lead to the retriever threads to read the elements in an incorrect order (for example, if they peek before polling). Anyway, this is just a guess; With the answer I'd like to offer another alternative that guarantees a fair FIFO mechanism, regardless of the number of threads/tasks/daemons accessing the queue – aran Jan 17 '21 at 02:09
  • This doesn't sound relevant to the question. Fairness has nothing to do with thread safety. Or a queue's FIFO behavior. It is about which thread gets to remove an element from the queue when there is contention. The OP has not said anything about there being multiple queue readers (or writers). – Stephen C Jan 17 '21 at 05:04
  • @StephenC to be honest, this is a guess, or assumption, I'm making in order to explain how a queue could be retrieved disordered; Regarding this, the only possible context I could think of was a multireader scenario. The fact that OP is specifically using a Concurrent Queue also helps in my assumption, but as said, I may be wrong. Regarding this last queue, another think that helps into "guessing" is the specifical behaviour of a Linked...queue regarding fairness. Even if the 2nd thread is assigned the next element, a 3rd thread may "rob" the lock and get first a newer element f the queue. – aran Jan 17 '21 at 05:12
  • @StephenC anyway, you are right here; There's no enough context and well, my answer is just something similar to a guess. I may be completely wrong here – aran Jan 17 '21 at 05:13
  • @StephenC *Fairness has nothing to do with thread safety* agreed, and sorry cause I sometimes don't explain myself correctly, but that's what I was trying to say with *Even if they are thread safe. the locking mechanism of the linkedqueue is unfair* , meaning there's no relation between both behaviours; Thread safe involves no concurrent modifications into the queue, while fairness is about maintaining an order to safely retrieve the element. – aran Jan 17 '21 at 05:28
  • I see that my question has been found incomplete. I completed. Comments and replies give me an idea. Can you please unlock it again? I need more answers. @StephenC – Onur Öçalan Jan 17 '21 at 13:21
  • I have voted to reopen it. That is all I can do. Next time, you need to 1) make sure that you include the necessary information in the question when you write it, and 2) make sure that you read and address the comments in a timely way. – Stephen C Jan 17 '21 at 13:34