func (l *loopyWriter) run() {
	var (
		it      interface{}
		err     error
		isEmpty bool
	)
	defer func() {
		errorf("transport: loopyWriter.run returning. Err: %v", err)
	}()
	for {
		it, err = l.cbuf.get(true)
		if err != nil {
			return
		}
		if err = l.handle(it); err != nil {
			return
		}
		if _, err = l.processData(); err != nil {
			return
		}
		gosched := true
	hasdata:
		for {
			it, err = l.cbuf.get(false)
			if err != nil {
				return
			}
			if it != nil {
				if err = l.handle(it); err != nil {
					return
				}
				if _, err = l.processData(); err != nil {
					return
				}
				continue hasdata
			}
			if isEmpty, err = l.processData(); err != nil {
				return
			}
			if !isEmpty {
				continue hasdata
			}
			if gosched {
				gosched = false
				if l.framer.writer.offset < minBatchSize {
					runtime.Gosched()
					continue hasdata
				}
			}
			l.framer.writer.Flush()
			break hasdata
		}
	}
}
run should be run in a separate goroutine.
package transport
import (
	"bytes"
	"fmt"
	"runtime"
	"sync"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/hpack"
)
type itemNode struct {
	it   interface{}
	next *itemNode
}
type itemList struct {
	head *itemNode
	tail *itemNode
}
func (il *itemList) enqueue(i interface{}) {
	n := &itemNode{it: i}
	if il.tail == nil {
		il.head, il.tail = n, n
		return
	}
	il.tail.next = n
	il.tail = n
}
func (il *itemList) peek() interface{} {
	return il.head.it
}
func (il *itemList) dequeue() interface{} {
	if il.head == nil {
		return nil
	}
	i := il.head.it
	il.head = il.head.next
	if il.head == nil {
		il.tail = nil
	}
	return i
}
func (il *itemList) dequeueAll() *itemNode {
	h := il.head
	il.head, il.tail = nil, nil
	return h
}
func (il *itemList) isEmpty() bool {
	return il.head == nil
}
type headerFrame struct {
	streamID   uint32
	hf         []hpack.HeaderField
	endStream  bool                        
	initStream func(uint32) (bool, error)  
	onWrite    func()
	wq         *writeQuota     
	cleanup    *cleanupStream  
	onOrphaned func(error)     
}
type cleanupStream struct {
	streamID uint32
	idPtr    *uint32
	rst      bool
	rstCode  http2.ErrCode
	onWrite  func()
}
type dataFrame struct {
	streamID  uint32
	endStream bool
	h         []byte
	d         []byte
	onEachWrite func()
}
type incomingWindowUpdate struct {
	streamID  uint32
	increment uint32
}
type outgoingWindowUpdate struct {
	streamID  uint32
	increment uint32
}
type incomingSettings struct {
	ss []http2.Setting
}
type outgoingSettings struct {
	ss []http2.Setting
}
type settingsAck struct {
}
type incomingGoAway struct {
}
type goAway struct {
	code      http2.ErrCode
	debugData []byte
	headsUp   bool
	closeConn bool
}
type ping struct {
	ack  bool
	data [8]byte
}
type outStreamState int
const (
	active outStreamState = iota
	empty
	waitingOnStreamQuota
)
type outStream struct {
	id               uint32
	state            outStreamState
	itl              *itemList
	bytesOutStanding int
	wq               *writeQuota
	next *outStream
	prev *outStream
}
func (s *outStream) deleteSelf() {
	if s.prev != nil {
		s.prev.next = s.next
	}
	if s.next != nil {
		s.next.prev = s.prev
	}
	s.next, s.prev = nil, nil
}
type outStreamList struct {
	head *outStream
	tail *outStream
}
func newOutStreamList() *outStreamList {
	head, tail := new(outStream), new(outStream)
	head.next = tail
	tail.prev = head
	return &outStreamList{
		head: head,
		tail: tail,
	}
}
func (l *outStreamList) enqueue(s *outStream) {
	e := l.tail.prev
	e.next = s
	s.prev = e
	s.next = l.tail
	l.tail.prev = s
}
func (l *outStreamList) dequeue() *outStream {
	b := l.head.next
	if b == l.tail {
		return nil
	}
	b.deleteSelf()
	return b
}
type controlBuffer struct {
	ch              chan struct{}
	done            <-chan struct{}
	mu              sync.Mutex
	consumerWaiting bool
	list            *itemList
	err             error
}
func newControlBuffer(done <-chan struct{}) *controlBuffer {
	return &controlBuffer{
		ch:   make(chan struct{}, 1),
		list: &itemList{},
		done: done,
	}
}
func (c *controlBuffer) put(it interface{}) error {
	_, err := c.executeAndPut(nil, it)
	return err
}
func (c *controlBuffer) executeAndPut(f func(it interface{}) bool, it interface{}) (bool, error) {
	var wakeUp bool
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return false, c.err
	}
	if f != nil {
		if !f(it) {  
			c.mu.Unlock()
			return false, nil
		}
	}
	if c.consumerWaiting {
		wakeUp = true
		c.consumerWaiting = false
	}
	c.list.enqueue(it)
	c.mu.Unlock()
	if wakeUp {
		select {
		case c.ch <- struct{}{}:
		default:
		}
	}
	return true, nil
}
func (c *controlBuffer) get(block bool) (interface{}, error) {
	for {
		c.mu.Lock()
		if c.err != nil {
			c.mu.Unlock()
			return nil, c.err
		}
		if !c.list.isEmpty() {
			h := c.list.dequeue()
			c.mu.Unlock()
			return h, nil
		}
		if !block {
			c.mu.Unlock()
			return nil, nil
		}
		c.consumerWaiting = true
		c.mu.Unlock()
		select {
		case <-c.ch:
		case <-c.done:
			c.finish()
			return nil, ErrConnClosing
		}
	}
}
func (c *controlBuffer) finish() {
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return
	}
	c.err = ErrConnClosing
	for head := c.list.dequeueAll(); head != nil; head = head.next {
		hdr, ok := head.it.(*headerFrame)
		if !ok {
			continue
		}
		if hdr.onOrphaned != nil {  
			hdr.onOrphaned(ErrConnClosing)
		}
	}
	c.mu.Unlock()
}
type side int
const (
	clientSide side = iota
	serverSide
)
type loopyWriter struct {
	side          side
	cbuf          *controlBuffer
	sendQuota     uint32
	oiws          uint32                 
	estdStreams   map[uint32]*outStream  
	activeStreams *outStreamList         
	framer        *framer
	hBuf          *bytes.Buffer   
	hEnc          *hpack.Encoder  
	bdpEst        *bdpEstimator
	draining      bool
	ssGoAwayHandler func(*goAway) (bool, error)
}
func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator) *loopyWriter {
	var buf bytes.Buffer
	l := &loopyWriter{
		side:          s,
		cbuf:          cbuf,
		sendQuota:     defaultWindowSize,
		oiws:          defaultWindowSize,
		estdStreams:   make(map[uint32]*outStream),
		activeStreams: newOutStreamList(),
		framer:        fr,
		hBuf:          &buf,
		hEnc:          hpack.NewEncoder(&buf),
		bdpEst:        bdpEst,
	}
	return l
}
const minBatchSize = 1000