services/device/internal/impl: tidy automatically

This change completes tidying support in the device manager: tidying
runs at regular intervals automatically.

Change-Id: I0f96466c488897cfd01ac0414e607d17da45dedd
diff --git a/services/device/internal/impl/device_service.go b/services/device/internal/impl/device_service.go
index 24641e1..68a4d76 100644
--- a/services/device/internal/impl/device_service.go
+++ b/services/device/internal/impl/device_service.go
@@ -110,6 +110,7 @@
 	disp           *dispatcher
 	uat            BlessingSystemAssociationStore
 	securityAgent  *securityAgentState
+	tidying        chan<- tidyRequests
 }
 
 // Version info for this device manager binary. Increment as appropriate when the binary changes.
@@ -677,21 +678,8 @@
 	}}, nil
 }
 
-// tidyHarness runs device manager cleanup operations
-func (s *deviceService) tidyHarness(ctx *context.T) error {
-	now := MockableNow()
-
-	if err := pruneDeletedInstances(ctx, s.config.Root, now); err != nil {
-		return err
-	}
-
-	if err := pruneUninstalledInstallations(ctx, s.config.Root, now); err != nil {
-		return err
-	}
-
-	return pruneOldLogs(ctx, s.config.Root, now)
-}
-
-func (s *deviceService) TidyNow(ctx *context.T, call rpc.ServerCall) error {
-	return s.tidyHarness(ctx)
+func (s *deviceService) TidyNow(ctx *context.T, _ rpc.ServerCall) error {
+	ec := make(chan error)
+	s.tidying <- tidyRequests{ctx: ctx, bc: ec}
+	return <-ec
 }
diff --git a/services/device/internal/impl/dispatcher.go b/services/device/internal/impl/dispatcher.go
index 62d4787..16f81d0 100644
--- a/services/device/internal/impl/dispatcher.go
+++ b/services/device/internal/impl/dispatcher.go
@@ -43,6 +43,8 @@
 	testMode       bool
 	// reap is the app process monitoring subsystem.
 	reap *reaper
+	// tidying is the automatic state tidying subsystem.
+	tidying chan<- tidyRequests
 }
 
 // dispatcher holds the state of the device manager dispatcher.
@@ -125,6 +127,7 @@
 			updating:       newUpdatingState(),
 			restartHandler: restartHandler,
 			testMode:       testMode,
+			tidying:        newTidyingDaemon(config.Root),
 		},
 		config:     config,
 		uat:        uat,
@@ -278,6 +281,7 @@
 			disp:           d,
 			uat:            d.uat,
 			securityAgent:  d.internal.securityAgent,
+			tidying:        d.internal.tidying,
 		})
 		return receiver, auth, nil
 	case appsSuffix:
diff --git a/services/device/internal/impl/tidyup.go b/services/device/internal/impl/tidyup.go
index 2ae0850..3b9c3ca 100644
--- a/services/device/internal/impl/tidyup.go
+++ b/services/device/internal/impl/tidyup.go
@@ -14,6 +14,8 @@
 	"v.io/v23/context"
 	"v.io/v23/services/device"
 	"v.io/v23/verror"
+
+	"v.io/x/lib/vlog"
 )
 
 // This file contains the various routines that the device manager uses
@@ -25,6 +27,10 @@
 	return fi.ModTime().Add(aboutOneDay).Before(now)
 }
 
+// AutomaticTidyingInterval defaults to 1 day.
+// Settable for tests.
+var AutomaticTidyingInterval = time.Hour * 24
+
 func shouldDelete(idir, suffix string, now time.Time) (bool, error) {
 	fi, err := os.Stat(filepath.Join(idir, suffix))
 	if err != nil {
@@ -205,3 +211,48 @@
 	}
 	return processErrors(ctx, allerrors)
 }
+
+// tidyHarness runs device manager cleanup operations
+func tidyHarness(ctx *context.T, root string) error {
+	now := MockableNow()
+
+	if err := pruneDeletedInstances(ctx, root, now); err != nil {
+		return err
+	}
+
+	if err := pruneUninstalledInstallations(ctx, root, now); err != nil {
+		return err
+	}
+
+	return pruneOldLogs(ctx, root, now)
+}
+
+// tidyDaemon runs in a Go routine, processing requests to tidy
+// or tidying on a schedule.
+func tidyDaemon(c <-chan tidyRequests, root string) {
+	for {
+		select {
+		case req, ok := <-c:
+			if !ok {
+				return
+			}
+			req.bc <- tidyHarness(req.ctx, root)
+		case <-time.After(AutomaticTidyingInterval):
+			if err := tidyHarness(nil, root); err != nil {
+				vlog.Errorf("tidyDaemon failed to tidy: %v", err)
+			}
+		}
+
+	}
+}
+
+type tidyRequests struct {
+	ctx *context.T
+	bc  chan<- error
+}
+
+func newTidyingDaemon(root string) chan<- tidyRequests {
+	c := make(chan tidyRequests)
+	go tidyDaemon(c, root)
+	return c
+}