blob: 2c6cc9c40bdaf00b8c21e02ee295ba8ccb6b0db8 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package ssh
import "context"
// doAsync runs a function in a goroutine asynchronously.
//
// Firstly, body is called regardless of whether ctx is already canceled or not.
//
// If body returns non-nil or ctx is canceled before body finishes, clean is
// called in the same goroutine as body after body finishes. clean can be used
// to clean up the effect of body. clean is not run if it is nil.
//
// The return value of doAsync is that of body if body finishes before ctx is
// canceled. Otherwise, ctx.Err() is returned.
func doAsync(ctx context.Context, body func() error, clean func()) (retErr error) {
bodyCh := make(chan error, 1) // result of body is sent
retCh := make(chan error, 1) // result of doAsync is sent
doneCh := make(chan struct{}) // closed when the goroutine finishes
go func() {
defer close(doneCh)
bodyCh <- body()
if err := <-retCh; err != nil && clean != nil {
clean()
}
}()
// Do not return until the goroutine finishes or ctx is canceled.
defer func() {
retCh <- retErr
select {
case <-doneCh:
case <-ctx.Done():
}
}()
// If ctx is already canceled, always return ctx.Err().
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case err := <-bodyCh:
return err
case <-ctx.Done():
return ctx.Err()
}
}