services/mgmt/device/impl/...:
Make testDeviceManager notice an exiting test device manager quickly, rather
than by waiting for timeouts to expire. This allows us to re-enable the
version update test in TestDeviceManagerUpdateAndRevert as the version
mismatch failure is now noticed quickly

Change-Id: I31375ea120a0c2007f90409fae7f6f71616b7227
diff --git a/services/mgmt/device/impl/config_service.go b/services/mgmt/device/impl/config_service.go
index cce62bd..819cf28 100644
--- a/services/mgmt/device/impl/config_service.go
+++ b/services/mgmt/device/impl/config_service.go
@@ -41,8 +41,10 @@
 // callback mechanism for a given key.
 type callbackListener interface {
 	// waitForValue blocks until the value that this listener is expecting
-	// arrives; or until the timeout expires.
+	// arrives, until the timeout expires, or until stop() is called
 	waitForValue(timeout time.Duration) (string, error)
+	// stop makes waitForValue return early
+	stop()
 	// cleanup cleans up any state used by the listener.  Should be called
 	// when the listener is no longer needed.
 	cleanup()
@@ -53,10 +55,11 @@
 
 // listener implements callbackListener
 type listener struct {
-	id string
-	cs *callbackState
-	ch <-chan string
-	n  string
+	id      string
+	cs      *callbackState
+	ch      <-chan string
+	n       string
+	stopper chan struct{}
 }
 
 func (l *listener) waitForValue(timeout time.Duration) (string, error) {
@@ -65,9 +68,15 @@
 		return value, nil
 	case <-time.After(timeout):
 		return "", verror.New(ErrOperationFailed, nil, fmt.Sprintf("Waiting for callback timed out after %v", timeout))
+	case <-l.stopper:
+		return "", verror.New(ErrOperationFailed, nil, fmt.Sprintf("Stopped while waiting for callack"))
 	}
 }
 
+func (l *listener) stop() {
+	close(l.stopper)
+}
+
 func (l *listener) cleanup() {
 	l.cs.unregister(l.id)
 }
@@ -84,11 +93,13 @@
 	// unregisterCallbacks executes before Set is called.
 	callbackChan := make(chan string, 1)
 	c.register(id, key, callbackChan)
+	stopchan := make(chan struct{}, 1)
 	return &listener{
-		id: id,
-		cs: c,
-		ch: callbackChan,
-		n:  callbackName,
+		id:      id,
+		cs:      c,
+		ch:      callbackChan,
+		n:       callbackName,
+		stopper: stopchan,
 	}
 }
 
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index 0059ee0..0871a95 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -396,10 +396,24 @@
 			vlog.Errorf("Clean() failed: %v", err)
 		}
 	}()
+
 	// Wait for the child process to start.
 	if err := handle.WaitForReady(childReadyTimeout); err != nil {
 		return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("WaitForReady(%v) failed: %v", childReadyTimeout, err))
 	}
+
+	// Watch for the exit of the child. Failures could cause it to happen at any time
+	waitchan := make(chan error, 1)
+	go func() {
+		// Wait timeout needs to be long enough to give the rest of the operations time to run
+		err := handle.Wait(2*childReadyTimeout + childWaitTimeout)
+		if err != nil {
+			waitchan <- verror.New(ErrOperationFailed, ctx, fmt.Sprintf("new device manager failed to exit cleanly: %v", err))
+		}
+		close(waitchan)
+		listener.stop()
+	}()
+
 	childName, err := listener.waitForValue(childReadyTimeout)
 	if err != nil {
 		return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("waitForValue(%v) failed: %v", childReadyTimeout, err))
@@ -410,8 +424,8 @@
 	if err := dmClient.Stop(ctx, 0); err != nil {
 		return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Stop() failed: %v", err))
 	}
-	if err := handle.Wait(childWaitTimeout); err != nil {
-		return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("New device manager failed to exit cleanly: %v", err))
+	if err := <-waitchan; err != nil {
+		return err
 	}
 	return nil
 }
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index e897549..fb6ac4e 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -454,15 +454,13 @@
 		t.Fatalf("script changed")
 	}
 
-	if false { // Disabled until we figure out how to make it not take 40 seconds to time out
-		// Try issuing an update with a binary that has a different major version number. It should fail
-		resolveExpectNotFound(t, ctx, "v2.5DM") // Ensure a clean slate.
-		*envelope = envelopeFromShell(sh, dmEnv, deviceManagerV10Cmd, application.DeviceManagerTitle, "v2.5DM")
-		updateDeviceExpectError(t, ctx, "v2DM", impl.ErrOperationFailed.ID)
+	// Try issuing an update with a binary that has a different major version number. It should fail
+	resolveExpectNotFound(t, ctx, "v2.5DM") // Ensure a clean slate.
+	*envelope = envelopeFromShell(sh, dmEnv, deviceManagerV10Cmd, application.DeviceManagerTitle, "v2.5DM")
+	updateDeviceExpectError(t, ctx, "v2DM", impl.ErrOperationFailed.ID)
 
-		if evalLink() != scriptPathV2 {
-			t.Fatalf("script changed")
-		}
+	if evalLink() != scriptPathV2 {
+		t.Fatalf("script changed")
 	}
 
 	// Create a third version of the device manager and issue an update.