Skip to content

Conversation

TheBlueMatt
Copy link
Contributor

Based on #9243 and the top commit off of #9375 ("Make CBlockIndex*es in net_processing const"), this no longer requires cs_main to call State() and removes a few cs_main's around Misbehaving() for an easy win.

@TheBlueMatt TheBlueMatt force-pushed the 2016-12-nodestate branch 4 times, most recently from 2b53e69 to fcc8c2c Compare December 24, 2016 19:38
const vector<string>& categories = mapMultiArgs["-debug"];
ptrCategory.reset(new set<string>(categories.begin(), categories.end()));
// thread_specific_ptr automatically deletes the set when the thread ends.
if (mapMultiArgs.count("-debug")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not IsMultiArgSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Prefer less diff? (note this is really from #9243, not strictly this PR).

@@ -412,6 +428,13 @@ bool SoftSetBoolArg(const std::string& strArg, bool fValue)
return SoftSetArg(strArg, std::string("0"));
}

void ForceSetArg(const std::string& strArg, const std::string& strValue)
{
mapArgs[strArg] = strValue;
Copy link
Contributor

@dcousens dcousens Dec 27, 2016

Choose a reason for hiding this comment

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

Why is this not LOCK(cs_args)?
(maybe I misinterpreted the meaning of force)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in #9243

cs_vSend is used for two purposes - to lock the datastructures used
to queue messages to place on the wire and to only call
SendMessages once at a time per-node. I believe SendMessages used
to access some of the vSendMsg stuff, but it doesn't anymore, so
these locks do not need to be on the same mutex, and also make
deadlocking much more likely.
@TheBlueMatt
Copy link
Contributor Author

Rebased after #9243 merge.

@sipa
Copy link
Member

sipa commented Dec 27, 2016

Wouldn't it be easier to use a shared_ptr wrapping for cleanup-after-last-use, instead of implementing refcounting yourself? You can still use a manual wrapper to lock/unlock.

@TheBlueMatt
Copy link
Contributor Author

TheBlueMatt commented Dec 27, 2016 via email

@TheBlueMatt
Copy link
Contributor Author

OK, @sipa found a better way to use shared_ptrs here so I went ahead and did that. It means removing some asserts, but I think its worth it.


void AddStateForNode(NodeId nodeid, const CAddress& addr, const std::string& addrName) {
LOCK(cs_main);
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName)));
Copy link
Member

Choose a reason for hiding this comment

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

The std::move(addrMany) here won't do what you want, as addrName is a const reference. You can't destroy addrName anyway, so just remove the std::move.

return CNodeStateAccessor();
pstate = it->second;
}
return CNodeStateAccessor(pstate);
Copy link
Member

Choose a reason for hiding this comment

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

You should add an std::move here if you want to avoid a copy.

std::shared_ptr<CNodeState> pstate;

CNodeStateAccessor() : pstate() { }
CNodeStateAccessor(std::shared_ptr<CNodeState> pstateIn) : pstate(pstateIn) { ENTER_CRITICAL_SECTION(pstate->cs); }
Copy link
Member

Choose a reason for hiding this comment

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

Add a std::move around pstateIn to avoid a copy.

@TheBlueMatt
Copy link
Contributor Author

Went down the rabbit hole here trying to track down a deadlock and then realized that it was already prevented. Note that (very much on purpose) cs_main is still used prior to all CNodeStateAccessors except for a few where we only call Misbehaving() or otherwise sure we are only locking one CNodeState at a time (to avoid deadlocks where we lock them in different orders).

@TheBlueMatt
Copy link
Contributor Author

Addressed @sipa's comments.


CNodeStateAccessor(CNodeStateAccessor&& o) { pstate = o.pstate; o.pstate = NULL; }

bool operator()() const { return (bool)pstate; }
Copy link
Member

Choose a reason for hiding this comment

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

explicit operator bool instead? Using the call operator for this seems strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dunno I dont really like the bool operator. If you really prefer it I'm happy to change it.

Copy link
Member

Choose a reason for hiding this comment

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

explicit bool matches the semantics of smart pointers, so I think it's a good fit here. But more importantly, the call operator makes it look as though something is happening to the accessor, so I'd really rather not use that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, switched to regular bool.

CNodeState* operator->() { return &(*pstate); }
const CNodeState* operator->() const { return &(*pstate); }
};

class NodeStateStorage {
/** Map maintaining per-node state. Requires cs_main. */
Copy link
Member

Choose a reason for hiding this comment

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

Stale comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

std::shared_ptr<CNodeState> pstate;

CNodeStateAccessor() : pstate() { }
CNodeStateAccessor(std::shared_ptr<CNodeState>&& pstateIn) : pstate(std::move(pstateIn)) { ENTER_CRITICAL_SECTION(pstate->cs); }
Copy link
Member

Choose a reason for hiding this comment

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

Why not just have a lock as a member?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didnt realize we had an RAII lock in sync.h that would still do DEBUG_LOCKORDER checks. Will fix with CMutexLock.

Copy link
Contributor Author

@TheBlueMatt TheBlueMatt Jan 1, 2017

Choose a reason for hiding this comment

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

Hmm, looking again using CMutexLock means adding __FILE__, __LINE__, etc macros in net_processing for parameters to CMutexLock, instead of letting ENTER_CRITICAL_SECTION figure it out. If you prefer I could try to add some crazy defines like CONSTRUCT_CMUTEXLOCK(mutex, lockName) which fills in a constructor for use in the CNodeStateAccessor constructor?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I think you actually want these coming from net_processing anyway, no? I think if you make the ctor inline and pass __LINE__ to CMutexLock, you'd end up seeing the source of the lock. Otherwise, you're always seeing it come from the same place I should think.

Taking that a step further, I think it would work to change CMutexLock's ctors to take default __FILE__ and __LINE__ args?

@theuni
Copy link
Member

theuni commented Dec 29, 2016

Wanted to make sure my nits so far were reasonable, so I went ahead and patched 'em in. Feel free to take from theuni@058193e

This removes reliance on net in net_processing for maintaining the
refcount == 0 invariant when calling FinalizeNode

This also removes a few asserts which used to be checked when there
were no more CNodeStates remaining, which we can no longer check for.

These should be pretty rarely checked anyway, so probably didn't
serve much use.
@TheBlueMatt
Copy link
Contributor Author

Fixed a few of @theuni's comments, rebased diff-tree is at https://0bin.net/paste/nkpqzdxyoAXkoX7B#STuXxIS32GXf3gMP1JbiIPrufL9gq1hcSH+g6yXSKdq

@theuni
Copy link
Member

theuni commented Jan 2, 2017

@TheBlueMatt Maybe I'm missing something big-picture here, but would it not be possible to hold a new global mutex rather than cs_main to serialize CNodeStateAccessor access? Or is there a reason that they must share the same lock?

@sipa
Copy link
Member

sipa commented Jan 2, 2017 via email

@theuni
Copy link
Member

theuni commented Jan 2, 2017

I suppose I'm trying to figure out, for the sake of reviewing this PR, what remains to be done before that can happen. I'm certainly not insinuating that we should hold this up until that point, I'd just like to get an idea of what other issues still need to be solved.

So if, for ex, we naively made CNodeState::cs static and dropped the cs_main guards, what kind of carnage would be expected?

@TheBlueMatt
Copy link
Contributor Author

Aside from the places that do, actually, require cs_main, a global order between various CNodeState::cs locks needs to be defined (or, equivalently, we could say that you may not hold multiple CNodeState::cs locks at once).

@TheBlueMatt
Copy link
Contributor Author

Without #9488 (which I do not think should make 0.14 at this juncture), this should not go in for 0.14.

@TheBlueMatt
Copy link
Contributor Author

Closing for now. Will make access exclusive and work towards a more whole solution for 0.15.

@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants