Skip to content

Clean up once code#359

Open
Kixunil wants to merge 4 commits into
rust-bitcoin:masterfrom
Kixunil:once-compar-exchange
Open

Clean up once code#359
Kixunil wants to merge 4 commits into
rust-bitcoin:masterfrom
Kixunil:once-compar-exchange

Conversation

@Kixunil

@Kixunil Kixunil commented Jan 5, 2022

Copy link
Copy Markdown
Collaborator

This was originally intended to just change swap to compare_exchange
however other changes were needed too:

  • Load with Release ordering seemed wrong because if it loaded DONE
    before while loop it wouldn't synchronize with the storing thread.
  • There's no point in repeatedly checking if the value is NONE so this
    check was moved about while loop
  • Due to a mistake I wanted to inspect using miri but couldn't because
    of FFI call. Separating the once implementation from consumer allowed
    writing a test in pure Rust and inspect via miri.
  • The initializing code now skips loading the value again because it
    already knows it's initialized.
  • Orderings are now explained in the comments

Closes #351

@Kixunil Kixunil force-pushed the once-compar-exchange branch from f32a5df to e19209d Compare January 5, 2022 18:05
Comment thread secp256k1-sys/src/lib.rs
fn rustsecp256k1_v0_4_1_context_preallocated_clone(cx: *const Context, prealloc: *mut c_void) -> *mut Context;
}

mod once {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs some kind of descriptive comment describing why its here, that it's only used in fuzzing, and that we don't really care too much...................all that said, maybe we should care a bit more, I realized yesterday that global-context-less-secure relies on std, even though the whole point of it is to avoid relying on std (it predates no-std so that's expected, but still). It only actually relies on std::sync::Once, so maybe we should use this there as well. Not sure how to get it across both crates, though, maybe just make a single file and symlink them across the two crates to avoid making it public?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In that case I think it should condinionaly use Once from std as suggested in #352

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The whole point of the global-context-less-secure feature is to work on no-std, and sadly Once is not no-std as it requires thread-parking support.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Since cargo features are additive it's still useful to avoid performance issues if two crates turn on these features. Although maybe they should've been --cfg parameters, not features...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, sorry, I misread your comment again lol. Yes, conditionally using Once if std is turned on is fine, but we still need to somehow have this once available in secp256k1 if we want to fix global-context-less-secure not building with no-std.

Comment thread secp256k1-sys/src/lib.rs
Comment thread secp256k1-sys/src/lib.rs
Comment thread secp256k1-sys/src/lib.rs
This was originally intended to just change `swap` to `compare_exchange`
however other changes were needed too:

* Load with `Release` ordering seemed wrong because if it loaded `DONE`
  before while loop it wouldn't synchronize with the storing thread.
* There's no point in repeatedly checking if the value is `NONE` so this
  check was moved about while loop
* Due to a mistake I wanted to inspect using `miri` but couldn't because
  of FFI call. Separating the once implementation from consumer allowed
  writing a test in pure Rust and inspect via `miri`.
* The initializing code now skips loading the value again because it
  already knows it's initialized.
* Orderings are now explained in the comments
@Kixunil Kixunil force-pushed the once-compar-exchange branch from e19209d to 36c96a9 Compare January 5, 2022 20:40
This caches the initialization state of preallocated context in a thread
local variable to avoid atomics in subsequent accesses.
Accessing `std::mem` instead of `core::mem` caused fuzzing to break
without `std`.
@Kixunil

Kixunil commented Jan 5, 2022

Copy link
Copy Markdown
Collaborator Author

I also fixed broken no_std fuzzing. Would've done in a separate PR but not worth conflict fixing I think.

Comment thread secp256k1-sys/src/lib.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Member

Are you planning on fixing global-context-less-secure + no-std here?

@Kixunil

Kixunil commented Jan 13, 2022

Copy link
Copy Markdown
Collaborator Author

Oh, didn't notice it's broken. Is there an advantage of doing it here?

@TheBlueMatt

TheBlueMatt commented Jan 14, 2022

Copy link
Copy Markdown
Member

I mentioned it in one of the discussions further up, but those were a mess, it's issue is use of std Once. I don't know if this PR is worth bothering unless we also bother refactoring it to make it available in both crates (I guess a symlink to a common module?) and fix global contact with it.

@Kixunil

Kixunil commented Jan 14, 2022

Copy link
Copy Markdown
Collaborator Author

OK, will try to find the time to look into it.

@tcharding tcharding left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few minor suggestions.

Comment thread secp256k1-sys/src/lib.rs
if have_ctx == NONE {
// Ordering: on success we're only signalling to other thread that work is in progress
// without transferring other data, so it should be Relaxed, on failure the value may be DONE,
// so we want to Acquire to safely proceed in that case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be 'so we want Acquire to safely proceed ...'?

Comment thread secp256k1-sys/src/lib.rs
// without transferring other data, so it should be Relaxed, on failure the value may be DONE,
// so we want to Acquire to safely proceed in that case.
// However compare_exchange doesn't allow failure ordering to be stronger than success
// so both are Acquire.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, this whole comment might look better like this (includes change above):

                    // Ordering: on success we're only signalling to other thread that work is in
                    // progress without transferring other data, so it should be Relaxed, on failure
                    // the value may be DONE, so we want Acquire to safely proceed in that case.
                    // However compare_exchange doesn't allow failure ordering to be stronger than
                    // success so both are Acquire.

Comment thread secp256k1-sys/src/lib.rs
}
}
while have_ctx != DONE {
// Another thread is building, just busy-loop until they're done.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This refers to the whole loop, right? Perhaps clearer if its outside the loop (above the while line).

Comment thread secp256k1-sys/src/lib.rs
assert_eq!(rustsecp256k1_v0_4_1_context_preallocated_create(
PREALLOCATED_CONTEXT[..].as_ptr() as *mut c_void,
SECP256K1_START_SIGN | SECP256K1_START_VERIFY),
PREALLOCATED_CONTEXT[..].as_ptr() as *mut Context);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we assign this pointer to a local variable?

            let ptr = PREALLOCATED_CONTEXT[..].as_ptr() as *mut c_void;
            assert_eq!(rustsecp256k1_v0_4_1_context_preallocated_create(ptr, SECP256K1_START_SIGN | SECP256K1_START_VERIFY), ptr);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

They are different types. Which is strange. It'd still be shorter to cast one of them but would be better to look if we can unify them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh dear, I missed the type difference :(

Comment thread secp256k1-sys/src/lib.rs
// This thread will read whatever the other thread wrote so this needs to be Acquire.
have_ctx = self.0.load(Ordering::Acquire);
#[cfg(feature = "std")]
::std::thread::yield_now();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

FTR yield_now is not great. Futex would be the best, cond var as a fallback.

Comment thread secp256k1-sys/src/lib.rs
assert_eq!(rustsecp256k1_v0_4_1_context_preallocated_create(
PREALLOCATED_CONTEXT[..].as_ptr() as *mut c_void,
SECP256K1_START_SIGN | SECP256K1_START_VERIFY),
PREALLOCATED_CONTEXT[..].as_ptr() as *mut Context);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

They are different types. Which is strange. It'd still be shorter to cast one of them but would be better to look if we can unify them.

Comment thread secp256k1-sys/src/lib.rs
const HAVE_CONTEXT_WORKING: usize = 1;
const HAVE_CONTEXT_DONE: usize = 2;
static HAVE_PREALLOCATED_CONTEXT: once::Once = once::Once::INIT;
static mut PREALLOCATED_CONTEXT: [u8; CTX_SIZE] = [0; CTX_SIZE];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Just realized this variable is very wrong due to alignment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, so the fix should be [AlignedType; CTX_SIZE / mem::size_of::<AlignedType>()], right? Although maybe we should have a separate type for that.

Now looking at ffi::Context and it's an int. WTF?

@elichai elichai Mar 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

idk what CTX_SIZE is and how was it calculated.
we can't have a type for that because we don't know the size of the context in compile time.
although everything here won't be needed when we start supporting the static contexts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

we don't know the size of the context in compile time.

I think we can know the maximum and guard against it changing by calling secp256k1_context_preallocated_size

@Kixunil Kixunil mentioned this pull request Jun 10, 2022
@tcharding

Copy link
Copy Markdown
Member

Whats the status of this PR @Kixunil, are there more things to be done or would you like it re-reviewed as is?

@Kixunil

Kixunil commented Nov 21, 2022

Copy link
Copy Markdown
Collaborator Author

Didn't have much time for it but meanwhile I came to conclusion that I really don't like spin locks for reasons that were mentioned elsewhere. I will probably still finish the PR to fix UB but then I want to make another one fixing the spin lock issue.

Sign up for free to 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.

Mild race condition in secp256k1_context_preallocated_create

4 participants