github.com/glycerine/xcryptossh@v7.0.4+incompatible/halter.go (about)

     1  package ssh
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  )
     8  
     9  // IdemCloseChan can have Close() called on it
    10  // multiple times, and it will only close
    11  // Chan once.
    12  type IdemCloseChan struct {
    13  	Chan   chan struct{}
    14  	closed bool
    15  	mut    sync.Mutex
    16  }
    17  
    18  // Reinit re-allocates the Chan, assinging
    19  // a new channel and reseting the state
    20  // as if brand new.
    21  func (c *IdemCloseChan) Reinit() {
    22  	c.mut.Lock()
    23  	defer c.mut.Unlock()
    24  	c.Chan = make(chan struct{})
    25  	c.closed = false
    26  }
    27  
    28  // NewIdemCloseChan makes a new IdemCloseChan.
    29  func NewIdemCloseChan() *IdemCloseChan {
    30  	return &IdemCloseChan{
    31  		Chan: make(chan struct{}),
    32  	}
    33  }
    34  
    35  var ErrAlreadyClosed = fmt.Errorf("Chan already closed")
    36  
    37  // Close returns ErrAlreadyClosed if it has been
    38  // called before. It never closes IdemClose.Chan more
    39  // than once, so it is safe to ignore the returned
    40  // error value. Close() is safe for concurrent access by multiple
    41  // goroutines. Close returns nil after the first time
    42  // it is called.
    43  func (c *IdemCloseChan) Close() error {
    44  	c.mut.Lock()
    45  	defer c.mut.Unlock()
    46  	if !c.closed {
    47  		close(c.Chan)
    48  		c.closed = true
    49  		return nil
    50  	}
    51  	return ErrAlreadyClosed
    52  }
    53  
    54  // IsClosed tells you if Chan is already closed or not.
    55  func (c *IdemCloseChan) IsClosed() bool {
    56  	c.mut.Lock()
    57  	defer c.mut.Unlock()
    58  	return c.closed
    59  }
    60  
    61  // Halter helps shutdown a goroutine, and manage
    62  // overall lifecycle of a resource.
    63  type Halter struct {
    64  
    65  	// ready is closed when
    66  	// the resouce embedding the Halter is ready.
    67  	ready IdemCloseChan
    68  
    69  	// The owning goutine should call MarkDone() as its last
    70  	// actual once it has received the ReqStop() signal.
    71  	// Err, if any, should be set before Done is called.
    72  	done IdemCloseChan
    73  
    74  	// Other goroutines call RequestStop() in order
    75  	// to request that the owning goroutine stop immediately.
    76  	// The owning goroutine should select on ReqStopChan()
    77  	// in order to recognize shutdown requests.
    78  	reqStop IdemCloseChan
    79  
    80  	// Err represents the "return value" of the
    81  	// function launched in the goroutine.
    82  	// To avoid races, it should be read only
    83  	// after Done has been closed. Goroutine
    84  	// functions should SetErr() (if non nil)
    85  	// prior to calling MarkDone().
    86  	err    error
    87  	errmut sync.Mutex
    88  
    89  	upstream   map[*Halter]*RunStatus // notify when done.
    90  	downstream map[*Halter]*RunStatus // send reqStop when we are reqStop
    91  	mut        sync.Mutex
    92  }
    93  
    94  func (h *Halter) Err() (err error) {
    95  	h.errmut.Lock()
    96  	err = h.err
    97  	h.errmut.Unlock()
    98  	return
    99  }
   100  
   101  func (h *Halter) SetErr(err error) {
   102  	h.errmut.Lock()
   103  	h.err = err
   104  	h.errmut.Unlock()
   105  }
   106  
   107  func (h *Halter) addUpstream(u *Halter) {
   108  	h.mut.Lock()
   109  	h.upstream[u] = nil
   110  	h.mut.Unlock()
   111  }
   112  
   113  func (h *Halter) removeUpstream(u *Halter) {
   114  	h.mut.Lock()
   115  	delete(h.upstream, u)
   116  	h.mut.Unlock()
   117  }
   118  
   119  // AddDownstream is the public API. To
   120  // prevent infinite loops, always use
   121  // AddDownstream: it will automatically
   122  // inform the d that h is now upstream.
   123  // There is no need to call d.addUpstream(h),
   124  // as AddDownstream will do that automatically.
   125  //
   126  func (h *Halter) AddDownstream(d *Halter) {
   127  	h.mut.Lock()
   128  	h.downstream[d] = nil
   129  	h.mut.Unlock()
   130  	d.addUpstream(h)
   131  }
   132  
   133  func (h *Halter) RemoveDownstream(d *Halter) {
   134  	h.mut.Lock()
   135  	delete(h.downstream, d)
   136  	h.mut.Unlock()
   137  	d.removeUpstream(h)
   138  }
   139  
   140  // RunStatus provides lifecycle snapshots.
   141  type RunStatus struct {
   142  
   143  	// lifecycle
   144  	Ready         bool
   145  	StopRequested bool
   146  	Done          bool
   147  
   148  	// can be waited on for finish.
   149  	// Once closed, call Status()
   150  	// again to get any Err that
   151  	// was the cause/leftover.
   152  	DoneCh <-chan struct{}
   153  
   154  	// final error if any.
   155  	Err error
   156  }
   157  
   158  func (h *Halter) Status() (r *RunStatus) {
   159  	// don't hold locks here!
   160  	r = &RunStatus{}
   161  	r.Ready = h.ready.IsClosed()
   162  	r.StopRequested = h.reqStop.IsClosed()
   163  	r.Done = h.done.IsClosed()
   164  	if r.Done {
   165  		r.Err = h.Err()
   166  	}
   167  	r.DoneCh = h.done.Chan
   168  	return
   169  }
   170  
   171  func NewHalter() *Halter {
   172  	return &Halter{
   173  		ready:      *NewIdemCloseChan(),
   174  		done:       *NewIdemCloseChan(),
   175  		reqStop:    *NewIdemCloseChan(),
   176  		upstream:   make(map[*Halter]*RunStatus),
   177  		downstream: make(map[*Halter]*RunStatus),
   178  	}
   179  }
   180  
   181  func (h *Halter) ReqStopChan() chan struct{} {
   182  	return h.reqStop.Chan
   183  }
   184  
   185  func (h *Halter) DoneChan() chan struct{} {
   186  	return h.done.Chan
   187  }
   188  
   189  func (h *Halter) ReadyChan() chan struct{} {
   190  	return h.ready.Chan
   191  }
   192  
   193  // RequestStop closes the h.ReqStop channel
   194  // if it has not already done so. Safe for
   195  // multiple goroutine access.
   196  func (h *Halter) RequestStop() {
   197  	h.reqStop.Close()
   198  
   199  	// recursively tell dowstream
   200  	h.mut.Lock()
   201  	for d := range h.downstream {
   202  		d.RequestStop()
   203  	}
   204  	h.mut.Unlock()
   205  }
   206  
   207  func (h *Halter) waitForDownstreamDone() {
   208  	h.mut.Lock()
   209  	for d := range h.downstream {
   210  		select {
   211  		case <-d.DoneChan():
   212  			d.removeUpstream(h)
   213  			//case <-time.After(10 * time.Second):
   214  			//	panic(fmt.Sprintf("Halter.waitForDownsreamDone waited over 10 seconds. len=%v", len(h.downstream)))
   215  		}
   216  	}
   217  	h.mut.Unlock()
   218  }
   219  
   220  // MarkReady closes the h.ready channel
   221  // if it has not already done so. Safe for
   222  // multiple goroutine access.
   223  func (h *Halter) MarkReady() {
   224  	h.ready.Close()
   225  }
   226  
   227  // MarkDone closes the h.DoneChan() channel
   228  // if it has not already done so. Safe for
   229  // multiple goroutine access. MarkDone
   230  // returns only once all downstream
   231  // Halters have called MarkDone. See
   232  // MarkDoneNoBlock for an alternative.
   233  //
   234  func (h *Halter) MarkDone() {
   235  	h.RequestStop()
   236  	h.waitForDownstreamDone()
   237  	h.done.Close()
   238  }
   239  
   240  // MarkDoneNoBlock doesn't wait for
   241  // downstream goroutines to be done
   242  // before it returns.
   243  func (h *Halter) MarkDoneNoBlock() {
   244  	h.RequestStop()
   245  	h.done.Close()
   246  }
   247  
   248  // IsStopRequested returns true iff h.ReqStop has been Closed().
   249  func (h *Halter) IsStopRequested() bool {
   250  	return h.reqStop.IsClosed()
   251  }
   252  
   253  // IsDone returns true iff h.Done has been Closed().
   254  func (h *Halter) IsDone() bool {
   255  	return h.done.IsClosed()
   256  }
   257  
   258  func (h *Halter) IsReady() bool {
   259  	return h.ready.IsClosed()
   260  }
   261  
   262  // MAD provides a link between context.Context
   263  //   and Halter.
   264  // MAD stands for mutual assured destruction.
   265  // When ctx is cancelled, then halt will be too.
   266  // When halt is done, then cancelctx will be called.
   267  func MAD(ctx context.Context, cancelctx context.CancelFunc, halt *Halter) {
   268  	go func() {
   269  		cchan := ctx.Done()
   270  		hchan1 := halt.reqStop.Chan
   271  		hchan2 := halt.done.Chan
   272  		cDone := false
   273  		hDone := false
   274  		for {
   275  			select {
   276  			case <-cchan:
   277  				halt.reqStop.Close()
   278  				halt.done.Close()
   279  				cDone = true
   280  				cchan = nil
   281  			case <-hchan1:
   282  				hDone = true
   283  				if cancelctx != nil {
   284  					cancelctx()
   285  				}
   286  				cancelctx = nil
   287  				hchan1 = nil
   288  				hchan2 = nil
   289  			case <-hchan2:
   290  				hDone = true
   291  				if cancelctx != nil {
   292  					cancelctx()
   293  				}
   294  				cancelctx = nil
   295  				hchan1 = nil
   296  				hchan2 = nil
   297  			}
   298  			if cDone && hDone {
   299  				return
   300  			}
   301  		}
   302  	}()
   303  }