spanner: client.Close cancels existing existing createSession calls

When client.Close is called, the invocation blocks until any CreateSession
requests that are currently inflight finish. This CL changes that behavior by
making a client.Close call immediately cancel inflight CreateSession requests.
This generally results in faster Close calls: if CreateSession is hanging it
is dramatically faster.

Change-Id: I25933e8032b95b10e2a298ad85bec262de9dac22
Reviewed-on: https://code-review.googlesource.com/c/36690
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Eno Compton <enocom@google.com>
diff --git a/spanner/session.go b/spanner/session.go
index 4c33dad..f26d6aa 100644
--- a/spanner/session.go
+++ b/spanner/session.go
@@ -760,7 +760,8 @@
 	// done is used to signal that health checker should be closed.
 	done chan struct{}
 	// once is used for closing channel done only once.
-	once sync.Once
+	once             sync.Once
+	maintainerCancel func()
 }
 
 // newHealthChecker initializes new instance of healthChecker.
@@ -769,12 +770,13 @@
 		workers = 1
 	}
 	hc := &healthChecker{
-		interval:       interval,
-		workers:        workers,
-		pool:           pool,
-		sampleInterval: sampleInterval,
-		ready:          make(chan struct{}),
-		done:           make(chan struct{}),
+		interval:         interval,
+		workers:          workers,
+		pool:             pool,
+		sampleInterval:   sampleInterval,
+		ready:            make(chan struct{}),
+		done:             make(chan struct{}),
+		maintainerCancel: func() {},
 	}
 	hc.waitWorkers.Add(1)
 	go hc.maintainer()
@@ -787,6 +789,9 @@
 
 // close closes the healthChecker and waits for all healthcheck workers to exit.
 func (hc *healthChecker) close() {
+	hc.mu.Lock()
+	hc.maintainerCancel()
+	hc.mu.Unlock()
 	hc.once.Do(func() { close(hc.done) })
 	hc.waitWorkers.Wait()
 }
@@ -984,9 +989,9 @@
 		}
 		sessionsToKeep := maxUint64(hc.pool.MinOpened,
 			minUint64(currSessionsOpened, hc.pool.MaxIdle+maxSessionsInUse))
-		hc.mu.Unlock()
-
 		ctx, cancel := context.WithTimeout(context.Background(), hc.sampleInterval)
+		hc.maintainerCancel = cancel
+		hc.mu.Unlock()
 
 		// Replenish or Shrink pool if needed.
 		// Note: we don't need to worry about pending create session requests, we only need to sample the current sessions in use.