Skip to content

Commit c84a274

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 9221d47 commit c84a274

File tree

6 files changed

+46
-73
lines changed

6 files changed

+46
-73
lines changed

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 3 additions & 1 deletion
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/functional_test_utils.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,9 +2150,10 @@ pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_rou
21502150
assert!(err.contains("Cannot send value that would put us over the max HTLC value in flight our peer will accept")));
21512151
}
21522152

2153-
pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) {
2154-
let our_payment_preimage = route_payment(&origin, expected_route, recv_value).0;
2155-
claim_payment(&origin, expected_route, our_payment_preimage);
2153+
pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) {
2154+
let res = route_payment(&origin, expected_route, recv_value);
2155+
claim_payment(&origin, expected_route, res.0);
2156+
res
21562157
}
21572158

21582159
pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {

lightning/src/ln/payment_tests.rs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,33 +1194,31 @@ fn test_trivial_inflight_htlc_tracking(){
11941194
let (_, _, chan_2_id, _) = create_announced_chan_between_nodes(&nodes, 1, 2);
11951195

11961196
// Send and claim the payment. Inflight HTLCs should be empty.
1197-
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 500000);
1198-
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
1199-
check_added_monitors!(nodes[0], 1);
1200-
pass_along_route(&nodes[0], &[&vec!(&nodes[1], &nodes[2])[..]], 500000, payment_hash, payment_secret);
1201-
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], payment_preimage);
1197+
let payment_hash = send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000).1;
1198+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
12021199
{
1203-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1204-
12051200
let mut node_0_per_peer_lock;
12061201
let mut node_0_peer_state_lock;
1207-
let mut node_1_per_peer_lock;
1208-
let mut node_1_peer_state_lock;
12091202
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1210-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
12111203

12121204
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
12131205
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
12141206
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
12151207
channel_1.get_short_channel_id().unwrap()
12161208
);
1209+
assert_eq!(chan_1_used_liquidity, None);
1210+
}
1211+
{
1212+
let mut node_1_per_peer_lock;
1213+
let mut node_1_peer_state_lock;
1214+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1215+
12171216
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
12181217
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
12191218
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
12201219
channel_2.get_short_channel_id().unwrap()
12211220
);
12221221

1223-
assert_eq!(chan_1_used_liquidity, None);
12241222
assert_eq!(chan_2_used_liquidity, None);
12251223
}
12261224
let pending_payments = nodes[0].node.list_recent_payments();
@@ -1233,30 +1231,32 @@ fn test_trivial_inflight_htlc_tracking(){
12331231
}
12341232

12351233
// Send the payment, but do not claim it. Our inflight HTLCs should contain the pending payment.
1236-
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 500000);
1234+
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000);
1235+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
12371236
{
1238-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1239-
12401237
let mut node_0_per_peer_lock;
12411238
let mut node_0_peer_state_lock;
1242-
let mut node_1_per_peer_lock;
1243-
let mut node_1_peer_state_lock;
12441239
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1245-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
12461240

12471241
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
12481242
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
12491243
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
12501244
channel_1.get_short_channel_id().unwrap()
12511245
);
1246+
// First hop accounts for expected 1000 msat fee
1247+
assert_eq!(chan_1_used_liquidity, Some(501000));
1248+
}
1249+
{
1250+
let mut node_1_per_peer_lock;
1251+
let mut node_1_peer_state_lock;
1252+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1253+
12521254
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
12531255
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
12541256
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
12551257
channel_2.get_short_channel_id().unwrap()
12561258
);
12571259

1258-
// First hop accounts for expected 1000 msat fee
1259-
assert_eq!(chan_1_used_liquidity, Some(501000));
12601260
assert_eq!(chan_2_used_liquidity, Some(500000));
12611261
}
12621262
let pending_payments = nodes[0].node.list_recent_payments();
@@ -1271,28 +1271,29 @@ fn test_trivial_inflight_htlc_tracking(){
12711271
nodes[0].node.timer_tick_occurred();
12721272
}
12731273

1274+
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
12741275
{
1275-
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
1276-
12771276
let mut node_0_per_peer_lock;
12781277
let mut node_0_peer_state_lock;
1279-
let mut node_1_per_peer_lock;
1280-
let mut node_1_peer_state_lock;
12811278
let channel_1 = get_channel_ref!(&nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1_id);
1282-
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
12831279

12841280
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
12851281
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
12861282
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
12871283
channel_1.get_short_channel_id().unwrap()
12881284
);
1285+
assert_eq!(chan_1_used_liquidity, None);
1286+
}
1287+
{
1288+
let mut node_1_per_peer_lock;
1289+
let mut node_1_peer_state_lock;
1290+
let channel_2 = get_channel_ref!(&nodes[1], nodes[2], node_1_per_peer_lock, node_1_peer_state_lock, chan_2_id);
1291+
12891292
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
12901293
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
12911294
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
12921295
channel_2.get_short_channel_id().unwrap()
12931296
);
1294-
1295-
assert_eq!(chan_1_used_liquidity, None);
12961297
assert_eq!(chan_2_used_liquidity, None);
12971298
}
12981299

lightning/src/routing/utxo.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,10 +747,11 @@ mod tests {
747747
Ok(TxOut { value: 1_000_000, script_pubkey: good_script }));
748748

749749
assert_eq!(chan_update_a.contents.timestamp, chan_update_b.contents.timestamp);
750-
assert!(network_graph.read_only().channels()
750+
let graph_lock = network_graph.read_only();
751+
assert!(graph_lock.channels()
751752
.get(&valid_announcement.contents.short_channel_id).as_ref().unwrap()
752753
.one_to_two.as_ref().unwrap().last_update !=
753-
network_graph.read_only().channels()
754+
graph_lock.channels()
754755
.get(&valid_announcement.contents.short_channel_id).as_ref().unwrap()
755756
.two_to_one.as_ref().unwrap().last_update);
756757
}

lightning/src/sync/debug_sync.rs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -124,21 +124,13 @@ impl LockMetadata {
124124
res
125125
}
126126

127-
// Returns whether we were a recursive lock (only relevant for read)
128-
fn _pre_lock(this: &Arc<LockMetadata>, read: bool) -> bool {
129-
let mut inserted = false;
127+
fn pre_lock(this: &Arc<LockMetadata>) {
130128
LOCKS_HELD.with(|held| {
131129
// For each lock which is currently locked, check that no lock's locked-before
132130
// set includes the lock we're about to lock, which would imply a lockorder
133131
// inversion.
134-
for (locked_idx, _locked) in held.borrow().iter() {
135-
if read && *locked_idx == this.lock_idx {
136-
// Recursive read locks are explicitly allowed
137-
return;
138-
}
139-
}
140132
for (locked_idx, locked) in held.borrow().iter() {
141-
if !read && *locked_idx == this.lock_idx {
133+
if *locked_idx == this.lock_idx {
142134
// With `feature = "backtrace"` set, we may be looking at different instances
143135
// of the same lock.
144136
debug_assert!(cfg!(feature = "backtrace"), "Tried to acquire a lock while it was held!");
@@ -162,14 +154,9 @@ impl LockMetadata {
162154
}
163155
}
164156
held.borrow_mut().insert(this.lock_idx, Arc::clone(this));
165-
inserted = true;
166157
});
167-
inserted
168158
}
169159

170-
fn pre_lock(this: &Arc<LockMetadata>) { Self::_pre_lock(this, false); }
171-
fn pre_read_lock(this: &Arc<LockMetadata>) -> bool { Self::_pre_lock(this, true) }
172-
173160
fn held_by_thread(this: &Arc<LockMetadata>) -> LockHeldState {
174161
let mut res = LockHeldState::NotHeldByThread;
175162
LOCKS_HELD.with(|held| {
@@ -276,7 +263,6 @@ pub struct RwLock<T: Sized> {
276263

277264
pub struct RwLockReadGuard<'a, T: Sized + 'a> {
278265
lock: &'a RwLock<T>,
279-
first_lock: bool,
280266
guard: StdRwLockReadGuard<'a, T>,
281267
}
282268

@@ -295,12 +281,6 @@ impl<T: Sized> Deref for RwLockReadGuard<'_, T> {
295281

296282
impl<T: Sized> Drop for RwLockReadGuard<'_, T> {
297283
fn drop(&mut self) {
298-
if !self.first_lock {
299-
// Note that its not strictly true that the first taken read lock will get unlocked
300-
// last, but in practice our locks are always taken as RAII, so it should basically
301-
// always be true.
302-
return;
303-
}
304284
LOCKS_HELD.with(|held| {
305285
held.borrow_mut().remove(&self.lock.deps.lock_idx);
306286
});
@@ -335,8 +315,11 @@ impl<T> RwLock<T> {
335315
}
336316

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

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

lightning/src/sync/test_lockorder_checks.rs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ fn recursive_lock_fail() {
1515
}
1616

1717
#[test]
18+
#[should_panic]
19+
#[cfg(not(feature = "backtrace"))]
1820
fn recursive_read() {
1921
let lock = RwLock::new(());
2022
let _a = lock.read().unwrap();
@@ -66,23 +68,6 @@ fn read_lockorder_fail() {
6668
}
6769
}
6870

69-
#[test]
70-
fn read_recursive_no_lockorder() {
71-
// Like the above, but note that no lockorder is implied when we recursively read-lock a
72-
// RwLock, causing this to pass just fine.
73-
let a = RwLock::new(());
74-
let b = RwLock::new(());
75-
let _outer = a.read().unwrap();
76-
{
77-
let _a = a.read().unwrap();
78-
let _b = b.read().unwrap();
79-
}
80-
{
81-
let _b = b.read().unwrap();
82-
let _a = a.read().unwrap();
83-
}
84-
}
85-
8671
#[test]
8772
#[should_panic]
8873
fn read_write_lockorder_fail() {

0 commit comments

Comments
 (0)