mirror of https://github.com/databricks/cli.git
Let sync return early if an error occurs (#235)
The previous approach would proceed to execute all requests prior to returning the first error. This is solved with `errgroup.WithContext` that cancels the context if a routine returns an error.
This commit is contained in:
parent
7ade32c734
commit
fe738ede6a
|
@ -118,8 +118,6 @@ func (s *Sync) notifyComplete(ctx context.Context, d diff) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sync) RunOnce(ctx context.Context) error {
|
func (s *Sync) RunOnce(ctx context.Context) error {
|
||||||
applyDiff := syncCallback(ctx, s)
|
|
||||||
|
|
||||||
// tradeoff: doing portable monitoring only due to macOS max descriptor manual ulimit setting requirement
|
// tradeoff: doing portable monitoring only due to macOS max descriptor manual ulimit setting requirement
|
||||||
// https://github.com/gorakhargosh/watchdog/blob/master/src/watchdog/observers/kqueue.py#L394-L418
|
// https://github.com/gorakhargosh/watchdog/blob/master/src/watchdog/observers/kqueue.py#L394-L418
|
||||||
all, err := s.fileSet.All()
|
all, err := s.fileSet.All()
|
||||||
|
@ -139,7 +137,7 @@ func (s *Sync) RunOnce(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = applyDiff(change)
|
err = s.applyDiff(ctx, change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,53 +6,63 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://docs.databricks.com/resources/limits.html#limits-api-rate-limits for per api
|
// Maximum number of concurrent requests during sync.
|
||||||
// rate limits
|
|
||||||
const MaxRequestsInFlight = 20
|
const MaxRequestsInFlight = 20
|
||||||
|
|
||||||
func syncCallback(ctx context.Context, s *Sync) func(localDiff diff) error {
|
// Perform a DELETE of the specified remote path.
|
||||||
return func(d diff) error {
|
func (s *Sync) applyDelete(ctx context.Context, group *errgroup.Group, remoteName string) {
|
||||||
// Abstraction over wait groups which allows you to get the errors
|
// Return early if the context has already been cancelled.
|
||||||
// returned in goroutines
|
select {
|
||||||
var g errgroup.Group
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Proceed.
|
||||||
|
}
|
||||||
|
|
||||||
// Allow MaxRequestLimit maxiumum concurrent api calls
|
group.Go(func() error {
|
||||||
g.SetLimit(MaxRequestsInFlight)
|
s.notifyProgress(ctx, EventActionDelete, remoteName, 0.0)
|
||||||
|
err := s.repoFiles.DeleteFile(ctx, remoteName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.notifyProgress(ctx, EventActionDelete, remoteName, 1.0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a PUT of the specified local path.
|
||||||
|
func (s *Sync) applyPut(ctx context.Context, group *errgroup.Group, localName string) {
|
||||||
|
// Return early if the context has already been cancelled.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Proceed.
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Go(func() error {
|
||||||
|
s.notifyProgress(ctx, EventActionPut, localName, 0.0)
|
||||||
|
err := s.repoFiles.PutFile(ctx, localName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.notifyProgress(ctx, EventActionPut, localName, 1.0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sync) applyDiff(ctx context.Context, d diff) error {
|
||||||
|
group, ctx := errgroup.WithContext(ctx)
|
||||||
|
group.SetLimit(MaxRequestsInFlight)
|
||||||
|
|
||||||
for _, remoteName := range d.delete {
|
for _, remoteName := range d.delete {
|
||||||
// Copy of remoteName created to make this safe for concurrent use.
|
s.applyDelete(ctx, group, remoteName)
|
||||||
// directly using remoteName can cause race conditions since the loop
|
|
||||||
// might iterate over to the next remoteName before the go routine function
|
|
||||||
// is evaluated
|
|
||||||
remoteNameCopy := remoteName
|
|
||||||
g.Go(func() error {
|
|
||||||
s.notifyProgress(ctx, EventActionDelete, remoteNameCopy, 0.0)
|
|
||||||
err := s.repoFiles.DeleteFile(ctx, remoteNameCopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
s.notifyProgress(ctx, EventActionDelete, remoteNameCopy, 1.0)
|
|
||||||
return nil
|
for _, localName := range d.put {
|
||||||
})
|
s.applyPut(ctx, group, localName)
|
||||||
}
|
|
||||||
for _, localRelativePath := range d.put {
|
|
||||||
// Copy of localName created to make this safe for concurrent use.
|
|
||||||
localRelativePathCopy := localRelativePath
|
|
||||||
g.Go(func() error {
|
|
||||||
s.notifyProgress(ctx, EventActionPut, localRelativePathCopy, 0.0)
|
|
||||||
err := s.repoFiles.PutFile(ctx, localRelativePathCopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.notifyProgress(ctx, EventActionPut, localRelativePathCopy, 1.0)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// wait for goroutines to finish and return first non-nil error return
|
|
||||||
// if any
|
|
||||||
if err := g.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for goroutines to finish and return first non-nil error return if any.
|
||||||
|
return group.Wait()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue