func (hc *healthChecker) healthCheck(s *session) {
	defer hc.markDone(s)
	if !s.pool.isValid() {
		s.destroy(false)
		return
	}
	if s.pool.MaxSessionAge != 0 && s.createTime.Add(s.pool.MaxSessionAge).Before(time.Now()) {
		if s.destroy(true) || !s.refreshIdle() {
			return
		}
	}
	if err := s.ping(); shouldDropSession(err) {
		s.destroy(false)
	}
}
healthCheck checks the health of the session and pings it if needed.
package spanner
import (
	"container/heap"
	"container/list"
	"fmt"
	"math/rand"
	"strings"
	"sync"
	"time"
	log "github.com/golang/glog"
	"golang.org/x/net/context"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)
type sessionHandle struct {
	mu sync.Mutex
	session *session
}
func (sh *sessionHandle) recycle() {
	sh.mu.Lock()
	defer sh.mu.Unlock()
	if sh.session == nil {
		return
	}
	sh.session.recycle()
	sh.session = nil
}
func (sh *sessionHandle) getID() string {
	sh.mu.Lock()
	defer sh.mu.Unlock()
	if sh.session == nil {
		return ""
	}
	return sh.session.getID()
}
func (sh *sessionHandle) getClient() sppb.SpannerClient {
	sh.mu.Lock()
	defer sh.mu.Unlock()
	if sh.session == nil {
		return nil
	}
	return sh.session.client
}
func (sh *sessionHandle) getMetadata() metadata.MD {
	sh.mu.Lock()
	defer sh.mu.Unlock()
	if sh.session == nil {
		return nil
	}
	return sh.session.md
}
func (sh *sessionHandle) getTransactionID() transactionID {
	sh.mu.Lock()
	defer sh.mu.Unlock()
	if sh.session == nil {
		return nil
	}
	return sh.session.tx
}
func (sh *sessionHandle) destroy() {
	sh.mu.Lock()
	s := sh.session
	sh.session = nil
	sh.mu.Unlock()
	if s == nil {
		return
	}
	s.destroy(false)
}
type session struct {
	client sppb.SpannerClient
	id string
	pool *sessionPool
	createTime time.Time
	mu sync.Mutex
	valid bool
	hcIndex int
	idleList *list.Element
	nextCheck time.Time
	checkingHealth bool
	md metadata.MD
	tx transactionID
}
func (s *session) isValid() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.valid
}
func (s *session) isWritePrepared() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.tx != nil
}
func (s *session) String() string {
	s.mu.Lock()
	defer s.mu.Unlock()
	return fmt.Sprintf("<id=%v, hcIdx=%v, idleList=%p, valid=%v, create=%v, nextcheck=%v>",
		s.id, s.hcIndex, s.idleList, s.valid, s.createTime, s.nextCheck)
}
func (s *session) ping() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	return runRetryable(ctx, func(ctx context.Context) error {
		_, err := s.client.GetSession(contextWithMetadata(ctx, s.pool.md), &sppb.GetSessionRequest{Name: s.getID()})  
		return err
	})
}
func (s *session) refreshIdle() bool {
	s.mu.Lock()
	validAndIdle := s.valid && s.idleList != nil
	s.mu.Unlock()
	if !validAndIdle {
		return false
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	var sid string
	err := runRetryable(ctx, func(ctx context.Context) error {
		session, e := s.client.CreateSession(contextWithMetadata(ctx, s.pool.md), &sppb.CreateSessionRequest{Database: s.pool.db})
		if e != nil {
			return e
		}
		sid = session.Name
		return nil
	})
	if err != nil {
		return false
	}
	s.pool.mu.Lock()
	s.mu.Lock()
	var recycle bool
	if s.valid && s.idleList != nil {
		sid, s.id = s.id, sid
		if s.tx != nil {
			s.tx = nil
			s.pool.idleWriteList.Remove(s.idleList)
			recycle = true
		}
	}
	s.mu.Unlock()
	s.pool.mu.Unlock()
	if recycle {
		s.pool.recycle(s)
	}
	if err = runRetryable(ctx, func(ctx context.Context) error {
		_, e := s.client.DeleteSession(contextWithMetadata(ctx, s.pool.md), &sppb.DeleteSessionRequest{Name: sid})
		return e
	}); err != nil {
		return false
	}
	return true
}
func (s *session) setHcIndex(i int) int {
	s.mu.Lock()
	defer s.mu.Unlock()
	oi := s.hcIndex
	s.hcIndex = i
	return oi
}
func (s *session) setIdleList(le *list.Element) *list.Element {
	s.mu.Lock()
	defer s.mu.Unlock()
	old := s.idleList
	s.idleList = le
	return old
}
func (s *session) invalidate() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	ov := s.valid
	s.valid = false
	return ov
}
func (s *session) setNextCheck(t time.Time) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.nextCheck = t
}
func (s *session) setTransactionID(tx transactionID) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.tx = tx
}
func (s *session) getID() string {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.id
}
func (s *session) getHcIndex() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.hcIndex
}
func (s *session) getIdleList() *list.Element {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.idleList
}
func (s *session) getNextCheck() time.Time {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.nextCheck
}
func (s *session) recycle() {
	s.setTransactionID(nil)
	if !s.pool.recycle(s) {
		s.destroy(false)
	}
}
func (s *session) destroy(isExpire bool) bool {
	if !s.pool.remove(s, isExpire) {
		return false
	}
	s.pool.hc.unregister(s)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	runRetryable(ctx, func(ctx context.Context) error {
		_, e := s.client.DeleteSession(ctx, &sppb.DeleteSessionRequest{Name: s.getID()})
		return e
	})
	return true
}
func (s *session) prepareForWrite(ctx context.Context) error {
	if s.isWritePrepared() {
		return nil
	}
	tx, err := beginTransaction(ctx, s.getID(), s.client)
	if err != nil {
		return err
	}
	s.setTransactionID(tx)
	return nil
}
type SessionPoolConfig struct {
	getRPCClient func() (sppb.SpannerClient, error)
	MaxOpened uint64
	MinOpened uint64
	MaxSessionAge time.Duration
	MaxBurst uint64
	WriteSessions float64
	HealthCheckWorkers int
	HealthCheckInterval time.Duration
}
func errNoRPCGetter() error {
	return spannerErrorf(codes.InvalidArgument, "require SessionPoolConfig.getRPCClient != nil, got nil")
}
func errMinOpenedGTMaxOpened(spc *SessionPoolConfig) error {
	return spannerErrorf(codes.InvalidArgument,
		"require SessionPoolConfig.MaxOpened >= SessionPoolConfig.MinOpened, got %v and %v", spc.MaxOpened, spc.MinOpened)
}
func (spc *SessionPoolConfig) validate() error {
	if spc.getRPCClient == nil {
		return errNoRPCGetter()
	}
	if spc.MinOpened > spc.MaxOpened && spc.MaxOpened > 0 {
		return errMinOpenedGTMaxOpened(spc)
	}
	return nil
}
type sessionPool struct {
	mu sync.Mutex
	valid bool
	db string
	idleList list.List
	idleWriteList list.List
	mayGetSession chan struct{}
	numOpened uint64
	createReqs uint64
	prepareReqs uint64
	SessionPoolConfig
	md metadata.MD
	hc *healthChecker
}
func newSessionPool(db string, config SessionPoolConfig, md metadata.MD) (*sessionPool, error) {
	if err := config.validate(); err != nil {
		return nil, err
	}
	pool := &sessionPool{
		db:                db,
		valid:             true,
		mayGetSession:     make(chan struct{}),
		SessionPoolConfig: config,
		md:                md,
	}
	if config.HealthCheckWorkers == 0 {
		config.HealthCheckWorkers = 10
	}
	if config.HealthCheckInterval == 0 {
		config.HealthCheckInterval = 5 * time.Minute
	}
	pool.hc = newHealthChecker(config.HealthCheckInterval, config.HealthCheckWorkers, pool)
	return pool, nil
}
func (p *sessionPool) isValid() bool {
	if p == nil {
		return false
	}
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.valid
}
func (p *sessionPool) close() {
	if p == nil {
		return
	}
	p.mu.Lock()
	if !p.valid {
		p.mu.Unlock()
		return
	}
	p.valid = false
	p.mu.Unlock()
	p.hc.close()
	p.hc.mu.Lock()
	allSessions := make([]*session, len(p.hc.queue.sessions))
	copy(allSessions, p.hc.queue.sessions)
	p.hc.mu.Unlock()
	for _, s := range allSessions {
		s.destroy(false)
	}
}
func errInvalidSessionPool() error {
	return spannerErrorf(codes.InvalidArgument, "invalid session pool")
}
func errGetSessionTimeout() error {
	return spannerErrorf(codes.Canceled, "timeout / context canceled during getting session")
}
func (p *sessionPool) shouldPrepareWrite() bool {
	return float64(p.numOpened)*p.WriteSessions > float64(p.idleWriteList.Len()+int(p.prepareReqs))
}
func (p *sessionPool) createSession(ctx context.Context) (*session, error) {
	doneCreate := func(done bool) {
		p.mu.Lock()
		if !done {
			p.numOpened--
		}
		p.createReqs--
		close(p.mayGetSession)
		p.mayGetSession = make(chan struct{})
		p.mu.Unlock()
	}
	sc, err := p.getRPCClient()
	if err != nil {
		doneCreate(false)
		return nil, err
	}
	var s *session
	err = runRetryable(ctx, func(ctx context.Context) error {
		sid, e := sc.CreateSession(ctx, &sppb.CreateSessionRequest{Database: p.db})
		if e != nil {
			return e
		}
		s = &session{valid: true, client: sc, id: sid.Name, pool: p, createTime: time.Now(), md: p.md}
		p.hc.register(s)
		return nil
	})
	if err != nil {
		doneCreate(false)
		return nil, err
	}
	doneCreate(true)
	return s, nil
}
func (p *sessionPool) isHealthy(s *session) bool {
	if s.getNextCheck().Add(2 * p.hc.getInterval()).Before(time.Now()) {
		if err := s.ping(); shouldDropSession(err) {
			s.destroy(false)
			return false
		}
		p.hc.scheduledHC(s)
	}
	return true
}
func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
	ctx = contextWithMetadata(ctx, p.md)
	for {
		var (
			s   *session
			err error
		)
		p.mu.Lock()
		if !p.valid {
			p.mu.Unlock()
			return nil, errInvalidSessionPool()
		}
		if p.idleList.Len() > 0 {
			s = p.idleList.Remove(p.idleList.Front()).(*session)
		} else if p.idleWriteList.Len() > 0 {
			s = p.idleWriteList.Remove(p.idleWriteList.Front()).(*session)
		}
		if s != nil {
			s.setIdleList(nil)
			p.mu.Unlock()
			if !p.isHealthy(s) {
				continue
			}
			return &sessionHandle{session: s}, nil
		}
		if (p.MaxOpened > 0 && p.numOpened >= p.MaxOpened) || (p.MaxBurst > 0 && p.createReqs >= p.MaxBurst) {
			mayGetSession := p.mayGetSession
			p.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, errGetSessionTimeout()
			case <-mayGetSession:
			}
			continue
		}
		p.numOpened++
		p.createReqs++
		p.mu.Unlock()
		if s, err = p.createSession(ctx); err != nil {
			return nil, toSpannerError(err)
		}
		return &sessionHandle{session: s}, nil
	}
}
func (p *sessionPool) takeWriteSession(ctx context.Context) (*sessionHandle, error) {
	ctx = contextWithMetadata(ctx, p.md)
	for {
		var (
			s   *session
			err error
		)
		p.mu.Lock()
		if !p.valid {
			p.mu.Unlock()
			return nil, errInvalidSessionPool()
		}
		if p.idleWriteList.Len() > 0 {
			s = p.idleWriteList.Remove(p.idleWriteList.Front()).(*session)
		} else if p.idleList.Len() > 0 {
			s = p.idleList.Remove(p.idleList.Front()).(*session)
		}
		if s != nil {
			s.setIdleList(nil)
			p.mu.Unlock()
			if !p.isHealthy(s) {
				continue
			}
			if !s.isWritePrepared() {
				if err = s.prepareForWrite(ctx); err != nil {
					return nil, toSpannerError(err)
				}
			}
			return &sessionHandle{session: s}, nil
		}
		if (p.MaxOpened > 0 && p.numOpened >= p.MaxOpened) || (p.MaxBurst > 0 && p.createReqs >= p.MaxBurst) {
			mayGetSession := p.mayGetSession
			p.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, errGetSessionTimeout()
			case <-mayGetSession:
			}
			continue
		}
		p.numOpened++
		p.createReqs++
		p.mu.Unlock()
		if s, err = p.createSession(ctx); err != nil {
			return nil, toSpannerError(err)
		}
		if err = s.prepareForWrite(ctx); err != nil {
			return nil, toSpannerError(err)
		}
		return &sessionHandle{session: s}, nil
	}
}
func (p *sessionPool) recycle(s *session) bool {
	p.mu.Lock()
	defer p.mu.Unlock()
	if !s.isValid() || !p.valid {
		return false
	}
	if p.MaxSessionAge != 0 && s.createTime.Add(p.MaxSessionAge).Before(time.Now()) && p.numOpened > p.MinOpened {
		return false
	}
	if s.isWritePrepared() {
		s.setIdleList(p.idleWriteList.PushFront(s))
	} else {
		s.setIdleList(p.idleList.PushFront(s))
	}
	close(p.mayGetSession)
	p.mayGetSession = make(chan struct{})
	return true
}
func (p *sessionPool) remove(s *session, isExpire bool) bool {
	p.mu.Lock()
	defer p.mu.Unlock()
	if isExpire && (p.numOpened <= p.MinOpened || s.getIdleList() == nil) {
		return false
	}
	ol := s.setIdleList(nil)
	if ol != nil {
		p.idleList.Remove(ol)
		p.idleWriteList.Remove(ol)
	}
	if s.invalidate() {
		p.numOpened--
		close(p.mayGetSession)
		p.mayGetSession = make(chan struct{})
		return true
	}
	return false
}
type hcHeap struct {
	sessions []*session
}
func (h hcHeap) Len() int {
	return len(h.sessions)
}
func (h hcHeap) Less(i, j int) bool {
	return h.sessions[i].getNextCheck().Before(h.sessions[j].getNextCheck())
}
func (h hcHeap) Swap(i, j int) {
	h.sessions[i], h.sessions[j] = h.sessions[j], h.sessions[i]
	h.sessions[i].setHcIndex(i)
	h.sessions[j].setHcIndex(j)
}
func (h *hcHeap) Push(s interface{}) {
	ns := s.(*session)
	ns.setHcIndex(len(h.sessions))
	h.sessions = append(h.sessions, ns)
}
func (h *hcHeap) Pop() interface{} {
	old := h.sessions
	n := len(old)
	s := old[n-1]
	h.sessions = old[:n-1]
	s.setHcIndex(-1)
	return s
}
type healthChecker struct {
	mu sync.Mutex
	queue hcHeap
	interval time.Duration
	workers int
	waitWorkers sync.WaitGroup
	pool *sessionPool
	closed bool
}
func newHealthChecker(interval time.Duration, workers int, pool *sessionPool) *healthChecker {
	if workers <= 0 {
		workers = 1
	}
	hc := &healthChecker{
		interval: interval,
		workers:  workers,
		pool:     pool,
	}
	for i := 0; i < hc.workers; i++ {
		hc.waitWorkers.Add(1)
		go hc.worker(i)
	}
	return hc
}
func (hc *healthChecker) close() {
	hc.mu.Lock()
	hc.closed = true
	hc.mu.Unlock()
	hc.waitWorkers.Wait()
}
func (hc *healthChecker) isClosing() bool {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	return hc.closed
}
func (hc *healthChecker) getInterval() time.Duration {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	return hc.interval
}
func (hc *healthChecker) scheduledHCLocked(s *session) {
	nsFromNow := rand.Int63n(int64(hc.interval)) + int64(hc.interval)/2
	s.setNextCheck(time.Now().Add(time.Duration(nsFromNow)))
	if hi := s.getHcIndex(); hi != -1 {
		heap.Fix(&hc.queue, hi)
	}
}
func (hc *healthChecker) scheduledHC(s *session) {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	hc.scheduledHCLocked(s)
}
func (hc *healthChecker) register(s *session) {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	hc.scheduledHCLocked(s)
	heap.Push(&hc.queue, s)
}
func (hc *healthChecker) unregister(s *session) {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	oi := s.setHcIndex(-1)
	if oi >= 0 {
		heap.Remove(&hc.queue, oi)
	}
}
func (hc *healthChecker) markDone(s *session) {
	hc.mu.Lock()
	defer hc.mu.Unlock()
	s.checkingHealth = false
}