Skip to content

multi: avoid sending spend nftns via goroutines.#2836

Closed
dnldd wants to merge 7 commits intodecred:masterfrom
dnldd:spend_pruner_improvments
Closed

multi: avoid sending spend nftns via goroutines.#2836
dnldd wants to merge 7 commits intodecred:masterfrom
dnldd:spend_pruner_improvments

Conversation

@dnldd
Copy link
Copy Markdown
Member

@dnldd dnldd commented Nov 29, 2021

This updates call sites sending spend journal notifications to the spend journal pruner to avoid sending notifications via goroutines. This should preserve the message ordering and avoid possibly spinning up an unbounded number of goroutines.

The spend pruner has also been refactored to batch spend prunes instead of processing single notifications individually.

@dnldd dnldd force-pushed the spend_pruner_improvments branch from 0785236 to 3cd3187 Compare December 8, 2021 23:12
@dnldd dnldd force-pushed the spend_pruner_improvments branch 3 times, most recently from 5dc3b56 to 962c417 Compare February 1, 2022 10:17
@dnldd dnldd marked this pull request as ready for review February 1, 2022 10:21
Copy link
Copy Markdown
Member

@davecgh davecgh left a comment

Choose a reason for hiding this comment

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

I still have more review to do, but there are already enough issues to address I figured I'd get them posted.

var (
// spendConsumerDepsBucketName is the name of the bucket used in storing
// spend journal consumer dependencies.
spendConsumerDepsBucketName = []byte("spendconsumerdeps")
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.

What about the data that is already there in the old bucket? Perhaps there is an upcoming commit that migrates it and then removes the bucket, but I don't see that being done here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The old bucket remains, this only changed the variable name: spendConsumerDepsBucketName -> spendConsumerDependenciesBucketName.

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.

Right, but that's my point. The actual data in the old bucket is still there and nothing is being done with it.

Copy link
Copy Markdown
Member Author

@dnldd dnldd Mar 22, 2022

Choose a reason for hiding this comment

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

the bucket is still being used: the spend pruner is initialized retrieving all the persisted dependencies from the bucket and the the entries in the bucket are updated periodically based on dependencies that can be pruned (see filterPrunableDependents). See call sites for dbUpdateSpendConsumerDependencies and dbFetchSpendConsumerDependencies.

Update: yes you're right, the data in the bucket prior to the spend heights addition will just be sitting there. Working on it.


// NewSpendConsumer initializes a spend consumer.
func NewSpendConsumer(id string, tipHash *chainhash.Hash, queryer ChainQueryer) *SpendConsumer {
func NewSpendConsumer(id string, tipHash *chainhash.Hash, queryer SpendDependencyQueryer) *SpendConsumer {
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 is a breaking change to the public API and therefore can't be done without a major module bump to blockchain.

I was a little bit unsure about if this case could sneak through since the new interface is a strict subset of previous interface and thus anything that satisfied the old one will still satisfy the new one, however, after discussing it in the dev channel, it is indeed a breaking change regardless.

As @jrick noted, a caller might be creating a closure that would break.

Translating it to a concrete example for this change, imagine if a caller had the following code:

var x func(string, *chainhash.Hash, indexers.ChainQueryer) = indexers.NewSpendConsumer

This change would cause the caller's code to fail to compile, which is expressly forbidden without a major module bump.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Noted, will wait for #2903 to be merged and rebase.

Comment thread blockchain/internal/spendpruner/db.go Outdated
Comment thread blockchain/internal/spendpruner/db_test.go Outdated
Comment thread blockchain/internal/spendpruner/db.go Outdated

// Persist all spend height map entries.
for blockHash, height := range spendHeights {
var b [8]byte
Copy link
Copy Markdown
Member

@davecgh davecgh Mar 17, 2022

Choose a reason for hiding this comment

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

Any reason for using 8 when you're encoding a uint32 which is 4 bytes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nope, should be 4 bytes there. Updating.

}

// Persist all spend height map entries.
for blockHash, height := range spendHeights {
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.

All of the code in this commit is chalk full of incorrect usage. The range statement overwrites the variables with each iteration and you're slicing them directly and passing them on to db code, so with any type of async behavior, it will be a race and overwrite things incorrectly.

package main

import (
	"encoding/binary"
	"fmt"
	"time"
)

func put(k []byte, v []byte) {
	go func(k, v []byte) {
		time.Sleep(time.Second)
		fmt.Printf("k: %x, v: %x\n", k, v)
	}(k, v)
}

func main() {
	foo := map[[32]byte]uint32{
		[32]byte{0x00}: 0,
		[32]byte{0x01}: 1,
		[32]byte{0x02}: 2,
	}

	for hash, height := range foo {
		var b [4]byte
		binary.LittleEndian.PutUint32(b[:], height)
		put(hash[:], b[:])
	}

	time.Sleep(time.Second * 2)
}

Output:

k: 0200000000000000000000000000000000000000000000000000000000000000, v: 00000000
k: 0200000000000000000000000000000000000000000000000000000000000000, v: 01000000
k: 0200000000000000000000000000000000000000000000000000000000000000, v: 02000000

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Noted, thanks. Updating.

Comment thread blockchain/internal/spendpruner/db.go Outdated

// Persist all spend height map entries.
for _, blockHash := range keys {
err := heightsBucket.Delete(blockHash[:])
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.

Incorrect as per previous comments.

Comment thread blockchain/internal/spendpruner/db.go Outdated
@dnldd
Copy link
Copy Markdown
Member Author

dnldd commented Mar 17, 2022

Thanks for the review 👍🏾, will start addressing the issues soon.

@dnldd dnldd force-pushed the spend_pruner_improvments branch 3 times, most recently from 684dcb4 to 720304d Compare March 24, 2022 01:10
@davecgh
Copy link
Copy Markdown
Member

davecgh commented Jun 24, 2022

Needs a rebase please.

dnldd added 7 commits June 26, 2022 20:39
This refactors the spend consumer by updating the queryer interface it uses.
The chain queryer interface has been replaced by a lighter one:
SpendDependencyQueryer.
This adds a spend journal height bucket for tracking the heights
of spend journal entries needed by consumers. database related
helpers have also been added.
This revers shorthands of the word dependency in function/mthod names
and comments to avoid confusion.
This adds a spend consumer to track spend dependencies needed for
invalidating and reconsidering blocks.
This updates call sites sending spend journal notifications to the
spend journal pruner to avoid sending notifications via goroutines.
This should preserve the message ordering and avoid possibly spinning
up an unbounded number of goroutines.

The spend pruner has also been refactored to batch spend prunes instead
of processing single notifications individually.
This updates the chainSetup function to return a startup function.
Call sites have been updated accordingly.
This adds generateDependencySpendHeights to generate the associated spend
heights for existing spend dependencies before the introduction of spend
heights. Associated tests have been added.
@dnldd dnldd force-pushed the spend_pruner_improvments branch from 720304d to 66bde17 Compare June 26, 2022 21:18
@davecgh
Copy link
Copy Markdown
Member

davecgh commented Jun 29, 2022

I see this was updated, but it doesn't look like it was fully rebased to play nicely with #2961 and hence the latest master. It's touching a lot of spend pruner code that is no longer in master.

I'll try to review the updates around it in the mean time, but it does need to be rebased to account for the removal of the spend pruner.

@dnldd
Copy link
Copy Markdown
Member Author

dnldd commented Jun 29, 2022

Noted, will update soon.

@dnldd
Copy link
Copy Markdown
Member Author

dnldd commented Jun 29, 2022

After a closer look the changes made here were focused on the spend pruner, I initially thought the startup func additions made to chainSetup could be kept but the only thing that was using it was the spend pruner. Closing this PR as a result.

@dnldd dnldd closed this Jun 29, 2022
@dnldd dnldd deleted the spend_pruner_improvments branch June 30, 2022 00:47
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.

2 participants