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

gps: use golang.org/x/sync/errgroup #1155

Merged
merged 1 commit into from
Sep 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/gps/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func TestMgrMethodsFailWithBadPath(t *testing.T) {
if _, _, err = sm.GetManifestAndLock(bad, nil, naiveAnalyzer{}); err == nil {
t.Error("GetManifestAndLock() did not error on bad input")
}
if err = sm.ExportProject(bad, nil, ""); err == nil {
if err = sm.ExportProject(context.Background(), bad, nil, ""); err == nil {
t.Error("ExportProject() did not error on bad input")
}
}
Expand Down Expand Up @@ -758,7 +758,7 @@ func TestErrAfterRelease(t *testing.T) {
t.Errorf("GetManifestAndLock errored after Release(), but with unexpected error: %T %s", terr, terr.Error())
}

err = sm.ExportProject(id, nil, "")
err = sm.ExportProject(context.Background(), id, nil, "")
if err == nil {
t.Errorf("ExportProject did not error after calling Release()")
} else if terr, ok := err.(smIsReleased); !ok {
Expand Down
41 changes: 14 additions & 27 deletions internal/gps/maybe_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,29 @@ type maybeSource interface {
getURL() string
}

type errorSlice []error

func (errs *errorSlice) Error() string {
var buf bytes.Buffer
for _, err := range *errs {
fmt.Fprintf(&buf, "\n\t%s", err)
}
return buf.String()
}

type maybeSources []maybeSource

func (mbs maybeSources) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
var e sourceFailures
var errs errorSlice
for _, mb := range mbs {
src, state, err := mb.try(ctx, cachedir, c, superv)
if err == nil {
return src, state, nil
}
e = append(e, sourceSetupFailure{
ident: mb.getURL(),
err: err,
})
errs = append(errs, errors.Wrapf(err, "failed to set up %q", mb.getURL()))
Copy link
Collaborator

Choose a reason for hiding this comment

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

errors.WithMessage is cheaper in cases like this too, when we don't need the stack trace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not going to make that optimization; it doesn't matter and will be net-negative the very first time someone looks to find where this error originates.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't the same logic apply to removing sourceFailures?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How? sourceFailures provides nothing at all.

Copy link
Collaborator

@jmank88 jmank88 Sep 11, 2017

Choose a reason for hiding this comment

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

Before this change the final cause in the chain of errors was a sourceFailures with all of its errors preserved, so fiddling with the error at the top level, or with the format verb, could potentially provide some insight.

Now it's an errors.New() with only the string messages of the original errors, so they'll have to come here to edit this method directly to follow the trail any deeper.

An improved version of sourceFailures might implement fmt.Formatter (or be generalized into a multiError{causes, msg}), but I think it's still offering something as-is.

}
return nil, 0, e

return nil, 0, errors.Wrap(&errs, "no valid source could be created")
}

// This really isn't generally intended to be used - the interface is for
Expand All @@ -56,27 +64,6 @@ func (mbs maybeSources) getURL() string {
return strings.Join(strslice, "\n")
}

type sourceSetupFailure struct {
ident string
err error
}

func (e sourceSetupFailure) Error() string {
return fmt.Sprintf("failed to set up %q, error %s", e.ident, e.err.Error())
}

type sourceFailures []sourceSetupFailure

func (sf sourceFailures) Error() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "no valid source could be created:")
for _, e := range sf {
fmt.Fprintf(&buf, "\n\t%s", e.Error())
}

return buf.String()
}

// sourceCachePath returns a url-sanitized source cache dir path.
func sourceCachePath(cacheDir, sourceURL string) string {
return filepath.Join(cacheDir, "sources", sanitizer.Replace(sourceURL))
Expand Down
117 changes: 46 additions & 71 deletions internal/gps/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
package gps

import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"sync"

"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)

// A Solution is returned by a solver run. It is mostly just a Lock, with some
Expand Down Expand Up @@ -62,103 +64,76 @@ func WriteDepTree(basedir string, l Lock, sm SourceManager, sv bool, logger *log
return fmt.Errorf("must provide non-nil Lock to WriteDepTree")
}

err := os.MkdirAll(basedir, 0777)
if err != nil {
if err := os.MkdirAll(basedir, 0777); err != nil {
return err
}

g, ctx := errgroup.WithContext(context.TODO())
lps := l.Projects()

type resp struct {
i int
err error
sem := make(chan struct{}, concurrentWriters)
var cnt struct {
sync.Mutex
i int
}
respCh := make(chan resp, len(lps))
writeCh := make(chan int, len(lps))
cancel := make(chan struct{})

// Queue work.
for i := range lps {
writeCh <- i
}
close(writeCh)
// Launch writers.
writers := concurrentWriters
if len(lps) < writers {
writers = len(lps)
}
var wg sync.WaitGroup
wg.Add(writers)
for i := 0; i < writers; i++ {
go func() {
defer wg.Done()
p := lps[i] // per-iteration copy

for i := range writeCh {
g.Go(func() error {
err := func() error {
select {
case <-cancel:
return
default:
case sem <- struct{}{}:
defer func() { <-sem }()
case <-ctx.Done():
return ctx.Err()
}

p := lps[i]
to := filepath.FromSlash(filepath.Join(basedir, string(p.Ident().ProjectRoot)))
ident := p.Ident()
projectRoot := string(ident.ProjectRoot)
to := filepath.FromSlash(filepath.Join(basedir, projectRoot))

if err := sm.ExportProject(p.Ident(), p.Version(), to); err != nil {
respCh <- resp{i, errors.Wrapf(err, "failed to export %s", p.Ident().ProjectRoot)}
continue
if err := sm.ExportProject(ctx, ident, p.Version(), to); err != nil {
return errors.Wrapf(err, "failed to export %s", projectRoot)
}

if sv {
select {
case <-cancel:
return
default:
if err := ctx.Err(); err != nil {
return err
}

if err := filepath.Walk(to, stripVendor); err != nil {
respCh <- resp{i, errors.Wrapf(err, "failed to strip vendor from %s", p.Ident().ProjectRoot)}
continue
return errors.Wrapf(err, "failed to strip vendor from %s", projectRoot)
}
}

respCh <- resp{i, nil}
}
}()
}
// Monitor writers
go func() {
wg.Wait()
close(respCh)
}()

// Log results and collect errors
var errs []error
var cnt int
for resp := range respCh {
cnt++
msg := "Wrote"
if resp.err != nil {
if len(errs) == 0 {
close(cancel)
return nil
}()

switch err {
case context.Canceled, context.DeadlineExceeded:
// Don't log "secondary" errors.
default:
msg := "Wrote"
if err != nil {
msg = "Failed to write"
}

// Log and increment atomically to prevent re-ordering.
cnt.Lock()
cnt.i++
logger.Printf("(%d/%d) %s %s@%s\n", cnt.i, len(lps), msg, p.Ident(), p.Version())
cnt.Unlock()
}
errs = append(errs, resp.err)
msg = "Failed to write"
}
p := lps[resp.i]
logger.Printf("(%d/%d) %s %s@%s\n", cnt, len(lps), msg, p.Ident(), p.Version())
}

if len(errs) > 0 {
logger.Println("Failed to write dep tree. The following errors occurred:")
for i, err := range errs {
logger.Printf("(%d/%d) %s\n", i+1, len(errs), err)
}
return err
})
}

err := g.Wait()
if err != nil {
os.RemoveAll(basedir)

return errors.New("failed to write dep tree")
}
return nil
return errors.Wrap(err, "failed to write dep tree")
}

func (r solution) Projects() []LockedProject {
Expand Down
3 changes: 2 additions & 1 deletion internal/gps/solve_basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gps

import (
"context"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -1460,7 +1461,7 @@ func (sm *depspecSourceManager) SyncSourceFor(id ProjectIdentifier) error {

func (sm *depspecSourceManager) Release() {}

func (sm *depspecSourceManager) ExportProject(id ProjectIdentifier, v Version, to string) error {
func (sm *depspecSourceManager) ExportProject(context.Context, ProjectIdentifier, Version, string) error {
return fmt.Errorf("dummy sm doesn't support exporting")
}

Expand Down
8 changes: 4 additions & 4 deletions internal/gps/source_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type SourceManager interface {

// ExportProject writes out the tree of the provided import path, at the
// provided version, to the provided directory.
ExportProject(ProjectIdentifier, Version, string) error
ExportProject(context.Context, ProjectIdentifier, Version, string) error

// DeduceRootProject takes an import path and deduces the corresponding
// project/source root.
Expand Down Expand Up @@ -460,17 +460,17 @@ func (sm *SourceMgr) SyncSourceFor(id ProjectIdentifier) error {

// ExportProject writes out the tree of the provided ProjectIdentifier's
// ProjectRoot, at the provided version, to the provided directory.
func (sm *SourceMgr) ExportProject(id ProjectIdentifier, v Version, to string) error {
func (sm *SourceMgr) ExportProject(ctx context.Context, id ProjectIdentifier, v Version, to string) error {
if atomic.LoadInt32(&sm.releasing) == 1 {
return smIsReleased{}
}

srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
srcg, err := sm.srcCoord.getSourceGatewayFor(ctx, id)
if err != nil {
return err
}

return srcg.exportVersionTo(context.TODO(), v, to)
return srcg.exportVersionTo(ctx, v, to)
}

// DeduceProjectRoot takes an import path and deduces the corresponding
Expand Down
10 changes: 10 additions & 0 deletions vendor/golang.org/x/net/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions vendor/golang.org/x/net/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions vendor/golang.org/x/net/AUTHORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions vendor/golang.org/x/net/CONTRIBUTING.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions vendor/golang.org/x/net/CONTRIBUTORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading