func (c *IrcClient) Close() error {
	if c.IsClosed() {
		return nil
	}
	c.isShutdownProtect.Lock()
	defer c.isShutdownProtect.Unlock()
	err := &ClientError{}
	err.Socket = c.conn.Close()
	c.isShutdown = true
	if c.killpump != nil {
		err.Pump = <-c.killpump
	}
	if c.killsiphon != nil {
		err.Siphon = <-c.killsiphon
	}
	return err.CheckNeeded()
}
Close closes the socket, sets an all-consuming dequeuer routine to
eat all the waiting-to-write goroutines, and then waits to acquire a mutex
that will allow it to safely close the writer channel and set a shutdown var.
package inet
import (
	"bytes"
	"io"
	"net"
	"sync"
	"time"
	"gopkg.in/inconshreveable/log15.v2"
)
const (
	bufferSize = 16348
	nBufferedWrites = 25
	defaultTimeScale = time.Second
)
var (
	pong = []byte("PONG")
	ping = []byte("PING :ping\r\n")
)
type ClientError struct {
	Socket error
	Pump   error
	Siphon error
}
func (c ClientError) CheckNeeded() (err error) {
	if c.Pump != nil || c.Siphon != nil || c.Socket != nil {
		err = c
	}
	return
}
func (c ClientError) Error() string {
	var b bytes.Buffer
	var wrote bool
	if c.Socket != nil {
		b.WriteString("Socket: ")
		b.WriteString(c.Socket.Error())
		wrote = true
	}
	if c.Pump != nil {
		if wrote {
			b.WriteString(" || ")
		}
		b.WriteString("Pump: ")
		b.WriteString(c.Pump.Error())
		wrote = true
	}
	if c.Siphon != nil {
		if wrote {
			b.WriteString(" || ")
		}
		b.WriteString("Siphon: ")
		b.WriteString(c.Siphon.Error())
	}
	return b.String()
}
type IrcClient struct {
	isShutdown        bool
	isShutdownProtect sync.RWMutex
	conn        net.Conn
	siphonchan  chan []byte
	pumpchan    chan []byte
	pumpservice chan chan []byte
	killpump    chan error
	killsiphon  chan error
	queue       Queue
	log log15.Logger
	lastwrite        time.Time
	penalty          time.Time
	timeout          time.Duration
	basestep         time.Duration
	lenPenaltyFactor float64
	scale time.Duration
	keepalive time.Duration
	readbuf []byte
	pos     int
}
func createIrcClient(conn net.Conn, logger log15.Logger) *IrcClient {
	if logger == nil {
		logger = log15.New()
		logger.SetHandler(log15.DiscardHandler())
	}
	return &IrcClient{
		conn:        conn,
		siphonchan:  make(chan []byte),
		pumpchan:    make(chan []byte),
		pumpservice: make(chan chan []byte),
		log:         logger,
		lastwrite:   time.Time{},
		scale:       defaultTimeScale,
	}
}
func NewIrcClient(conn net.Conn, logger log15.Logger, lenPenaltyFactor int,
	timeout, basestep, keepalive, scale time.Duration) *IrcClient {
	c := createIrcClient(conn, logger)
	if scale != 0 {
		c.scale = scale
	}
	if lenPenaltyFactor > 0 {
		c.lenPenaltyFactor = 1.0 / float64(lenPenaltyFactor)
	}
	c.timeout = timeout
	c.basestep = basestep
	c.keepalive = keepalive
	return c
}
func (c *IrcClient) SpawnWorkers(pump, siphon bool) {
	c.isShutdownProtect.Lock()
	defer c.isShutdownProtect.Unlock()
	c.isShutdown = false
	if pump {
		c.killpump = make(chan error)
		go c.pump()
	}
	if siphon {
		c.killsiphon = make(chan error)
		go c.siphon()
	}
}
func (c *IrcClient) calcSleepTime(t time.Time, msgLen int) time.Duration {
	roundToScale := func(in time.Duration) time.Duration {
		return ((in + (c.scale / 2)) / c.scale) * c.scale
	}
	if c.lastwrite.After(c.penalty) {
		c.penalty = c.lastwrite
	}
	applyPenalty := roundToScale(c.penalty.Sub(t)) >= c.timeout
	c.penalty = c.penalty.Add(c.basestep + roundToScale(
		time.Duration(float64(c.scale)*float64(msgLen)*c.lenPenaltyFactor)))
	if applyPenalty {
		sleep := roundToScale(c.penalty.Sub(t) - c.timeout)
		if sleep > c.timeout {
			sleep = c.timeout
		}
		return sleep
	}
	return 0
}
func (c *IrcClient) pump() {
	var err error
	var sleeper <-chan time.Time
	var pinger <-chan time.Time
	if c.keepalive != 0 {
		pinger = time.After(c.keepalive)
	}
	defer close(c.pumpservice)
	for err == nil {
		select {
		case c.pumpservice <- c.pumpchan:
			message := <-c.pumpchan
			if len(message) == 0 {
				break
			}
			if bytes.HasPrefix(message, pong) {
				if err = c.writeMessage(message); err != nil {
					break
				}
			} else if sleeper == nil {
				sleepTime := c.calcSleepTime(time.Now(), len(message))
				if sleepTime == 0 {
					if err = c.writeMessage(message); err != nil {
						break
					}
				} else {
					c.queue.Enqueue(message)
					sleeper = time.After(sleepTime)
				}
			} else {
				c.queue.Enqueue(message)
			}
		case <-sleeper:
			message := c.queue.Dequeue()
			if err = c.writeMessage(message); err != nil {
				break
			}
			if c.queue.length > 0 {
				sleepTime := c.calcSleepTime(time.Now(), len(message))
				sleeper = time.After(sleepTime)
			} else {
				sleeper = nil
			}
		case <-pinger:
			if sleeper != nil {
				c.queue.Enqueue(ping)
			} else {
				if err = c.writeMessage(ping); err != nil {
					break
				}
			}
			pinger = time.After(c.keepalive)
		case c.killpump <- nil:
			return
		}
	}
	c.killpump <- err
}
func (c *IrcClient) writeMessage(msg []byte) error {
	var n int
	var err error
	for written := 0; written < len(msg); written += n {
		n, err = c.conn.Write(msg[written:])
		wrote := msg[written : len(msg)-2]
		if err != nil {
			c.log.Error("Write error", "err", err, "msg", wrote)
			return err
		}
		c.log.Debug(string(wrote), "direction", "send")
		c.lastwrite = time.Now()
	}
	return nil
}
func (c *IrcClient) siphon() {
	buf := make([]byte, bufferSize)
	var err error
	var shutdown bool
	var position, n int
	for err == nil {
		n, err = c.conn.Read(buf[position:])
		if n > 0 {
			position, shutdown = c.extractMessages(buf[:n+position])
			if shutdown {
				return
			}
		}
	}
	close(c.siphonchan)
	c.killsiphon <- err
}
func (c *IrcClient) extractMessages(buf []byte) (int, bool) {
	send := func(chunk []byte) bool {
		cpy := make([]byte, len(chunk)-2)
		copy(cpy, chunk[:len(chunk)-2])
		select {
		case c.siphonchan <- cpy:
			c.log.Debug(string(cpy), "direction", "recv")
			return false
		case c.killsiphon <- nil:
			return true
		}
	}
	start, remaining, abort := findChunks(buf, send)
	if abort {
		return 0, true
	} else if remaining {
		copy(buf[:len(buf)-start], buf[start:])
		return len(buf) - start, false
	}
	return 0, false
}