buildlet: add context argument to most of the remaining Client methods

(And update all the callers in the tree.)

Updates golang/go#35707

Change-Id: I54769bbe374f31ae1dd07776b27818db91ce8c70
Reviewed-on: https://go-review.googlesource.com/c/build/+/208157
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/buildlet/buildletclient.go b/buildlet/buildletclient.go
index dc2cf1a..a6f0d49 100644
--- a/buildlet/buildletclient.go
+++ b/buildlet/buildletclient.go
@@ -323,7 +323,7 @@
 			return
 		case <-time.After(10 * time.Second):
 			t0 := time.Now()
-			if _, err := c.Status(); err != nil {
+			if _, err := c.Status(context.Background()); err != nil {
 				failInARow++
 				if failInARow == 3 {
 					log.Printf("Buildlet %v failed three heartbeats; final error: %v", c, err)
@@ -406,12 +406,12 @@
 // If dir is empty, they're placed at the root of the buildlet's work directory.
 // The dir is created if necessary.
 // The Reader must be of a tar.gz file.
-func (c *Client) PutTar(r io.Reader, dir string) error {
+func (c *Client) PutTar(ctx context.Context, r io.Reader, dir string) error {
 	req, err := http.NewRequest("PUT", c.URL()+"/writetgz?dir="+url.QueryEscape(dir), r)
 	if err != nil {
 		return err
 	}
-	return c.doOK(req)
+	return c.doOK(req.WithContext(ctx))
 }
 
 // PutTarFromURL tells the buildlet to download the tar.gz file from tarURL
@@ -419,7 +419,7 @@
 // If dir is empty, they're placed at the root of the buildlet's work directory.
 // The dir is created if necessary.
 // The url must be of a tar.gz file.
-func (c *Client) PutTarFromURL(tarURL, dir string) error {
+func (c *Client) PutTarFromURL(ctx context.Context, tarURL, dir string) error {
 	form := url.Values{
 		"url": {tarURL},
 	}
@@ -428,11 +428,11 @@
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	return c.doOK(req)
+	return c.doOK(req.WithContext(ctx))
 }
 
 // Put writes the provided file to path (relative to workdir) and sets mode.
-func (c *Client) Put(r io.Reader, path string, mode os.FileMode) error {
+func (c *Client) Put(ctx context.Context, r io.Reader, path string, mode os.FileMode) error {
 	param := url.Values{
 		"path": {path},
 		"mode": {fmt.Sprint(int64(mode))},
@@ -441,7 +441,7 @@
 	if err != nil {
 		return err
 	}
-	return c.doOK(req)
+	return c.doOK(req.WithContext(ctx))
 }
 
 // GetTar returns a .tar.gz stream of the given directory, relative to the buildlet's work dir.
@@ -614,7 +614,7 @@
 }
 
 // RemoveAll deletes the provided paths, relative to the work directory.
-func (c *Client) RemoveAll(paths ...string) error {
+func (c *Client) RemoveAll(ctx context.Context, paths ...string) error {
 	if len(paths) == 0 {
 		return nil
 	}
@@ -624,7 +624,7 @@
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	return c.doOK(req)
+	return c.doOK(req.WithContext(ctx))
 }
 
 // DestroyVM shuts down the buildlet and destroys the VM instance.
@@ -681,7 +681,7 @@
 }
 
 // Status returns an Status value describing this buildlet.
-func (c *Client) Status() (Status, error) {
+func (c *Client) Status(ctx context.Context) (Status, error) {
 	select {
 	case <-c.peerDead:
 		return Status{}, c.deadErr
@@ -692,6 +692,7 @@
 	if err != nil {
 		return Status{}, err
 	}
+	req = req.WithContext(ctx)
 	resp, err := c.doHeaderTimeout(req, 10*time.Second) // plenty of time
 	if err != nil {
 		return Status{}, err
@@ -712,11 +713,12 @@
 }
 
 // WorkDir returns the absolute path to the buildlet work directory.
-func (c *Client) WorkDir() (string, error) {
+func (c *Client) WorkDir(ctx context.Context) (string, error) {
 	req, err := http.NewRequest("GET", c.URL()+"/workdir", nil)
 	if err != nil {
 		return "", err
 	}
+	req = req.WithContext(ctx)
 	resp, err := c.doHeaderTimeout(req, 10*time.Second) // plenty of time
 	if err != nil {
 		return "", err
@@ -800,7 +802,7 @@
 // ListDir lists the contents of a directory.
 // The fn callback is run for each entry.
 // The directory dir itself is not included.
-func (c *Client) ListDir(dir string, opts ListDirOpts, fn func(DirEntry)) error {
+func (c *Client) ListDir(ctx context.Context, dir string, opts ListDirOpts, fn func(DirEntry)) error {
 	param := url.Values{
 		"dir":       {dir},
 		"recursive": {fmt.Sprint(opts.Recursive)},
@@ -811,7 +813,7 @@
 	if err != nil {
 		return err
 	}
-	resp, err := c.do(req)
+	resp, err := c.do(req.WithContext(ctx))
 	if err != nil {
 		return err
 	}
diff --git a/cmd/coordinator/coordinator.go b/cmd/coordinator/coordinator.go
index 1f9400a..c3de70c 100644
--- a/cmd/coordinator/coordinator.go
+++ b/cmd/coordinator/coordinator.go
@@ -1866,7 +1866,7 @@
 
 	if st.useSnapshot() {
 		sp := st.CreateSpan("write_snapshot_tar")
-		if err := bc.PutTarFromURL(st.SnapshotURL(buildEnv), "go"); err != nil {
+		if err := bc.PutTarFromURL(st.ctx, st.SnapshotURL(buildEnv), "go"); err != nil {
 			return sp.Done(fmt.Errorf("failed to put snapshot to buildlet: %v", err))
 		}
 		sp.Done(nil)
@@ -2217,7 +2217,7 @@
 func (st *buildStatus) writeGoSourceTo(bc *buildlet.Client) error {
 	// Write the VERSION file.
 	sp := st.CreateSpan("write_version_tar")
-	if err := bc.PutTar(buildgo.VersionTgz(st.Rev), "go"); err != nil {
+	if err := bc.PutTar(st.ctx, buildgo.VersionTgz(st.Rev), "go"); err != nil {
 		return sp.Done(fmt.Errorf("writing VERSION tgz: %v", err))
 	}
 
@@ -2226,7 +2226,7 @@
 		return err
 	}
 	sp = st.CreateSpan("write_go_src_tar")
-	if err := bc.PutTar(srcTar, "go"); err != nil {
+	if err := bc.PutTar(st.ctx, srcTar, "go"); err != nil {
 		return sp.Done(fmt.Errorf("writing tarball from Gerrit: %v", err))
 	}
 	return sp.Done(nil)
@@ -2239,12 +2239,12 @@
 	}
 	const bootstrapDir = "go1.4" // might be newer; name is the default
 	sp := st.CreateSpan("write_go_bootstrap_tar")
-	return sp.Done(st.bc.PutTarFromURL(u, bootstrapDir))
+	return sp.Done(st.bc.PutTarFromURL(st.ctx, u, bootstrapDir))
 }
 
 func (st *buildStatus) cleanForSnapshot(bc *buildlet.Client) error {
 	sp := st.CreateSpan("clean_for_snapshot")
-	return sp.Done(bc.RemoveAll(
+	return sp.Done(bc.RemoveAll(st.ctx,
 		"go/doc/gopher",
 		"go/pkg/bootstrap",
 	))
@@ -2257,7 +2257,7 @@
 	// a couple times at 1 minute. Some buildlets might be far
 	// away on the network, so be more lenient. The timeout mostly
 	// is here to prevent infinite hangs.
-	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	ctx, cancel := context.WithTimeout(st.ctx, 5*time.Minute)
 	defer cancel()
 
 	tsp := st.CreateSpan("fetch_snapshot_reader_from_buildlet")
@@ -2295,7 +2295,7 @@
 }
 
 func (st *buildStatus) distTestList() (names []string, remoteErr, err error) {
-	workDir, err := st.bc.WorkDir()
+	workDir, err := st.bc.WorkDir(st.ctx)
 	if err != nil {
 		err = fmt.Errorf("distTestList, WorkDir: %v", err)
 		return
@@ -2441,7 +2441,7 @@
 func (st *buildStatus) runSubrepoTests() (remoteErr, err error) {
 	st.LogEventTime("fetching_subrepo", st.SubName)
 
-	workDir, err := st.bc.WorkDir()
+	workDir, err := st.bc.WorkDir(st.ctx)
 	if err != nil {
 		err = fmt.Errorf("error discovering workdir for helper %s: %v", st.bc.IPPort(), err)
 		return nil, err
@@ -2451,7 +2451,7 @@
 
 	// Check out the provided sub-repo to the buildlet's workspace.
 	// Need to do this first, so we can run go env GOMOD in it.
-	err = buildgo.FetchSubrepo(st, st.bc, st.SubName, st.SubRev)
+	err = buildgo.FetchSubrepo(st.ctx, st, st.bc, st.SubName, st.SubRev)
 	if err != nil {
 		return nil, err
 	}
@@ -2492,7 +2492,7 @@
 		// Look for inner modules, in order to test them too. See golang.org/issue/32528.
 		repoPath := importPathOfRepo(st.SubName)
 		sp := st.CreateSpan("listing_subrepo_modules", st.SubName)
-		err = st.bc.ListDir("gopath/src/"+repoPath, buildlet.ListDirOpts{Recursive: true}, func(e buildlet.DirEntry) {
+		err = st.bc.ListDir(st.ctx, "gopath/src/"+repoPath, buildlet.ListDirOpts{Recursive: true}, func(e buildlet.DirEntry) {
 			goModFile := path.Base(e.Name()) == "go.mod" && !e.IsDir()
 			if !goModFile {
 				return
@@ -2645,7 +2645,7 @@
 	// fetch checks out the provided sub-repo to the buildlet's workspace.
 	fetch := func(repo, rev string) error {
 		fetched[repo] = true
-		return buildgo.FetchSubrepo(st, st.bc, repo, rev)
+		return buildgo.FetchSubrepo(st.ctx, st, st.bc, repo, rev)
 	}
 
 	// findDeps uses 'go list' on the checked out repo to find its
@@ -2855,7 +2855,7 @@
 		if rev == "" {
 			rev = "master" // should happen rarely; ok if it does.
 		}
-		b, err := st.goBuilder().EnumerateBenchmarks(st.bc, rev, st.trySet.affectedPkgs())
+		b, err := st.goBuilder().EnumerateBenchmarks(st.ctx, st.bc, rev, st.trySet.affectedPkgs())
 		sp.Done(err)
 		if err == nil {
 			benches = b
@@ -2871,7 +2871,7 @@
 	st.LogEventTime("starting_tests", fmt.Sprintf("%d tests", len(set.items)))
 	startTime := time.Now()
 
-	workDir, err := st.bc.WorkDir()
+	workDir, err := st.bc.WorkDir(st.ctx)
 	if err != nil {
 		return nil, fmt.Errorf("error discovering workdir for main buildlet, %s: %v", st.bc.Name(), err)
 	}
@@ -2916,11 +2916,11 @@
 					defer st.LogEventTime("DEV_HELPER_SLEEP", bc.Name())
 				}
 				st.LogEventTime("got_empty_test_helper", bc.String())
-				if err := bc.PutTarFromURL(st.SnapshotURL(buildEnv), "go"); err != nil {
+				if err := bc.PutTarFromURL(st.ctx, st.SnapshotURL(buildEnv), "go"); err != nil {
 					log.Printf("failed to extract snapshot for helper %s: %v", bc.Name(), err)
 					return
 				}
-				workDir, err := bc.WorkDir()
+				workDir, err := bc.WorkDir(st.ctx)
 				if err != nil {
 					log.Printf("error discovering workdir for helper %s: %v", bc.Name(), err)
 					return
diff --git a/cmd/coordinator/remote.go b/cmd/coordinator/remote.go
index 6058c6d..ec461ae 100644
--- a/cmd/coordinator/remote.go
+++ b/cmd/coordinator/remote.go
@@ -628,7 +628,7 @@
 			err = <-errc
 		}()
 	}
-	workDir, err := rb.buildlet.WorkDir()
+	workDir, err := rb.buildlet.WorkDir(ctx)
 	if err != nil {
 		fmt.Fprintf(s, "Error getting WorkDir: %v\n", err)
 		return
diff --git a/cmd/coordinator/reverse.go b/cmd/coordinator/reverse.go
index d1fefcc..407c961 100644
--- a/cmd/coordinator/reverse.go
+++ b/cmd/coordinator/reverse.go
@@ -250,7 +250,7 @@
 	b.inUseTime = time.Now()
 	res := make(chan error, 1)
 	go func() {
-		_, err := b.client.Status()
+		_, err := b.client.Status(context.Background())
 		res <- err
 	}()
 	p.mu.Unlock()
@@ -328,7 +328,7 @@
 func (p *reverseBuildletPool) cleanedBuildlet(b *buildlet.Client, lg logger) (*buildlet.Client, error) {
 	// Clean up any files from previous builds.
 	sp := lg.CreateSpan("clean_buildlet", b.String())
-	err := b.RemoveAll(".")
+	err := b.RemoveAll(context.Background(), ".")
 	sp.Done(err)
 	if err != nil {
 		b.Close()
@@ -598,7 +598,7 @@
 		}
 	}()
 	tstatus := time.Now()
-	status, err := client.Status()
+	status, err := client.Status(context.Background())
 	if err != nil {
 		log.Printf("Reverse connection %s/%s for %s did not answer status after %v: %v",
 			hostname, r.RemoteAddr, hostType, time.Since(tstatus), err)
diff --git a/cmd/debugnewvm/debugnewvm.go b/cmd/debugnewvm/debugnewvm.go
index c82ba8b..68f0285 100644
--- a/cmd/debugnewvm/debugnewvm.go
+++ b/cmd/debugnewvm/debugnewvm.go
@@ -124,7 +124,7 @@
 	if err != nil {
 		log.Fatalf("StartNewVM: %v", err)
 	}
-	dir, err := bc.WorkDir()
+	dir, err := bc.WorkDir(ctx)
 	log.Printf("WorkDir: %v, %v", dir, err)
 
 	if *sleepSec > 0 {
@@ -146,7 +146,7 @@
 		if u := bconf.GoBootstrapURL(env); u != "" {
 			log.Printf("Pushing 'go1.4' Go bootstrap dir ...")
 			const bootstrapDir = "go1.4" // might be newer; name is the default
-			if err := bc.PutTarFromURL(u, bootstrapDir); err != nil {
+			if err := bc.PutTarFromURL(ctx, u, bootstrapDir); err != nil {
 				bc.Close()
 				log.Fatalf("Putting Go bootstrap: %v", err)
 			}
@@ -155,13 +155,13 @@
 		// Push Go code
 		log.Printf("Pushing 'go' dir...")
 		goTarGz := "https://go.googlesource.com/go/+archive/" + *buildRev + ".tar.gz"
-		if err := bc.PutTarFromURL(goTarGz, "go"); err != nil {
+		if err := bc.PutTarFromURL(ctx, goTarGz, "go"); err != nil {
 			bc.Close()
 			log.Fatalf("Putting go code: %v", err)
 		}
 
 		// Push a synthetic VERSION file to prevent git usage:
-		if err := bc.PutTar(buildgo.VersionTgz(*buildRev), "go"); err != nil {
+		if err := bc.PutTar(ctx, buildgo.VersionTgz(*buildRev), "go"); err != nil {
 			bc.Close()
 			log.Fatalf("Putting VERSION file: %v", err)
 		}
diff --git a/cmd/gomote/ls.go b/cmd/gomote/ls.go
index 7305fb7..fc8f4fe 100644
--- a/cmd/gomote/ls.go
+++ b/cmd/gomote/ls.go
@@ -5,6 +5,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"os"
@@ -44,7 +45,7 @@
 		Digest:    digest,
 		Skip:      strings.Split(skip, ","),
 	}
-	return bc.ListDir(dir, opts, func(bi buildlet.DirEntry) {
+	return bc.ListDir(context.Background(), dir, opts, func(bi buildlet.DirEntry) {
 		fmt.Fprintf(os.Stdout, "%s\n", bi)
 	})
 }
diff --git a/cmd/gomote/ping.go b/cmd/gomote/ping.go
index c876e5f..4232b93 100644
--- a/cmd/gomote/ping.go
+++ b/cmd/gomote/ping.go
@@ -5,6 +5,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"os"
@@ -29,13 +30,14 @@
 	if err != nil {
 		return err
 	}
-	wd, err := bc.WorkDir()
+	ctx := context.Background()
+	wd, err := bc.WorkDir(ctx)
 	if err != nil {
 		return err
 	}
 	if status {
 		fmt.Printf("workdir: %v\n", wd)
-		s, err := bc.Status()
+		s, err := bc.Status(ctx)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/gomote/push.go b/cmd/gomote/push.go
index abff220..3bb75be 100644
--- a/cmd/gomote/push.go
+++ b/cmd/gomote/push.go
@@ -9,6 +9,7 @@
 	"bufio"
 	"bytes"
 	"compress/gzip"
+	"context"
 	"crypto/sha1"
 	"errors"
 	"flag"
@@ -73,7 +74,8 @@
 			"go1.4/src", "go1.4/pkg",
 		},
 	}
-	if err := bc.ListDir(".", lsOpts, func(ent buildlet.DirEntry) {
+	ctx := context.Background()
+	if err := bc.ListDir(ctx, ".", lsOpts, func(ent buildlet.DirEntry) {
 		name := ent.Name()
 		if strings.HasPrefix(name, "go1.4/") {
 			haveGo14 = true
@@ -93,7 +95,7 @@
 			if dryRun {
 				log.Printf("(Dry-run) Would have pushed go1.4")
 			} else {
-				if err := bc.PutTarFromURL(u, "go1.4"); err != nil {
+				if err := bc.PutTarFromURL(ctx, u, "go1.4"); err != nil {
 					return err
 				}
 			}
@@ -231,7 +233,7 @@
 			log.Printf("(Dry-run) Would have deleted remote files: %q", withGo)
 		} else {
 			log.Printf("Deleting remote files: %q", withGo)
-			if err := bc.RemoveAll(withGo...); err != nil {
+			if err := bc.RemoveAll(ctx, withGo...); err != nil {
 				return fmt.Errorf("Deleting remote unwanted files: %v", err)
 			}
 		}
@@ -286,7 +288,7 @@
 			log.Printf("(Dry-run mode; not doing anything.")
 			return nil
 		}
-		if err := bc.PutTar(tgz, "go"); err != nil {
+		if err := bc.PutTar(ctx, tgz, "go"); err != nil {
 			return fmt.Errorf("writing tarball to buildlet: %v", err)
 		}
 	}
diff --git a/cmd/gomote/put.go b/cmd/gomote/put.go
index 89880ff..39bcece 100644
--- a/cmd/gomote/put.go
+++ b/cmd/gomote/put.go
@@ -6,6 +6,7 @@
 
 import (
 	"archive/tar"
+	"context"
 	"errors"
 	"flag"
 	"fmt"
@@ -51,11 +52,13 @@
 		return err
 	}
 
+	ctx := context.Background()
+
 	if tarURL != "" {
 		if fs.NArg() != 1 {
 			fs.Usage()
 		}
-		if err := bc.PutTarFromURL(tarURL, dir); err != nil {
+		if err := bc.PutTarFromURL(ctx, tarURL, dir); err != nil {
 			return err
 		}
 		if rev != "" {
@@ -69,7 +72,7 @@
 			}, int64(version.Len()), version)
 			tgz := vtar.TarGz()
 			defer tgz.Close()
-			return bc.PutTar(tgz, dir)
+			return bc.PutTar(ctx, tgz, dir)
 		}
 		return nil
 	}
@@ -83,7 +86,7 @@
 		defer f.Close()
 		tgz = f
 	}
-	return bc.PutTar(tgz, dir)
+	return bc.PutTar(ctx, tgz, dir)
 }
 
 // put go1.4 in the workdir
@@ -108,7 +111,8 @@
 		fmt.Printf("No GoBootstrapURL defined for %q; ignoring. (may be baked into image)\n", name)
 		return nil
 	}
-	return bc.PutTarFromURL(u, "go1.4")
+	ctx := context.Background()
+	return bc.PutTarFromURL(ctx, u, "go1.4")
 }
 
 // put single file
@@ -169,5 +173,6 @@
 		dest = filepath.Base(src)
 	}
 
-	return bc.Put(r, dest, mode)
+	ctx := context.Background()
+	return bc.Put(ctx, r, dest, mode)
 }
diff --git a/cmd/gomote/rm.go b/cmd/gomote/rm.go
index baa0bb7..7350f9a 100644
--- a/cmd/gomote/rm.go
+++ b/cmd/gomote/rm.go
@@ -5,6 +5,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"os"
@@ -29,5 +30,6 @@
 	if err != nil {
 		return err
 	}
-	return bc.RemoveAll(args...)
+	ctx := context.Background()
+	return bc.RemoveAll(ctx, args...)
 }
diff --git a/cmd/perfrun/perfrun.go b/cmd/perfrun/perfrun.go
index 6b86865..afe019c 100644
--- a/cmd/perfrun/perfrun.go
+++ b/cmd/perfrun/perfrun.go
@@ -38,12 +38,13 @@
 // the standard benchmark format
 // (https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md).
 func runBench(out io.Writer, bench, src string, commits []string) error {
+	ctx := context.TODO()
 	bc, err := namedClient(*buildletBench)
 	if err != nil {
 		return err
 	}
 	log.Printf("Using buildlet %s", bc.RemoteName())
-	workDir, err := bc.WorkDir()
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		log.Printf("Getting WorkDir: %v", err)
 		return err
@@ -52,14 +53,14 @@
 		log.Printf("Installing prebuilt rev %s", rev)
 		dir := fmt.Sprintf("go-%s", rev)
 		// Copy pre-built trees
-		if err := bc.PutTarFromURL(buildEnv.SnapshotURL(src, rev), dir); err != nil {
+		if err := bc.PutTarFromURL(ctx, buildEnv.SnapshotURL(src, rev), dir); err != nil {
 			log.Printf("failed to extract snapshot for %s: %v", rev, err)
 			return err
 		}
 		// Build binaries
 		log.Printf("Building bench binary for rev %s", rev)
 		var buf bytes.Buffer
-		remoteErr, err := bc.Exec(context.Background(), path.Join(dir, "bin", "go"), buildlet.ExecOpts{
+		remoteErr, err := bc.Exec(ctx, path.Join(dir, "bin", "go"), buildlet.ExecOpts{
 			Output:   &buf,
 			ExtraEnv: []string{"GOROOT=" + path.Join(workDir, dir)},
 			Args:     []string{"test", "-c"},
diff --git a/cmd/release/release.go b/cmd/release/release.go
index fe71eab..eda9046 100644
--- a/cmd/release/release.go
+++ b/cmd/release/release.go
@@ -235,6 +235,7 @@
 }
 
 func (b *Build) make() error {
+	ctx := context.TODO()
 	bc, ok := dashboard.Builders[b.Builder]
 	if !ok {
 		return fmt.Errorf("unknown builder: %v", bc)
@@ -251,7 +252,7 @@
 	}
 	defer client.Close()
 
-	work, err := client.WorkDir()
+	work, err := client.WorkDir(ctx)
 	if err != nil {
 		return err
 	}
@@ -269,14 +270,14 @@
 			b.logf("failed to open tarball %q: %v", *tarball, err)
 			return err
 		}
-		if err := client.PutTar(tarFile, goDir); err != nil {
+		if err := client.PutTar(ctx, tarFile, goDir); err != nil {
 			b.logf("failed to put tarball %q into dir %q: %v", *tarball, goDir, err)
 			return err
 		}
 		tarFile.Close()
 	} else {
 		tar := "https://go.googlesource.com/go/+archive/" + *rev + ".tar.gz"
-		if err := client.PutTarFromURL(tar, goDir); err != nil {
+		if err := client.PutTarFromURL(ctx, tar, goDir); err != nil {
 			b.logf("failed to put tarball %q into dir %q: %v", tar, goDir, err)
 			return err
 		}
@@ -299,7 +300,7 @@
 		}
 		dir := goPath + "/src/golang.org/x/" + r.repo
 		tar := "https://go.googlesource.com/" + r.repo + "/+archive/" + r.rev + ".tar.gz"
-		if err := client.PutTarFromURL(tar, dir); err != nil {
+		if err := client.PutTarFromURL(ctx, tar, dir); err != nil {
 			b.logf("failed to put tarball %q into dir %q: %v", tar, dir, err)
 			return err
 		}
@@ -307,19 +308,19 @@
 
 	if u := bc.GoBootstrapURL(buildEnv); u != "" && !b.Source {
 		b.logf("Installing go1.4.")
-		if err := client.PutTarFromURL(u, go14); err != nil {
+		if err := client.PutTarFromURL(ctx, u, go14); err != nil {
 			return err
 		}
 	}
 
 	// Write out version file.
 	b.logf("Writing VERSION file.")
-	if err := client.Put(strings.NewReader(*version), "go/VERSION", 0644); err != nil {
+	if err := client.Put(ctx, strings.NewReader(*version), "go/VERSION", 0644); err != nil {
 		return err
 	}
 
 	b.logf("Cleaning goroot (pre-build).")
-	if err := client.RemoveAll(addPrefix(goDir, preBuildCleanFiles)...); err != nil {
+	if err := client.RemoveAll(ctx, addPrefix(goDir, preBuildCleanFiles)...); err != nil {
 		return err
 	}
 
@@ -327,18 +328,18 @@
 		b.logf("Skipping build.")
 
 		// Remove unwanted top-level directories and verify only "go" remains:
-		if err := client.RemoveAll("tmp", "gocache"); err != nil {
+		if err := client.RemoveAll(ctx, "tmp", "gocache"); err != nil {
 			return err
 		}
-		if err := b.checkTopLevelDirs(client); err != nil {
+		if err := b.checkTopLevelDirs(ctx, client); err != nil {
 			return fmt.Errorf("verifying no unwanted top-level directories: %v", err)
 		}
-		if err := b.checkPerm(client); err != nil {
+		if err := b.checkPerm(ctx, client); err != nil {
 			return fmt.Errorf("verifying file permissions: %v", err)
 		}
 
 		finalFilename := *version + "." + b.String() + ".tar.gz"
-		return b.fetchTarball(client, finalFilename)
+		return b.fetchTarball(ctx, client, finalFilename)
 	}
 
 	// Set up build environment.
@@ -443,7 +444,7 @@
 	// Remove race detector *.syso files for other GOOS/GOARCHes (except for the source release).
 	if !b.Source {
 		okayRace := fmt.Sprintf("race_%s_%s.syso", b.OS, b.Arch)
-		err := client.ListDir(".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
+		err := client.ListDir(ctx, ".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
 			name := strings.TrimPrefix(ent.Name(), "go/")
 			if strings.HasPrefix(name, "src/runtime/race/race_") &&
 				strings.HasSuffix(name, ".syso") &&
@@ -457,12 +458,12 @@
 	}
 
 	b.logf("Cleaning goroot (post-build).")
-	if err := client.RemoveAll(addPrefix(goDir, postBuildCleanFiles)...); err != nil {
+	if err := client.RemoveAll(ctx, addPrefix(goDir, postBuildCleanFiles)...); err != nil {
 		return err
 	}
 	// Users don't need the api checker binary pre-built. It's
 	// used by tests, but all.bash builds it first.
-	if err := client.RemoveAll(b.toolDir() + "/api"); err != nil {
+	if err := client.RemoveAll(ctx, b.toolDir()+"/api"); err != nil {
 		return err
 	}
 	// Remove go/pkg/${GOOS}_${GOARCH}/cmd. This saves a bunch of
@@ -473,7 +474,7 @@
 	//
 	// Also remove go/pkg/${GOOS}_${GOARCH}_{dynlink,shared,testcshared_shared}
 	// per Issue 20038.
-	if err := client.RemoveAll(
+	if err := client.RemoveAll(ctx,
 		b.pkgDir()+"/cmd",
 		b.pkgDir()+"_dynlink",
 		b.pkgDir()+"_shared",
@@ -483,13 +484,13 @@
 	}
 
 	b.logf("Pushing and running releaselet.")
-	err = client.Put(strings.NewReader(releaselet), "releaselet.go", 0666)
+	err = client.Put(ctx, strings.NewReader(releaselet), "releaselet.go", 0666)
 	if err != nil {
 		return err
 	}
 	if err := runGo("run", "releaselet.go"); err != nil {
 		log.Printf("releaselet failed: %v", err)
-		client.ListDir(".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
+		client.ListDir(ctx, ".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
 			log.Printf("remote: %v", ent)
 		})
 		return err
@@ -544,23 +545,23 @@
 	// Need to delete everything except the final "go" directory,
 	// as we make the tarball relative to workdir.
 	b.logf("Cleaning workdir.")
-	if err := client.RemoveAll(cleanFiles...); err != nil {
+	if err := client.RemoveAll(ctx, cleanFiles...); err != nil {
 		return err
 	}
 
 	// And verify there's no other top-level stuff besides the "go" directory:
-	if err := b.checkTopLevelDirs(client); err != nil {
+	if err := b.checkTopLevelDirs(ctx, client); err != nil {
 		return fmt.Errorf("verifying no unwanted top-level directories: %v", err)
 	}
 
-	if err := b.checkPerm(client); err != nil {
+	if err := b.checkPerm(ctx, client); err != nil {
 		return fmt.Errorf("verifying file permissions: %v", err)
 	}
 
 	switch b.OS {
 	default:
 		untested := stagingFile(".tar.gz")
-		if err := b.fetchTarball(client, untested); err != nil {
+		if err := b.fetchTarball(ctx, client, untested); err != nil {
 			return fmt.Errorf("fetching and writing tarball: %v", err)
 		}
 		releases = append(releases, releaseFile{
@@ -584,7 +585,7 @@
 	} else {
 		if u := bc.GoBootstrapURL(buildEnv); u != "" {
 			b.logf("Installing go1.4 (second time, for all.bash).")
-			if err := client.PutTarFromURL(u, go14); err != nil {
+			if err := client.PutTarFromURL(ctx, u, go14); err != nil {
 				return err
 			}
 		}
@@ -595,7 +596,7 @@
 		if *watch && *target != "" {
 			execOut = io.MultiWriter(out, os.Stdout)
 		}
-		remoteErr, err := client.Exec(context.Background(), filepath.Join(goDir, bc.AllScript()), buildlet.ExecOpts{
+		remoteErr, err := client.Exec(ctx, filepath.Join(goDir, bc.AllScript()), buildlet.ExecOpts{
 			Output:   execOut,
 			ExtraEnv: env,
 			Args:     bc.AllScriptArgs(),
@@ -621,9 +622,9 @@
 
 // checkTopLevelDirs checks that all files under client's "."
 // ($WORKDIR) are are under "go/".
-func (b *Build) checkTopLevelDirs(client *buildlet.Client) error {
+func (b *Build) checkTopLevelDirs(ctx context.Context, client *buildlet.Client) error {
 	var badFileErr error // non-nil once an unexpected file/dir is found
-	if err := client.ListDir(".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
+	if err := client.ListDir(ctx, ".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
 		name := ent.Name()
 		if !(strings.HasPrefix(name, "go/") || strings.HasPrefix(name, `go\`)) {
 			b.logf("unexpected file: %q", name)
@@ -639,7 +640,7 @@
 
 // checkPerm checks that files in client's $WORKDIR/go directory
 // have expected permissions.
-func (b *Build) checkPerm(client *buildlet.Client) error {
+func (b *Build) checkPerm(ctx context.Context, client *buildlet.Client) error {
 	var badPermErr error // non-nil once an unexpected perm is found
 	checkPerm := func(ent buildlet.DirEntry, allowed ...string) {
 		for _, p := range allowed {
@@ -652,7 +653,7 @@
 			badPermErr = fmt.Errorf("unexpected file %q perm %q found", ent.Name(), ent.Perm())
 		}
 	}
-	if err := client.ListDir("go", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
+	if err := client.ListDir(ctx, "go", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
 		switch b.OS {
 		default:
 			checkPerm(ent, "drwxr-xr-x", "-rw-r--r--", "-rwxr-xr-x")
@@ -663,7 +664,7 @@
 		return err
 	}
 	if !b.Source {
-		if err := client.ListDir("go/bin", buildlet.ListDirOpts{}, func(ent buildlet.DirEntry) {
+		if err := client.ListDir(ctx, "go/bin", buildlet.ListDirOpts{}, func(ent buildlet.DirEntry) {
 			switch b.OS {
 			default:
 				checkPerm(ent, "-rwxr-xr-x")
@@ -677,9 +678,9 @@
 	return badPermErr
 }
 
-func (b *Build) fetchTarball(client *buildlet.Client, dest string) error {
+func (b *Build) fetchTarball(ctx context.Context, client *buildlet.Client, dest string) error {
 	b.logf("Downloading tarball.")
-	tgz, err := client.GetTar(context.Background(), ".")
+	tgz, err := client.GetTar(ctx, ".")
 	if err != nil {
 		return err
 	}
diff --git a/internal/buildgo/benchmarks.go b/internal/buildgo/benchmarks.go
index c540519..bc89223 100644
--- a/internal/buildgo/benchmarks.go
+++ b/internal/buildgo/benchmarks.go
@@ -41,13 +41,13 @@
 }
 
 // buildGo1 builds the Go 1 benchmarks.
-func buildGo1(conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer) (remoteErr, err error) {
-	workDir, err := bc.WorkDir()
+func buildGo1(ctx context.Context, conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer) (remoteErr, err error) {
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		return nil, err
 	}
 	var found bool
-	if err := bc.ListDir(path.Join(goroot, "test/bench/go1"), buildlet.ListDirOpts{}, func(e buildlet.DirEntry) {
+	if err := bc.ListDir(ctx, path.Join(goroot, "test/bench/go1"), buildlet.ListDirOpts{}, func(e buildlet.DirEntry) {
 		switch e.Name() {
 		case "go1.test", "go1.test.exe":
 			found = true
@@ -58,7 +58,7 @@
 	if found {
 		return nil, nil
 	}
-	return bc.Exec(context.TODO(), path.Join(goroot, "bin", "go"), buildlet.ExecOpts{
+	return bc.Exec(ctx, path.Join(goroot, "bin", "go"), buildlet.ExecOpts{
 		Output:   w,
 		ExtraEnv: []string{"GOROOT=" + conf.FilePathJoin(workDir, goroot)},
 		Args:     []string{"test", "-c"},
@@ -67,12 +67,12 @@
 }
 
 // buildPkg builds a package's benchmarks.
-func buildPkg(conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer, pkg, name string) (remoteErr, err error) {
-	workDir, err := bc.WorkDir()
+func buildPkg(ctx context.Context, conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer, pkg, name string) (remoteErr, err error) {
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		return nil, err
 	}
-	return bc.Exec(context.TODO(), path.Join(goroot, "bin", "go"), buildlet.ExecOpts{
+	return bc.Exec(ctx, path.Join(goroot, "bin", "go"), buildlet.ExecOpts{
 		Output:   w,
 		ExtraEnv: []string{"GOROOT=" + conf.FilePathJoin(workDir, goroot)},
 		Args:     []string{"test", "-c", "-o", conf.FilePathJoin(workDir, goroot, name), pkg},
@@ -80,17 +80,17 @@
 }
 
 // buildXBenchmark builds a benchmark from x/benchmarks.
-func buildXBenchmark(sl spanlog.Logger, conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer, rev, pkg, name string) (remoteErr, err error) {
-	workDir, err := bc.WorkDir()
+func buildXBenchmark(ctx context.Context, sl spanlog.Logger, conf *dashboard.BuildConfig, bc *buildlet.Client, goroot string, w io.Writer, rev, pkg, name string) (remoteErr, err error) {
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		return nil, err
 	}
-	if err := bc.ListDir("gopath/src/golang.org/x/benchmarks", buildlet.ListDirOpts{}, func(buildlet.DirEntry) {}); err != nil {
-		if err := FetchSubrepo(sl, bc, "benchmarks", rev); err != nil {
+	if err := bc.ListDir(ctx, "gopath/src/golang.org/x/benchmarks", buildlet.ListDirOpts{}, func(buildlet.DirEntry) {}); err != nil {
+		if err := FetchSubrepo(ctx, sl, bc, "benchmarks", rev); err != nil {
 			return nil, err
 		}
 	}
-	return bc.Exec(context.TODO(), path.Join(goroot, "bin/go"), buildlet.ExecOpts{
+	return bc.Exec(ctx, path.Join(goroot, "bin/go"), buildlet.ExecOpts{
 		Output: w,
 		ExtraEnv: []string{
 			"GOROOT=" + conf.FilePathJoin(workDir, goroot),
@@ -103,8 +103,8 @@
 // EnumerateBenchmarks returns a slice of the benchmarks to be run for the built Go distribution found in gb.Goroot.
 // If benchmarksRev is non-empty, it is the revision of x/benchmarks to check out for additional benchmarks.
 // pkgs contains a list of possibly duplicate packages that will be searched for benchmarks.
-func (gb GoBuilder) EnumerateBenchmarks(bc *buildlet.Client, benchmarksRev string, pkgs []string) ([]*BenchmarkItem, error) {
-	workDir, err := bc.WorkDir()
+func (gb GoBuilder) EnumerateBenchmarks(ctx context.Context, bc *buildlet.Client, benchmarksRev string, pkgs []string) ([]*BenchmarkItem, error) {
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		err = fmt.Errorf("buildBench, WorkDir: %v", err)
 		return nil, err
@@ -112,7 +112,7 @@
 
 	// Fetch x/benchmarks
 	if benchmarksRev != "" {
-		if err := FetchSubrepo(gb.Logger, bc, "benchmarks", benchmarksRev); err != nil {
+		if err := FetchSubrepo(ctx, gb.Logger, bc, "benchmarks", benchmarksRev); err != nil {
 			return nil, err
 		}
 	}
@@ -126,7 +126,7 @@
 			args:     []string{"-test.bench", re, "-test.benchmem"},
 			preamble: "pkg: test/bench/go1\n",
 			build: func(bc *buildlet.Client, goroot string, w io.Writer) (error, error) {
-				return buildGo1(gb.Conf, bc, goroot, w)
+				return buildGo1(ctx, gb.Conf, bc, goroot, w)
 			},
 		})
 	}
@@ -134,7 +134,7 @@
 	// Enumerate x/benchmarks
 	if benchmarksRev != "" {
 		var buf bytes.Buffer
-		remoteErr, err := bc.Exec(context.TODO(), path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{
+		remoteErr, err := bc.Exec(ctx, path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{
 			Output: &buf,
 			ExtraEnv: []string{
 				"GOROOT=" + gb.Conf.FilePathJoin(workDir, gb.Goroot),
@@ -153,7 +153,7 @@
 			name := "bench-" + path.Base(pkg) + ".exe"
 			out = append(out, &BenchmarkItem{
 				binary: name, args: nil, build: func(bc *buildlet.Client, goroot string, w io.Writer) (error, error) {
-					return buildXBenchmark(gb.Logger, gb.Conf, bc, goroot, w, benchmarksRev, pkg, name)
+					return buildXBenchmark(ctx, gb.Logger, gb.Conf, bc, goroot, w, benchmarksRev, pkg, name)
 				}})
 		}
 	}
@@ -188,7 +188,7 @@
 				dir:    path.Join(gb.Goroot, "src", pkg),
 				args:   []string{"-test.bench", ".", "-test.benchmem", "-test.run", "^$", "-test.benchtime", "100ms"},
 				build: func(bc *buildlet.Client, goroot string, w io.Writer) (error, error) {
-					return buildPkg(gb.Conf, bc, goroot, w, pkg, name)
+					return buildPkg(ctx, gb.Conf, bc, goroot, w, pkg, name)
 				}})
 		}
 	}
@@ -198,14 +198,14 @@
 // runOneBenchBinary runs a binary on the buildlet and writes its output to w with a trailing newline.
 //
 // TODO: this signature is too big. Make it a method of something?
-func runOneBenchBinary(conf *dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, goroot, dir, binaryPath string, args []string) (remoteErr, err error) {
+func runOneBenchBinary(ctx context.Context, conf *dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, goroot, dir, binaryPath string, args []string) (remoteErr, err error) {
 	defer w.Write([]byte{'\n'})
-	workDir, err := bc.WorkDir()
+	workDir, err := bc.WorkDir(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("runOneBenchBinary, WorkDir: %v", err)
 	}
 	// Some benchmarks need GOROOT so they can invoke cmd/go.
-	return bc.Exec(context.TODO(), binaryPath, buildlet.ExecOpts{
+	return bc.Exec(ctx, binaryPath, buildlet.ExecOpts{
 		Output: w,
 		Dir:    dir,
 		Args:   args,
@@ -228,16 +228,16 @@
 
 func buildRev(ctx context.Context, buildEnv *buildenv.Environment, sl spanlog.Logger, conf *dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, goroot string, br BuilderRev) error {
 	if br.SnapshotExists(context.TODO(), buildEnv) {
-		return bc.PutTarFromURL(br.SnapshotURL(buildEnv), goroot)
+		return bc.PutTarFromURL(ctx, br.SnapshotURL(buildEnv), goroot)
 	}
-	if err := bc.PutTar(VersionTgz(br.Rev), goroot); err != nil {
+	if err := bc.PutTar(ctx, VersionTgz(br.Rev), goroot); err != nil {
 		return err
 	}
 	srcTar, err := sourcecache.GetSourceTgz(sl, "go", br.Rev)
 	if err != nil {
 		return err
 	}
-	if err := bc.PutTar(srcTar, goroot); err != nil {
+	if err := bc.PutTar(ctx, srcTar, goroot); err != nil {
 		return err
 	}
 	builder := GoBuilder{
@@ -259,7 +259,7 @@
 // TODO(quentin): Support len(revs) != 2.
 func (b *BenchmarkItem) Run(ctx context.Context, buildEnv *buildenv.Environment, sl spanlog.Logger, conf *dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, revs []BuilderRev) (remoteErr, err error) {
 	// Ensure we have a built parent repo.
-	if err := bc.ListDir("go-parent", buildlet.ListDirOpts{}, func(buildlet.DirEntry) {}); err != nil {
+	if err := bc.ListDir(ctx, "go-parent", buildlet.ListDirOpts{}, func(buildlet.DirEntry) {}); err != nil {
 		pbr := revs[1]
 		sp := sl.CreateSpan("bench_build_parent", bc.Name())
 		err = buildRev(ctx, buildEnv, sl, conf, bc, w, "go-parent", pbr)
@@ -297,7 +297,7 @@
 			fmt.Fprintf(&c.out, "iteration: %d\nstart-time: %s\n", i, time.Now().UTC().Format(time.RFC3339))
 			binaryPath := path.Join(c.path, b.binary)
 			sp := sl.CreateSpan("run_one_bench", binaryPath)
-			remoteErr, err = runOneBenchBinary(conf, bc, &c.out, c.path, b.dir, binaryPath, b.args)
+			remoteErr, err = runOneBenchBinary(ctx, conf, bc, &c.out, c.path, b.dir, binaryPath, b.args)
 			sp.Done(err)
 			if err != nil || remoteErr != nil {
 				c.out.WriteTo(w)
diff --git a/internal/buildgo/buildgo.go b/internal/buildgo/buildgo.go
index 780f6a7..951da1e 100644
--- a/internal/buildgo/buildgo.go
+++ b/internal/buildgo/buildgo.go
@@ -181,12 +181,12 @@
 //
 // The GOPATH workspace is assumed to be the "gopath" directory
 // in the buildlet's work directory.
-func FetchSubrepo(sl spanlog.Logger, bc *buildlet.Client, repo, rev string) error {
+func FetchSubrepo(ctx context.Context, sl spanlog.Logger, bc *buildlet.Client, repo, rev string) error {
 	tgz, err := sourcecache.GetSourceTgz(sl, repo, rev)
 	if err != nil {
 		return err
 	}
-	return bc.PutTar(tgz, "gopath/src/"+subrepoPrefix+repo)
+	return bc.PutTar(ctx, tgz, "gopath/src/"+subrepoPrefix+repo)
 }
 
 // VersionTgz returns an io.Reader of a *.tar.gz file containing only