github.com/karrick/gorill@v1.10.3/multiWriteCloserFanOut.go (about)

     1  package gorill
     2  
     3  import (
     4  	"io"
     5  	"sync"
     6  )
     7  
     8  // MultiWriteCloserFanOut is a structure that allows additions to and removals from the list of
     9  // io.WriteCloser objects that will be written to.
    10  type MultiWriteCloserFanOut struct {
    11  	lock        sync.RWMutex
    12  	writerMap   map[io.WriteCloser]struct{}
    13  	writerSlice []io.WriteCloser
    14  }
    15  
    16  // NewMultiWriteCloserFanOut returns a MultiWriteCloserFanOut that is go-routine safe.
    17  //
    18  //   bb1 = gorill.NewNopCloseBuffer()
    19  //   bb2 = gorill.NewNopCloseBuffer()
    20  //   mw = gorill.NewMultiWriteCloserFanOut(bb1, bb2)
    21  //   n, err := mw.Write([]byte("blob"))
    22  //   if want := 4; n != want {
    23  //   	t.Errorf("Actual: %#v; Expected: %#v", n, want)
    24  //   }
    25  //   if err != nil {
    26  //   	t.Errorf("Actual: %#v; Expected: %#v", err, nil)
    27  //   }
    28  //   if want := "blob"; bb1.String() != want {
    29  //   	t.Errorf("Actual: %#v; Expected: %#v", bb1.String(), want)
    30  //   }
    31  //   if want := "blob"; bb2.String() != want {
    32  //   	t.Errorf("Actual: %#v; Expected: %#v", bb2.String(), want)
    33  //   }
    34  func NewMultiWriteCloserFanOut(writers ...io.WriteCloser) *MultiWriteCloserFanOut {
    35  	mwc := &MultiWriteCloserFanOut{writerMap: make(map[io.WriteCloser]struct{})}
    36  	for _, w := range writers {
    37  		mwc.writerMap[w] = struct{}{}
    38  	}
    39  	mwc.update()
    40  	return mwc
    41  }
    42  
    43  // update makes the slice reflect contents of the altered map
    44  func (mwc *MultiWriteCloserFanOut) update() {
    45  	mwc.writerSlice = make([]io.WriteCloser, 0, len(mwc.writerMap))
    46  	for iow := range mwc.writerMap {
    47  		mwc.writerSlice = append(mwc.writerSlice, iow)
    48  	}
    49  }
    50  
    51  // Add adds an io.WriteCloser to the list of writers to be written to whenever this
    52  // MultiWriteCloserFanOut is written to.  It returns the number of io.WriteCloser instances attached
    53  // to the MultiWriteCloserFanOut instance.
    54  //
    55  //   bb1 = gorill.NewNopCloseBuffer()
    56  //   mw = gorill.NewMultiWriteCloserFanOut(bb1)
    57  //   bb2 = gorill.NewNopCloseBuffer()
    58  //   mw.Add(bb2)
    59  func (mwc *MultiWriteCloserFanOut) Add(w io.WriteCloser) int {
    60  	mwc.lock.Lock()
    61  	defer mwc.lock.Unlock()
    62  
    63  	mwc.writerMap[w] = struct{}{}
    64  	mwc.update()
    65  	return len(mwc.writerSlice)
    66  }
    67  
    68  // Close will close the underlying io.WriteCloser, and releases resources.
    69  func (mwc *MultiWriteCloserFanOut) Close() error {
    70  	mwc.lock.Lock()
    71  	defer mwc.lock.Unlock()
    72  
    73  	var errors ErrList
    74  	for _, iowc := range mwc.writerSlice {
    75  		errors.Append(iowc.Close())
    76  	}
    77  	return errors.Err()
    78  }
    79  
    80  // Count returns the number of io.WriteCloser instances attached to the MultiWriteCloserFanOut
    81  // instance.
    82  //
    83  //   mw = gorill.NewMultiWriteCloserFanOut()
    84  //   count := mw.Count() // returns 1
    85  //   mw.Add(gorill.NewNopCloseBuffer())
    86  //   count = mw.Count() // returns 2
    87  func (mwc *MultiWriteCloserFanOut) Count() int {
    88  	mwc.lock.RLock()
    89  	defer mwc.lock.RUnlock()
    90  
    91  	return len(mwc.writerSlice)
    92  }
    93  
    94  // IsEmpty returns true if and only if there are no writers in the list of writers to be written to.
    95  //
    96  //   mw = gorill.NewMultiWriteCloserFanOut()
    97  //   mw.IsEmpty() // returns true
    98  //   mw.Add(gorill.NewNopCloseBuffer())
    99  //   mw.IsEmpty() // returns false
   100  func (mwc *MultiWriteCloserFanOut) IsEmpty() bool {
   101  	mwc.lock.RLock()
   102  	defer mwc.lock.RUnlock()
   103  
   104  	return len(mwc.writerSlice) == 0
   105  }
   106  
   107  // Remove removes an io.WriteCloser from the list of writers to be written to whenever this
   108  // MultiWriteCloserFanOut is written to.  It returns the number of io.WriteCloser instances attached
   109  // to the MultiWriteCloserFanOut instance.
   110  //
   111  //   bb1 = gorill.NewNopCloseBuffer()
   112  //   bb2 = gorill.NewNopCloseBuffer()
   113  //   mw = gorill.NewMultiWriteCloserFanOut(bb1, bb2)
   114  //   remaining := mw.Remove(bb1) // returns 1
   115  func (mwc *MultiWriteCloserFanOut) Remove(w io.WriteCloser) int {
   116  	mwc.lock.Lock()
   117  	defer mwc.lock.Unlock()
   118  
   119  	delete(mwc.writerMap, w)
   120  	mwc.update()
   121  	return len(mwc.writerSlice)
   122  }
   123  
   124  // Write writes the data to all the writers in the MultiWriteCloserFanOut.  It removes and invokes Close
   125  // method for all io.WriteClosers that returns an error when written to.
   126  //
   127  //   bb1 = gorill.NewNopCloseBuffer()
   128  //   bb2 = gorill.NewNopCloseBuffer()
   129  //   mw = gorill.NewMultiWriteCloserFanOut(bb1, bb2)
   130  //   n, err := mw.Write([]byte("blob"))
   131  //   if want := 4; n != want {
   132  //   	t.Errorf("Actual: %#v; Expected: %#v", n, want)
   133  //   }
   134  //   if err != nil {
   135  //   	t.Errorf("Actual: %#v; Expected: %#v", err, nil)
   136  //   }
   137  func (mwc *MultiWriteCloserFanOut) Write(data []byte) (int, error) {
   138  	mwc.lock.RLock()
   139  	defer mwc.lock.RUnlock()
   140  
   141  	// NOTE: the complexity of wait group and go routines does not
   142  	// solve the slow writer problem, but it helps
   143  	var lock sync.Mutex
   144  	var wg sync.WaitGroup
   145  	var errored []io.WriteCloser
   146  	wg.Add(len(mwc.writerSlice))
   147  	for _, sw := range mwc.writerSlice {
   148  		go func(w io.WriteCloser) {
   149  			n, err := w.Write(data)
   150  			if n != len(data) {
   151  				err = io.ErrShortWrite
   152  			}
   153  			if err != nil {
   154  				lock.Lock()
   155  				errored = append(errored, w)
   156  				lock.Unlock()
   157  			}
   158  			wg.Done()
   159  		}(sw)
   160  	}
   161  	wg.Wait()
   162  	if len(errored) > 0 {
   163  		for _, w := range errored {
   164  			delete(mwc.writerMap, w)
   165  			w.Close() // BUG might cause bug when client tries to later Close ???
   166  		}
   167  		mwc.update()
   168  	}
   169  	return len(data), nil
   170  }