Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/bazel/bazel.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ var ErrDigestMismatch = errors.New("body does not match its digest")
type store interface {
Get(ctx context.Context, a ids.ActionID) (cache.Result, error)
Has(ctx context.Context, a ids.ActionID) bool
HasRemote(ctx context.Context, a ids.ActionID) bool
PutStaged(ctx context.Context, a ids.ActionID, o ids.OutputID, stagedPath string, size int64) (string, error)
}

Expand Down Expand Up @@ -187,6 +188,11 @@ func (s *Store) Has(ctx context.Context, k Kind, d Digest) bool {
return s.cache.Has(ctx, k.actionID(d))
}

// HasRemote reports whether a digest is available from the shared cache tier.
func (s *Store) HasRemote(ctx context.Context, k Kind, d Digest) bool {
return s.cache.HasRemote(ctx, k.actionID(d))
}

// Put stores a body under a digest, streaming it and hashing it on the way
// past. It never holds the body in memory, and publishes by hardlink rather
// than by copy, so the bytes cross the disk once however large they are.
Expand Down
3 changes: 3 additions & 0 deletions internal/bazel/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ func (failingStore) Get(context.Context, ids.ActionID) (cache.Result, error) {
// Has always reports absent, so an upload is always attempted.
func (failingStore) Has(context.Context, ids.ActionID) bool { return false }

// HasRemote always reports absent because the test store has no shared tier.
func (failingStore) HasRemote(context.Context, ids.ActionID) bool { return false }

// PutStaged always fails.
func (failingStore) PutStaged(context.Context, ids.ActionID, ids.OutputID, string, int64) (string, error) {
return "", errors.New("no space left on device")
Expand Down
19 changes: 19 additions & 0 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,25 @@ func (c *Cache) getRemote(ctx context.Context, a ids.ActionID) (Result, error) {
return Result{OutputID: outputID, Size: size, DiskPath: path, Time: time.Unix(0, created)}, nil
}

// HasRemote reports whether an action and its body are both available from the shared tier.
//
// It opens the remote body and closes it without reading, which obtains the
// storage service's presence verdict without faulting bytes into the local tier.
func (c *Cache) HasRemote(ctx context.Context, a ids.ActionID) bool {
if !c.cfg.RemoteEnabled() {
return false
}
outputID, _, err := c.rem.GetAction(ctx, a)
if err != nil {
return false
}
body, _, err := c.rem.GetObject(ctx, outputID)
if err != nil {
return false
}
return body.Close() == nil
}

// Has reports whether an action already resolves to a readable body,
// refreshing its last-used time if it does.
//
Expand Down
19 changes: 11 additions & 8 deletions internal/reapi/rrcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ func (m *rrccMetrics) Snapshot() RRCCMetricsSnapshot {
func (s *Server) RRCCMetrics() RRCCMetricsSnapshot { return s.rrccMetrics.Snapshot() }

// validateRRCCLocalClosure accepts ordinary action results and synthetic
// repository-cache results whose complete closure is local. A missing local body
// is a cache miss: returning the ActionResult would make Bazel fail later while
// lazily reading the injected repository.
// repository-cache results whose complete closure is available locally or from
// the shared tier. A missing body is a cache miss: returning the ActionResult
// would make Bazel fail later while lazily reading the injected repository.
func (s *Server) validateRRCCLocalClosure(ctx context.Context, result *repb.ActionResult) bool {
marker, tree, ok := rrccOutputs(result)
if !ok {
return true
}
if !s.hasLocalCAS(ctx, marker) {
if !s.hasCAS(ctx, marker) {
s.rrccMetrics.markerMissing.Add(1)
s.logf("bazel grpc: rrcc local closure missing marker %s", marker.GetHash())
return false
Expand All @@ -71,6 +71,9 @@ func (s *Server) validateRRCCLocalClosure(ctx context.Context, result *repb.Acti
return false
}
file, size, ok := s.store.OpenLocal(ctx, bazel.KindCAS, treeDigest)
if !ok && s.store.HasRemote(ctx, bazel.KindCAS, treeDigest) {
file, size, ok = s.store.Open(ctx, bazel.KindCAS, treeDigest)
}
if !ok {
s.rrccMetrics.treeMissing.Add(1)
s.logf("bazel grpc: rrcc local closure missing tree %s", tree.GetHash())
Expand All @@ -96,7 +99,7 @@ func (s *Server) validateRRCCLocalClosure(ctx context.Context, result *repb.Acti
}
for _, directory := range append([]*repb.Directory{contents.GetRoot()}, contents.GetChildren()...) {
for _, node := range directory.GetFiles() {
if !s.hasLocalCAS(ctx, node.GetDigest()) {
if !s.hasCAS(ctx, node.GetDigest()) {
s.rrccMetrics.fileMissing.Add(1)
s.logf("bazel grpc: rrcc local closure missing file %s", node.GetDigest().GetHash())
return false
Expand All @@ -107,10 +110,10 @@ func (s *Server) validateRRCCLocalClosure(ctx context.Context, result *repb.Acti
return true
}

// hasLocalCAS reports whether a valid digest is currently present in the local cache.
func (s *Server) hasLocalCAS(ctx context.Context, d *repb.Digest) bool {
// hasCAS reports whether a valid digest is available locally or from the shared tier.
func (s *Server) hasCAS(ctx context.Context, d *repb.Digest) bool {
parsed, err := digest(d)
return err == nil && s.store.Has(ctx, bazel.KindCAS, parsed)
return err == nil && (s.store.Has(ctx, bazel.KindCAS, parsed) || s.store.HasRemote(ctx, bazel.KindCAS, parsed))
}

// rrccOutputs recognizes Bazel's synthetic remote repository-contents result shape.
Expand Down
Loading