hickory_net/client/
memoize_client_handle.rs1use core::pin::Pin;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use futures_util::future::FutureExt;
12use futures_util::lock::Mutex;
13use futures_util::stream::Stream;
14
15use super::{
16 ClientHandle,
17 rc_stream::{RcStream, rc_stream},
18};
19use crate::{
20 NetError,
21 proto::op::{DnsRequest, DnsResponse, Query},
22 xfer::DnsHandle,
23};
24
25#[derive(Clone)]
32#[must_use = "queries can only be sent through a ClientHandle"]
33pub struct MemoizeClientHandle<H: ClientHandle> {
34 client: H,
35 active_queries: Arc<Mutex<HashMap<Query, RcStream<<H as DnsHandle>::Response>>>>,
36}
37
38impl<H> MemoizeClientHandle<H>
39where
40 H: ClientHandle,
41{
42 pub fn new(client: H) -> Self {
44 Self {
45 client,
46 active_queries: Arc::new(Mutex::new(HashMap::new())),
47 }
48 }
49
50 async fn inner_send(
51 request: DnsRequest,
52 active_queries: Arc<Mutex<HashMap<Query, RcStream<<H as DnsHandle>::Response>>>>,
53 client: H,
54 ) -> impl Stream<Item = Result<DnsResponse, NetError>> {
55 let query = request.queries.first().expect("no query!").clone();
57
58 let mut active_queries = active_queries.lock().await;
60
61 if let Some(rc_stream) = active_queries.get(&query) {
64 return rc_stream.clone();
65 };
66
67 active_queries
69 .entry(query)
70 .or_insert_with(|| rc_stream(client.send(request)))
71 .clone()
72 }
73}
74
75impl<H: ClientHandle> DnsHandle for MemoizeClientHandle<H> {
76 type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
77 type Runtime = H::Runtime;
78
79 fn send(&self, request: DnsRequest) -> Self::Response {
80 Box::pin(
81 Self::inner_send(
82 request,
83 Arc::clone(&self.active_queries),
84 self.client.clone(),
85 )
86 .flatten_stream(),
87 )
88 }
89}
90
91#[cfg(test)]
92mod test {
93 #![allow(clippy::dbg_macro, clippy::print_stdout)]
94
95 use core::pin::Pin;
96 use std::sync::Arc;
97
98 use futures_util::lock::Mutex;
99 use futures_util::stream;
100
101 use super::*;
102 use crate::{
103 proto::{
104 op::{DnsRequest, DnsResponse, Message, MessageType, OpCode, Query},
105 rr::RecordType,
106 },
107 runtime::TokioRuntimeProvider,
108 xfer::{DnsHandle, FirstAnswer},
109 };
110 use test_support::subscribe;
111
112 #[derive(Clone)]
113 struct TestClient {
114 i: Arc<Mutex<u16>>,
115 }
116
117 impl DnsHandle for TestClient {
118 type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
119 type Runtime = TokioRuntimeProvider;
120
121 fn send(&self, request: DnsRequest) -> Self::Response {
122 let i = Arc::clone(&self.i);
123 Box::pin(stream::once(async move {
124 let mut i = i.lock().await;
125 let message = Message::new(*i, MessageType::Query, OpCode::Query).into_response();
126 std::println!(
127 "sending {}: {}",
128 *i,
129 request.queries.first().expect("no query!").clone()
130 );
131
132 *i += 1;
133
134 Ok(DnsResponse::from_message(message).unwrap())
135 }))
136 }
137 }
138
139 #[test]
140 fn test_memoized() {
141 use futures_executor::block_on;
142
143 subscribe();
144
145 let client = MemoizeClientHandle::new(TestClient {
146 i: Arc::new(Mutex::new(0)),
147 });
148
149 let mut test1 = Message::query();
150 test1.add_query(Query::new().set_query_type(RecordType::A).clone());
151
152 let mut test2 = Message::query();
153 test2.add_query(Query::new().set_query_type(RecordType::AAAA).clone());
154
155 let result = block_on(client.send(DnsRequest::from(test1.clone())).first_answer()).unwrap();
156 assert_eq!(result.id, 0);
157
158 let result = block_on(client.send(DnsRequest::from(test2.clone())).first_answer()).unwrap();
159 assert_eq!(result.id, 1);
160
161 let result = block_on(client.send(DnsRequest::from(test1)).first_answer()).unwrap();
163 assert_eq!(result.id, 0);
164
165 let result = block_on(client.send(DnsRequest::from(test2)).first_answer()).unwrap();
166 assert_eq!(result.id, 1);
167 }
168}