Skip to content
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

Refactor Backfill #78

Merged
merged 4 commits into from
Dec 1, 2022
Merged

Refactor Backfill #78

merged 4 commits into from
Dec 1, 2022

Conversation

cnkarz
Copy link
Contributor

@cnkarz cnkarz commented Nov 29, 2022

Removes channel closing by sender to allow multithreading

@cnkarz cnkarz requested a review from hugjobk November 29, 2022 12:42
Copy link
Contributor

@hugjobk hugjobk left a comment

Choose a reason for hiding this comment

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

If you dont close the channel, there is no way for other goroutine to know when to stop reading from that channel.

I suggest you create a channel inside the backfill function and return the read-only channel to the caller. This will give backfill function fully control over the channel preventing other goroutines from closing it.

@cnkarz cnkarz requested a review from hugjobk November 29, 2022 17:17
Copy link
Contributor

@hugjobk hugjobk left a comment

Choose a reason for hiding this comment

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

Why dont you make Backfill function non-blocking (start a goroutine inside the function); create the channel inside the function and return it the the caller?

When the caller creates a channel and pass it to a function, it is expected to take ownership over that channel so that the function shouldnt close that channel but leave it for the caller to control the life circle of that channel.

This is how the Backfill function should be used:

c, err := Backfill()
if err != nil {
    return
}
for m := range c {
}

@cnkarz
Copy link
Contributor Author

cnkarz commented Nov 30, 2022

Why dont you make Backfill function non-blocking (start a goroutine inside the function)

That is bad practice. It can cause memory leak.

create the channel inside the function and return it the the caller?

Channel would close when function returns.

When the caller creates a channel and pass it to a function, it is expected to take ownership over that channel so that the function shouldnt close that channel but leave it for the caller to control the life circle of that channel.

Sender function can close to signal there is nothing else to send. Otherwise how could you iterate over a channel? It is receiver function that shouldn't close.

@hugjobk
Copy link
Contributor

hugjobk commented Nov 30, 2022

Why dont you make Backfill function non-blocking (start a goroutine inside the function)

That is bad practice. It can cause memory leak.

create the channel inside the function and return it the the caller?

Channel would close when function returns.

When the caller creates a channel and pass it to a function, it is expected to take ownership over that channel so that the function shouldnt close that channel but leave it for the caller to control the life circle of that channel.

Sender function can close to signal there is nothing else to send. Otherwise how could you iterate over a channel? It is receiver function that shouldn't close.

I dont believe it is a bad practice. This is an example of how a function should be designed in async-await model:

func Backfill() chan<- int {
	c := make(chan int, 1000)
	go func() {
		// Long running process
		for i := 0; i < 1000000; i++ {
			c <- i
		}
		close(c)
	}()
	return c
}

With this design, the caller doesnt need to create a channel nor start a goroutine by itself. It makes an async function act like a sync function from the caller's perspective, which is much simpler to use

@cnkarz cnkarz requested a review from hugjobk November 30, 2022 11:53
@cnkarz
Copy link
Contributor Author

cnkarz commented Nov 30, 2022

I thought you wanted to put the whole function inside a separate goroutine which is bad practice, such as:

func Backfill {
    go func() {
      ...
    }()
}

This design will save the caller 1 line of code. So it is more of a convenience. Anyway, I implemented it for you.

old:

func connectorDoingBackfill() {
	c := make(chan types.Log, 1000)
	go ethereum.Backfill(c,...)
	for m := range c{
           ...
        }
}

new:

func connectorDoingBackfill() {
	c := ethereum.Backfill()
	for m := range c{
          .... 
       }
}

@cnkarz cnkarz force-pushed the feature/backfill-chan-close branch 2 times, most recently from 27cdae7 to f87fa0d Compare November 30, 2022 18:08
@hugjobk
Copy link
Contributor

hugjobk commented Dec 1, 2022

Why dont you just modify Backfill and BackfillFrom instead of adding a new function? It is confusing for users having too many options to choose from. This will make Backfill and BackfillFrom deprecated btw

@cnkarz cnkarz changed the title Remove chan close Refactor Backfill Dec 1, 2022
@cnkarz
Copy link
Contributor Author

cnkarz commented Dec 1, 2022

You can't just modify Backfill and BackfillFrom as they work differently. I created new functions and deprecated these two.


// HistoricalEvents queries past blocks for the events emitted by the given contract addresses.
// These events are provided in a channel and ready to be consumed by the caller.
func HistoricalEvents(ctx context.Context, client *ethclient.Client, addresses []common.Address, fromBlock uint64, toBlock uint64) chan types.Log {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you return a read-only channel instead? Like this:

chan<- types.Log

// These events are provided in a channel and ready to be consumed by the caller.
func HistoricalEvents(ctx context.Context, client *ethclient.Client, addresses []common.Address, fromBlock uint64, toBlock uint64) chan types.Log {
if fromBlock == toBlock {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Return a nil channel can cause unexpexted result for caller who reads from this channel.
You can return a closed channel or return a nil channel with an error instead.

}

if fromBlock >= toBlock {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Return a nil channel can cause unexpexted result for caller who reads from this channel.
You can return a closed channel or return a nil channel with an error instead

toBlock, err := client.BlockNumber(ctx)
if err != nil {
log.Error().Err(err).Msg("failed to get block number")
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Return a nil channel can cause unexpexted result for caller who reads from this channel.
You can return a closed channel or return a nil channel with an error instead

}
return HistoricalEvents(ctx, client, addresses, toBlock-numBlocks, toBlock)
default:
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Return a nil channel can cause unexpexted result for caller who reads from this channel.
You can return a closed channel or return a nil channel with an error instead

// * fromBlock > 0 && numBlocks > 0 => Backfill from fromBlock to fromBlock+numBlocks
// * fromBlock > 0 && numBlocks = 0 => Backfill from fromBlock to current latest block
// * fromBlock = 0 && numBlocks > 0 => Backfill last numBlocks blocks
func HistoricalEventsWithQueryParams(ctx context.Context, client *ethclient.Client, addresses []common.Address, fromBlock uint64, numBlocks uint64) chan types.Log {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you return a read-only channel instead? Like this:

chan<- types.Log

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's send-only but I changed them to receive-only <-chan ..

@cnkarz cnkarz requested a review from hugjobk December 1, 2022 05:37
Copy link
Contributor

@hugjobk hugjobk left a comment

Choose a reason for hiding this comment

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

Approved but here is some things you might want to change:

  • If you already close the channel before returning it, you dont need to return an error anymore since it is fine reading from a closed channel.
  • If you want to return an error, which I think is not necessary, then you can return a nil channel since returning an error already means you should not read from the channel.

So you can just remove the second return, which is the error, for convinient. Remember to log the error if you dont return them

@cnkarz cnkarz merged commit 1727362 into main Dec 1, 2022
@cnkarz cnkarz deleted the feature/backfill-chan-close branch December 2, 2022 04:03
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.

None yet

2 participants