ref: Cancel NtpSync when the VClockD is closed

When VClockD is being shutdown, pending NtpUpdates can take a while
to finish before shutdown may complete. This makes unit testing
slower as each shutdown of the v23 runtime can take upto 30 seconds
for NtpUpdate to finish. This shortens the time dramatically by passing
the existing closed chan from VClockD through to NtpUpdate and sample.

Change-Id: Ia40b1bb9c3c5cae1d277bb3e46757502ab1cb8ca
diff --git a/services/syncbase/vclock/ntp.go b/services/syncbase/vclock/ntp.go
index af48b99..2cd536c 100644
--- a/services/syncbase/vclock/ntp.go
+++ b/services/syncbase/vclock/ntp.go
@@ -32,7 +32,7 @@
 type NtpSource interface {
 	// NtpSync obtains 'sampleCount' samples of NtpData from an NTP server and
 	// returns the one with the smallest 'delay' value.
-	NtpSync(sampleCount int) (*NtpData, error)
+	NtpSync(sampleCount int, closed <-chan struct{}) (*NtpData, error)
 }
 
 // newVClockNtpSource returns a new NtpSource implementation that talks to the
@@ -53,13 +53,18 @@
 
 var _ NtpSource = (*ntpSourceImpl)(nil)
 
-func (ns *ntpSourceImpl) NtpSync(sampleCount int) (*NtpData, error) {
+func (ns *ntpSourceImpl) NtpSync(sampleCount int, closed <-chan struct{}) (*NtpData, error) {
 	if ns.ntpHost == "" {
 		return nil, fmt.Errorf("vclock: NtpSync: no NTP server")
 	}
 	var res *NtpData
 	for i := 0; i < sampleCount; i++ {
-		if sample, err := ns.sample(); err == nil {
+		select {
+		case <-closed:
+			break
+		default:
+		}
+		if sample, err := ns.sample(closed); err == nil {
 			if (res == nil) || (sample.delay < res.delay) {
 				res = sample
 			}
@@ -86,7 +91,7 @@
 // time as soon it receives a response from server.
 // Based on the 4 timestamps the client can compute the skew between the
 // two vclocks and the roundtrip network delay for the request.
-func (ns *ntpSourceImpl) sample() (*NtpData, error) {
+func (ns *ntpSourceImpl) sample(closed <-chan struct{}) (*NtpData, error) {
 	raddr, err := net.ResolveUDPAddr("udp", ns.ntpHost)
 	if err != nil {
 		return nil, err
@@ -116,7 +121,17 @@
 	}
 
 	con.SetDeadline(time.Now().Add(5 * time.Second))
+	done := make(chan struct{})
+	go func() {
+		select {
+		case <-closed:
+			con.Close()
+		case <-done:
+			return
+		}
+	}()
 	_, err = con.Read(msg)
+	close(done)
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/syncbase/vclock/test_util.go b/services/syncbase/vclock/test_util.go
index 6426918..ff4b4f9 100644
--- a/services/syncbase/vclock/test_util.go
+++ b/services/syncbase/vclock/test_util.go
@@ -61,7 +61,7 @@
 	Data *NtpData
 }
 
-func (ns *ntpSourceMockImpl) NtpSync(sampleCount int) (*NtpData, error) {
+func (ns *ntpSourceMockImpl) NtpSync(sampleCount int, closed <-chan struct{}) (*NtpData, error) {
 	if ns.Err != nil {
 		return nil, ns.Err
 	}
diff --git a/services/syncbase/vclock/vclockd.go b/services/syncbase/vclock/vclockd.go
index c5e1b3d..cedb5d9 100644
--- a/services/syncbase/vclock/vclockd.go
+++ b/services/syncbase/vclock/vclockd.go
@@ -165,7 +165,7 @@
 	defer vlog.VI(2).Info("vclockd: DoNtpUpdate: end")
 
 	d.ntpSourceMu.RLock()
-	ntpData, err := d.ntpSource.NtpSync(NtpSampleCount)
+	ntpData, err := d.ntpSource.NtpSync(NtpSampleCount, d.closed)
 	d.ntpSourceMu.RUnlock()
 	if err != nil {
 		// Do not use Errorf, since this happens whenever a device is disconnected,