-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Report progress during pin add
#3671
Conversation
@whyrusleeping let me know if this is along the lines of what you are thinking. Also, how would we test this output, you said to output something every x time. But that will be hard to test as the amount of time an operation takes is anything but deterministic. |
Looking pretty good so far 👍 A couple important things to keep in mind:
|
I would think changing the type of the output based on flag would be a bad idea.
Okay. To avoid performance problems was trying to avoid updating the number after each call to visit. However, if don't think that will be a problem (I am mainly concerned about the locking overhead) I can update the number one node at a time. I will add a second number for links so far, but nor sure how useful it will be. |
The type remains the same, it just enables a streaming mode for progress output.
The cost of fetching and unmarshaling each node is going to vastly exceed the tiny cost of the mutex here (its not even a R/W Lock). If it ends up being an issue we could even switch to atomic integer operations |
The type of the object return will change from a
Okay. |
Yeah, changing between a chan and the concrete type there is fine. The type field in the command object will still stay the same |
@whyrusleeping okay, I increased the granularity and avoided using a channel when One question, should we continue to use the Marshaler to report progress or switch to PostRun? |
@kevina Does it work correctly implemented in the marshaler? |
@whyrusleeping I'm sorry I couldn't parse that sentence. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test for the progress tracker in merkledag_test.go with a few different structures. One running on a single node with no children, one running on a fairly large graph, and maybe one running on a linked list of nodes.
Also change the timer to something more like 500ms
I was planning to, but got stuck on the best way to create some random structures. Any hints on how to do thie?
How about one second? The current time was only for manually testing. |
@kevina something like this should work: https://ipfs.io/ipfs/QmRsQHn9yV1VgGU3fEkBtpnwjVgKkrRXFKGS2cRoJMyG4m |
Okay thanks that helped. I will try to write the tests, and finish this P.R, later today or tomorrow. |
3f02784
to
995c4ec
Compare
pin add
pin add
Okay, assuming tests pass I consider this done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments, mostly looks good. This will be great to have
core/commands/pin.go
Outdated
|
||
r, ok := res.Output().(*AddPinOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can use a type switch here:
switch out := res.Output().(type) {
case *AddPinOutput:
case <chan interface{}:
default:
}
core/commands/pin.go
Outdated
return | ||
} | ||
|
||
res.SetOutput(&PinOutput{added}) | ||
v := new(dag.ProgressTracker) | ||
ctx := context.WithValue(req.Context(), "progress", v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have a helper function on the progress tracker to do this for us nicely:
v := new(ProgressTracker)
ctx := v.DeriveContext(req.Context())
core/commands/pin.go
Outdated
if r.Progress == -1 || r.Pins != nil { | ||
added = r.Pins | ||
} else { | ||
if progressLine { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the if progressLine
stuff for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use \r
to replace write over the current line with a new progress line, but I only want to do that if the last line to stderr was a progress line
merkledag/merkledag.go
Outdated
@@ -377,18 +377,39 @@ func EnumerateChildren(ctx context.Context, ds LinkService, root *cid.Cid, visit | |||
} else if err != nil { | |||
return err | |||
} | |||
v, _ := ctx.Value("progress").(*ProgressTracker) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the context does not have a progress tracker in it, I think this may panic (type asserting a nil value?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@whyrusleeping I think it is okay: https://play.golang.org/p/xlvE92J8pj
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, cool. Makes sense now that i think about it
core/commands/pin.go
Outdated
go func() { | ||
defer close(out) | ||
for { | ||
timer := time.NewTimer(500 * time.Millisecond) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not using a time.Ticker? Also, you are recreating timers and only the last one will stop so you are accumulating resources without free-ing them... you could use Timer.Reset() at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timers should be GCed unless I am missing something. Timer.Stop() is always called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hsanjuan Good catch.
We should be making this timer outside of the for loop.
The way things are, we are making a new timer every loop, and they all tick every 500ms, and then they don't get closed until the goroutine returns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timers should be GCed
You're right, it's actually tickers not timers, but it's still generating garbage which needs to be GCed when it could easily not do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly don't think it makes any difference. Nevertheless I just pushed a commit to move the creation of the timer to outside the loop at the expense of slightly more code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes a huge difference.
Every call to NewTimer
creates an entry in the runtimes timer heap: https://golang.org/src/time/sleep.go?s=2220:2252#L84
https://golang.org/src/runtime/time.go#L94
They build up and generate events until you close them. Do not make this mistake, it causes weird side effects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dear @whyrusleeping,
I did make a mistake, but the mistake I think you were pointing out:
They build up and generate events until you close them. Do not make this mistake, it causes weird side effects
And calling stopTimer which I thought I did should removes them from the heap, https://golang.org/src/runtime/time.go#L71.
This mistake I made was assuming that the defer in a loop would fire before the next iteration. This is how a similar construct would of worked in C++. It seams I was wrong and go takes a more simple minded approach. The defer does not fire until the end of the function as demonstrated in this code (https://play.golang.org/p/zKTz1Kc0K_)
func main() {
for i := 0; i < 10; i++ {
defer fmt.Println("ByBy")
println("Hello")
}
}
I personally think go is wrong here, but that is not something I wish to have an extended discussion on. I will just know to keep in mind how defer works in go and try not to make similar assumptions based on how C++ works, as go is obviously not C++.
core/commands/pin.go
Outdated
res.SetOutput((<-chan interface{})(out)) | ||
go func() { | ||
duration := 500 * time.Millisecond | ||
timer := time.NewTimer(duration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably makes sense to use time.Ticker
here so you don't have to reset it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@whyrusleeping Okay done. Please double check that the ticker will be correctly garbage collected.
d045e89
to
582dcb6
Compare
This LGTM, Just waiting on a 👍 from @Kubuxu now |
I am afraid this will break API implementations, someone might not be expecting that he will get objects with Progress instead of Pins field set. This is why it is important to spec those API as if we stated that you should filter the NDJSON stream for value you need it would be OK, but right now, anything can happen. I will do proper CR tomorrow. |
Also I am not happy with testing of this code, I know it can be hard but it should be possible. |
Is that also showing coverage from the sharness tests? I added some test which should at least cover most of the new code and some of that might not have been covered before. |
Ahh, it wasn't rebased to master. Master has sharness test coverage collection. Could you rebase it? |
582dcb6
to
e530d19
Compare
Done. |
If I did things correctly. That will only happen if |
@Kubuxu the coverage tests are green now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs rebase but LGTM, sorry for being late.
Actually once it rebased the progress indicator will no longer work due to the fact that FetchGraph now calls EnumerateChildrenAsync. I will fix that by moving the progress reporting into the Visit callback and also make sure to always show the progress indicator to make it easier to test for. Expect something in the next few hours, end of day at the latest. |
e530d19
to
e3fb71e
Compare
Okay I rebased and made some changes. You might want to look at the last two commits. Note the earlier commits are technically non-functional due to the change of FetchGraph now calling EnumerateChildrenASync instead of EnumerateChildren. I can rebase/squash my commits if you think it will be best. |
Yeah, didn't notice it at first. So LGTM |
License: MIT Signed-off-by: Kevin Atkinson <[email protected]>
e3fb71e
to
cdd29c2
Compare
Okay. Just squashed and rebased. |
Closes #3624
License: MIT
Signed-off-by: Kevin Atkinson [email protected]