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 }