#wy1 Rust Redis Don't understand why the code passed the test

I’ve finished the task #wy1 but unfortunately didn’t get what’s going on.

All the tests I do locally return me only a single PONG response regardless of how many times I put PING in the message.
However, I passed the test and completed the stage since I have to reuse the provided code example.
What do I miss?

Here are my logs:

user@user codecrafters-redis-rust % echo -ne '*1\r\n$4\r\nping\r\n' | nc localhost 6379      
+PONG

Logs from your program will appear here!

accepted new connection

received 14 bytes

data: [42, 49, 13, 10, 36, 52, 13, 10, 112, 105, 110, 103, 13, 10]

received 0 bytes

data: []

-----------------------------------------------------------------------------------------

user@user codecrafters-redis-rust % echo -ne '*1\r\n$4\r\nping\nping\r\n' | nc localhost 6379  
+PONG

accepted new connection

received 19 bytes

data: [42, 49, 13, 10, 36, 52, 13, 10, 112, 105, 110, 103, 10, 112, 105, 110, 103, 13, 10]

received 0 bytes

data: []

And here’s a snippet of my code:

fn handle_client(mut stream: TcpStream) {
    let mut buf = [0; 512];
    loop {
        let bytes_read = stream.read(&mut buf).expect("Failed to read from client");

        println!("received {} bytes", bytes_read);
        println!("data: {:?}", &buf[..bytes_read]);

        if bytes_read == 0 {
            return;
        }

        let response = "+PONG\r\n";
        stream.write_all(response.as_bytes()).expect("Failed to write to client");
    }
}

Hi @soyarym, it’s true that handle_client itself responds with only a single PONG.

This means any function calling handle_client must must have been handling multiple interactions.

Could you clarify what’s calling handle_client in your setup?


Just took a closer look at the test that you ran:

'*1\r\n$4\r\nping\nping\r\n' does not seem to be correct.

A real redis-server would also send back only one pong for that:

Hi @andy1li , thank u for the answer.
Here’s the comment made by codecrafters-bot about this specific part of the code that I reused:

The original code reads from the stream once and then sends a response. The updated code, however, introduces a loop to continuously read from the stream and send a response for each read operation.

loop {
    let read_count = stream.read(&mut buf).unwrap();
    if read_count == 0 {
        break;
    }
    stream.write(b"+PONG\r\n").unwrap();
}
In this loop, stream.read(&mut buf).unwrap() reads data into buf and returns the number of bytes read. If read_count is 0, it means there's no more data to read, so the loop breaks. Otherwise, it sends a response with stream.write(b"+PONG\r\n").unwrap(). This allows the server to respond to multiple PING commands sent by the same connection.

Also, given that the goal of this stage is to send two PING commands $ echo -e "PING\nPING" | redis-cli using the same connection and receive two +PONG\r\n in response, I’m kind of struggling to figure out what is the actual testing command and how the provided code is supposed to deal with 2 pings.

Here’s my whole code:

#![allow(unused_imports)]

use std::io::{BufRead, BufReader, Read, Write};
use std::net::{TcpListener, TcpStream};

fn main() {
    // You can use print statements as follows for debugging, they'll be visible when running tests.
    println!("Logs from your program will appear here!");

    // Uncomment this block to pass the first stage

    let listener = TcpListener::bind("127.0.0.1:6379").expect("Could not bind");

    for stream in listener.incoming() {
        match stream {
            Ok(_stream) => {
                println!("accepted new connection");
                handle_client(_stream);
            }
            Err(e) => {
                println!("error: {}", e);
            }
        }
    }
}

fn handle_client(mut stream: TcpStream) {
    let mut buf = [0; 512];
    loop {
        let bytes_read = stream.read(&mut buf).expect("Failed to read from client");

        println!("received {} bytes", bytes_read);
        println!("data: {:?}", &buf[..bytes_read]);

        if bytes_read == 0 {
            return;
        }

        let response = "+PONG\r\n";
        stream.write_all(response.as_bytes()).expect("Failed to write to client");
    }
}

Thanks!

You can find the source code of our tester for this stage here. It basically sends a PING command to your Redis server three times:

for i := 1; i <= 3; i++ {
	runPing(logger, client);
}

Here’s an explanation how listener.incoming() works from the Rust book:

The incoming method on TcpListener returns an iterator that gives us a sequence of streams (more specifically, streams of type TcpStream ). A single stream represents an open connection between the client and the server. A connection is the name for the full request and response process in which a client connects to the server, the server generates a response, and the server closes the connection. As such, we will read from the TcpStream to see what the client sent and then write our response to the stream to send data back to the client. Overall, this for loop will process each connection in turn and produce a series of streams for us to handle.

@andy1li ,

Okay, after I’ve checked the testing code I can see it sends PING requests sequentially, so my Rust code now finally makes perfect sense to me, thanks!

However, could you explain why the following task description includes the example of sending the two PING requests in one packet? It’s kinda misleading as I genuinely thought that this is what the testing request looks like.

Tests

The tester will execute your program like this:

$ ./your_program.sh

It’ll then send two PING commands using the same connection:

$ echo -e "PING\nPING" | redis-cli

Thanks!

  1. echo -e "PING\nPING" write the following output to stdout:

PING
PING

  1. | pipes these two lines into redis-cli
  2. redis-cli translates these using the RESP protocol and sends them as two separate PING commands to your Redis server.

Does this answer your question?

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

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