1use std::io;
17use std::time::Duration;
18
19use kevy_resp::Reply;
20use kevy_resp_client::RespClient;
21
22use crate::{Connection, num_f64, string, unexpected};
23
24pub type ZPopHit = (Vec<u8>, Vec<u8>, f64);
26
27impl Connection {
28 pub fn blpop(
36 &mut self,
37 keys: &[&[u8]],
38 timeout: Option<Duration>,
39 ) -> io::Result<Option<(Vec<u8>, Vec<u8>)>> {
40 check_blocking_args(keys, timeout)?;
41 match self {
42 Self::Embedded(s) => s.blpop(keys, timeout),
43 Self::Remote(c) => pop_kv(blocking_request(c, b"BLPOP", keys, timeout)?),
44 }
45 }
46
47 pub fn brpop(
50 &mut self,
51 keys: &[&[u8]],
52 timeout: Option<Duration>,
53 ) -> io::Result<Option<(Vec<u8>, Vec<u8>)>> {
54 check_blocking_args(keys, timeout)?;
55 match self {
56 Self::Embedded(s) => s.brpop(keys, timeout),
57 Self::Remote(c) => pop_kv(blocking_request(c, b"BRPOP", keys, timeout)?),
58 }
59 }
60
61 pub fn bzpopmin(
67 &mut self,
68 keys: &[&[u8]],
69 timeout: Option<Duration>,
70 ) -> io::Result<Option<ZPopHit>> {
71 check_blocking_args(keys, timeout)?;
72 match self {
73 Self::Embedded(s) => s.bzpopmin(keys, timeout),
74 Self::Remote(c) => pop_kv_score(blocking_request(c, b"BZPOPMIN", keys, timeout)?),
75 }
76 }
77}
78
79fn check_blocking_args(keys: &[&[u8]], timeout: Option<Duration>) -> io::Result<()> {
83 if keys.is_empty() {
84 return Err(io::Error::new(
85 io::ErrorKind::InvalidInput,
86 "blocking pop needs at least one key",
87 ));
88 }
89 if timeout == Some(Duration::ZERO) {
90 return Err(io::Error::new(
91 io::ErrorKind::InvalidInput,
92 "timeout Some(0) is ambiguous (wire 0 = wait forever); use None to wait forever",
93 ));
94 }
95 Ok(())
96}
97
98fn blocking_request(
102 c: &mut RespClient,
103 verb: &[u8],
104 keys: &[&[u8]],
105 timeout: Option<Duration>,
106) -> io::Result<Reply> {
107 let mut args = Vec::with_capacity(keys.len() + 2);
108 args.push(verb.to_vec());
109 args.extend(keys.iter().map(|k| k.to_vec()));
110 args.push(match timeout {
111 None => b"0".to_vec(),
112 Some(d) => format!("{}", d.as_secs_f64()).into_bytes(),
113 });
114 c.request(&args)
115}
116
117fn pop_kv(reply: Reply) -> io::Result<Option<(Vec<u8>, Vec<u8>)>> {
119 match reply {
120 Reply::Array(items) if items.len() == 2 => {
121 let mut it = items.into_iter();
122 match (it.next().unwrap(), it.next().unwrap()) {
123 (Reply::Bulk(k), Reply::Bulk(v)) => Ok(Some((k, v))),
124 (a, _) => Err(unexpected(a)),
125 }
126 }
127 Reply::Nil | Reply::Null => Ok(None),
128 Reply::Error(e) => Err(io::Error::other(string(e))),
129 other => Err(unexpected(other)),
130 }
131}
132
133fn pop_kv_score(reply: Reply) -> io::Result<Option<ZPopHit>> {
135 match reply {
136 Reply::Array(items) if items.len() == 3 => {
137 let mut it = items.into_iter();
138 match (it.next().unwrap(), it.next().unwrap(), it.next().unwrap()) {
139 (Reply::Bulk(k), Reply::Bulk(m), Reply::Bulk(s)) => {
140 Ok(Some((k, m, num_f64(&s)?)))
141 }
142 (a, _, _) => Err(unexpected(a)),
143 }
144 }
145 Reply::Nil | Reply::Null => Ok(None),
146 Reply::Error(e) => Err(io::Error::other(string(e))),
147 other => Err(unexpected(other)),
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn embedded_blpop_immediate_hit() {
157 let mut c = Connection::open("mem://").unwrap();
158 c.rpush(b"q", &[&b"a"[..], &b"b"[..]]).unwrap();
159 let hit = c.blpop(&[&b"q"[..]], Some(Duration::from_secs(1))).unwrap();
160 assert_eq!(hit, Some((b"q".to_vec(), b"a".to_vec())));
161 let hit = c.brpop(&[&b"q"[..]], Some(Duration::from_secs(1))).unwrap();
162 assert_eq!(hit, Some((b"q".to_vec(), b"b".to_vec())));
163 }
164
165 #[test]
166 fn embedded_blpop_timeout_returns_none() {
167 let mut c = Connection::open("mem://").unwrap();
168 let hit = c
169 .blpop(&[&b"empty"[..]], Some(Duration::from_millis(30)))
170 .unwrap();
171 assert_eq!(hit, None);
172 }
173
174 #[test]
175 fn embedded_bzpopmin_pops_lowest_score() {
176 let mut c = Connection::open("mem://").unwrap();
177 c.zadd(b"z", &[(2.0, b"hi".as_ref()), (1.0, b"lo".as_ref())])
178 .unwrap();
179 let hit = c
180 .bzpopmin(&[&b"z"[..]], Some(Duration::from_secs(1)))
181 .unwrap();
182 assert_eq!(hit, Some((b"z".to_vec(), b"lo".to_vec(), 1.0)));
183 let miss = c
184 .bzpopmin(&[&b"gone"[..]], Some(Duration::from_millis(30)))
185 .unwrap();
186 assert_eq!(miss, None);
187 }
188
189 #[test]
190 fn zero_some_timeout_and_empty_keys_rejected() {
191 let mut c = Connection::open("mem://").unwrap();
192 let err = c.blpop(&[&b"q"[..]], Some(Duration::ZERO)).unwrap_err();
193 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
194 let err = c.blpop(&[], Some(Duration::from_secs(1))).unwrap_err();
195 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
196 }
197}