Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Simplify and fix AtomicCounter by TheBlueMatt · Pull Request #3302 · lightningdevkit/rust-lightning · GitHub
Skip to content

Simplify and fix AtomicCounter - #3302

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups
Sep 12, 2024
Merged

Simplify and fix AtomicCounter#3302
tnull merged 4 commits into
lightningdevkit:mainfrom
TheBlueMatt:2024-09-atomic-cleanups

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

AtomicCounter was slightly race-y on 32-bit platforms because it
increments the high AtomicUsize independently from the low
AtomicUsize, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.

This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.

However, we can also optimize the counter somewhat by using the
target_has_atomic = "64" cfg flag, which we do here, allowing us
to use AtomicU64 even on 32-bit platforms where 64-bit atomics
are available.

This changes some test behavior slightly, which requires
adaptation.

Fixes#3000

@codecov

codecovBot commented Sep 8, 2024

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.57%. Comparing base (d35239c) to head (1c2bd09).
Report is 33 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #3302 +/- ##
==========================================
+ Coverage 89.85% 90.57% +0.72% 
==========================================
Files 126 126 Lines 104145 109040 +4895 Branches 104145 109040 +4895 ==========================================
+ Hits 93577 98761 +5184 + Misses 7894 7624 -270 + Partials 2674 2655 -19 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tnull
tnull self-requested a review September 9, 2024 07:53
Comment threadlightning/src/ln/monitor_tests.rs
Comment threadlightning/src/util/atomic_counter.rs Outdated
Comment threadlightning/src/util/atomic_counter.rs Outdated
counter: Mutex::new(0),
}
}
pub(crate) fn get_increment(&self) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Preexisting, this naming seems a bit confusing as we're actually not returning the incremented counter. Maybe the field should be called next_counter and the method just next()?

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think. Feel free to squash the fixup.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed.

@tnull

Copy link
Copy Markdown
Contributor

Squashed.

It seems you're missing some imports here, CI is sad:

error[E0412]: cannot find type `Mutex` in this scope
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:13:11
|
13 | counter: Mutex<u64>,
| ^^^^^ not found in this scope
|
help: consider importing this struct through its public re-export
|
5 + use crate::sync::Mutex;
|

etc

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gah

$ git diff-tree -U1 7b1f07bd3 38ecd35c7
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index d00c7341b..6dbe54e64 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -5,3 +5,3 @@ use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
-use crate::prelude::*;+use crate::sync::Mutex;
$ 

tnull
tnull previously approved these changes Sep 11, 2024

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, happy to land it if CI passes.

`InMemorySigner` has various private keys in it which makes
`Debug` either useless or dangerous (because most keys won't log
anything, but if they did we'd risk logging private key material).
`blinded_path_with_custom_tlv` indirectly relied on route CLTV
randomization when sending because nodes were at substantially
different block heights after setup. Instead we make sure all nodes
are at the same height which makes the test more robust.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

FFS

$ git diff-tree -U1 38ecd35c7 dccdd3c15
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 41354c695..4b8a9e025 100644
--- a/lightning/src/sign/mod.rs+++ b/lightning/src/sign/mod.rs@@ -1029,3 +1029,2 @@ pub trait ChangeDestinationSource {
/// a secure external signer.
-#[derive(Debug)]
pub struct InMemorySigner {
@@ -2483,3 +2482,2 @@ impl PhantomKeysManager {
/// An implementation of [`EntropySource`] using ChaCha20.
-#[derive(Debug)]
pub struct RandomBytes {
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 6dbe54e64..3627ccf8a 100644
--- a/lightning/src/util/atomic_counter.rs+++ b/lightning/src/util/atomic_counter.rs@@ -7,3 +7,2 @@ use crate::sync::Mutex;
-#[derive(Debug)]
pub(crate) struct AtomicCounter {
$ 

@tnull

Copy link
Copy Markdown
Contributor

FFS

Nope:

error[E0596]: cannot borrow `mtx` as mutable, as it is not declared as mutable
--> /home/runner/work/rust-lightning/rust-lightning/lightning/src/util/atomic_counter.rs:30:5
|
30 | *mtx += 1;
| ^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
29 | let mut mtx = self.counter.lock().unwrap();
| +++
For more information about this error, try `rustc --explain E0596`.
warning: `lightning` (lib) generated 1 warning
error: could not compile `lightning` (lib) due to 1 previous error; 1 warning emitted
Error: Process completed with exit code 101.

@TheBlueMatt

TheBlueMatt commented Sep 12, 2024

Copy link
Copy Markdown
CollaboratorAuthor

Grr, guess I didn't have the arm compiler installed so running CI locally didn't test it...

$ git diff-tree -U2 dccdd3c15 68d4c9673
diff --git a/lightning/src/util/atomic_counter.rs b/lightning/src/util/atomic_counter.rs
index 3627ccf8a..b1fd7323f 100644
--- a/lightning/src/util/atomic_counter.rs
+++ b/lightning/src/util/atomic_counter.rs
@@ -38,5 +38,5 @@ impl AtomicCounter {
}
#[cfg(not(target_has_atomic = "64"))] {
- let mtx = self.counter.lock().unwrap();
+ let mut mtx = self.counter.lock().unwrap();
*mtx = count;
}
$ 

`AtomicCounter` was slightly race-y on 32-bit platforms because it
increments the high `AtomicUsize` independently from the low
`AtomicUsize`, leading to a potential race where another thread
could observe the low increment but not the high increment and see
a value of 0 twice.
This isn't a big deal because (a) most platforms are 64-bit these
days, (b) 32-bit platforms aren't super likely to have their
counter overflow 32 bits anyway, and (c) the two writes are
back-to-back so having another thread read during that window is
very unlikely.
However, we can also optimize the counter somewhat by using the
`target_has_atomic = "64"` cfg flag, which we do here, allowing us
to use `AtomicU64` even on 32-bit platforms where 64-bit atomics
are available.
This changes some test behavior slightly, which requires
adaptation.
Fixeslightningdevkit#3000
Its a counter, `next` is super clear, `get_increment` is a bit
less so.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, landing this as pretty trivial.

@tnull
tnull merged commit a75fdab into lightningdevkit:mainSep 12, 2024
#[cfg(target_has_atomic = "64")]
counter: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
counter: Mutex<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is to produce some unique values that maybe don't need to be sequential you can still have a sensible lock-free implementation. Roughly like this:

letmut low = self.low.load(Relaxed);letmut high = self.high.load(Relaxed);loop{let new_low = if low == u32::MAX{// don't use fetch_add to avoid incrementing high by more than 1ifletErr(new) = self.high.compare_exchange(high, high + 1,Relaxed,Relaxed){
high = new;}0}else{
low + 1}// FTR this cannot be weakmatchself.low.compare_exchange(low, new_low,Relaxed,Relaxed){Ok(_) => break,Err(new) => low = new,}}
u64::from(high) << 32 | u64::from(low)

This assumes that a thread doesn't get scheduled-out after incrementing high for so long that other thread(s) manage to increment the counter by 2^32, which I think is a reasonable assumption. There's still a chance that high gets bumped by more than one though if a thread managed to bump it and before it updates low another thread reads both of them. This is quite unfrequent and could be dealt with by sacrificing one bit of high which gets set first and then reset at the end.

};
(high << 32) | low
#[cfg(target_has_atomic = "64")] {
self.counter.fetch_add(1, Ordering::AcqRel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have AcqRel however to my understanding this is not a synchronization primitive so Relaxed is appropriate.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtomicCounter starts at 0x100000001

3 participants

@TheBlueMatt@tnull@Kixunil