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

Commit 88b7ad9

Browse files
authored
Merge pull request #1135 from darkowlzz/concurrent-status
fix(status): concurrent BasicStatus creation
2 parents 5e004f4 + 17bb4d6 commit 88b7ad9

File tree

2 files changed

+255
-76
lines changed

2 files changed

+255
-76
lines changed

cmd/dep/status.go

Lines changed: 188 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"io/ioutil"
1414
"log"
1515
"sort"
16+
"sync"
1617
"text/tabwriter"
1718

1819
"github.com/golang/dep"
@@ -42,6 +43,17 @@ print an extended status output for each dependency of the project.
4243
Status returns exit code zero if all dependencies are in a "good state".
4344
`
4445

46+
const (
47+
shortRev uint8 = iota
48+
longRev
49+
)
50+
51+
var (
52+
errFailedUpdate = errors.New("failed to fetch updates")
53+
errFailedListPkg = errors.New("failed to list packages")
54+
errMultipleFailures = errors.New("multiple sources of failure")
55+
)
56+
4557
func (cmd *statusCommand) Name() string { return "status" }
4658
func (cmd *statusCommand) Args() string { return "[package...]" }
4759
func (cmd *statusCommand) ShortHelp() string { return statusShortHelp }
@@ -97,7 +109,7 @@ func (out *tableOutput) BasicLine(bs *BasicStatus) {
97109
bs.getConsolidatedConstraint(),
98110
formatVersion(bs.Version),
99111
formatVersion(bs.Revision),
100-
formatVersion(bs.Latest),
112+
bs.getConsolidatedLatest(shortRev),
101113
bs.PackageCount,
102114
)
103115
}
@@ -223,8 +235,21 @@ func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error {
223235
}
224236
}
225237

226-
digestMismatch, hasMissingPkgs, err := runStatusAll(ctx, out, p, sm)
238+
digestMismatch, hasMissingPkgs, errCount, err := runStatusAll(ctx, out, p, sm)
227239
if err != nil {
240+
// If it's only update errors
241+
if err == errFailedUpdate {
242+
// Print the results with unknown data
243+
ctx.Out.Println(buf.String())
244+
245+
// Print the help when in non-verbose mode
246+
if !ctx.Verbose {
247+
ctx.Out.Printf("The status of %d projects are unknown due to errors. Rerun with `-v` flag to see details.\n", errCount)
248+
}
249+
} else {
250+
// List package failure or multiple failures
251+
ctx.Out.Println("Failed to get status. Rerun with `-v` flag to see details.")
252+
}
228253
return err
229254
}
230255

@@ -248,8 +273,8 @@ type rawStatus struct {
248273
ProjectRoot string
249274
Constraint string
250275
Version string
251-
Revision gps.Revision
252-
Latest gps.Version
276+
Revision string
277+
Latest string
253278
PackageCount int
254279
}
255280

@@ -264,6 +289,7 @@ type BasicStatus struct {
264289
Latest gps.Version
265290
PackageCount int
266291
hasOverride bool
292+
hasError bool
267293
}
268294

269295
func (bs *BasicStatus) getConsolidatedConstraint() string {
@@ -291,13 +317,31 @@ func (bs *BasicStatus) getConsolidatedVersion() string {
291317
return version
292318
}
293319

320+
func (bs *BasicStatus) getConsolidatedLatest(revSize uint8) string {
321+
latest := ""
322+
if bs.Latest != nil {
323+
switch revSize {
324+
case shortRev:
325+
latest = formatVersion(bs.Latest)
326+
case longRev:
327+
latest = bs.Latest.String()
328+
}
329+
}
330+
331+
if bs.hasError {
332+
latest += "unknown"
333+
}
334+
335+
return latest
336+
}
337+
294338
func (bs *BasicStatus) marshalJSON() *rawStatus {
295339
return &rawStatus{
296340
ProjectRoot: bs.ProjectRoot,
297341
Constraint: bs.getConsolidatedConstraint(),
298342
Version: formatVersion(bs.Version),
299-
Revision: bs.Revision,
300-
Latest: bs.Latest,
343+
Revision: string(bs.Revision),
344+
Latest: bs.getConsolidatedLatest(longRev),
301345
PackageCount: bs.PackageCount,
302346
}
303347
}
@@ -308,18 +352,16 @@ type MissingStatus struct {
308352
MissingPackages []string
309353
}
310354

311-
func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (bool, bool, error) {
312-
var digestMismatch, hasMissingPkgs bool
313-
355+
func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (digestMismatch bool, hasMissingPkgs bool, errCount int, err error) {
314356
if p.Lock == nil {
315-
return digestMismatch, hasMissingPkgs, errors.Errorf("no Gopkg.lock found. Run `dep ensure` to generate lock file")
357+
return false, false, 0, errors.Errorf("no Gopkg.lock found. Run `dep ensure` to generate lock file")
316358
}
317359

318360
// While the network churns on ListVersions() requests, statically analyze
319361
// code from the current project.
320362
ptree, err := pkgtree.ListPackages(p.ResolvedAbsRoot, string(p.ImportRoot))
321363
if err != nil {
322-
return digestMismatch, hasMissingPkgs, errors.Wrapf(err, "analysis of local packages failed")
364+
return false, false, 0, errors.Wrapf(err, "analysis of local packages failed")
323365
}
324366

325367
// Set up a solver in order to check the InputHash.
@@ -339,12 +381,12 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana
339381
}
340382

341383
if err := ctx.ValidateParams(sm, params); err != nil {
342-
return digestMismatch, hasMissingPkgs, err
384+
return false, false, 0, err
343385
}
344386

345387
s, err := gps.Prepare(params, sm)
346388
if err != nil {
347-
return digestMismatch, hasMissingPkgs, errors.Wrapf(err, "could not set up solver for input hashing")
389+
return false, false, 0, errors.Wrapf(err, "could not set up solver for input hashing")
348390
}
349391

350392
cm := collectConstraints(ptree, p, sm)
@@ -366,85 +408,156 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana
366408

367409
logger.Println("Checking upstream projects:")
368410

411+
// BasicStatus channel to collect all the BasicStatus.
412+
bsCh := make(chan *BasicStatus, len(slp))
413+
414+
// Error channels to collect different errors.
415+
errListPkgCh := make(chan error, len(slp))
416+
errListVerCh := make(chan error, len(slp))
417+
418+
var wg sync.WaitGroup
419+
369420
for i, proj := range slp {
421+
wg.Add(1)
370422
logger.Printf("(%d/%d) %s\n", i+1, len(slp), proj.Ident().ProjectRoot)
371423

372-
bs := BasicStatus{
373-
ProjectRoot: string(proj.Ident().ProjectRoot),
374-
PackageCount: len(proj.Packages()),
375-
}
424+
go func(proj gps.LockedProject) {
425+
bs := BasicStatus{
426+
ProjectRoot: string(proj.Ident().ProjectRoot),
427+
PackageCount: len(proj.Packages()),
428+
}
429+
430+
// Get children only for specific outputers
431+
// in order to avoid slower status process.
432+
switch out.(type) {
433+
case *dotOutput:
434+
ptr, err := sm.ListPackages(proj.Ident(), proj.Version())
376435

377-
// Get children only for specific outputers
378-
// in order to avoid slower status process
379-
switch out.(type) {
380-
case *dotOutput:
381-
ptr, err := sm.ListPackages(proj.Ident(), proj.Version())
436+
if err != nil {
437+
bs.hasError = true
438+
errListPkgCh <- err
439+
}
382440

383-
if err != nil {
384-
return digestMismatch, hasMissingPkgs, errors.Wrapf(err, "analysis of %s package failed", proj.Ident().ProjectRoot)
441+
prm, _ := ptr.ToReachMap(true, false, false, nil)
442+
bs.Children = prm.FlattenFn(paths.IsStandardImportPath)
385443
}
386444

387-
prm, _ := ptr.ToReachMap(true, false, false, nil)
388-
bs.Children = prm.FlattenFn(paths.IsStandardImportPath)
389-
}
445+
// Split apart the version from the lock into its constituent parts.
446+
switch tv := proj.Version().(type) {
447+
case gps.UnpairedVersion:
448+
bs.Version = tv
449+
case gps.Revision:
450+
bs.Revision = tv
451+
case gps.PairedVersion:
452+
bs.Version = tv.Unpair()
453+
bs.Revision = tv.Revision()
454+
}
390455

391-
// Split apart the version from the lock into its constituent parts
392-
switch tv := proj.Version().(type) {
393-
case gps.UnpairedVersion:
394-
bs.Version = tv
395-
case gps.Revision:
396-
bs.Revision = tv
397-
case gps.PairedVersion:
398-
bs.Version = tv.Unpair()
399-
bs.Revision = tv.Revision()
456+
// Check if the manifest has an override for this project. If so,
457+
// set that as the constraint.
458+
if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
459+
bs.hasOverride = true
460+
bs.Constraint = pp.Constraint
461+
} else {
462+
bs.Constraint = gps.Any()
463+
for _, c := range cm[bs.ProjectRoot] {
464+
bs.Constraint = c.Intersect(bs.Constraint)
465+
}
466+
}
467+
468+
// Only if we have a non-rev and non-plain version do/can we display
469+
// anything wrt the version's updateability.
470+
if bs.Version != nil && bs.Version.Type() != gps.IsVersion {
471+
c, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]
472+
if !has {
473+
c.Constraint = gps.Any()
474+
}
475+
// TODO: This constraint is only the constraint imposed by the
476+
// current project, not by any transitive deps. As a result,
477+
// transitive project deps will always show "any" here.
478+
bs.Constraint = c.Constraint
479+
480+
vl, err := sm.ListVersions(proj.Ident())
481+
if err == nil {
482+
gps.SortPairedForUpgrade(vl)
483+
484+
for _, v := range vl {
485+
// Because we've sorted the version list for
486+
// upgrade, the first version we encounter that
487+
// matches our constraint will be what we want.
488+
if c.Constraint.Matches(v) {
489+
bs.Latest = v.Revision()
490+
break
491+
}
492+
}
493+
} else {
494+
// Failed to fetch version list (could happen due to
495+
// network issue).
496+
bs.hasError = true
497+
errListVerCh <- err
498+
}
499+
}
500+
501+
bsCh <- &bs
502+
503+
wg.Done()
504+
}(proj)
505+
}
506+
507+
wg.Wait()
508+
close(bsCh)
509+
close(errListPkgCh)
510+
close(errListVerCh)
511+
512+
// Newline after printing the status progress output.
513+
logger.Println()
514+
515+
// List Packages errors. This would happen only for dot output.
516+
if len(errListPkgCh) > 0 {
517+
err = errFailedListPkg
518+
if ctx.Verbose {
519+
for err := range errListPkgCh {
520+
ctx.Err.Println(err.Error())
521+
}
522+
ctx.Err.Println()
400523
}
524+
}
401525

402-
// Check if the manifest has an override for this project. If so,
403-
// set that as the constraint.
404-
if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
405-
bs.hasOverride = true
406-
bs.Constraint = pp.Constraint
526+
// List Version errors.
527+
if len(errListVerCh) > 0 {
528+
if err == nil {
529+
err = errFailedUpdate
407530
} else {
408-
bs.Constraint = gps.Any()
409-
for _, c := range cm[bs.ProjectRoot] {
410-
bs.Constraint = c.Intersect(bs.Constraint)
411-
}
531+
err = errMultipleFailures
412532
}
413533

414-
// Only if we have a non-rev and non-plain version do/can we display
415-
// anything wrt the version's updateability.
416-
if bs.Version != nil && bs.Version.Type() != gps.IsVersion {
417-
c, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]
418-
if !has {
419-
c.Constraint = gps.Any()
420-
}
421-
// TODO: This constraint is only the constraint imposed by the
422-
// current project, not by any transitive deps. As a result,
423-
// transitive project deps will always show "any" here.
424-
bs.Constraint = c.Constraint
425-
426-
vl, err := sm.ListVersions(proj.Ident())
427-
if err == nil {
428-
gps.SortPairedForUpgrade(vl)
429-
430-
for _, v := range vl {
431-
// Because we've sorted the version list for
432-
// upgrade, the first version we encounter that
433-
// matches our constraint will be what we want.
434-
if c.Constraint.Matches(v) {
435-
bs.Latest = v.Revision()
436-
break
437-
}
438-
}
534+
// Count ListVersions error because we get partial results when
535+
// this happens.
536+
errCount = len(errListVerCh)
537+
if ctx.Verbose {
538+
for err := range errListVerCh {
539+
ctx.Err.Println(err.Error())
439540
}
541+
ctx.Err.Println()
440542
}
543+
}
441544

442-
out.BasicLine(&bs)
545+
// A map of ProjectRoot and *BasicStatus. This is used in maintain the
546+
// order of BasicStatus in output by collecting all the BasicStatus and
547+
// then using them in order.
548+
bsMap := make(map[string]*BasicStatus)
549+
for bs := range bsCh {
550+
bsMap[bs.ProjectRoot] = bs
443551
}
444-
logger.Println()
552+
553+
// Use the collected BasicStatus in outputter.
554+
for _, proj := range slp {
555+
out.BasicLine(bsMap[string(proj.Ident().ProjectRoot)])
556+
}
557+
445558
out.BasicFooter()
446559

447-
return digestMismatch, hasMissingPkgs, nil
560+
return false, false, errCount, err
448561
}
449562

450563
// Hash digest mismatch may indicate that some deps are no longer
@@ -453,7 +566,6 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana
453566
//
454567
// It's possible for digests to not match, but still have a correct
455568
// lock.
456-
digestMismatch = true
457569
rm, _ := ptree.ToReachMap(true, true, false, nil)
458570

459571
external := rm.FlattenFn(paths.IsStandardImportPath)
@@ -486,7 +598,7 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana
486598
ctx.Err.Printf("\t%s: %s\n", fail.ex, fail.err.Error())
487599
}
488600

489-
return digestMismatch, hasMissingPkgs, errors.New("address issues with undeducible import paths to get more status information")
601+
return true, false, 0, errors.New("address issues with undeducible import paths to get more status information")
490602
}
491603

492604
out.MissingHeader()
@@ -506,7 +618,7 @@ outer:
506618
}
507619
out.MissingFooter()
508620

509-
return digestMismatch, hasMissingPkgs, nil
621+
return true, hasMissingPkgs, 0, nil
510622
}
511623

512624
func formatVersion(v gps.Version) string {

0 commit comments

Comments
 (0)