Skip to content

Commit 46428a7

Browse files
committed
Refuse recursive read locks in lockorder testing
Our existing lockorder tests assume that a read lock on a thread that is already holding the same read lock is totally fine. This isn't at all true. The `std` `RwLock` behavior is platform-dependent - on most platforms readers can starve writers as readers will never block for a pending writer. However, on platforms where this is not the case, one thread trying to take a write lock may deadlock with another thread that both already has, and is attempting to take again, a read lock. Worse, our in-tree `FairRwLock` exhibits this behavior explicitly on all platforms to avoid the starvation issue. Thus, we shouldn't have any special handling for allowing recursive read locks, so we simply remove it here.
1 parent cd0bfdb commit 46428a7

File tree

4 files changed

+37
-62
lines changed

4 files changed

+37
-62
lines changed

lightning/src/ln/chanmon_update_fail_tests.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1426,9 +1426,11 @@ fn monitor_failed_no_reestablish_response() {
14261426
{
14271427
let mut node_0_per_peer_lock;
14281428
let mut node_0_peer_state_lock;
1429+
get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, channel_id).announcement_sigs_state = AnnouncementSigsState::PeerReceived;
1430+
}
1431+
{
14291432
let mut node_1_per_peer_lock;
14301433
let mut node_1_peer_state_lock;
1431-
get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, channel_id).announcement_sigs_state = AnnouncementSigsState::PeerReceived;
14321434
get_channel_ref!(nodes[1], nodes[0], node_1_per_peer_lock, node_1_peer_state_lock, channel_id).announcement_sigs_state = AnnouncementSigsState::PeerReceived;
14331435
}
14341436

lightning/src/ln/payment_tests.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -1275,83 +1275,88 @@ fn test_trivial_inflight_htlc_tracking(){
12751275

12761276
// Send and claim the payment. Inflight HTLCs should be empty.
12771277
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 500000);
1278+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
12781279
{
1279-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1280-
12811280
let mut node_0_per_peer_lock;
12821281
let mut node_0_peer_state_lock;
1283-
let mut node_1_per_peer_lock;
1284-
let mut node_1_peer_state_lock;
12851282
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1286-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
12871283

12881284
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
12891285
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
12901286
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
12911287
channel_1.get_short_channel_id().unwrap()
12921288
);
1289+
assert_eq!(chan_1_used_liquidity, None);
1290+
}
1291+
{
1292+
let mut node_1_per_peer_lock;
1293+
let mut node_1_peer_state_lock;
1294+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1295+
12931296
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
12941297
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
12951298
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
12961299
channel_2.get_short_channel_id().unwrap()
12971300
);
12981301

1299-
assert_eq!(chan_1_used_liquidity, None);
13001302
assert_eq!(chan_2_used_liquidity, None);
13011303
}
13021304

13031305
// Send the payment, but do not claim it. Our inflight HTLCs should contain the pending payment.
13041306
let (payment_preimage, _, _) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 500000);
1307+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
13051308
{
1306-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1307-
13081309
let mut node_0_per_peer_lock;
13091310
let mut node_0_peer_state_lock;
1310-
let mut node_1_per_peer_lock;
1311-
let mut node_1_peer_state_lock;
13121311
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1313-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
13141312

13151313
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
13161314
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
13171315
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
13181316
channel_1.get_short_channel_id().unwrap()
13191317
);
1318+
// First hop accounts for expected 1000 msat fee
1319+
assert_eq!(chan_1_used_liquidity, Some(501000));
1320+
}
1321+
{
1322+
let mut node_1_per_peer_lock;
1323+
let mut node_1_peer_state_lock;
1324+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1325+
13201326
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
13211327
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
13221328
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
13231329
channel_2.get_short_channel_id().unwrap()
13241330
);
13251331

1326-
// First hop accounts for expected 1000 msat fee
1327-
assert_eq!(chan_1_used_liquidity, Some(501000));
13281332
assert_eq!(chan_2_used_liquidity, Some(500000));
13291333
}
13301334

13311335
// Now, let's claim the payment. This should result in the used liquidity to return `None`.
13321336
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
1337+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
13331338
{
1334-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1335-
13361339
let mut node_0_per_peer_lock;
13371340
let mut node_0_peer_state_lock;
1338-
let mut node_1_per_peer_lock;
1339-
let mut node_1_peer_state_lock;
13401341
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1341-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
13421342

13431343
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
13441344
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
13451345
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
13461346
channel_1.get_short_channel_id().unwrap()
13471347
);
1348+
assert_eq!(chan_1_used_liquidity, None);
1349+
}
1350+
{
1351+
let mut node_1_per_peer_lock;
1352+
let mut node_1_peer_state_lock;
1353+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1354+
13481355
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
13491356
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
13501357
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
13511358
channel_2.get_short_channel_id().unwrap()
13521359
);
1353-
1354-
assert_eq!(chan_1_used_liquidity, None);
13551360
assert_eq!(chan_2_used_liquidity, None);
13561361
}
13571362
}

lightning/src/sync/debug_sync.rs

+7-24
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,13 @@ impl LockMetadata {
122122
res
123123
}
124124

125-
// Returns whether we were a recursive lock (only relevant for read)
126-
fn _pre_lock(this: &Arc<LockMetadata>, read: bool) -> bool {
127-
let mut inserted = false;
125+
fn pre_lock(this: &Arc<LockMetadata>) {
128126
LOCKS_HELD.with(|held| {
129127
// For each lock which is currently locked, check that no lock's locked-before
130128
// set includes the lock we're about to lock, which would imply a lockorder
131129
// inversion.
132-
for (locked_idx, _locked) in held.borrow().iter() {
133-
if read && *locked_idx == this.lock_idx {
134-
// Recursive read locks are explicitly allowed
135-
return;
136-
}
137-
}
138130
for (locked_idx, locked) in held.borrow().iter() {
139-
if !read && *locked_idx == this.lock_idx {
131+
if *locked_idx == this.lock_idx {
140132
// With `feature = "backtrace"` set, we may be looking at different instances
141133
// of the same lock.
142134
debug_assert!(cfg!(feature = "backtrace"), "Tried to acquire a lock while it was held!");
@@ -160,14 +152,9 @@ impl LockMetadata {
160152
}
161153
}
162154
held.borrow_mut().insert(this.lock_idx, Arc::clone(this));
163-
inserted = true;
164155
});
165-
inserted
166156
}
167157

168-
fn pre_lock(this: &Arc<LockMetadata>) { Self::_pre_lock(this, false); }
169-
fn pre_read_lock(this: &Arc<LockMetadata>) -> bool { Self::_pre_lock(this, true) }
170-
171158
fn try_locked(this: &Arc<LockMetadata>) {
172159
LOCKS_HELD.with(|held| {
173160
// Since a try-lock will simply fail if the lock is held already, we do not
@@ -255,7 +242,6 @@ pub struct RwLock<T: Sized> {
255242

256243
pub struct RwLockReadGuard<'a, T: Sized + 'a> {
257244
lock: &'a RwLock<T>,
258-
first_lock: bool,
259245
guard: StdRwLockReadGuard<'a, T>,
260246
}
261247

@@ -274,12 +260,6 @@ impl<T: Sized> Deref for RwLockReadGuard<'_, T> {
274260

275261
impl<T: Sized> Drop for RwLockReadGuard<'_, T> {
276262
fn drop(&mut self) {
277-
if !self.first_lock {
278-
// Note that its not strictly true that the first taken read lock will get unlocked
279-
// last, but in practice our locks are always taken as RAII, so it should basically
280-
// always be true.
281-
return;
282-
}
283263
LOCKS_HELD.with(|held| {
284264
held.borrow_mut().remove(&self.lock.deps.lock_idx);
285265
});
@@ -314,8 +294,11 @@ impl<T> RwLock<T> {
314294
}
315295

316296
pub fn read<'a>(&'a self) -> LockResult<RwLockReadGuard<'a, T>> {
317-
let first_lock = LockMetadata::pre_read_lock(&self.deps);
318-
self.inner.read().map(|guard| RwLockReadGuard { lock: self, guard, first_lock }).map_err(|_| ())
297+
// Note that while we could be taking a recursive read lock here, Rust's `RwLock` may
298+
// deadlock trying to take a second read lock if another thread is waiting on the write
299+
// lock. Its platform dependent (but our in-tree `FairRwLock` guarantees this behavior).
300+
LockMetadata::pre_lock(&self.deps);
301+
self.inner.read().map(|guard| RwLockReadGuard { lock: self, guard }).map_err(|_| ())
319302
}
320303

321304
pub fn write<'a>(&'a self) -> LockResult<RwLockWriteGuard<'a, T>> {

lightning/src/sync/test_lockorder_checks.rs

+2-17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ fn recursive_lock_fail() {
1010
}
1111

1212
#[test]
13+
#[should_panic]
14+
#[cfg(not(feature = "backtrace"))]
1315
fn recursive_read() {
1416
let lock = RwLock::new(());
1517
let _a = lock.read().unwrap();
@@ -61,23 +63,6 @@ fn read_lockorder_fail() {
6163
}
6264
}
6365

64-
#[test]
65-
fn read_recursive_no_lockorder() {
66-
// Like the above, but note that no lockorder is implied when we recursively read-lock a
67-
// RwLock, causing this to pass just fine.
68-
let a = RwLock::new(());
69-
let b = RwLock::new(());
70-
let _outer = a.read().unwrap();
71-
{
72-
let _a = a.read().unwrap();
73-
let _b = b.read().unwrap();
74-
}
75-
{
76-
let _b = b.read().unwrap();
77-
let _a = a.read().unwrap();
78-
}
79-
}
80-
8166
#[test]
8267
#[should_panic]
8368
fn read_write_lockorder_fail() {

0 commit comments

Comments
 (0)