github.com/karrick/gorill@v1.10.3/spooledWriteCloser.go (about) 1 package gorill 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "sync" 8 "time" 9 ) 10 11 // DefaultBufSize is the default size of the underlying bufio.Writer buffer. 12 const DefaultBufSize = 4096 13 14 // DefaultFlushPeriod is the default frequency of buffer flushes. 15 const DefaultFlushPeriod = 15 * time.Second 16 17 // SpooledWriteCloser spools bytes written to it through a bufio.Writer, periodically flushing data 18 // written to underlying io.WriteCloser. 19 type SpooledWriteCloser struct { 20 bufSize int 21 bw *bufio.Writer 22 flushPeriod time.Duration 23 halted bool 24 iowc io.WriteCloser 25 jobs chan *rillJob 26 jobsDone sync.WaitGroup 27 lock sync.RWMutex 28 } 29 30 // SpooledWriteCloserSetter is any function that modifies a SpooledWriteCloser being instantiated. 31 type SpooledWriteCloserSetter func(*SpooledWriteCloser) error 32 33 // Flush is used to configure a new SpooledWriteCloser to periodically flush. 34 func Flush(periodicity time.Duration) SpooledWriteCloserSetter { 35 return func(sw *SpooledWriteCloser) error { 36 if periodicity <= 0 { 37 return fmt.Errorf("periodicity must be greater than 0: %s", periodicity) 38 } 39 sw.flushPeriod = periodicity 40 return nil 41 } 42 } 43 44 // BufSize is used to configure a new SpooledWriteCloser's buffer size. 45 func BufSize(size int) SpooledWriteCloserSetter { 46 return func(sw *SpooledWriteCloser) error { 47 if size <= 0 { 48 return fmt.Errorf("buffer size must be greater than 0: %d", size) 49 } 50 sw.bufSize = size 51 return nil 52 } 53 } 54 55 // NewSpooledWriteCloser returns a SpooledWriteCloser that spools bytes written to it through a 56 // bufio.Writer, periodically forcing the bufio.Writer to flush its contents. 57 func NewSpooledWriteCloser(iowc io.WriteCloser, setters ...SpooledWriteCloserSetter) (*SpooledWriteCloser, error) { 58 w := &SpooledWriteCloser{ 59 bufSize: DefaultBufSize, 60 flushPeriod: DefaultFlushPeriod, 61 iowc: iowc, 62 jobs: make(chan *rillJob, 1), 63 } 64 for _, setter := range setters { 65 if err := setter(w); err != nil { 66 return nil, err 67 } 68 } 69 w.bw = bufio.NewWriterSize(iowc, w.bufSize) 70 w.jobsDone.Add(1) 71 go func() { 72 ticker := time.NewTicker(w.flushPeriod) 73 defer ticker.Stop() 74 defer w.jobsDone.Done() 75 for { 76 select { 77 case job, more := <-w.jobs: 78 if !more { 79 return 80 } 81 switch job.op { 82 case _write: 83 n, err := w.bw.Write(job.data) 84 job.results <- rillResult{n, err} 85 case _flush: 86 err := w.bw.Flush() 87 job.results <- rillResult{0, err} 88 } 89 case <-ticker.C: 90 w.bw.Flush() 91 } 92 } 93 }() 94 return w, nil 95 } 96 97 // Write spools a byte slice of data to be written to the SpooledWriteCloser. 98 func (w *SpooledWriteCloser) Write(data []byte) (int, error) { 99 w.lock.RLock() 100 defer w.lock.RUnlock() 101 102 if w.halted { 103 return 0, ErrWriteAfterClose{} 104 } 105 106 job := newRillJob(_write, data) 107 w.jobs <- job 108 // wait for results 109 result := <-job.results 110 return result.n, result.err 111 } 112 113 // Flush causes all data not yet written to the output stream to be flushed. 114 func (w *SpooledWriteCloser) Flush() error { 115 w.lock.RLock() 116 defer w.lock.RUnlock() 117 118 if w.halted { 119 return ErrWriteAfterClose{} 120 } 121 122 job := newRillJob(_flush, nil) 123 w.jobs <- job 124 result := <-job.results 125 // wait for results 126 return result.err 127 } 128 129 // Close frees resources when a SpooledWriteCloser is no longer needed. 130 func (w *SpooledWriteCloser) Close() error { 131 w.lock.Lock() 132 defer w.lock.Unlock() 133 134 close(w.jobs) 135 w.jobsDone.Wait() 136 w.halted = true 137 138 var errors ErrList 139 errors.Append(w.bw.Flush()) 140 errors.Append(w.iowc.Close()) 141 return errors.Err() 142 }