github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/compressio/compressio.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package compressio provides parallel compression and decompression, as well
    16  // as optional SHA-256 hashing.
    17  //
    18  // The stream format is defined as follows.
    19  //
    20  // /------------------------------------------------------\
    21  // |                 chunk size (4-bytes)                 |
    22  // +------------------------------------------------------+
    23  // |              (optional) hash (32-bytes)              |
    24  // +------------------------------------------------------+
    25  // |           compressed data size (4-bytes)             |
    26  // +------------------------------------------------------+
    27  // |                   compressed data                    |
    28  // +------------------------------------------------------+
    29  // |              (optional) hash (32-bytes)              |
    30  // +------------------------------------------------------+
    31  // |           compressed data size (4-bytes)             |
    32  // +------------------------------------------------------+
    33  // |                       ......                         |
    34  // \------------------------------------------------------/
    35  //
    36  // where each subsequent hash is calculated from the following items in order
    37  //
    38  //	compressed data
    39  //	compressed data size
    40  //	previous hash
    41  //
    42  // so the stream integrity cannot be compromised by switching and mixing
    43  // compressed chunks.
    44  package compressio
    45  
    46  import (
    47  	"bytes"
    48  	"compress/flate"
    49  	"crypto/hmac"
    50  	"crypto/sha256"
    51  	"encoding/binary"
    52  	"errors"
    53  	"hash"
    54  	"io"
    55  	"runtime"
    56  
    57  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    58  )
    59  
    60  var bufPool = sync.Pool{
    61  	New: func() any {
    62  		return bytes.NewBuffer(nil)
    63  	},
    64  }
    65  
    66  var chunkPool = sync.Pool{
    67  	New: func() any {
    68  		return new(chunk)
    69  	},
    70  }
    71  
    72  // chunk is a unit of work.
    73  type chunk struct {
    74  	// compressed is compressed data.
    75  	//
    76  	// This will always be returned to the bufPool directly when work has
    77  	// finished (in schedule) and therefore must be allocated.
    78  	compressed *bytes.Buffer
    79  
    80  	// uncompressed is the uncompressed data.
    81  	//
    82  	// This is not returned to the bufPool automatically, since it may
    83  	// correspond to a inline slice (provided directly to Read or Write).
    84  	uncompressed *bytes.Buffer
    85  
    86  	// The current hash object. Only used in compress mode.
    87  	h hash.Hash
    88  
    89  	// The hash from previous chunks. Only used in uncompress mode.
    90  	lastSum []byte
    91  
    92  	// The expected hash after current chunk. Only used in uncompress mode.
    93  	sum []byte
    94  }
    95  
    96  // newChunk allocates a new chunk object (or pulls one from the pool). Buffers
    97  // will be allocated if nil is provided for compressed or uncompressed.
    98  func newChunk(lastSum []byte, sum []byte, compressed *bytes.Buffer, uncompressed *bytes.Buffer) *chunk {
    99  	c := chunkPool.Get().(*chunk)
   100  	c.lastSum = lastSum
   101  	c.sum = sum
   102  	if compressed != nil {
   103  		c.compressed = compressed
   104  	} else {
   105  		c.compressed = bufPool.Get().(*bytes.Buffer)
   106  	}
   107  	if uncompressed != nil {
   108  		c.uncompressed = uncompressed
   109  	} else {
   110  		c.uncompressed = bufPool.Get().(*bytes.Buffer)
   111  	}
   112  	return c
   113  }
   114  
   115  // result is the result of some work; it includes the original chunk.
   116  type result struct {
   117  	*chunk
   118  	err error
   119  }
   120  
   121  // worker is a compression/decompression worker.
   122  //
   123  // The associated worker goroutine reads in uncompressed buffers from input and
   124  // writes compressed buffers to its output. Alternatively, the worker reads
   125  // compressed buffers from input and writes uncompressed buffers to its output.
   126  //
   127  // The goroutine will exit when input is closed, and the goroutine will close
   128  // output.
   129  type worker struct {
   130  	hashPool *hashPool
   131  	input    chan *chunk
   132  	output   chan result
   133  
   134  	// scratch is a temporary buffer used for marshalling. This is declared
   135  	// unfront here to avoid reallocation.
   136  	scratch [4]byte
   137  }
   138  
   139  // work is the main work routine; see worker.
   140  func (w *worker) work(compress bool, level int) {
   141  	defer close(w.output)
   142  
   143  	var h hash.Hash
   144  
   145  	for c := range w.input {
   146  		if h == nil && w.hashPool != nil {
   147  			h = w.hashPool.getHash()
   148  		}
   149  		if compress {
   150  			mw := io.Writer(c.compressed)
   151  			if h != nil {
   152  				mw = io.MultiWriter(mw, h)
   153  			}
   154  
   155  			// Encode this slice.
   156  			fw, err := flate.NewWriter(mw, level)
   157  			if err != nil {
   158  				w.output <- result{c, err}
   159  				continue
   160  			}
   161  
   162  			// Encode the input.
   163  			if _, err := io.CopyN(fw, c.uncompressed, int64(c.uncompressed.Len())); err != nil {
   164  				w.output <- result{c, err}
   165  				continue
   166  			}
   167  			if err := fw.Close(); err != nil {
   168  				w.output <- result{c, err}
   169  				continue
   170  			}
   171  
   172  			// Write the hash, if enabled.
   173  			if h != nil {
   174  				binary.BigEndian.PutUint32(w.scratch[:], uint32(c.compressed.Len()))
   175  				h.Write(w.scratch[:4])
   176  				c.h = h
   177  				h = nil
   178  			}
   179  		} else {
   180  			// Check the hash of the compressed contents.
   181  			if h != nil {
   182  				h.Write(c.compressed.Bytes())
   183  				binary.BigEndian.PutUint32(w.scratch[:], uint32(c.compressed.Len()))
   184  				h.Write(w.scratch[:4])
   185  				io.CopyN(h, bytes.NewReader(c.lastSum), int64(len(c.lastSum)))
   186  
   187  				sum := h.Sum(nil)
   188  				h.Reset()
   189  				if !hmac.Equal(c.sum, sum) {
   190  					w.output <- result{c, ErrHashMismatch}
   191  					continue
   192  				}
   193  			}
   194  
   195  			// Decode this slice.
   196  			fr := flate.NewReader(c.compressed)
   197  
   198  			// Decode the input.
   199  			if _, err := io.Copy(c.uncompressed, fr); err != nil {
   200  				w.output <- result{c, err}
   201  				continue
   202  			}
   203  		}
   204  
   205  		// Send the output.
   206  		w.output <- result{c, nil}
   207  	}
   208  }
   209  
   210  type hashPool struct {
   211  	// mu protexts the hash list.
   212  	mu sync.Mutex
   213  
   214  	// key is the key used to create hash objects.
   215  	key []byte
   216  
   217  	// hashes is the hash object free list. Note that this cannot be
   218  	// globally shared across readers or writers, as it is key-specific.
   219  	hashes []hash.Hash
   220  }
   221  
   222  // getHash gets a hash object for the pool. It should only be called when the
   223  // pool key is non-nil.
   224  func (p *hashPool) getHash() hash.Hash {
   225  	p.mu.Lock()
   226  	defer p.mu.Unlock()
   227  
   228  	if len(p.hashes) == 0 {
   229  		return hmac.New(sha256.New, p.key)
   230  	}
   231  
   232  	h := p.hashes[len(p.hashes)-1]
   233  	p.hashes = p.hashes[:len(p.hashes)-1]
   234  	return h
   235  }
   236  
   237  func (p *hashPool) putHash(h hash.Hash) {
   238  	h.Reset()
   239  
   240  	p.mu.Lock()
   241  	defer p.mu.Unlock()
   242  
   243  	p.hashes = append(p.hashes, h)
   244  }
   245  
   246  // pool is common functionality for reader/writers.
   247  type pool struct {
   248  	// workers are the compression/decompression workers.
   249  	workers []worker
   250  
   251  	// chunkSize is the chunk size. This is the first four bytes in the
   252  	// stream and is shared across both the reader and writer.
   253  	chunkSize uint32
   254  
   255  	// mu protects below; it is generally the responsibility of users to
   256  	// acquire this mutex before calling any methods on the pool.
   257  	mu sync.Mutex
   258  
   259  	// nextInput is the next worker for input (scheduling).
   260  	nextInput int
   261  
   262  	// nextOutput is the next worker for output (result).
   263  	nextOutput int
   264  
   265  	// buf is the current active buffer; the exact semantics of this buffer
   266  	// depending on whether this is a reader or a writer.
   267  	buf *bytes.Buffer
   268  
   269  	// lasSum records the hash of the last chunk processed.
   270  	lastSum []byte
   271  
   272  	// hashPool is the hash object pool. It cannot be embedded into pool
   273  	// itself as worker refers to it and that would stop pool from being
   274  	// GCed.
   275  	hashPool *hashPool
   276  }
   277  
   278  // init initializes the worker pool.
   279  //
   280  // This should only be called once.
   281  func (p *pool) init(key []byte, workers int, compress bool, level int) {
   282  	if key != nil {
   283  		p.hashPool = &hashPool{key: key}
   284  	}
   285  	p.workers = make([]worker, workers)
   286  	for i := 0; i < len(p.workers); i++ {
   287  		p.workers[i] = worker{
   288  			hashPool: p.hashPool,
   289  			input:    make(chan *chunk, 1),
   290  			output:   make(chan result, 1),
   291  		}
   292  		go p.workers[i].work(compress, level) // S/R-SAFE: In save path only.
   293  	}
   294  	runtime.SetFinalizer(p, (*pool).stop)
   295  }
   296  
   297  // stop stops all workers.
   298  func (p *pool) stop() {
   299  	for i := 0; i < len(p.workers); i++ {
   300  		close(p.workers[i].input)
   301  	}
   302  	p.workers = nil
   303  	p.hashPool = nil
   304  }
   305  
   306  // handleResult calls the callback.
   307  func handleResult(r result, callback func(*chunk) error) error {
   308  	defer func() {
   309  		r.chunk.compressed.Reset()
   310  		bufPool.Put(r.chunk.compressed)
   311  		chunkPool.Put(r.chunk)
   312  	}()
   313  	if r.err != nil {
   314  		return r.err
   315  	}
   316  	return callback(r.chunk)
   317  }
   318  
   319  // schedule schedules the given buffers.
   320  //
   321  // If c is non-nil, then it will return as soon as the chunk is scheduled. If c
   322  // is nil, then it will return only when no more work is left to do.
   323  //
   324  // If no callback function is provided, then the output channel will be
   325  // ignored.  You must be sure that the input is schedulable in this case.
   326  func (p *pool) schedule(c *chunk, callback func(*chunk) error) error {
   327  	for {
   328  		var (
   329  			inputChan  chan *chunk
   330  			outputChan chan result
   331  		)
   332  		if c != nil && len(p.workers) != 0 {
   333  			inputChan = p.workers[(p.nextInput+1)%len(p.workers)].input
   334  		}
   335  		if callback != nil && p.nextOutput != p.nextInput && len(p.workers) != 0 {
   336  			outputChan = p.workers[(p.nextOutput+1)%len(p.workers)].output
   337  		}
   338  		if inputChan == nil && outputChan == nil {
   339  			return nil
   340  		}
   341  
   342  		select {
   343  		case inputChan <- c:
   344  			p.nextInput++
   345  			return nil
   346  		case r := <-outputChan:
   347  			p.nextOutput++
   348  			if err := handleResult(r, callback); err != nil {
   349  				return err
   350  			}
   351  		}
   352  	}
   353  }
   354  
   355  // Reader is a compressed reader.
   356  type Reader struct {
   357  	pool
   358  
   359  	// in is the source.
   360  	in io.Reader
   361  
   362  	// scratch is a temporary buffer used for marshalling. This is declared
   363  	// unfront here to avoid reallocation.
   364  	scratch [4]byte
   365  }
   366  
   367  var _ io.Reader = (*Reader)(nil)
   368  
   369  // NewReader returns a new compressed reader. If key is non-nil, the data stream
   370  // is assumed to contain expected hash values, which will be compared against
   371  // hash values computed from the compressed bytes. See package comments for
   372  // details.
   373  func NewReader(in io.Reader, key []byte) (*Reader, error) {
   374  	r := &Reader{
   375  		in: in,
   376  	}
   377  
   378  	// Use double buffering for read.
   379  	r.init(key, 2*runtime.GOMAXPROCS(0), false, 0)
   380  
   381  	if _, err := io.ReadFull(in, r.scratch[:4]); err != nil {
   382  		return nil, err
   383  	}
   384  	r.chunkSize = binary.BigEndian.Uint32(r.scratch[:4])
   385  
   386  	if r.hashPool != nil {
   387  		h := r.hashPool.getHash()
   388  		binary.BigEndian.PutUint32(r.scratch[:], r.chunkSize)
   389  		h.Write(r.scratch[:4])
   390  		r.lastSum = h.Sum(nil)
   391  		r.hashPool.putHash(h)
   392  		sum := make([]byte, len(r.lastSum))
   393  		if _, err := io.ReadFull(r.in, sum); err != nil {
   394  			return nil, err
   395  		}
   396  		if !hmac.Equal(r.lastSum, sum) {
   397  			return nil, ErrHashMismatch
   398  		}
   399  	}
   400  
   401  	return r, nil
   402  }
   403  
   404  // errNewBuffer is returned when a new buffer is completed.
   405  var errNewBuffer = errors.New("buffer ready")
   406  
   407  // ErrHashMismatch is returned if the hash does not match.
   408  var ErrHashMismatch = errors.New("hash mismatch")
   409  
   410  // ReadByte implements wire.Reader.ReadByte.
   411  func (r *Reader) ReadByte() (byte, error) {
   412  	var p [1]byte
   413  	n, err := r.Read(p[:])
   414  	if n != 1 {
   415  		return p[0], err
   416  	}
   417  	// Suppress EOF.
   418  	return p[0], nil
   419  }
   420  
   421  // Read implements io.Reader.Read.
   422  func (r *Reader) Read(p []byte) (int, error) {
   423  	r.mu.Lock()
   424  	defer r.mu.Unlock()
   425  
   426  	// Total bytes completed; this is declared up front because it must be
   427  	// adjustable by the callback below.
   428  	done := 0
   429  
   430  	// Total bytes pending in the asynchronous workers for buffers. This is
   431  	// used to process the proper regions of the input as inline buffers.
   432  	var (
   433  		pendingPre    = r.nextInput - r.nextOutput
   434  		pendingInline = 0
   435  	)
   436  
   437  	// Define our callback for completed work.
   438  	callback := func(c *chunk) error {
   439  		// Check for an inline buffer.
   440  		if pendingPre == 0 && pendingInline > 0 {
   441  			pendingInline--
   442  			done += c.uncompressed.Len()
   443  			return nil
   444  		}
   445  
   446  		// Copy the resulting buffer to our intermediate one, and
   447  		// return errNewBuffer to ensure that we aren't called a second
   448  		// time. This error code is handled specially below.
   449  		//
   450  		// c.buf will be freed and return to the pool when it is done.
   451  		if pendingPre > 0 {
   452  			pendingPre--
   453  		}
   454  		r.buf = c.uncompressed
   455  		return errNewBuffer
   456  	}
   457  
   458  	for done < len(p) {
   459  		// Do we have buffered data available?
   460  		if r.buf != nil {
   461  			n, err := r.buf.Read(p[done:])
   462  			done += n
   463  			if err == io.EOF {
   464  				// This is the uncompressed buffer, it can be
   465  				// returned to the pool at this point.
   466  				r.buf.Reset()
   467  				bufPool.Put(r.buf)
   468  				r.buf = nil
   469  			} else if err != nil {
   470  				// Should never happen.
   471  				defer r.stop()
   472  				return done, err
   473  			}
   474  			continue
   475  		}
   476  
   477  		// Read the length of the next chunk and reset the
   478  		// reader. The length is used to limit the reader.
   479  		//
   480  		// See writer.flush.
   481  		if _, err := io.ReadFull(r.in, r.scratch[:4]); err != nil {
   482  			// This is generally okay as long as there
   483  			// are still buffers outstanding. We actually
   484  			// just wait for completion of those buffers here
   485  			// and continue our loop.
   486  			if err := r.schedule(nil, callback); err == nil {
   487  				// We've actually finished all buffers; this is
   488  				// the normal EOF exit path.
   489  				defer r.stop()
   490  				return done, io.EOF
   491  			} else if err == errNewBuffer {
   492  				// A new buffer is now available.
   493  				continue
   494  			} else {
   495  				// Some other error occurred; we cannot
   496  				// process any further.
   497  				defer r.stop()
   498  				return done, err
   499  			}
   500  		}
   501  		l := binary.BigEndian.Uint32(r.scratch[:4])
   502  
   503  		// Read this chunk and schedule decompression.
   504  		compressed := bufPool.Get().(*bytes.Buffer)
   505  		if _, err := io.CopyN(compressed, r.in, int64(l)); err != nil {
   506  			// Some other error occurred; see above.
   507  			if err == io.EOF {
   508  				err = io.ErrUnexpectedEOF
   509  			}
   510  			return done, err
   511  		}
   512  
   513  		var sum []byte
   514  		if r.hashPool != nil {
   515  			sum = make([]byte, len(r.lastSum))
   516  			if _, err := io.ReadFull(r.in, sum); err != nil {
   517  				if err == io.EOF {
   518  					err = io.ErrUnexpectedEOF
   519  				}
   520  				return done, err
   521  			}
   522  		}
   523  
   524  		// Are we doing inline decoding?
   525  		//
   526  		// Note that we need to check the length here against
   527  		// bytes.MinRead, since the bytes library will choose to grow
   528  		// the slice if the available capacity is not at least
   529  		// bytes.MinRead. This limits inline decoding to chunkSizes
   530  		// that are at least bytes.MinRead (which is not unreasonable).
   531  		var c *chunk
   532  		start := done + ((pendingPre + pendingInline) * int(r.chunkSize))
   533  		if len(p) >= start+int(r.chunkSize) && len(p) >= start+bytes.MinRead {
   534  			c = newChunk(r.lastSum, sum, compressed, bytes.NewBuffer(p[start:start]))
   535  			pendingInline++
   536  		} else {
   537  			c = newChunk(r.lastSum, sum, compressed, nil)
   538  		}
   539  		r.lastSum = sum
   540  		if err := r.schedule(c, callback); err == errNewBuffer {
   541  			// A new buffer was completed while we were reading.
   542  			// That's great, but we need to force schedule the
   543  			// current buffer so that it does not get lost.
   544  			//
   545  			// It is safe to pass nil as an output function here,
   546  			// because we know that we just freed up a slot above.
   547  			r.schedule(c, nil)
   548  		} else if err != nil {
   549  			// Some other error occurred; see above.
   550  			defer r.stop()
   551  			return done, err
   552  		}
   553  	}
   554  
   555  	// Make sure that everything has been decoded successfully, otherwise
   556  	// parts of p may not actually have completed.
   557  	for pendingInline > 0 {
   558  		if err := r.schedule(nil, func(c *chunk) error {
   559  			if err := callback(c); err != nil {
   560  				return err
   561  			}
   562  			// The nil case means that an inline buffer has
   563  			// completed. The callback will have already removed
   564  			// the inline buffer from the map, so we just return an
   565  			// error to check the top of the loop again.
   566  			return errNewBuffer
   567  		}); err != errNewBuffer {
   568  			// Some other error occurred; see above.
   569  			return done, err
   570  		}
   571  	}
   572  
   573  	// Need to return done here, since it may have been adjusted by the
   574  	// callback to compensation for partial reads on some inline buffer.
   575  	return done, nil
   576  }
   577  
   578  // Writer is a compressed writer.
   579  type Writer struct {
   580  	pool
   581  
   582  	// out is the underlying writer.
   583  	out io.Writer
   584  
   585  	// closed indicates whether the file has been closed.
   586  	closed bool
   587  
   588  	// scratch is a temporary buffer used for marshalling. This is declared
   589  	// unfront here to avoid reallocation.
   590  	scratch [4]byte
   591  }
   592  
   593  var _ io.Writer = (*Writer)(nil)
   594  
   595  // NewWriter returns a new compressed writer. If key is non-nil, hash values are
   596  // generated and written out for compressed bytes. See package comments for
   597  // details.
   598  //
   599  // The recommended chunkSize is on the order of 1M. Extra memory may be
   600  // buffered (in the form of read-ahead, or buffered writes), and is limited to
   601  // O(chunkSize * [1+GOMAXPROCS]).
   602  func NewWriter(out io.Writer, key []byte, chunkSize uint32, level int) (*Writer, error) {
   603  	w := &Writer{
   604  		pool: pool{
   605  			chunkSize: chunkSize,
   606  			buf:       bufPool.Get().(*bytes.Buffer),
   607  		},
   608  		out: out,
   609  	}
   610  	w.init(key, 1+runtime.GOMAXPROCS(0), true, level)
   611  
   612  	binary.BigEndian.PutUint32(w.scratch[:], chunkSize)
   613  	if _, err := w.out.Write(w.scratch[:4]); err != nil {
   614  		return nil, err
   615  	}
   616  
   617  	if w.hashPool != nil {
   618  		h := w.hashPool.getHash()
   619  		binary.BigEndian.PutUint32(w.scratch[:], chunkSize)
   620  		h.Write(w.scratch[:4])
   621  		w.lastSum = h.Sum(nil)
   622  		w.hashPool.putHash(h)
   623  		if _, err := io.CopyN(w.out, bytes.NewReader(w.lastSum), int64(len(w.lastSum))); err != nil {
   624  			return nil, err
   625  		}
   626  	}
   627  
   628  	return w, nil
   629  }
   630  
   631  // flush writes a single buffer.
   632  func (w *Writer) flush(c *chunk) error {
   633  	// Prefix each chunk with a length; this allows the reader to safely
   634  	// limit reads while buffering.
   635  	l := uint32(c.compressed.Len())
   636  
   637  	binary.BigEndian.PutUint32(w.scratch[:], l)
   638  	if _, err := w.out.Write(w.scratch[:4]); err != nil {
   639  		return err
   640  	}
   641  
   642  	// Write out to the stream.
   643  	if _, err := io.CopyN(w.out, c.compressed, int64(c.compressed.Len())); err != nil {
   644  		return err
   645  	}
   646  
   647  	if w.hashPool != nil {
   648  		io.CopyN(c.h, bytes.NewReader(w.lastSum), int64(len(w.lastSum)))
   649  		sum := c.h.Sum(nil)
   650  		w.hashPool.putHash(c.h)
   651  		c.h = nil
   652  		if _, err := io.CopyN(w.out, bytes.NewReader(sum), int64(len(sum))); err != nil {
   653  			return err
   654  		}
   655  		w.lastSum = sum
   656  	}
   657  
   658  	return nil
   659  }
   660  
   661  // WriteByte implements wire.Writer.WriteByte.
   662  //
   663  // Note that this implementation is necessary on the object itself, as an
   664  // interface-based dispatch cannot tell whether the array backing the slice
   665  // escapes, therefore the all bytes written will generate an escape.
   666  func (w *Writer) WriteByte(b byte) error {
   667  	var p [1]byte
   668  	p[0] = b
   669  	n, err := w.Write(p[:])
   670  	if n != 1 {
   671  		return err
   672  	}
   673  	return nil
   674  }
   675  
   676  // Write implements io.Writer.Write.
   677  func (w *Writer) Write(p []byte) (int, error) {
   678  	w.mu.Lock()
   679  	defer w.mu.Unlock()
   680  
   681  	// Did we close already?
   682  	if w.closed {
   683  		return 0, io.ErrUnexpectedEOF
   684  	}
   685  
   686  	// See above; we need to track in the same way.
   687  	var (
   688  		pendingPre    = w.nextInput - w.nextOutput
   689  		pendingInline = 0
   690  	)
   691  	callback := func(c *chunk) error {
   692  		if pendingPre == 0 && pendingInline > 0 {
   693  			pendingInline--
   694  			return w.flush(c)
   695  		}
   696  		if pendingPre > 0 {
   697  			pendingPre--
   698  		}
   699  		err := w.flush(c)
   700  		c.uncompressed.Reset()
   701  		bufPool.Put(c.uncompressed)
   702  		return err
   703  	}
   704  
   705  	for done := 0; done < len(p); {
   706  		// Construct an inline buffer if we're doing an inline
   707  		// encoding; see above regarding the bytes.MinRead constraint.
   708  		if w.buf.Len() == 0 && len(p) >= done+int(w.chunkSize) && len(p) >= done+bytes.MinRead {
   709  			bufPool.Put(w.buf) // Return to the pool; never scheduled.
   710  			w.buf = bytes.NewBuffer(p[done : done+int(w.chunkSize)])
   711  			done += int(w.chunkSize)
   712  			pendingInline++
   713  		}
   714  
   715  		// Do we need to flush w.buf? Note that this case should be hit
   716  		// immediately following the inline case above.
   717  		left := int(w.chunkSize) - w.buf.Len()
   718  		if left == 0 {
   719  			if err := w.schedule(newChunk(nil, nil, nil, w.buf), callback); err != nil {
   720  				return done, err
   721  			}
   722  			// Reset the buffer, since this has now been scheduled
   723  			// for compression. Note that this may be trampled
   724  			// immediately by the bufPool.Put(w.buf) above if the
   725  			// next buffer happens to be inline, but that's okay.
   726  			w.buf = bufPool.Get().(*bytes.Buffer)
   727  			continue
   728  		}
   729  
   730  		// Read from p into w.buf.
   731  		toWrite := len(p) - done
   732  		if toWrite > left {
   733  			toWrite = left
   734  		}
   735  		n, err := w.buf.Write(p[done : done+toWrite])
   736  		done += n
   737  		if err != nil {
   738  			return done, err
   739  		}
   740  	}
   741  
   742  	// Make sure that everything has been flushed, we can't return until
   743  	// all the contents from p have been used.
   744  	for pendingInline > 0 {
   745  		if err := w.schedule(nil, func(c *chunk) error {
   746  			if err := callback(c); err != nil {
   747  				return err
   748  			}
   749  			// The flush was successful, return errNewBuffer here
   750  			// to break from the loop and check the condition
   751  			// again.
   752  			return errNewBuffer
   753  		}); err != errNewBuffer {
   754  			return len(p), err
   755  		}
   756  	}
   757  
   758  	return len(p), nil
   759  }
   760  
   761  // Close implements io.Closer.Close.
   762  func (w *Writer) Close() error {
   763  	w.mu.Lock()
   764  	defer w.mu.Unlock()
   765  
   766  	// Did we already close? After the call to Close, we always mark as
   767  	// closed, regardless of whether the flush is successful.
   768  	if w.closed {
   769  		return io.ErrUnexpectedEOF
   770  	}
   771  	w.closed = true
   772  	defer w.stop()
   773  
   774  	// Schedule any remaining partial buffer; we pass w.flush directly here
   775  	// because the final buffer is guaranteed to not be an inline buffer.
   776  	if w.buf.Len() > 0 {
   777  		if err := w.schedule(newChunk(nil, nil, nil, w.buf), w.flush); err != nil {
   778  			return err
   779  		}
   780  	}
   781  
   782  	// Flush all scheduled buffers; see above.
   783  	if err := w.schedule(nil, w.flush); err != nil {
   784  		return err
   785  	}
   786  
   787  	// Close the underlying writer (if necessary).
   788  	if closer, ok := w.out.(io.Closer); ok {
   789  		return closer.Close()
   790  	}
   791  	return nil
   792  }