Skip to content

refactor: fix warnings from clang-tidy-20 and bitcoin-tidy #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 29, 2025

Conversation

ryanofsky
Copy link
Collaborator

@ryanofsky ryanofsky commented May 12, 2025

Warnings are described in commit messages and were reported by Sjors in bitcoin/bitcoin#31802 (comment)

@hebasto
Copy link
Member

hebasto commented May 12, 2025

Concept ACK.

ryanofsky added 3 commits May 15, 2025 11:24
Reported by Sjors Provoost <[email protected]>
bitcoin/bitcoin#31802 (comment)

/ci_container_base/include/mp/proxy-io.h:252:16: error: forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead [bugprone-move-forwarding-reference,-warnings-as-errors]
/ci_container_base/include/mp/proxy-io.h:336:18: error: forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead [bugprone-move-forwarding-reference,-warnings-as-errors]
/ci_container_base/include/mp/util.h:186:12: error: forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead [bugprone-move-forwarding-reference,-warnings-as-errors]
https://cirrus-ci.com/task/6187773452877824?logs=ci#L4712
/ci_container_base/include/mp/type-interface.h:47:75: error: forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead [bugprone-move-forwarding-reference,-warnings-as-errors]
https://cirrus-ci.com/task/6187773452877824?logs=ci#L4712

Error was caused by mp/type-interface.h CustomBuildField field using std::move
instead of std::forward.

This commit:

- Adds std::forward and std::move several places to preserve lvalue/rvalue
  status.

- Defines a FunctionTraits::Fwd<...>(...) helper which does same thing as
  std::forward except it takes parameter number instead of a parameter type so
  generated code doesn't need as verbose as std::forward calls would be.

- Changes C++ code generator to pass arguments as `M0::Fwd<1>(arg1)` instead of
  `arg1` in generated code. This change can be verified by diffing a generated
  file like build/src/ipc/capnp/mining.capnp.proxy-client.c++ in the bitcoin
  build before and after this change.

Unlike the previous commit which resolved bugprone-move-forwarding-reference
errors by just switching std::move to std::forward, this commit required more
changes because just switching from std::move to std::forward in the
type-interface.h CustomBuildField function would lead to another error in the
CustomMakeProxyServer function there which is expecting an rvalue, not an
lvalue.

That error could have alternately been fixed by changing CustomMakeProxyServer
to accept lvalues and copy the shared_ptr. But this would be slightly less
efficient and it is better to resolve the problem at the root and just use
perfect forwarding everywhere, all the way up the call stack. This required the
changes to the code generator and clientInvoke described above.
/ci_container_base/include/mp/proxy-io.h:637:1: error: Variable with non-trivial destructor cannot be thread_local. [bitcoin-nontrivial-threadlocal,-warnings-as-errors]
https://cirrus-ci.com/task/6187773452877824?logs=ci#L4720
@ryanofsky
Copy link
Collaborator Author

ryanofsky commented May 15, 2025

Rebased 8d8e9f6 -> e6a71ae (pr/tidy.1 -> pr/tidy.2, compare) fixing typos and conflict with #165
Updated e6a71ae -> a5bff37 (pr/tidy.2 -> pr/tidy.3, compare) expanding a comment

@Sjors
Copy link
Member

Sjors commented May 16, 2025

I added the latest version of this to bitcoin/bitcoin#31802, the tidy job is still happy.

Some background for the first two commits: https://clang.llvm.org/extra/clang-tidy/checks/bugprone/move-forwarding-reference.html

With that in mind the first commit seems fine, the second one e922bb8 is a bit over my head due to the changes in src/mpgen.cpp which I'm not familiar with.

I don't have an informed opinion on the last commit a5bff37 that suppresses bitcoin-nontrivial-threadlocal, but it has a comment with rationale.

@ryanofsky
Copy link
Collaborator Author

Thanks @Sjors. Now that this git repo is a subtree and a bitcoin-core project, I would like to (1) stop my previous practice of merging PRs like this that do not have any ACKs and (2) try to encourage more review to happen earlier in the upstream PR's in this repository rather than later in the downstream PRs in the bitcoin repository.

So would you feel comfortable reviewing more and maybe giving an ACK along the lines of...

  • Testing this PR and confirming it fixes the warnings
  • Looking at the code and confirming it only disables one bitcoin-tidy warning and resolves several bugprone-move-forwarding-reference warnings by changing std::move to std::forward in some places and changing lvalue references to rvalue references other places

... or however you might actually review it? I am happy to answer any questions that could help clarify things. The second commit should be the only complicated one here, and I can break it down. It is:

  • Adding std::forward and std::move several places to preserve lvalue/rvalue status.
  • Defining a FunctionTraits::Fwd<...>(...) helper which does same thing as std::forward except it takes parameter number instead of a parameter type so generated code doesn't need as verbose as std::forward calls would be.
  • Changing C++ code generator to pass arguments as M0::Fwd<1>(arg1) instead of arg1 in generated code. You could verify this by diffing a generated file like build/src/ipc/capnp/mining.capnp.proxy-client.c++ in the bitcoin build before and after this change.

Maybe I should add some of these explanations in code comments or commit messages too. Would welcome any feedback.

@Sjors
Copy link
Member

Sjors commented May 16, 2025

Maybe I should add some of these explanations in code comments

This might help more people understand the code.

@ryanofsky
Copy link
Collaborator Author

re: #172 (comment)

This might help more people understand the code.

Thanks, added new information above to code comments and commit messages.

Updated a5bff37 -> 57a65b8 (pr/tidy.3 -> pr/tidy.4, compare) updating these comments

@Sjors
Copy link
Member

Sjors commented May 23, 2025

ACK 57a65b8

I studied the second commit more, along with some of mp/gen.cpp, thanks for the extra comments.

I wrote:

I don't have an informed opinion on the last commit a5bff37 that suppresses bitcoin-nontrivial-threadlocal, but it has a comment with rationale.

The original nontrivial-threadlocal check was added in bitcoin/bitcoin#30146 and reviewed by @maflcko and @hebasto, both already on this thread.

@Sjors
Copy link
Member

Sjors commented May 28, 2025

cc @TheCharlatan

Comment on lines +638 to +640
// Silence nonstandard bitcoin tidy error "Variable with non-trivial destructor
// cannot be thread_local" which should not be a problem on modern platforms, and
// could lead to a small memory leak at worst on older ones.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mmh, the original PR that triggered the introduction of this check (bitcoin/bitcoin#30095), links to a freebsd bug that seems to have been fixed by now.
@vasild is this check even still relevant at all?

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that we try to avoid it purely out of style/preference: bitcoin/bitcoin#30095 (comment). But no strong opinion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've read through the history again of it again and came to the same conclusion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

re: #172 (comment)

My understanding is that we try to avoid it purely out of style/preference: bitcoin/bitcoin#30095 (comment). But no strong opinion.

Agree in general it's good to avoid thread_local destructors. Code will be more clear and explicit if it joins threads and deletes any state associated with them after they are joined, instead adding isolated thread_local variables that get initialized and cleaned up less deterministically.

But in this case, the libmultiprocess library needs to track state associated with threads that it doesn't have any control of.

In the bitcoin codebase, node, wallet, gui, and RPC threads are all able to make IPC calls, and the IPC implementation needs to track information about threads that make IPC calls so it can guarantee calls from the same local thread always run on the same remote thread, and guarantee that if the remote thread makes a callback it will run on the same local thread (otherwise things like recursive locks will not work). The IPC implementation also should be able to free state associated with threads when they are destroyed, so it uses a thread_local struct to do all these things.

I think keeping the bitcoin-tidy check but allowing use of a thread_local destructor here to detect when unmanaged external threads are destroyed is a reasonable fix. Other possible fixes not relying on thread_local might be possible too, but would involve other tradeoffs: they might rely on OS specific calls instead, or change the memory model to delete state when connections are closed not when threads are destroyed, or change the threading model to not always run calls from same IPC client thread on same IPC server thread. These fixes would add complexity other places, so I don't think would be preferable.

Copy link
Member

@Sjors Sjors May 30, 2025

Choose a reason for hiding this comment

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

guarantee calls from the same local thread always run on the same remote thread, and guarantee that if the remote thread makes a callback it will run on the same local thread

I assume you mean that local threads correspond one to one with remote threads, perhaps even sharing the same name, but they're not actually the same thread given that they're in a different process?

Copy link
Member

@Sjors Sjors May 30, 2025

Choose a reason for hiding this comment

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

I'm curious to learn how non-multiprocess clients like #174 experience this one-to-one mapping.

Even in my own Sv2 sidecar application I haven't really paid attention to this. I guess it just works(tm).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, my understanding is that modern FreeBSD versions should be fine with this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

re: #172 (comment)

I'm curious to learn how non-multiprocess clients like #174 experience this one-to-one mapping.

When you are using the libmultiprocess library as the IPC client, it forces you into the 1:1 threading model. The first time you make an IPC call from a local thread, the library will automatically make a remote ThreadMap.makeThread call to execute the current IPC request and any future requests on.

But if you are using the Cap'n Proto interface directly from rust or c++ without libmultiprocess, you don't need to have a 1:1 threading model. You can create remote threads freely and make any IPC method call which takes a Context argument run on whichever thread you specify in the Context.thread field.

re: #172 (comment)

I assume you mean that local threads correspond one to one with remote threads, perhaps even sharing the same name, but they're not actually the same thread given that they're in a different process?

Yes that's right. The sentence is also trying to say if a local thread makes an IPC call, and during that IPC call the remote thread needs to call back into the local process, the callback will be executed on literally the same thread that made the first IPC call and is currently waiting for the response.

It's important for this to happen, for example, if the thread making the first IPC call locks a recursive mutex before the call. A callback from the server to the client will need to run on literally the same client thread to avoid deadlocking if it locks the mutex recursively.

@TheCharlatan
Copy link
Collaborator

ACK 57a65b8

Copy link
Collaborator Author

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Thanks for the reviews! Will try to merge this and update the subtree

Comment on lines +638 to +640
// Silence nonstandard bitcoin tidy error "Variable with non-trivial destructor
// cannot be thread_local" which should not be a problem on modern platforms, and
// could lead to a small memory leak at worst on older ones.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

re: #172 (comment)

My understanding is that we try to avoid it purely out of style/preference: bitcoin/bitcoin#30095 (comment). But no strong opinion.

Agree in general it's good to avoid thread_local destructors. Code will be more clear and explicit if it joins threads and deletes any state associated with them after they are joined, instead adding isolated thread_local variables that get initialized and cleaned up less deterministically.

But in this case, the libmultiprocess library needs to track state associated with threads that it doesn't have any control of.

In the bitcoin codebase, node, wallet, gui, and RPC threads are all able to make IPC calls, and the IPC implementation needs to track information about threads that make IPC calls so it can guarantee calls from the same local thread always run on the same remote thread, and guarantee that if the remote thread makes a callback it will run on the same local thread (otherwise things like recursive locks will not work). The IPC implementation also should be able to free state associated with threads when they are destroyed, so it uses a thread_local struct to do all these things.

I think keeping the bitcoin-tidy check but allowing use of a thread_local destructor here to detect when unmanaged external threads are destroyed is a reasonable fix. Other possible fixes not relying on thread_local might be possible too, but would involve other tradeoffs: they might rely on OS specific calls instead, or change the memory model to delete state when connections are closed not when threads are destroyed, or change the threading model to not always run calls from same IPC client thread on same IPC server thread. These fixes would add complexity other places, so I don't think would be preferable.

@ryanofsky ryanofsky merged commit 27c7e8e into bitcoin-core:master May 29, 2025
ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request May 29, 2025
…e5a581

27c7e8e5a581 Merge bitcoin-core/libmultiprocess#172: refactor: fix warnings from clang-tidy-20 and bitcoin-tidy
2fe87d016be4 Merge bitcoin-core/libmultiprocess#173: doc: Fix error string typo
57a65b854664 clang-tidy: Suppress bitcoin-nontrivial-threadlocal error
0d8012f656fe Merge bitcoin-core/libmultiprocess#165: clang-tidy: fix warnings introduced in version 19
3a96cdc18f2d clang-tidy: Fix bugprone-move-forwarding-reference error
c1e8c1a02864 clang-tidy: Fix bugprone-move-forwarding-reference errors
aa19285303ff use ranges transform
a78137ca73b8 make member function const
ca3226ec8ab7 replace custom tuple unpacking code with `std::apply`
949fe85fc91f replace SFINAE trick with `if constexpr`
44ee4b40b89a doc: Fix error string typo

git-subtree-dir: src/ipc/libmultiprocess
git-subtree-split: 27c7e8e5a581b3c41330e758951251ef11807b11
fanquake added a commit to bitcoin/bitcoin that referenced this pull request May 30, 2025
154af1e Squashed 'src/ipc/libmultiprocess/' changes from 35944ffd23fa..27c7e8e5a581 (Ryan Ofsky)

Pull request description:

  Includes:

  - bitcoin-core/libmultiprocess#165
  - bitcoin-core/libmultiprocess#173
  - bitcoin-core/libmultiprocess#172

  These changes are needed to fix CI errors in #31802.

  The changes can be verified by running `test/lint/git-subtree-check.sh src/ipc/libmultiprocess` as described in [developer notes](https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#subtrees) and [lint instructions](https://github.com/bitcoin/bitcoin/tree/master/test/lint#git-subtree-checksh)

ACKs for top commit:
  Sjors:
    ACK 9f65654
  TheCharlatan:
    ACK 9f65654

Tree-SHA512: 745789a6fba552aa066f9f69592b16a5277999dbf0413eaed7fe25cd440b78af57615edfce781592873659fda91463bef7c5dac202b80362bd86f6a90ab20d69
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 3, 2025
Fix std::function CustomBuildField overload which is incompatible with a recent
change in 3a96cdc from
bitcoin-core#172 which changed
generated IPC client code to pass it an rvalue std::function reference instead
of an lvalue reference.

There was no test coverage for the type-function.h header earlier but the next
commit adds a test which would have caught the problem in the CustomBuildField
declaration.

Motivation for this change is to avoid a build error in
bitcoin/bitcoin#29409 when rebased on top of
bitcoin/bitcoin#32641 which includes
bitcoin-core#172
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 5, 2025
Fix std::function CustomBuildField overload which is incompatible with a recent
change in 3a96cdc from
bitcoin-core#172 which changed
generated IPC client code to pass it an rvalue std::function reference instead
of an lvalue reference.

There was no test coverage for the type-function.h header earlier but the next
commit adds a test which would have caught the problem in the CustomBuildField
declaration.

Motivation for this change is to avoid a build error in
bitcoin/bitcoin#29409 when rebased on top of
bitcoin/bitcoin#32641 which includes
bitcoin-core#172
ryanofsky added a commit that referenced this pull request Jun 11, 2025
688140b test: Add coverage for type-function.h (Ryan Ofsky)
8b96229 type-function.h: Fix CustomBuildField overload (Ryan Ofsky)

Pull request description:

  Fix `std::function` `CustomBuildField` overload which is incompatible with a recent change in 3a96cdc from #172 which changed generated IPC client code to pass it an rvalue `std::function` reference instead of an lvalue reference.

  Motivation for this change is to avoid a build error in bitcoin/bitcoin#29409, when rebased on top of bitcoin/bitcoin#32641 which includes #172.

ACKs for top commit:
  TheCharlatan:
    ACK 688140b

Tree-SHA512: aeb5cbea0b4aaf7f84c4c30255546f367c4c6ca154c831f648f6ba03599b23beb7032477599671c10da790ff2ff592ae8ce3098bee294b431cfca82b1530f42d
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.

7 participants