Inconsistent Memory Behavior Of Streams Between Local and Test Environment

I’ve encountered a very strange behavior between my local program and the test environment, i’m implementing the xread command and i got it working on local, but when i test it with the codecrafters cli it doesn’t work.

Apparently somewhere in between the XADD and the XREAD commands the value in memory changes, since i get the confirmation of the XADD command and i also log all the values stored in memory, but when the test sends a XREAD command, the value in memory is different. I commented all the implementation of the xread implementation in the code and it only logs out the values stored in memory.

In the screenshot i have at the left pane the codecrafters test, and in the top right pane my redis running locally, at the left bottom i sent the exact same commands the codecrafters test did.

And there is a inconsistency between the value saved after the XADD and the values that XREAD logs out, however this doesn’t happen locally.
Here is my code:

Interesting… I’m betting this is a concurrency bug, will take a look this week!

Hey there,

Someone from our team took a look at this, here’s what they found:

The bug occurs due to incorrect handling of byte arrays used for reading network data and storing it in an in-memory datastore. The root cause is the direct manipulation of the byte array without creating a separate copy. When the user reads network data, the bytes are stored in a byte array. The user then performs various manipulations directly on this byte array, modifying its contents. After the manipulations, the modified byte array is stored in an in-memory datastore.
The issue arises when the user reads network data again. Since the same byte array is reused for reading the new network data, it overwrites the previous contents of the array. As a result, all the data stored in the in-memory datastore, which referenced the same byte array, is also overwritten and corrupted.

I was able to fix the issue by updating this function like this:

func (r *RESP) GetParams(buffer []byte) ([][]byte, error) {
	newBuffer := make([]byte, len(buffer))
	copy(newBuffer, buffer)
	params := bytes.Split(newBuffer, []byte{'\r', '\n'})
	if len(params) < 3 {
		return nil, fmt.Errorf("Invalid command: %s", newBuffer)
	}
	return params, nil
}

After making this change, all the subsequent operations are done on a copied slice, so it doesn’t get corrupted on the next network read.

Hope this helps!

We’re still unsure how this would’ve worked locally btw - I’d expect the same bug to be present when testing locally too. The only case where this would not be true would be if a separate connection was used each time.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.