Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

[WIP] Parallelised gps.WriteDepTree #903

Closed
wants to merge 1 commit into from

Conversation

aneshas
Copy link

@aneshas aneshas commented Jul 26, 2017

Fixes #895

gps.WriteDepTree now writes vendor/ tree concurrently with
configurable number of worker routines.

Added BenchmarkCreateVendorTreeParallel benchmark which
runs 1/3 faster than when running sequentially.

Your feedback is welcome.
I added NumDepTreeWorkers as a global at this point will change it based on the feedback.

Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

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

good start 😄

@@ -10,6 +10,10 @@ import (
"path/filepath"
)

// NumDepTreeWorkers determines the number of
// WriteDepTree worker goroutines
var NumDepTreeWorkers = 2
Copy link
Member

Choose a reason for hiding this comment

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

Let's add this as a parameter to WriteDepTree(). We've avoided package global state in gps so far, and want to continue that trend 😄

Copy link
Author

Choose a reason for hiding this comment

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

np, I was unsure if you wanted to change the func signature :D

Copy link
Member

Choose a reason for hiding this comment

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

yep! we haven't stabilized the API yet, so there's no obligation to keep it the same.

worksz := len(projects)

workChan := make(chan LockedProject, worksz)
doneChan := make(chan error, worksz)
Copy link
Member

Choose a reason for hiding this comment

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

let's create some local tuple type here for this message - something like

type done struct {
	root ProjectRoot
	err error
}

so that we don't have to shoehorn all that information into the error, and we can pass back up the lower-level errors unmodified.

Copy link
Author

Choose a reason for hiding this comment

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

ok, no problem, I didn't want to add too much complexity, didn't know to which extent you want this to be modified. I get your point, will do :)

Copy link
Member

Choose a reason for hiding this comment

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

for sure, that's how we figure these things out 😄

projects := l.Projects()
worksz := len(projects)

workChan := make(chan LockedProject, worksz)
Copy link
Member

Choose a reason for hiding this comment

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

so, my default position is that there are only two correct values for channel sizes: 0, and 1. while we could do it this way, it's creating problems elsewhere (like early termination, noted below). i think a refactored implementation that uses unbuffered channels would give us tighter control over such things.

close(workChan)

for i := 0; i < worksz; i++ {
err := <-doneChan
Copy link
Member

Choose a reason for hiding this comment

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

as-written, this could interleave poorly:

  1. one goroutine sends experiences and sends an error while other(s) still writing
  2. control receives the error, and begins os.RemoveAll()
  3. other goroutine(s) are trying to write to dirs that are disappearing from underneath them

additionally, it's also crucial that we guarantee the worker goroutines have terminated by the time the WriteDepTree() func returns.

switching to an unbuffered channel approach will, i think, make it easier to do the necessary flow control here.

Copy link
Author

Choose a reason for hiding this comment

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

so, what you are saying is basically this:

we should always wait for all worker routines to finish
if some routine reports an error, note the error and wait for others to finish
return a fmt.Errorf filled with info of all errs (gathered from the tuples received - if any)

Copy link
Author

Choose a reason for hiding this comment

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

I will refactor and make another pr tomorrow and we can discuss further :)

@aneshas aneshas changed the title Parallelised gps.WriteDepTree [WIP] Parallelised gps.WriteDepTree Jul 27, 2017
@aneshas aneshas force-pushed the parallelize-write-dep-tree branch from bc9e9d3 to d010acc Compare July 27, 2017 09:18
@aneshas
Copy link
Author

aneshas commented Jul 27, 2017

@sdboyer While doing the refactor I kinda got the idea that WriteDepTree itself was
getting to clunky and it will get eve larger so I wanted to brake it down to child functions but as I went down even the signature is kinda strange it already has 4 parameters adding workerCount pumps
it up to 5 ...

If you can see what I wanted to do with depTreeJob it kinda ocuured to me that instead of receiving
5 arguments WriteDepTree could receive the depTreeJob itself (whatever the name will be) as config ??

Or should the WriteDepTree be a method on let's say DepTree (which would be instantiated via constructor) .. ?
eg.

tree := gps.NewDepTree(gps.DepTreeConfig{
    Basedir: "...",
    Lock: ...,
    SourceManager: ...,
})

err := tree.Write(true, 2) // stripVendor, workerCount
...

Or do you just want to leave it as it is, but it would be ugly?

I hope you can see what I'm getting at

@sdboyer
Copy link
Member

sdboyer commented Jul 27, 2017

yes, i see the general idea of what you're going for.

as long as we're talking about the signature design, it's worth noting that we'll also need to consider that the sv param will eventually become a bitmask, and that we'll need to add a context.Context param for cancellation control.

we have a few options, not all mutually exclusive:

  1. "hide" one parameter by making this a method, presumably of SourceManager
  2. condense basedir, parallelism and stripping controls into a config struct
  3. don't allow external control over parallelism - this func is basically a convenience wrapper, relying on no particularly magical internal logic (except the stripVendor func, but whatever), so dependers are freely able to reimplement it
  4. just eat the larger number of params

i'm actually inclined to stick with 3+4, because i don't see us adding much more in the way of params to this in the future, and we have to balance the convenience of this one method with cluttering the overall gps namespace with helpers.

@aneshas
Copy link
Author

aneshas commented Jul 27, 2017

I see.
So let's say we stick with 3+4. By saying "don't allow external control over parallelism" what do you suggest we do with the workerCount? Make it fixed or.. ?
And even if it is just a convenience it would still probably be good to break it down to a few smaller funcs, or you suggest that I don't do that also?

@sdboyer
Copy link
Member

sdboyer commented Jul 27, 2017

(sorry, I realize this is pulling a 180 on the concurrency thing)

probably start with just plain fixed, and then maybe we make some adjustments based on GOMAXPROCS.

if you think that splitting it up into some smaller funcs will help, then sure, go for it. as long as they're not exported, there's no general rules design rules in place - we can discuss the design as you go.

@aneshas aneshas force-pushed the parallelize-write-dep-tree branch 2 times, most recently from 87bc95f to 74dedc7 Compare July 27, 2017 15:52
@aneshas
Copy link
Author

aneshas commented Jul 28, 2017

@sdboyer when you have time could you look at the latest update

Defered workChan close

Removed commented out code

Decoupled concurrency code from the WriteDepTree func to workerPool
All errors are now being reported and WriteDepTree decides how to act
(currently calls removeAll)

Broken down WriteDepTree to child functions in order
to receive feedback if on the right track

Fixed accidental err race condition

gps.WriteDepTree now writes vendor/ tree in parallel with
configurable number of worker routines
@aneshas aneshas force-pushed the parallelize-write-dep-tree branch from 74dedc7 to d8fd115 Compare July 28, 2017 15:02
Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

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

sorry for delayed response! looks like we're heading in a better direction, but we're still missing semantics for aborting early in the event of an error on any individual attempt.

note that whatever approach you take should ideally also be easy to extend with a context.Context() for explicit cancellation semantics, later.

@sdboyer
Copy link
Member

sdboyer commented Aug 28, 2017

sorry, this ended up being finished up in another PR 😢

@sdboyer sdboyer closed this Aug 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants