Skip to content

Commit 2ec027f

Browse files
authored
Bugfix/133- fix util unit test memory leak (#625)
* Remove cyclic dependency in vnet::UdpConn - vnet::UdpConn holds Arc to vnet::ConnObserver, which would most of the time hold the Arc to vnet::UdpConn. We use Weak when pointing upward (i.e. parent) * Remove cyclic dependency in vnet::resolver - We replace the Arc pointing to parent with Weak * Fix util::UdpConn owns it's own Arc - util::UdpConn should not own the table of UdpConn because newly created util::UdpConn will always be added into the table. This table of UdpConn is sufficiently owned by Listener and the ListenConfig::read_loop. * Remove cyclic dependency in vnet::router - The Arc pointing to parent is replaced by Weak - Nic and RouterInternal should not point to each other. Make the nics table in RouterInternal Weak. * Fix clippy warning --------- Co-authored-by: mutexd <lichunchen25@gmail.com>
1 parent a1611af commit 2ec027f

File tree

5 files changed

+33
-35
lines changed

5 files changed

+33
-35
lines changed

util/src/conn/conn_udp_listener.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ impl ListenConfig {
211211
}
212212
}
213213

214-
let udp_conn = Arc::new(UdpConn::new(Arc::clone(pconn), Arc::clone(conns), raddr));
214+
let udp_conn = Arc::new(UdpConn::new(Arc::clone(pconn), raddr));
215215
{
216216
let accept_ch = accept_ch_tx.lock().await;
217217
if let Some(tx) = &*accept_ch {
@@ -235,20 +235,14 @@ impl ListenConfig {
235235
/// UdpConn augments a connection-oriented connection over a UdpSocket
236236
pub struct UdpConn {
237237
pconn: Arc<dyn Conn + Send + Sync>,
238-
conns: Arc<Mutex<HashMap<String, Arc<UdpConn>>>>,
239238
raddr: SocketAddr,
240239
buffer: Buffer,
241240
}
242241

243242
impl UdpConn {
244-
fn new(
245-
pconn: Arc<dyn Conn + Send + Sync>,
246-
conns: Arc<Mutex<HashMap<String, Arc<UdpConn>>>>,
247-
raddr: SocketAddr,
248-
) -> Self {
243+
fn new(pconn: Arc<dyn Conn + Send + Sync>, raddr: SocketAddr) -> Self {
249244
UdpConn {
250245
pconn,
251-
conns,
252246
raddr,
253247
buffer: Buffer::new(0, 0),
254248
}
@@ -287,8 +281,6 @@ impl Conn for UdpConn {
287281
}
288282

289283
async fn close(&self) -> Result<()> {
290-
let mut conns = self.conns.lock().await;
291-
conns.remove(self.raddr.to_string().as_str());
292284
Ok(())
293285
}
294286

util/src/vnet/conn.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod conn_test;
33

44
use std::net::{IpAddr, SocketAddr};
55
use std::sync::atomic::Ordering;
6-
use std::sync::Arc;
6+
use std::sync::{Arc, Weak};
77

88
use async_trait::async_trait;
99
use portable_atomic::AtomicBool;
@@ -34,7 +34,7 @@ pub(crate) struct UdpConn {
3434
read_ch_tx: Arc<Mutex<Option<ChunkChTx>>>,
3535
read_ch_rx: Mutex<mpsc::Receiver<Box<dyn Chunk + Send + Sync>>>,
3636
closed: AtomicBool,
37-
obs: Arc<Mutex<dyn ConnObserver + Send + Sync>>,
37+
obs: Weak<Mutex<dyn ConnObserver + Send + Sync>>,
3838
}
3939

4040
impl UdpConn {
@@ -45,13 +45,14 @@ impl UdpConn {
4545
) -> Self {
4646
let (read_ch_tx, read_ch_rx) = mpsc::channel(MAX_READ_QUEUE_SIZE);
4747

48+
let weak_obs = Arc::downgrade(&obs);
4849
UdpConn {
4950
loc_addr,
5051
rem_addr: RwLock::new(rem_addr),
5152
read_ch_tx: Arc::new(Mutex::new(Some(read_ch_tx))),
5253
read_ch_rx: Mutex::new(read_ch_rx),
5354
closed: AtomicBool::new(false),
54-
obs,
55+
obs: weak_obs,
5556
}
5657
}
5758

@@ -112,8 +113,10 @@ impl Conn for UdpConn {
112113
/// send_to writes a packet with payload p to addr.
113114
/// send_to can be made to time out and return
114115
async fn send_to(&self, buf: &[u8], target: SocketAddr) -> Result<usize> {
116+
let obs = self.obs.upgrade().ok_or_else(|| Error::ErrVnetDisabled)?;
117+
115118
let src_ip = {
116-
let obs = self.obs.lock().await;
119+
let obs = obs.lock().await;
117120
match obs.determine_source_ip(self.loc_addr.ip(), target.ip()) {
118121
Some(ip) => ip,
119122
None => return Err(Error::ErrLocAddr),
@@ -126,7 +129,7 @@ impl Conn for UdpConn {
126129
chunk.user_data = buf.to_vec();
127130
{
128131
let c: Box<dyn Chunk + Send + Sync> = Box::new(chunk);
129-
let obs = self.obs.lock().await;
132+
let obs = obs.lock().await;
130133
obs.write(c).await?
131134
}
132135

@@ -142,6 +145,8 @@ impl Conn for UdpConn {
142145
}
143146

144147
async fn close(&self) -> Result<()> {
148+
let obs = self.obs.upgrade().ok_or_else(|| Error::ErrVnetDisabled)?;
149+
145150
if self.closed.load(Ordering::SeqCst) {
146151
return Err(Error::ErrAlreadyClosed);
147152
}
@@ -151,7 +156,7 @@ impl Conn for UdpConn {
151156
reach_ch.take();
152157
}
153158
{
154-
let obs = self.obs.lock().await;
159+
let obs = obs.lock().await;
155160
obs.on_closed(self.loc_addr).await;
156161
}
157162

util/src/vnet/resolver.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ use std::future::Future;
66
use std::net::IpAddr;
77
use std::pin::Pin;
88
use std::str::FromStr;
9-
use std::sync::Arc;
9+
use std::sync::Weak;
1010

1111
use tokio::sync::Mutex;
1212

1313
use crate::error::*;
1414

1515
#[derive(Default)]
1616
pub(crate) struct Resolver {
17-
parent: Option<Arc<Mutex<Resolver>>>,
17+
parent: Option<Weak<Mutex<Resolver>>>,
1818
hosts: HashMap<String, IpAddr>,
1919
}
2020

@@ -31,7 +31,7 @@ impl Resolver {
3131
r
3232
}
3333

34-
pub(crate) fn set_parent(&mut self, p: Arc<Mutex<Resolver>>) {
34+
pub(crate) fn set_parent(&mut self, p: Weak<Mutex<Resolver>>) {
3535
self.parent = Some(p);
3636
}
3737

@@ -55,10 +55,9 @@ impl Resolver {
5555
}
5656

5757
// mutex must be unlocked before calling into parent Resolver
58-
if let Some(parent) = &self.parent {
59-
let parent2 = Arc::clone(parent);
58+
if let Some(parent) = self.parent.clone().and_then(|p| p.upgrade()).clone() {
6059
Box::pin(async move {
61-
let p = parent2.lock().await;
60+
let p = parent.lock().await;
6261
p.lookup(host_name).await
6362
})
6463
} else {

util/src/vnet/resolver/resolver_test.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use std::sync::Arc;
23

34
const DEMO_IP: &str = "1.2.3.4";
45

@@ -49,7 +50,8 @@ async fn test_resolver_cascaded() -> Result<()> {
4950
let ip1 = IpAddr::from_str(ip_addr1)?;
5051
r1.add_host(name1.to_owned(), ip_addr1.to_owned())?;
5152

52-
r1.set_parent(Arc::new(Mutex::new(r0)));
53+
let resolver0 = Arc::new(Mutex::new(r0));
54+
r1.set_parent(Arc::downgrade(&resolver0));
5355

5456
if let Some(resolved) = r1.lookup(name0.to_owned()).await {
5557
assert_eq!(resolved, ip0, "should match");

util/src/vnet/router.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::ops::{Add, Sub};
88
use std::pin::Pin;
99
use std::str::FromStr;
1010
use std::sync::atomic::Ordering;
11-
use std::sync::Arc;
11+
use std::sync::{Arc, Weak};
1212
use std::time::SystemTime;
1313

1414
use async_trait::async_trait;
@@ -77,12 +77,12 @@ pub type ChunkFilterFn = Box<dyn (Fn(&(dyn Chunk + Send + Sync)) -> bool) + Send
7777

7878
#[derive(Default)]
7979
pub struct RouterInternal {
80-
pub(crate) nat_type: Option<NatType>, // read-only
81-
pub(crate) ipv4net: IpNet, // read-only
82-
pub(crate) parent: Option<Arc<Mutex<Router>>>, // read-only
83-
pub(crate) nat: NetworkAddressTranslator, // read-only
84-
pub(crate) nics: HashMap<String, Arc<Mutex<dyn Nic + Send + Sync>>>, // read-only
85-
pub(crate) chunk_filters: Vec<ChunkFilterFn>, // requires mutex [x]
80+
pub(crate) nat_type: Option<NatType>, // read-only
81+
pub(crate) ipv4net: IpNet, // read-only
82+
pub(crate) parent: Option<Weak<Mutex<Router>>>, // read-only
83+
pub(crate) nat: NetworkAddressTranslator, // read-only
84+
pub(crate) nics: HashMap<String, Weak<Mutex<dyn Nic + Send + Sync>>>, // read-only
85+
pub(crate) chunk_filters: Vec<ChunkFilterFn>, // requires mutex [x]
8686
pub(crate) last_id: u8, // requires mutex [x], used to assign the last digit of IPv4 address
8787
}
8888

@@ -157,7 +157,7 @@ impl Nic for Router {
157157
async fn set_router(&self, parent: Arc<Mutex<Router>>) -> Result<()> {
158158
{
159159
let mut router_internal = self.router_internal.lock().await;
160-
router_internal.parent = Some(Arc::clone(&parent));
160+
router_internal.parent = Some(Arc::downgrade(&parent));
161161
}
162162

163163
let parent_resolver = {
@@ -166,7 +166,7 @@ impl Nic for Router {
166166
};
167167
{
168168
let mut resolver = self.resolver.lock().await;
169-
resolver.set_parent(parent_resolver);
169+
resolver.set_parent(Arc::downgrade(&parent_resolver));
170170
}
171171

172172
let mut mapped_ips = vec![];
@@ -492,7 +492,7 @@ impl Router {
492492
// check if the destination is in our subnet
493493
if ipv4net.contains(&dst_ip) {
494494
// search for the destination NIC
495-
if let Some(nic) = ri.nics.get(&dst_ip.to_string()) {
495+
if let Some(nic) = ri.nics.get(&dst_ip.to_string()).and_then(|p| p.upgrade()) {
496496
// found the NIC, forward the chunk to the NIC.
497497
// call to NIC must unlock mutex
498498
let ni = nic.lock().await;
@@ -504,7 +504,7 @@ impl Router {
504504
} else {
505505
// the destination is outside of this subnet
506506
// is this WAN?
507-
if let Some(parent) = &ri.parent {
507+
if let Some(parent) = &ri.parent.clone().and_then(|p| p.upgrade()) {
508508
// Pass it to the parent via NAT
509509
if let Some(to_parent) = ri.nat.translate_outbound(&*c).await? {
510510
// call to parent router mutex unlock mutex
@@ -545,7 +545,7 @@ impl RouterInternal {
545545
if !self.ipv4net.contains(ip) {
546546
return Err(Error::ErrStaticIpIsBeyondSubnet);
547547
}
548-
self.nics.insert(ip.to_string(), Arc::clone(&nic));
548+
self.nics.insert(ip.to_string(), Arc::downgrade(&nic));
549549
ipnets.push(IpNet::from_str(&format!(
550550
"{}/{}",
551551
ip,

0 commit comments

Comments
 (0)