Skip to content

Conversation

buck54321
Copy link
Member

Ethereum has very short block times. And just for conservation of
resources, tightly-grouped blocks need not trigger tightly-grouped
conf and penalty checks. This adds a minimum delay window to block
notifications. If blocks comes farther than the minimum delay apart,
nothing changes from the current behavior. If 1 or more blocks
arrives within the delay window of another, they will be collected into a
single block notification that occurs once the min delay has passed.

The newly implemented SignalMeterer is only used in the swapper for
block notifications for now, but might also be useful for the client.

@buck54321 buck54321 changed the title server/swap: metered block-triggered swap confirmation tests server/swap: metered block-triggered conf and penalty checks May 26, 2022
@chappjc
Copy link
Member

chappjc commented May 26, 2022

Cool. I guess I should get #1563 out of draft. Lock contention can be just as bad as blocking the ws inHandler or backing up conf checks depending on where it is blocking.

Copy link
Contributor

@LasTshaMAN LasTshaMAN left a comment

Choose a reason for hiding this comment

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

Minor things

Comment on lines 67 to 71
if s.scheduled != nil {
if !s.scheduled.Stop() {
<-s.scheduled.C
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe also need to s.scheduled = nil inside of this if (assuming Stop method is only meant to cancels any scheduled signals and not completely prevent further usage of SignalMeterer),

as the following condition will be hit, it seems:

image

Copy link
Member Author

@buck54321 buck54321 May 31, 2022

Choose a reason for hiding this comment

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

and not completely prevent further usage of SignalMeterer

I always just assumed this was a one-Stop type.

Copy link
Contributor

@LasTshaMAN LasTshaMAN May 31, 2022

Choose a reason for hiding this comment

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

yeah, that would be the default assumption for me as well,

just thought comment could be more precise (maybe this code can be re-used in other places as well in the future)

Stop further signalling and cancel any scheduled signals.

@@ -34,6 +35,7 @@ var (
// txWaitExpiration is the longest the Swapper will wait for a coin waiter.
// This could be thought of as the maximum allowable backend latency.
txWaitExpiration = 2 * time.Minute
minBlockPeriod = time.Second * 10
Copy link
Contributor

Choose a reason for hiding this comment

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

might be useful to describe how/why this value was chosen

Copy link
Member Author

Choose a reason for hiding this comment

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

I really pulled it out of thin air. The value is open for debate. Anybody want a different value?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe just some explanation regarding why this delay is needed in the first place would be nice to have (idk, one can search through git history and find this PR with the answer as well),

can suggest something like:

// minBlockPeriod helps with limiting notification bursts when blocks are generated too fast (e.g. in Ethereum occasionally several blocks can are generated in a matter of several seconds).

Copy link
Member

Choose a reason for hiding this comment

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

Agree a sentence would be good here.

As far as the choice of 10s goes, I suppose that's short enough that it would not be an annoying delay, but also long enough that checks shouldn't pile up too much. (it works for me)

// the previous signal, that signal will be scheduled for later to respect the
// configured delay. If multiple signals arrive within minDelay, they will be
// grouped into a single delayed signal. Error signals are never delayed.
type SignalMeterer struct {
Copy link
Member

Choose a reason for hiding this comment

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

Because you have NewSignalMeterer this does not need to be exported maybe?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so. It's used as a concrete type by an importing package. I think it's customary to export it.

Copy link
Member

@chappjc chappjc May 31, 2022

Choose a reason for hiding this comment

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

Yes agree with @buck54321 . Some linters will even nag if an exported constructor returns and unexported type because it can be "annoying to use" or something to that extent.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. Understood.

@chappjc chappjc self-requested a review June 7, 2022 16:35
Copy link
Member

@chappjc chappjc left a comment

Choose a reason for hiding this comment

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

Cool little utility. Overall seems like good logic and it works.

I don't totally love the goroutine use as a means to get unlimited buffering courtesy of the runtime, and I have some ideas there, but nothing that really has to change.

defer s.mtx.Unlock()
// Errors are passed immediately.
if err != nil {
go s.recv(err)
Copy link
Member

Choose a reason for hiding this comment

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

I think you might as well replace recv func(error) with recv chan error, and doing s.recv <- err instead of go s.recv(err).

The goroutine allocations and runtime overhead seem avoidable, and it feels tailored for a channel anyway. I get the sense you're using goroutines as a way to use the runtime for effectively unlimited buffering, which could backfire in addition to being a bit inefficient.

Plus in Swapper, I think a channel is more natural since you've got a select loop going already. illustration: 124ec64

And as usual lately I'm looking at patterns that eliminate locks and fields with event loops and closure, so I'm toying with this alternate mechanism: 817b800
Just toying around, don't feel like you need to take either suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

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

817b800 is beautiful 😍.

Comment on lines 35 to 36
s.mtx.Lock()
defer s.mtx.Unlock()
// Errors are passed immediately.
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Can move the locking until after this first early return

Comment on lines +793 to +802
// TODO: This pattern would still send the block trigger half of the
// time if the ctxMaster is canceled.
Copy link
Member

Choose a reason for hiding this comment

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

Good catch. That's annoying. Seems harmless, just not really intended.

@@ -34,6 +35,7 @@ var (
// txWaitExpiration is the longest the Swapper will wait for a coin waiter.
// This could be thought of as the maximum allowable backend latency.
txWaitExpiration = 2 * time.Minute
minBlockPeriod = time.Second * 10
Copy link
Member

Choose a reason for hiding this comment

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

Agree a sentence would be good here.

As far as the choice of 10s goes, I suppose that's short enough that it would not be an annoying delay, but also long enough that checks shouldn't pile up too much. (it works for me)

@chappjc chappjc added this to the 0.5 milestone Jun 9, 2022
Ethereum has very short block times. And just for conservation of
resources, tightly-grouped blocks need not trigger tightly-grouped
conf and penalty checks. This adds a minimum delay window to block
notifications. If blocks comes farther than the minimum delay apart,
nothing changes from the current behavior. If 1 or more blocks
arrives within the delay window of another, they will be collected into a
single block notification that occurs once the min delay has passed.

The newly implemented DelayedRelay is only used in the swapper for
block notifications for now, but might also be useful for the client.

Co-authored-by: Jonathan Chappelow <chappjc@gmail.com>
@chappjc chappjc merged commit c2b380d into decred:master Jun 16, 2022
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.

4 participants