Import changes for goma server

  - 385321ccbc2988a9c4aedfd8d14576685c862edd server: use debug.FreeOSMemory instead of runtime.GC
  - 42d70fca0b08e96a1131fcf1b2e2f0ad013da146 file-server: run gc if it closes to memory limit
  - 9429c487a29f3ea81339f7a95319f1e926e0b511 roll cloud.google.com/go v0.43.0 -> v0.44.0
  - 3f170eff09fc736e254180059e8e3cc48fc729ab Revert "gcs: use minimum object attrs"
  - cac1c4d83526f60224aba62a8b5cd8bac331f039 roll google.golang.org/api v0.7.0 -> v0.9.0
  - 01fdf185317e4caf233dcce5712fb8dc32b399ea gcs: use minimum object attrs
  - 14ec7e530bfe82ff5d656b422d00323955300f95 [inventory] Fix a potential DoS bug in pickCmd
  - a52be572c6807d30456e825e5a7698362d968562 [descriptor] Add dart toolchain support
  - 75e121b560bdd00623d861a09c0857cf0ad8fc5d memroy -> memory
  - 9c1f9e3da7dc4e30a92dd2aaf0cbd29ea852ca15 roll google.golang.org/grpc 1.22.1 -> 1.23.0
  - 134589fd7a3ebd74517e286897d58b44480a26ad use go 1.12.8
  - edde7c0ba6454173f3ec5a2d74c81f6d4a7528e0 remoteexec: retry for status Aborted
  - 8e18012b0006f36ff3020a3aadb92ce456f4c35c command: pubsub-error uses last value
  - 2610736422dbd6900481649d871480e3afb5e2c5 command: reports pubsubErrors=0 if pubsub is available
  - aa10576e7eec25a69153ec4c5d7128223914cec3 command: set expiration policy to subscriptions
  - 0a93bf24a2eef1314f7e419e739161ca843aa82a remoteexec: retry if aborted but caller ctx is active.
  - fd6b433ad85171b8c2d8a07694ec9e7c2f810399 command: fallback poller if subscription is not available
  - f020d98f49a687a2b67d21ddecc3cd62a7cc4a3a roll google.golang.org/grpc 1.22.0 to 1.22.1

GitOrigin-RevId: 385321ccbc2988a9c4aedfd8d14576685c862edd
Change-Id: Ida45d03d064ba365a882f8d4ed0ba3d5111704ac
TBR=yyanagisawa@google.com, tikuta@google.com, sque@google.com
diff --git a/cache/gcs/cache.go b/cache/gcs/cache.go
index 13c6845..ae672b0 100644
--- a/cache/gcs/cache.go
+++ b/cache/gcs/cache.go
@@ -25,12 +25,12 @@
 
 // AdmissionController checks incoming request.
 type AdmissionController interface {
-	AdmitPut(*pb.PutReq) error
+	AdmitPut(context.Context, *pb.PutReq) error
 }
 
 type nullAdmissionController struct{}
 
-func (nullAdmissionController) AdmitPut(*pb.PutReq) error { return nil }
+func (nullAdmissionController) AdmitPut(context.Context, *pb.PutReq) error { return nil }
 
 // Cache represents key-value cache using google cloud storage.
 type Cache struct {
@@ -80,7 +80,7 @@
 
 func (c *Cache) Put(ctx context.Context, in *pb.PutReq) (*pb.PutResp, error) {
 	logger := log.FromContext(ctx)
-	if err := c.AdmissionController.AdmitPut(in); err != nil {
+	if err := c.AdmissionController.AdmitPut(ctx, in); err != nil {
 		logger.Warnf("admission error: %v", err)
 		return nil, err
 	}
diff --git a/cipd_manifest.txt b/cipd_manifest.txt
index 9ee7676..c1ed9fc 100644
--- a/cipd_manifest.txt
+++ b/cipd_manifest.txt
@@ -13,7 +13,7 @@
 # https://chrome-infra-packages.appspot.com/
 
 # go
-infra/go/${platform} version:1.12.7
+infra/go/${platform} version:1.12.8
 
 # protoc
 # If the version you want is missing, please follow the instruction in:
diff --git a/cipd_manifest.versions b/cipd_manifest.versions
index aa8ddf8..e872a11 100644
--- a/cipd_manifest.versions
+++ b/cipd_manifest.versions
@@ -2,8 +2,8 @@
 # Do not modify manually. All changes will be overwritten.
 
 infra/go/linux-amd64
-	version:1.12.7
-	lBm3oqkMtwrbllgPo-1cOqivkJcZpRUY-hO74LduBdMC
+	version:1.12.8
+	OLqQMFvS8z1pNzfA-phVWbqmITB3vqFIKGUMhEZtEcAC
 
 infra/tools/protoc/linux-amd64
 	protobuf_version:v3.7.0
diff --git a/cmd/exec_server/main.go b/cmd/exec_server/main.go
index 68eaece..583489b 100644
--- a/cmd/exec_server/main.go
+++ b/cmd/exec_server/main.go
@@ -186,13 +186,7 @@
 		SubscriberID:   fmt.Sprintf("toolchain-config-%s-%s", server.ClusterName(ctx), server.HostName(ctx)),
 		RemoteexecAddr: *remoteexecAddr,
 	}
-	cs.w, err = cs.configmap.Watcher(ctx)
-	if err != nil {
-		if cs.psclient != nil {
-			cs.psclient.Close()
-		}
-		return nil, fmt.Errorf("watch failed: %v", err)
-	}
+	cs.w = cs.configmap.Watcher(ctx)
 	cs.loader = &command.ConfigMapLoader{
 		ConfigMap: cs.configmap,
 		ConfigLoader: command.ConfigLoader{
@@ -290,6 +284,10 @@
 	if err != nil {
 		logger.Fatal(err)
 	}
+	err = view.Register(command.DefaultViews...)
+	if err != nil {
+		logger.Fatal(err)
+	}
 	err = view.Register(exec.DefaultViews...)
 	if err != nil {
 		logger.Fatal(err)
diff --git a/cmd/file_server/main.go b/cmd/file_server/main.go
index 3424cce..3c5b333 100644
--- a/cmd/file_server/main.go
+++ b/cmd/file_server/main.go
@@ -45,17 +45,18 @@
 	limit int64
 }
 
-func (a admissionController) AdmitPut(in *cachepb.PutReq) error {
+func (a admissionController) AdmitPut(ctx context.Context, in *cachepb.PutReq) error {
 	if a.limit <= 0 {
 		return nil
 	}
 	rss := server.ResidentMemorySize()
 	s := int64(len(in.Kv.Key) + len(in.Kv.Value))
-	if rss+s <= a.limit {
+	if rss+2*s <= a.limit {
 		return nil
 	}
+	newRSS := server.GC(ctx)
 	// TODO: with retryinfo?
-	return status.Errorf(codes.ResourceExhausted, "memory size %d + req:%d > limit %d", rss, s, a.limit)
+	return status.Errorf(codes.ResourceExhausted, "memory size %d + req:%d > limit %d: gc->%d", rss, s, a.limit, newRSS)
 }
 
 func main() {
diff --git a/command/configmap.go b/command/configmap.go
index 4275fd4..cd86678 100644
--- a/command/configmap.go
+++ b/command/configmap.go
@@ -9,23 +9,42 @@
 	"context"
 	"errors"
 	"fmt"
+	"math/rand"
 	"path"
 	"sort"
 	"strings"
 	"time"
 
-	"google.golang.org/api/iterator"
-
 	"cloud.google.com/go/pubsub"
 	"cloud.google.com/go/storage"
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/ptypes"
 	"github.com/googleapis/google-cloud-go-testing/storage/stiface"
+	"go.opencensus.io/stats"
+	"go.opencensus.io/stats/view"
+	"google.golang.org/api/iterator"
 
 	"go.chromium.org/goma/server/log"
 	cmdpb "go.chromium.org/goma/server/proto/command"
 )
 
+var (
+	pubsubErrors = stats.Int64(
+		"go.chromium.org/goma/command/configmap.pubsub-error",
+		"configmap pubsub error",
+		stats.UnitDimensionless)
+
+	// DefaultViews are the default views provided by this package.
+	// You need to register the view for data to actually be collected.
+	DefaultViews = []*view.View{
+		{
+			Description: "configmap pubsub error",
+			Measure:     pubsubErrors,
+			Aggregation: view.LastValue(),
+		},
+	}
+)
+
 // ConfigMapLoader loads toolchain_config config map.
 //
 // ConfigMap provides Watcher, Seqs, Bucket and RuntimeConfigs.
@@ -44,7 +63,7 @@
 // ConfigMap is an interface to access toolchain config map.
 type ConfigMap interface {
 	// Watcher returns config map watcher.
-	Watcher(ctx context.Context) (ConfigMapWatcher, error)
+	Watcher(ctx context.Context) ConfigMapWatcher
 
 	// Seqs returns a map of config name to sequence.
 	Seqs(ctx context.Context) (map[string]string, error)
@@ -178,6 +197,34 @@
 	return w.s.Delete(ctx)
 }
 
+type configMapBucketPoller struct {
+	baseDelay time.Duration
+	done      chan bool
+}
+
+func (w configMapBucketPoller) Next(ctx context.Context) error {
+	logger := log.FromContext(ctx)
+	dur := time.Duration(float64(w.baseDelay) * (1 + 0.2*(rand.Float64()*2-1)))
+	logger.Infof("poll wait %s", dur)
+	select {
+	case <-ctx.Done():
+		return ctx.Err()
+	case <-w.done:
+		return errors.New("poller closed")
+	case <-time.After(dur):
+		// trigger to load seqs, but loader might detect no seq updates.
+		return nil
+	}
+}
+
+func (w configMapBucketPoller) Close() error {
+	ctx := context.Background()
+	logger := log.FromContext(ctx)
+	logger.Infof("poller close")
+	close(w.done)
+	return nil
+}
+
 func (c ConfigMapBucket) configMap(ctx context.Context) (*cmdpb.ConfigMap, error) {
 	bucket, obj, err := splitGCSPath(c.URI)
 	if err != nil {
@@ -221,7 +268,23 @@
 
 var storageNotification = cloudStorageNotification
 
-func (c ConfigMapBucket) Watcher(ctx context.Context) (ConfigMapWatcher, error) {
+func (c ConfigMapBucket) Watcher(ctx context.Context) ConfigMapWatcher {
+	logger := log.FromContext(ctx)
+	w, err := c.pubsubWatcher(ctx)
+	if err == nil {
+		stats.Record(ctx, pubsubErrors.M(0))
+		logger.Infof("use pubsub watcher")
+		return w
+	}
+	stats.Record(ctx, pubsubErrors.M(1))
+	logger.Errorf("failed to use pubsub watcher: %v", err)
+	return configMapBucketPoller{
+		baseDelay: 1 * time.Hour,
+		done:      make(chan bool),
+	}
+}
+
+func (c ConfigMapBucket) pubsubWatcher(ctx context.Context) (ConfigMapWatcher, error) {
 	bucket, _, err := splitGCSPath(c.URI)
 	if err != nil {
 		return nil, err
@@ -257,6 +320,11 @@
 		logger.Infof("subscriber:%s not found. creating", c.SubscriberID)
 		subscription, err = c.PubsubClient.CreateSubscription(ctx, c.SubscriberID, pubsub.SubscriptionConfig{
 			Topic: topic,
+			// experimental config.
+			// minimum is 1 day
+			// +12 hours margin, to cover summar time switch (+1 hour)
+			// b/112820308
+			ExpirationPolicy: 36 * time.Hour,
 		})
 		if err != nil {
 			return nil, fmt.Errorf("create subscription:%s err:%v", c.SubscriberID, err)
diff --git a/command/descriptor/descriptor.go b/command/descriptor/descriptor.go
index be043c8..9bb076f 100644
--- a/command/descriptor/descriptor.go
+++ b/command/descriptor/descriptor.go
@@ -529,6 +529,12 @@
 				return nil, fmt.Errorf("target %s [%s]: %v", c.Filename, sha256, err)
 			}
 		}
+	case "dartanalyzer":
+		v, t, err = dartAnalyzerVersionTarget(c.Filename, c.Runner)
+		if err != nil {
+			return nil, err
+		}
+
 	default:
 		// subprogram would not have version,target (?)
 	}
@@ -706,3 +712,25 @@
 	}
 	return ClangClTarget(out)
 }
+
+// DartAnalyzerVersionTarget returns the dartanalyzer's version and target from
+// output of `dartanalyzer --version`
+func DartAnalyzerVersionTarget(out []byte) (string, string, error) {
+	// output should be like
+	// `dartanalyzer version 2.1.1-dev.1.0`.
+	const dartanalyzerPrefix = "dartanalyzer version "
+	output := strings.TrimSpace(string(out))
+	if !strings.HasPrefix(output, dartanalyzerPrefix) {
+		return "", "", fmt.Errorf("failed to parse dartanalyzer version: %q", output)
+	}
+	// dartanalyzer does not have a target. Always return linux here.
+	return output[len(dartanalyzerPrefix):], "x86_64-unknown-linux-gnu", nil
+}
+
+func dartAnalyzerVersionTarget(cmd string, runner Runner) (string, string, error) {
+	out, err := runner(cmd, "--version")
+	if err != nil {
+		return "", "", fmt.Errorf("failed to take dartanalyzer version and target: %v", err)
+	}
+	return DartAnalyzerVersionTarget(out)
+}
diff --git a/command/descriptor/descriptor_test.go b/command/descriptor/descriptor_test.go
index 8c97244..1c4008c 100644
--- a/command/descriptor/descriptor_test.go
+++ b/command/descriptor/descriptor_test.go
@@ -338,6 +338,46 @@
 	}
 }
 
+func TestDartAnalyzerVersionTarget(t *testing.T) {
+	for _, tc := range []struct {
+		out     string
+		version string
+		target  string
+	}{
+		{
+			out:     "dartanalyzer version 2.1.1-dev.1.0\n",
+			version: "2.1.1-dev.1.0",
+			target:  "x86_64-unknown-linux-gnu",
+		},
+		{
+			out:     "dartanalyzer version 2.5.0-edge.2b3336b51eda58fe049c8a8d81b7576aaa98c218\n",
+			version: "2.5.0-edge.2b3336b51eda58fe049c8a8d81b7576aaa98c218",
+			target:  "x86_64-unknown-linux-gnu",
+		},
+	} {
+		version, target, err := DartAnalyzerVersionTarget([]byte(tc.out))
+		if err != nil {
+			t.Errorf("expect no error from test case %v+, got an error: %v", tc, err)
+		}
+		if version != tc.version {
+			t.Errorf("expect version %q, got %q", tc.version, version)
+		}
+		if target != tc.target {
+			t.Errorf("expect target %q, got %q", tc.target, target)
+		}
+	}
+
+	// failure case
+	for _, tc := range []string{
+		"Dart VM version: 2.5.0-edge.2b3336b51eda58fe049c8a8d81b7576aaa98c218 (Wed Jul 17 09:11:15 2019 +0000) on \"linux_x64\"",
+	} {
+		_, _, err := DartAnalyzerVersionTarget([]byte(tc))
+		if err == nil {
+			t.Errorf("expect parsing error in %q, but got nil", tc)
+		}
+	}
+}
+
 func TestResolveSymlinks(t *testing.T) {
 	d := &Descriptor{
 		fname: "dummy",
diff --git a/exec/inventory.go b/exec/inventory.go
index d34d8da..48e5cb8 100644
--- a/exec/inventory.go
+++ b/exec/inventory.go
@@ -53,6 +53,13 @@
 	}
 )
 
+const (
+	maxKeyLength = 255
+	// Printable ASCII characters range from 0x20 to 0x7e
+	validKeyValueMin = 32
+	validKeyValueMax = 126
+)
+
 type resultValue string
 
 const (
@@ -72,6 +79,21 @@
 )
 
 func recordToolchainSelect(ctx context.Context, s selector, result resultValue) error {
+	// tag string cannot be over 255 or containing non-printable ascii characters.
+	// https://github.com/census-instrumentation/opencensus-go/blob/264a2a48d94c062252389fffbc308ba555e35166/tag/validate.go
+	tagNormalizer := func(tag string) string {
+		if len(tag) > maxKeyLength {
+			tag = tag[:maxKeyLength]
+		}
+		buf := []rune(tag)
+		for i, v := range buf {
+			if validKeyValueMin > v || v > validKeyValueMax {
+				buf[i] = '_'
+			}
+		}
+		return string(buf)
+	}
+
 	// selector string can be too long, more than tag value limit
 	// (255 ASCII characters).
 	// http://b/115441117
@@ -80,13 +102,10 @@
 	fmt.Fprintf(&buf, " t:%s", s.Target)
 	fmt.Fprintf(&buf, " b:%s", s.BinaryHash)
 	fmt.Fprintf(&buf, " v:%s", s.Version)
-	sval := buf.String()
-	if len(sval) > 255 {
-		sval = sval[:255]
-	}
+
 	ctx, err := tag.New(ctx,
-		tag.Upsert(selectorKey, sval),
-		tag.Upsert(resultKey, string(result)))
+		tag.Upsert(selectorKey, tagNormalizer(buf.String())),
+		tag.Upsert(resultKey, tagNormalizer(string(result))))
 	if err != nil {
 		return err
 	}
@@ -263,7 +282,7 @@
 	record := func(ctx context.Context, s selector, result resultValue) {
 		err := recordToolchainSelect(ctx, s, result)
 		if err != nil {
-			logger.Fatalf("failed to record stats: %s=%s", s, result)
+			logger.Errorf("failed to record stats: %s=%s, err: %v", s, result, err)
 		}
 	}
 
diff --git a/go.mod b/go.mod
index da967e7..3490d9b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@
 go 1.12
 
 require (
-	cloud.google.com/go v0.43.0
+	cloud.google.com/go v0.44.0
 	contrib.go.opencensus.io/exporter/stackdriver v0.12.2
 	github.com/bazelbuild/remote-apis v0.0.0-20190606163526-a5c577357528
 	github.com/fsnotify/fsnotify v1.4.7
@@ -31,8 +31,8 @@
 	golang.org/x/net v0.0.0-20190620200207-3b0461eec859
 	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
-	google.golang.org/api v0.7.0
-	google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610
-	google.golang.org/grpc v1.22.0
+	google.golang.org/api v0.9.0
+	google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64
+	google.golang.org/grpc v1.23.0
 	honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b // indirect
 )
diff --git a/go.sum b/go.sum
index 6c2ca7f..8ce04e8 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,8 @@
 cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
 cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w=
 cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
+cloud.google.com/go v0.44.0 h1:0BWXxb/yzTc5MjzcLfBceY2xuwawl5cIbCC7qsLuktA=
+cloud.google.com/go v0.44.0/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1 h1:Dll2uFfOVI3fa8UzsHyP6z0M6fEc9ZTAMo+Y3z282Xg=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.2 h1:jU1p9F07ASK11wYgSTPKtFlTvTtCDj6R1d3nRt0ZHDE=
@@ -287,6 +289,9 @@
 google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
 google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -320,6 +325,8 @@
 google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU=
 google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -333,6 +340,10 @@
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
 google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
diff --git a/remoteexec/client.go b/remoteexec/client.go
index 3e3f991..14f7ffc 100644
--- a/remoteexec/client.go
+++ b/remoteexec/client.go
@@ -182,8 +182,9 @@
 	type responseStream interface {
 		Recv() (*lpb.Operation, error)
 	}
+	pctx := ctx
 	err := rpc.Retry{}.Do(ctx, func() error {
-		ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
+		ctx, cancel := context.WithTimeout(pctx, 1*time.Minute)
 		defer cancel()
 		var stream responseStream
 		var err error
@@ -226,7 +227,7 @@
 				logger.Errorf("%s", err)
 				return err
 			}
-			return erespErr(ctx, resp)
+			return erespErr(pctx, resp)
 		}
 	})
 	recordFinish()
@@ -264,6 +265,19 @@
 		// codes.Unavailable, so that rpc.Retry will retry.
 		st.Code = int32(codes.Unavailable)
 		return status.FromProto(st).Err()
+
+	case codes.Aborted:
+		if ctx.Err() == nil {
+			// ctx is not cancelled, but returned
+			// code = Aborted, context canceled
+			// in this case, it would be retriable.
+			logger.Warnf("execute reponse: aborted %s, but ctx is still active", st)
+			st = proto.Clone(st).(*spb.Status)
+			// codes.Unavailable, so that rpc.Retry will retry.
+			st.Code = int32(codes.Unavailable)
+			return status.FromProto(st).Err()
+		}
+		fallthrough
 	default:
 		logger.Errorf("execute response: error %s", st)
 	}
diff --git a/server/process_collector.go b/server/process_collector.go
index 6d2d6ef..384645f 100644
--- a/server/process_collector.go
+++ b/server/process_collector.go
@@ -13,7 +13,7 @@
 	"io"
 	"io/ioutil"
 	"os"
-	"runtime"
+	"runtime/debug"
 	"strconv"
 	"sync/atomic"
 	"time"
@@ -53,7 +53,7 @@
 			Aggregation: view.LastValue(),
 		},
 		{
-			Name:        "go.chromium.org/goma/server/server/process-virtual-memroy",
+			Name:        "go.chromium.org/goma/server/server/process-virtual-memory",
 			Description: "Virtual memory size",
 			Measure:     virtualMemorySize,
 			Aggregation: view.LastValue(),
@@ -85,7 +85,7 @@
 	logger.Infof("GC start: rss=%d", rss)
 	select {
 	case gcSema <- true:
-		runtime.GC()
+		debug.FreeOSMemory()
 		<-gcSema
 	default:
 		logger.Infof("GC already running")
diff --git a/server/process_collector_test.go b/server/process_collector_test.go
index e94f1d1..0ef11a9 100644
--- a/server/process_collector_test.go
+++ b/server/process_collector_test.go
@@ -79,7 +79,7 @@
 		t.Errorf("parseStatMemory(ctx, procSelfStat)=_, _, %v", err)
 	}
 	if got, want := vsize, int64(8044544); got != want {
-		t.Errorf("parseStatMemroy(ctx, procSelfStat).vss=%d; want=%d", got, want)
+		t.Errorf("parseStatMemory(ctx, procSelfStat).vss=%d; want=%d", got, want)
 	}
 	if got, want := rss, int64(169*os.Getpagesize()); got != want {
 		t.Errorf("parseStatMemory(ctx, procSelfStat).rss=%d; want=%d", got, want)