-
Notifications
You must be signed in to change notification settings - Fork 70
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
progressutil: refactor to use channels+select #63
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,32 +15,22 @@ | |
package progressutil | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"sync" | ||
"time" | ||
) | ||
|
||
type copyReader struct { | ||
reader io.Reader | ||
current int64 | ||
total int64 | ||
done bool | ||
doneLock sync.Mutex | ||
pb *ProgressBar | ||
} | ||
|
||
func (cr *copyReader) getDone() bool { | ||
cr.doneLock.Lock() | ||
val := cr.done | ||
cr.doneLock.Unlock() | ||
return val | ||
} | ||
var ( | ||
ErrAlreadyStarted = errors.New("cannot add copies after PrintAndWait has been called") | ||
) | ||
|
||
func (cr *copyReader) setDone(val bool) { | ||
cr.doneLock.Lock() | ||
cr.done = val | ||
cr.doneLock.Unlock() | ||
type copyReader struct { | ||
reader io.Reader | ||
current int64 | ||
total int64 | ||
pb *ProgressBar | ||
} | ||
|
||
func (cr *copyReader) Read(p []byte) (int, error) { | ||
|
@@ -50,9 +40,6 @@ func (cr *copyReader) Read(p []byte) (int, error) { | |
if err == nil { | ||
err = err1 | ||
} | ||
if err != nil { | ||
cr.setDone(true) | ||
} | ||
return n, err | ||
} | ||
|
||
|
@@ -66,21 +53,35 @@ func (cr *copyReader) updateProgressBar() error { | |
return cr.pb.SetCurrentProgress(progress) | ||
} | ||
|
||
// NewCopyProgressPrinter returns a new CopyProgressPrinter | ||
func NewCopyProgressPrinter() *CopyProgressPrinter { | ||
return &CopyProgressPrinter{results: make(chan error)} | ||
} | ||
|
||
// CopyProgressPrinter will perform an arbitrary number of io.Copy calls, while | ||
// continually printing the progress of each copy. | ||
type CopyProgressPrinter struct { | ||
readers []*copyReader | ||
errors []error | ||
results chan error | ||
|
||
lock sync.Mutex | ||
readers []*copyReader | ||
started bool | ||
pbp *ProgressBarPrinter | ||
} | ||
|
||
// AddCopy adds a copy for this CopyProgressPrinter to perform. An io.Copy call | ||
// will be made to copy bytes from reader to dest, and name and size will be | ||
// used to label the progress bar and display how much progress has been made. | ||
// If size is 0, the total size of the reader is assumed to be unknown. | ||
func (cpp *CopyProgressPrinter) AddCopy(reader io.Reader, name string, size int64, dest io.Writer) { | ||
// AddCopy can only be called before PrintAndWait; otherwise, ErrAlreadyStarted | ||
// will be returned. | ||
func (cpp *CopyProgressPrinter) AddCopy(reader io.Reader, name string, size int64, dest io.Writer) error { | ||
cpp.lock.Lock() | ||
defer cpp.lock.Unlock() | ||
|
||
if cpp.started { | ||
return ErrAlreadyStarted | ||
} | ||
if cpp.pbp == nil { | ||
cpp.pbp = &ProgressBarPrinter{} | ||
cpp.pbp.PadToBeEven = true | ||
|
@@ -96,60 +97,56 @@ func (cpp *CopyProgressPrinter) AddCopy(reader io.Reader, name string, size int6 | |
cr.pb.SetPrintAfter(cr.formattedProgress()) | ||
|
||
cpp.readers = append(cpp.readers, cr) | ||
cpp.lock.Unlock() | ||
|
||
go func() { | ||
_, err := io.Copy(dest, cr) | ||
if err != nil { | ||
cpp.lock.Lock() | ||
cpp.errors = append(cpp.errors, err) | ||
cpp.lock.Unlock() | ||
} | ||
cpp.results <- err | ||
}() | ||
return nil | ||
} | ||
|
||
// PrintAndWait will print the progress for each copy operation added with | ||
// AddCopy to printTo every printInterval. This will continue until every added | ||
// copy is finished, or until cancel is written to. | ||
// PrintAndWait may only be called once; any subsequent calls will immediately | ||
// return ErrAlreadyStarted. After PrintAndWait has been called, no more | ||
// copies may be added to the CopyProgressPrinter. | ||
func (cpp *CopyProgressPrinter) PrintAndWait(printTo io.Writer, printInterval time.Duration, cancel chan struct{}) error { | ||
for { | ||
// If cancel is not nil, see if anything has been written to it. If | ||
// something has, return, otherwise keep drawing. | ||
if cancel != nil { | ||
select { | ||
case <-cancel: | ||
return nil | ||
default: | ||
} | ||
} | ||
|
||
cpp.lock.Lock() | ||
readers := cpp.readers | ||
errors := cpp.errors | ||
cpp.lock.Lock() | ||
if cpp.started { | ||
cpp.lock.Unlock() | ||
return ErrAlreadyStarted | ||
} | ||
cpp.started = true | ||
cpp.lock.Unlock() | ||
|
||
if len(errors) > 0 { | ||
return errors[0] | ||
} | ||
n := len(cpp.readers) | ||
if n == 0 { | ||
// Nothing to do. | ||
return nil | ||
} | ||
|
||
if len(readers) > 0 { | ||
t := time.NewTicker(printInterval) | ||
for i := 0; i < n; { | ||
select { | ||
case <-cancel: | ||
return nil | ||
case <-t.C: | ||
_, err := cpp.pbp.Print(printTo) | ||
if err != nil { | ||
return err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, this will leave a dangling goroutine started in |
||
} | ||
} else { | ||
} | ||
|
||
allDone := true | ||
for _, r := range readers { | ||
allDone = allDone && r.getDone() | ||
} | ||
if allDone && len(readers) > 0 { | ||
return nil | ||
case err := <-cpp.results: | ||
i++ | ||
if err == nil { | ||
_, err = cpp.pbp.Print(printTo) | ||
} | ||
if err != nil { | ||
return err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this case snippet. Due to re-using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's only discarding if the copy error was nil... I'm not sure about the multiple thing - can you elaborate? Luca Bruno notifications@github.com schrieb am So., 29. Mai 2016, 18:17:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You are right, I misread it.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right, great point! I had this check in one of the versions (that's why Luca Bruno notifications@github.com schrieb am So., 29. Mai 2016, 21:58:
|
||
} | ||
} | ||
|
||
time.Sleep(printInterval) | ||
} | ||
return nil | ||
} | ||
|
||
func (cr *copyReader) formattedProgress() string { | ||
|
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 we cancel we'll have a dangling goroutine started in
AddCopy
blocking oncpp.results
.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.
Since we can't anticipate the size in the current interface let's just add a cancel channel for the copiers to listen on while they're trying to send to cpp results