Skip to content

Commit 4408f83

Browse files
committed
Make cache lookups succeed for keys without dependencies
Keys without dependencies are very rare, especially if they don't have any overrides in place. One way to support them would be to let the initial lookup return an empty value. However, that means we then need to support empty sets of additional dependencies. Instead of doing that, let's allow the initial lookup to return a value as well. This means we also stick to one round trip in that case.
1 parent 9287570 commit 4408f83

5 files changed

Lines changed: 244 additions & 52 deletions

File tree

pkg/model/evaluation/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,19 @@ go_test(
5656
embed = [":evaluation"],
5757
deps = [
5858
"//pkg/model/core",
59+
"//pkg/proto/model/core",
60+
"//pkg/proto/model/evaluation/cache",
5961
"//pkg/storage/object",
60-
"//pkg/storage/tag",
6162
"@com_github_buildbarn_bb_storage//pkg/clock",
6263
"@com_github_buildbarn_bb_storage//pkg/program",
6364
"@com_github_buildbarn_bb_storage//pkg/testutil",
65+
"@com_github_buildbarn_bb_storage//pkg/util",
6466
"@com_github_stretchr_testify//require",
6567
"@org_golang_google_grpc//codes",
6668
"@org_golang_google_grpc//status",
6769
"@org_golang_google_protobuf//proto",
6870
"@org_golang_google_protobuf//reflect/protoreflect", # keep
71+
"@org_golang_google_protobuf//types/known/anypb",
6972
"@org_golang_google_protobuf//types/known/emptypb",
7073
"@org_golang_google_protobuf//types/known/wrapperspb",
7174
"@org_uber_go_mock//gomock",

pkg/model/evaluation/recursive_computer.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -647,28 +647,36 @@ func (vs *messageValueState[TReference, TMetadata]) compute(ctx context.Context,
647647
LocalReference: ks.keyReference,
648648
}),
649649
}
650-
/*
651-
if false {
652-
result.SubsequentLookup = &model_evaluation_cache_pb.LookupTagKeyData_SubsequentLookup{
653-
SelectedGranularityLevel: 67,
654-
DependenciesHash: []byte("TODO"),
655-
}
656-
}
657-
*/
658650
return result
659651
}).SortAndSetReferences()
660652
tagKeyHash, err := model_tag.NewDecodableKeyHashFromMessage(tagKeyData, rc.cacheObjectReader.GetDecodingParametersSizeBytes())
661653
if err != nil {
662654
return nil, err
663655
}
664-
if rootCacheResultValue, err := model_tag.ResolveDecodableTag(ctx, rc.tagResolver, tagKeyHash); err == nil {
665-
// TODO: Actually inspect the cache result.
666-
rootCacheResultReference := rootCacheResultValue
667-
vs.rootCacheResultReference = vs.rootCacheResultReference
668-
vs.gotRootCacheResultReference = true
669-
670-
if _, err := rc.cacheObjectReader.ReadObject(ctx, rootCacheResultReference); err == nil {
671-
return nil, errors.New("got root cache result")
656+
if rootCacheResultReference, err := model_tag.ResolveDecodableTag(ctx, rc.tagResolver, tagKeyHash); err == nil {
657+
if lookupResult, err := rc.cacheObjectReader.ReadObject(ctx, rootCacheResultReference); err == nil {
658+
switch r := lookupResult.Message.Result.(type) {
659+
case *model_evaluation_cache_pb.LookupResult_Initial_:
660+
// TODO: Actually inspect the cache result.
661+
vs.rootCacheResultReference = rootCacheResultReference
662+
vs.gotRootCacheResultReference = true
663+
return nil, errors.New("got initial result")
664+
case *model_evaluation_cache_pb.LookupResult_HitValue:
665+
// It turns out the current key does
666+
// not have any dependencies. This
667+
// means that the initial lookup
668+
// returned a value immediately.
669+
cachedAnyValue, err := model_core.FlattenAny(model_core.Nested(lookupResult, r.HitValue))
670+
if err != nil {
671+
return nil, err
672+
}
673+
cachedValue, err := model_core.UnmarshalTopLevelAnyNew(cachedAnyValue)
674+
if err != nil {
675+
return nil, err
676+
}
677+
vs.value = cachedValue.Decay()
678+
return nil, nil
679+
}
672680
} else if status.Code(err) != codes.NotFound {
673681
return nil, err
674682
}

pkg/model/evaluation/recursive_computer_test.go

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
model_core "bonanza.build/pkg/model/core"
99
model_evaluation "bonanza.build/pkg/model/evaluation"
10+
model_core_pb "bonanza.build/pkg/proto/model/core"
1011
model_evaluation_cache_pb "bonanza.build/pkg/proto/model/evaluation/cache"
1112
"bonanza.build/pkg/storage/object"
1213

@@ -19,6 +20,7 @@ import (
1920
"google.golang.org/grpc/codes"
2021
"google.golang.org/grpc/status"
2122
"google.golang.org/protobuf/proto"
23+
"google.golang.org/protobuf/types/known/anypb"
2224
"google.golang.org/protobuf/types/known/emptypb"
2325
"google.golang.org/protobuf/types/known/wrapperspb"
2426

@@ -420,7 +422,7 @@ func TestRecursiveComputer(t *testing.T) {
420422
},
421423
),
422424
),
423-
).Return(model_core.Message[*model_evaluation_cache_pb.LookupResult, object.LocalReference]{}, status.Error(codes.NotFound, "Tag does not exist"))
425+
).Return(model_core.Message[*model_evaluation_cache_pb.LookupResult, object.LocalReference]{}, status.Error(codes.NotFound, "Object does not exist"))
424426
computer.EXPECT().ComputeMessageValue(gomock.Any(), gomock.Any(), gomock.Any()).
425427
DoAndReturn(func(ctx context.Context, key model_core.Message[proto.Message, object.LocalReference], e model_evaluation.Environment[object.LocalReference, model_core.ReferenceMetadata]) (model_core.PatchedMessage[proto.Message, model_core.ReferenceMetadata], error) {
426428
return model_core.NewSimplePatchedMessage[model_core.ReferenceMetadata, proto.Message](
@@ -443,4 +445,179 @@ func TestRecursiveComputer(t *testing.T) {
443445
Value: 42,
444446
}, value.Message)
445447
})
448+
449+
t.Run("CacheLookupReturningUnexpectedResult", func(t *testing.T) {
450+
// The first cache lookup should typically contain an
451+
// Initial message, describing the initial set of
452+
// dependencies that need to be considered. If the
453+
// message is malformed and contains another type of
454+
// message, we should just ignore it.
455+
computer := NewMockComputerForTesting(ctrl)
456+
objectManager := NewMockObjectManagerForTesting(ctrl)
457+
tagResolver := NewMockBoundResolverForTesting(ctrl)
458+
cacheObjectReader := NewMockCacheObjectReaderForTesting(ctrl)
459+
cacheObjectReader.EXPECT().GetDecodingParametersSizeBytes().Return(16).AnyTimes()
460+
cacheDeterministicEncoder := NewMockDeterministicBinaryEncoder(ctrl)
461+
cacheKeyedEncoder := NewMockKeyedBinaryEncoder(ctrl)
462+
463+
queuesFactory := model_evaluation.NewSimpleRecursiveComputerEvaluationQueuesFactory[object.LocalReference, model_core.ReferenceMetadata](1)
464+
queues := queuesFactory.NewQueues()
465+
recursiveComputer := model_evaluation.NewRecursiveComputer(
466+
computer,
467+
queues,
468+
object.SHA256V1ReferenceFormat,
469+
objectManager,
470+
tagResolver,
471+
/* actionTagKeyReference = */ object.MustNewSHA256V1LocalReference("4466f601ba900ce4e0d606dc68dd0c35c5e976792784400514f42905d6deba6e", 64722, 0, 0, 0),
472+
cacheObjectReader,
473+
cacheDeterministicEncoder,
474+
cacheKeyedEncoder,
475+
clock.SystemClock,
476+
)
477+
478+
keyState, err := recursiveComputer.GetOrCreateKeyState(
479+
model_core.NewSimpleTopLevelMessage[object.LocalReference, proto.Message](
480+
&wrapperspb.UInt32Value{
481+
Value: 12,
482+
},
483+
),
484+
)
485+
require.NoError(t, err)
486+
487+
tagResolver.EXPECT().ResolveTag(
488+
gomock.Any(),
489+
[...]byte{
490+
0x57, 0x12, 0x01, 0xf0, 0x80, 0xf7, 0xe1, 0x9f,
491+
0x41, 0x44, 0x87, 0x88, 0x9b, 0xf3, 0xc9, 0x4f,
492+
0x61, 0x68, 0x71, 0xd5, 0x07, 0x06, 0x3e, 0x92,
493+
0xdc, 0x40, 0x42, 0xbf, 0x2a, 0x3c, 0x01, 0xec,
494+
},
495+
).Return(object.MustNewSHA256V1LocalReference("829d56c5c910ff51bb09192f5655cd51c02e54c3e16b00d2fde8a6b7e3d814fc", 95938, 0, 0, 0), nil)
496+
cacheObjectReader.EXPECT().ReadObject(
497+
gomock.Any(),
498+
util.Must(
499+
model_core.NewDecodable(
500+
object.MustNewSHA256V1LocalReference("829d56c5c910ff51bb09192f5655cd51c02e54c3e16b00d2fde8a6b7e3d814fc", 95938, 0, 0, 0),
501+
[]byte{
502+
0x9c, 0x80, 0x78, 0x1a, 0xa4, 0x29, 0x20, 0x6d,
503+
0x76, 0xa4, 0x4e, 0x8f, 0x4c, 0x2e, 0xce, 0xd5,
504+
},
505+
),
506+
),
507+
).Return(model_core.NewSimpleMessage[object.LocalReference](
508+
&model_evaluation_cache_pb.LookupResult{
509+
Result: &model_evaluation_cache_pb.LookupResult_MissingDependencies_{
510+
MissingDependencies: &model_evaluation_cache_pb.LookupResult_MissingDependencies{},
511+
},
512+
},
513+
), nil)
514+
computer.EXPECT().ComputeMessageValue(gomock.Any(), gomock.Any(), gomock.Any()).
515+
DoAndReturn(func(ctx context.Context, key model_core.Message[proto.Message, object.LocalReference], e model_evaluation.Environment[object.LocalReference, model_core.ReferenceMetadata]) (model_core.PatchedMessage[proto.Message, model_core.ReferenceMetadata], error) {
516+
return model_core.NewSimplePatchedMessage[model_core.ReferenceMetadata, proto.Message](
517+
&wrapperspb.UInt64Value{Value: 13},
518+
), nil
519+
})
520+
521+
var value model_core.Message[proto.Message, object.LocalReference]
522+
require.NoError(
523+
t,
524+
program.RunLocal(ctx, func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error {
525+
queues.ProcessAllEvaluatableKeys(dependenciesGroup, recursiveComputer)
526+
527+
var err error
528+
value, err = recursiveComputer.WaitForMessageValue(ctx, keyState)
529+
return err
530+
}),
531+
)
532+
testutil.RequireEqualProto(t, &wrapperspb.UInt64Value{
533+
Value: 13,
534+
}, value.Message)
535+
})
536+
537+
t.Run("CacheLookupWithoutDependencies", func(t *testing.T) {
538+
// In the extremely rare case that a key has no
539+
// dependencies, the initial lookup result may a value
540+
// directly. There should be no need to call into the
541+
// computer, as the cached value can be used.
542+
computer := NewMockComputerForTesting(ctrl)
543+
objectManager := NewMockObjectManagerForTesting(ctrl)
544+
tagResolver := NewMockBoundResolverForTesting(ctrl)
545+
cacheObjectReader := NewMockCacheObjectReaderForTesting(ctrl)
546+
cacheObjectReader.EXPECT().GetDecodingParametersSizeBytes().Return(16).AnyTimes()
547+
cacheDeterministicEncoder := NewMockDeterministicBinaryEncoder(ctrl)
548+
cacheKeyedEncoder := NewMockKeyedBinaryEncoder(ctrl)
549+
550+
queuesFactory := model_evaluation.NewSimpleRecursiveComputerEvaluationQueuesFactory[object.LocalReference, model_core.ReferenceMetadata](1)
551+
queues := queuesFactory.NewQueues()
552+
recursiveComputer := model_evaluation.NewRecursiveComputer(
553+
computer,
554+
queues,
555+
object.SHA256V1ReferenceFormat,
556+
objectManager,
557+
tagResolver,
558+
/* actionTagKeyReference = */
559+
object.MustNewSHA256V1LocalReference("2fbd13f1ee48876ec1e85961b59877c3916fb10ae23e7e1e45083feecafe1804", 57483, 0, 0, 0),
560+
cacheObjectReader,
561+
cacheDeterministicEncoder,
562+
cacheKeyedEncoder,
563+
clock.SystemClock,
564+
)
565+
566+
keyState, err := recursiveComputer.GetOrCreateKeyState(
567+
model_core.NewSimpleTopLevelMessage[object.LocalReference, proto.Message](
568+
&wrapperspb.UInt32Value{
569+
Value: 24,
570+
},
571+
),
572+
)
573+
require.NoError(t, err)
574+
575+
tagResolver.EXPECT().ResolveTag(
576+
gomock.Any(),
577+
[...]byte{
578+
0x1e, 0x0f, 0x30, 0xc1, 0x1c, 0x0a, 0x4d, 0x1c,
579+
0xd6, 0x18, 0x13, 0x8d, 0xa3, 0x58, 0x40, 0xfa,
580+
0x31, 0x22, 0x07, 0x66, 0x38, 0x45, 0x22, 0x9b,
581+
0x70, 0x31, 0xbb, 0xac, 0xe8, 0x70, 0x35, 0x07,
582+
},
583+
).Return(object.MustNewSHA256V1LocalReference("e1749fb7b82448ae6941d4f7e515818efbf781e6fe2738465bd9ce876b8d9866", 2345, 0, 0, 0), nil)
584+
cacheObjectReader.EXPECT().ReadObject(
585+
gomock.Any(),
586+
util.Must(
587+
model_core.NewDecodable(
588+
object.MustNewSHA256V1LocalReference("e1749fb7b82448ae6941d4f7e515818efbf781e6fe2738465bd9ce876b8d9866", 2345, 0, 0, 0),
589+
[]byte{
590+
0xe5, 0x83, 0xfe, 0x6a, 0x73, 0xa3, 0x7f, 0xe9,
591+
0x5c, 0xb8, 0x4f, 0x3a, 0x54, 0xdf, 0xd3, 0x87,
592+
},
593+
),
594+
),
595+
).Return(model_core.NewSimpleMessage[object.LocalReference](
596+
&model_evaluation_cache_pb.LookupResult{
597+
Result: &model_evaluation_cache_pb.LookupResult_HitValue{
598+
HitValue: &model_core_pb.Any{
599+
Value: util.Must(anypb.New(&wrapperspb.UInt64Value{
600+
Value: 26,
601+
})),
602+
References: &model_core_pb.ReferenceSet{},
603+
},
604+
},
605+
},
606+
), nil)
607+
608+
var value model_core.Message[proto.Message, object.LocalReference]
609+
require.NoError(
610+
t,
611+
program.RunLocal(ctx, func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error {
612+
queues.ProcessAllEvaluatableKeys(dependenciesGroup, recursiveComputer)
613+
614+
var err error
615+
value, err = recursiveComputer.WaitForMessageValue(ctx, keyState)
616+
return err
617+
}),
618+
)
619+
testutil.RequireEqualProto(t, &wrapperspb.UInt64Value{
620+
Value: 26,
621+
}, value.Message)
622+
})
446623
}

0 commit comments

Comments
 (0)