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  }