Skip to content

Commit

Permalink
Merge pull request #78 from codecrafters-io/CC-986-blocking-xread
Browse files Browse the repository at this point in the history
CC-986 add support for blocking xread
  • Loading branch information
libmartinito authored Feb 28, 2024
2 parents 2a4a188 + 73cd9a8 commit 76f3dae
Show file tree
Hide file tree
Showing 12 changed files with 816 additions and 69 deletions.
2 changes: 1 addition & 1 deletion internal/stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestStages(t *testing.T) {
NormalizeOutputFunc: normalizeTesterOutput,
},
"streams_pass": {
UntilStageSlug: "streams-xread-multiple",
UntilStageSlug: "streams-xread-block-max-id",
CodePath: "./test_helpers/pass_all",
ExpectedExitCode: 0,
StdoutFixturePath: "./test_helpers/fixtures/streams/pass",
Expand Down
328 changes: 328 additions & 0 deletions internal/test_helpers/course_definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2156,3 +2156,331 @@ stages:
marketing_md: |
In this stage, you'll add extend support to `XREAD` to allow querying multiple streams.
- slug: "streams-xread-block"
primary_extension_slug: "streams"
name: "Support blocking reads"
difficulty: hard
description_md: |
In this stage, you'll add extend support to `XREAD` to allow for turning it into a blocking command.
### Understanding blocking
`BLOCK` is one of the optional parameters that could be passed in to the `XREAD` command.
Without blocking, the current implementation of our command is synchronous. This means that the command can get new data as long as there are items available.
If we want to wait for new data coming in, we need blocking.
Here's how it works. Let's use the entries previously shown as an example.
```bash
- 1526985054069-0 # (ID of the first entry)
temperature: 36 # (A key value pair in the first entry)
humidity: 95 # (Another key value pair in the first entry)
- 1526985054079-0 # (ID of the second entry)
temperature: 37 # (A key value pair in the first entry)
humidity: 94 # (Another key value pair in the first entry)
# ... (and so on)
```
On one instance of the redis-cli, we'd add an entry and send a blocking `XREAD` command.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
```
Then, on another instance of the redis-cli, we add another entry.
```bash
$ other-redis-cli xadd some_key 1526985054079-0 temperature 37 humidity 94
"1526985054079-0"
```
If the command was sent within 1000 milliseconds, the redis-cli will respond with the added entry.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
1) 1) "some_key"
2) 1) 1) 1526985054079-0
2) 1) temperature
2) 37
3) humidity
4) 94
```
If not, the return type would be a RESP encoded [null](https://redis.io/docs/reference/protocol-spec/#nulls).
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
(nil)
```
### Tests
The tester will execute your program like this:
```bash
$ ./spawn_redis_server.sh
```
First, an entry will be added to a stream.
```bash
$ redis-cli xadd stream_key 0-1 temperature 96
```
It'll then send an `XREAD` command to your server with the `BLOCK` command.
```bash
$ redis-cli xread block 1000 streams stream_key 0-0
```
On another instance of the redis-cli, another entry will be added in 500 milliseconds after sending the `XREAD` command.
```bash
$ redis-cli xadd stream_key 0-2 temperature 95
```
Your server should respond with the following:
```bash
%1\r\n
$9\r\stream_key\r\n
*2\r\n
$3\r\n0-2\r\n
*2\r\n
$3\r\ntemperature\r\n
$3\r\n96\r\n
```
This is the RESP encoded representation of `{ "stream_key": ["0-2", ["temperature", "96"]] }`. It'll send another `XREAD` command to your server with the `BLOCK` command but this time, it'll wait for 2000 milliseconds before checking the response of your server.
```bash
$ redis-cli xread block 1000 streams stream_key 0-2
```
Your server should respond with `_\r\n` which is a RESP encoded `null`.
### Notes
- In the response, the items are separated onto new lines for readability. The tester expects all of these to be in one line.
marketing_md: |
In this stage, you'll add extend support to `XREAD` to allow querying multiple streams.
- slug: "streams-xread-block-no-timeout"
primary_extension_slug: "streams"
name: "Support blocking reads without timeout"
difficulty: medium
description_md: |
In this stage, you'll add extend support to `XREAD` to allow for the blocking command not timing out.
### Understandign blocking without timeout
Here's how it works. Let's use the entries previously shown as an example.
```bash
- 1526985054069-0 # (ID of the first entry)
temperature: 36 # (A key value pair in the first entry)
humidity: 95 # (Another key value pair in the first entry)
- 1526985054079-0 # (ID of the second entry)
temperature: 37 # (A key value pair in the first entry)
humidity: 94 # (Another key value pair in the first entry)
# ... (and so on)
```
On one instance of the redis-cli, we'd add an entry and send a blocking `XREAD` command with 0 as the time passed in.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
```
Then, on another instance of the redis-cli, we add another entry.
```bash
$ other-redis-cli xadd some_key 1526985054079-0 temperature 37 humidity 94
"1526985054079-0"
```
The difference now is that the first instance of the redis-cli doesn't time out and responds with null no matter how much time passes. It will wait until another entry is added. The return value after an entry is added is similar to the last stage.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
1) 1) "some_key"
2) 1) 1) 1526985054079-0
2) 1) temperature
2) 37
3) humidity
4) 94
```
### Tests
The tester will execute your program like this:
```bash
$ ./spawn_redis_server.sh
```
First, an entry will be added to a stream.
```bash
$ redis-cli xadd stream_key 0-1 temperature 96
```
It'll then send an `XREAD` command to your server with the `BLOCK` command with the time passed in being 0.
```bash
$ redis-cli xread block 0 streams stream_key 0-0
```
It'll then wait for 1000 milliseconds before checking if there is a response. Your server should not have a new response. It'll then add another entry.
```bash
$ redis-cli xadd stream_key 0-2 temperature 95
```
Your server should respond with the following:
```bash
%1\r\n
$9\r\stream_key\r\n
*2\r\n
$3\r\n0-2\r\n
*2\r\n
$3\r\ntemperature\r\n
$3\r\n95\r\n
```
This is the RESP encoded representation of `{ "stream_key": ["0-2", ["temperature", "95"]] }`.
### Notes
- In the response, the items are separated onto new lines for readability. The tester expects all of these to be in one line.
marketing_md: |
In this stage, you'll add extend support to `XREAD` to allow for the blocking command not timing out.
- slug: "streams-xread-block-max-id"
primary_extension_slug: "streams"
name: "Support blocking reads using $"
difficulty: easy
description_md: |
In this stage, you'll add extend support to `XREAD` to allow for passing in `$` as the ID for a blocking command.
### Understanding $
Using `$` as the ID passed to a blocking `XREAD` command signals that we only want new entries. This is similar to passing in the maximum ID we currently have in the stream.
Here's how it works. Let's use the entries previously shown as an example.
```bash
- 1526985054069-0 # (ID of the first entry)
temperature: 36 # (A key value pair in the first entry)
humidity: 95 # (Another key value pair in the first entry)
- 1526985054079-0 # (ID of the second entry)
temperature: 37 # (A key value pair in the first entry)
humidity: 94 # (Another key value pair in the first entry)
# ... (and so on)
```
On one instance of the redis-cli, we'd add an entry and send a blocking `XREAD` command with 0 as the time passed in.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key $
```
Then, on another instance of the redis-cli, we add another entry.
```bash
$ other-redis-cli xadd some_key 1526985054079-0 temperature 37 humidity 94
"1526985054079-0"
```
Similar to the behavior detailed in the earlier stages, if the command was sent within 1000 milliseconds, the redis-cli will respond with the new entry.
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
1) 1) "some_key"
2) 1) 1) 1526985054079-0
2) 1) temperature
2) 37
3) humidity
4) 94
```
If not, the return type would still be a RESP encoded [null](https://redis.io/docs/reference/protocol-spec/#nulls).
```bash
$ redis-cli xadd some_key 1526985054069-0 temperature 36 humidity 95
"1526985054069-0"
$ redis-cli xread block 1000 streams some_key 1526985054069-0
(nil)
```
### Tests
The tester will execute your program like this:
```bash
$ ./spawn_redis_server.sh
```
First, an entry will be added to a stream.
```bash
$ redis-cli xadd stream_key 0-1 temperature 96
```
It'll then send an `XREAD` command to your server with the `BLOCK` command with `$` as the ID.
```bash
$ redis-cli xread block 0 streams stream_key $
```
On another instance of the redis-cli, another entry will be added in 500 milliseconds after sending the `XREAD` command.
```bash
$ redis-cli xadd stream_key 0-2 temperature 95
```
Your server should respond with the following:
```bash
%1\r\n
$9\r\stream_key\r\n
*2\r\n
$3\r\n0-2\r\n
*2\r\n
$3\r\ntemperature\r\n
$3\r\n96\r\n
```
This is the RESP encoded representation of `{ "stream_key": ["0-2", ["temperature", "95"]] }`. It'll send another `XREAD` command to your server with the `BLOCK` command but this time, it'll wait for 2000 milliseconds before checking the response of your server.
```bash
$ redis-cli xread block 1000 streams stream_key $
```
Your server should respond with `_\r\n` which is a RESP encoded `null`.
### Notes
- In the response, the items are separated onto new lines for readability. The tester expects all of these to be in one line.
marketing_md: |
In this stage, you'll add extend support to `XREAD` to allow for passing in `$` as the ID for a blocking command.
6 changes: 3 additions & 3 deletions internal/test_helpers/fixtures/expiry/pass
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ Debug = true
[stage-7] Running tests for Stage #7: expiry
[stage-7] $ ./spawn_redis_server.sh
[stage-7] $ redis-cli set grapes apples px 100
[stage-7] Received OK (at 15:59:35.740)
[stage-7] $ redis-cli get grapes (sent at 15:59:35.740, key should not be expired)
[stage-7] Received OK (at 21:51:33.704)
[stage-7] $ redis-cli get grapes (sent at 21:51:33.704, key should not be expired)
[stage-7] Received "apples"
[stage-7] Sleeping for 101ms
[stage-7] $ redis-cli get grapes (sent at 15:59:35.843, key should be expired)
[stage-7] $ redis-cli get grapes (sent at 21:51:33.807, key should be expired)
[stage-7] Test passed.
[stage-7] Terminating program
[stage-7] Program terminated successfully
2 changes: 1 addition & 1 deletion internal/test_helpers/fixtures/ping-pong/eof
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Debug = true
[stage-2] $ redis-cli ping
[stage-2] Reading response...
[stage-2] Hint: 'connection reset by peer' usually means that your program closed the connection before sending a complete response.
[stage-2] read tcp 127.0.0.1:58439->127.0.0.1:6379: read: connection reset by peer
[stage-2] read tcp 127.0.0.1:52144->127.0.0.1:6379: read: connection reset by peer
[stage-2] Test failed
[stage-2] Terminating program
[stage-2] Program terminated successfully
Loading

0 comments on commit 76f3dae

Please sign in to comment.