Merge "services/device/device: updateall: kill instance only if running; recover if err"
diff --git a/services/device/device/doc.go b/services/device/device/doc.go
index 53c9a17..3fe77dd 100644
--- a/services/device/device/doc.go
+++ b/services/device/device/doc.go
@@ -295,7 +295,7 @@
 <object name> is the vanadium object name to update, as follows:
 
 <devicename>/apps/apptitle/installationid/instanceid: updates the given
-instance, suspending/resuming it if running
+instance, killing/restarting it if running
 
 <devicename>/apps/apptitle/installationid: updates the given installation and
 then all its instances
diff --git a/services/device/device/instance_impl.go b/services/device/device/instance_impl.go
index 8f20c8d..7a3febd 100644
--- a/services/device/device/instance_impl.go
+++ b/services/device/device/instance_impl.go
@@ -37,6 +37,8 @@
 	return nil
 }
 
+const killDeadline = 10 * time.Second
+
 var cmdKill = &cmdline.Command{
 	Run:      runKill,
 	Name:     "kill",
@@ -53,7 +55,7 @@
 	}
 	appName := args[0]
 
-	if err := device.ApplicationClient(appName).Kill(gctx, 5*time.Second); err != nil {
+	if err := device.ApplicationClient(appName).Kill(gctx, killDeadline); err != nil {
 		return fmt.Errorf("Kill failed: %v", err)
 	}
 	fmt.Fprintf(cmd.Stdout(), "Kill succeeded\n")
diff --git a/services/device/device/instance_impl_test.go b/services/device/device/instance_impl_test.go
index e58d1e1..fdbe781 100644
--- a/services/device/device/instance_impl_test.go
+++ b/services/device/device/instance_impl_test.go
@@ -68,7 +68,7 @@
 		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
 	}
 	expected := []interface{}{
-		KillStimulus{"Kill", 5 * time.Second},
+		KillStimulus{"Kill", 10 * time.Second},
 	}
 	if got := appTape.Play(); !reflect.DeepEqual(expected, got) {
 		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
diff --git a/services/device/device/updateall.go b/services/device/device/updateall.go
index 4a4e57f..c660863 100644
--- a/services/device/device/updateall.go
+++ b/services/device/device/updateall.go
@@ -33,7 +33,7 @@
 	ArgsLong: `
 <object name> is the vanadium object name to update, as follows:
 
-<devicename>/apps/apptitle/installationid/instanceid: updates the given instance, suspending/resuming it if running
+<devicename>/apps/apptitle/installationid/instanceid: updates the given instance, killing/restarting it if running
 
 <devicename>/apps/apptitle/installationid: updates the given installation and then all its instances
 
@@ -83,6 +83,18 @@
 	return nil
 }
 
+func instanceIsRunning(von string) (bool, error) {
+	status, err := device.ApplicationClient(von).Status(gctx)
+	if err != nil {
+		return false, fmt.Errorf("Failed to get status for instance %q: %v", von, err)
+	}
+	s, ok := status.(device.StatusInstance)
+	if !ok {
+		return false, fmt.Errorf("Status for instance %q of wrong type (%T)", von, status)
+	}
+	return s.Value.State == device.InstanceStateRunning, nil
+}
+
 func updateInstance(cmd *cmdline.Command, von string) (retErr error) {
 	defer func() {
 		if retErr == nil {
@@ -92,9 +104,27 @@
 			fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", retErr)
 		}
 	}()
-	// Try killing the app in case it was running.
-	switch err := device.ApplicationClient(von).Kill(gctx, 5*time.Second); {
-	case err == nil:
+	running, err := instanceIsRunning(von)
+	if err != nil {
+		return err
+	}
+	if running {
+		// Try killing the app.
+		if err := device.ApplicationClient(von).Kill(gctx, killDeadline); err != nil {
+			// Check the app's state again in case we killed it,
+			// nevermind any errors.  The sleep is because Kill
+			// currently (4/29/15) returns asynchronously with the
+			// device manager shooting the app down.
+			time.Sleep(time.Second)
+			running, rerr := instanceIsRunning(von)
+			if rerr != nil {
+				return rerr
+			}
+			if running {
+				return fmt.Errorf("failed to kill instance %q: %v", von, err)
+			}
+			fmt.Fprintf(cmd.Stderr(), "Kill(%s) returned an error (%s) but app is now not running.\n", von, err)
+		}
 		// App was running, and we killed it.
 		defer func() {
 			// Re-start the instance.
@@ -107,21 +137,14 @@
 				}
 			}
 		}()
-	case verror.ErrorID(err) == deviceimpl.ErrInvalidOperation.ID:
-		// App was likely not running.
-		//
-		// TODO(caprita): change returned error to distinguish no-op
-		// app sttate transitions from failed state transitions.
-	default:
-		return fmt.Errorf("failed to suspend instance %q: %v.\n", von, err)
 	}
 	// Update the instance.
 	switch err := device.ApplicationClient(von).Update(gctx); {
 	case err == nil:
 		return nil
 	case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
-		// TODO(caprita): Ideally, we wouldn't even attempt a suspend /
-		// resume if there's no newer version of the application.
+		// TODO(caprita): Ideally, we wouldn't even attempt a kill /
+		// restart if there's no newer version of the application.
 		fmt.Fprintf(cmd.Stdout(), "Instance %q already up to date.\n", von)
 		return nil
 	default: