Skip to content

Commit

Permalink
Split caching logic into it's own type to use with LoaderClient (#17118)
Browse files Browse the repository at this point in the history
Currently all the caching logic for schemas is in `pluginLoader` itself,
that means other loader implementations (like the grpc `LoaderClient`)
don't do any caching.

This splits the cache logic out of `pluginLoader` into a new
`cachedLoader`. `NewPluginLoader` internally wraps with that new type
(unless `cacheOptions.disableFileCache` is set). But other loaders can
now call `NewCachedLoader` to get a caching layer added. We'll want to
go through the grpc codegen and converter implementations to make use of
this.

---------

Co-authored-by: Justin Van Patten <jvp@justinvp.com>
  • Loading branch information
Frassle and justinvp authored Aug 31, 2024
1 parent ce1d6f6 commit d9df477
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: pkg
description: Added `NewCachedLoader` for caching schema loads.
76 changes: 24 additions & 52 deletions pkg/codegen/schema/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"context"
"errors"
"fmt"
"sync"

"github.com/natefinch/atomic"

Expand Down Expand Up @@ -68,10 +67,7 @@ type ReferenceLoader interface {
}

type pluginLoader struct {
m sync.RWMutex

host plugin.Host
entries map[string]PackageReference
host plugin.Host

cacheOptions pluginLoaderCacheOptions
}
Expand All @@ -87,40 +83,20 @@ type pluginLoaderCacheOptions struct {
}

func NewPluginLoader(host plugin.Host) ReferenceLoader {
return &pluginLoader{
host: host,
entries: map[string]PackageReference{},
}
return newPluginLoaderWithOptions(host, pluginLoaderCacheOptions{})
}

func newPluginLoaderWithOptions(host plugin.Host, cacheOptions pluginLoaderCacheOptions) ReferenceLoader {
return &pluginLoader{
host: host,
entries: map[string]PackageReference{},
var l ReferenceLoader
l = &pluginLoader{
host: host,

cacheOptions: cacheOptions,
}
}

func (l *pluginLoader) getPackage(key string) (PackageReference, bool) {
if l.cacheOptions.disableEntryCache {
return nil, false
if !cacheOptions.disableFileCache {
l = NewCachedLoader(l)
}
p, ok := l.entries[key]
return p, ok
}

func (l *pluginLoader) setPackage(key string, p PackageReference) PackageReference {
if l.cacheOptions.disableEntryCache {
return p
}

if p, ok := l.entries[key]; ok {
return p
}

l.entries[key] = p
return p
return l
}

func (l *pluginLoader) LoadPackage(pkg string, version *semver.Version) (*Package, error) {
Expand Down Expand Up @@ -178,19 +154,6 @@ func (l *pluginLoader) LoadPackageReferenceV2(
return DefaultPulumiPackage.Reference(), nil
}

l.m.Lock()
defer l.m.Unlock()

var key string
if descriptor.Parameterization == nil {
key = packageIdentity(descriptor.Name, descriptor.Version)
} else {
key = packageIdentity(descriptor.Parameterization.Name, &descriptor.Parameterization.Version)
}
if p, ok := l.getPackage(key); ok {
return p, nil
}

schemaBytes, version, err := l.loadSchemaBytes(ctx, descriptor)
if err != nil {
return nil, err
Expand Down Expand Up @@ -225,7 +188,7 @@ func (l *pluginLoader) LoadPackageReferenceV2(
if err != nil {
return nil, err
}
return l.setPackage(key, p), nil
return p, nil
}

// deprecated: use LoadPackageReferenceV2
Expand Down Expand Up @@ -258,16 +221,25 @@ func LoadPackageReferenceV2(
return nil, err
}

if descriptor.Name != ref.Name() ||
descriptor.Version != nil &&
name := descriptor.Name
if descriptor.Parameterization != nil {
name = descriptor.Parameterization.Name
}
version := descriptor.Version
if descriptor.Parameterization != nil {
version = &descriptor.Parameterization.Version
}

if name != ref.Name() ||
version != nil &&
ref.Version() != nil &&
!ref.Version().Equals(*descriptor.Version) {
if l, ok := loader.(*pluginLoader); ok {
return nil, fmt.Errorf("req: %s@%v: entries: %v (returned %s@%v)", descriptor.Name, descriptor.Version,
!ref.Version().Equals(*version) {
if l, ok := loader.(*cachedLoader); ok {
return nil, fmt.Errorf("req: %s@%v: entries: %v (returned %s@%v)", name, version,
l.entries, ref.Name(), ref.Version())
}
return nil, fmt.Errorf(
"loader returned %s@%v: expected %s@%v", ref.Name(), ref.Version(), descriptor.Name, descriptor.Version)
"loader returned %s@%v: expected %s@%v", ref.Name(), ref.Version(), name, version)
}

return ref, nil
Expand Down
84 changes: 84 additions & 0 deletions pkg/codegen/schema/loader_cached.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package schema

import (
"context"
"sync"

"github.com/blang/semver"
)

func NewCachedLoader(loader ReferenceLoader) ReferenceLoader {
return &cachedLoader{
loader: loader,
entries: make(map[string]PackageReference),
}
}

type cachedLoader struct {
loader ReferenceLoader

m sync.RWMutex
entries map[string]PackageReference
}

func (l *cachedLoader) LoadPackage(pkg string, version *semver.Version) (*Package, error) {
ref, err := l.LoadPackageReference(pkg, version)
if err != nil {
return nil, err
}
return ref.Definition()
}

func (l *cachedLoader) LoadPackageV2(ctx context.Context, descriptor *PackageDescriptor) (*Package, error) {
ref, err := l.LoadPackageReferenceV2(ctx, descriptor)
if err != nil {
return nil, err
}
return ref.Definition()
}

func (l *cachedLoader) LoadPackageReference(pkg string, version *semver.Version) (PackageReference, error) {
return l.LoadPackageReferenceV2(context.Background(), &PackageDescriptor{
Name: pkg,
Version: version,
})
}

func (l *cachedLoader) LoadPackageReferenceV2(
ctx context.Context, descriptor *PackageDescriptor,
) (PackageReference, error) {
l.m.Lock()
defer l.m.Unlock()

var key string
if descriptor.Parameterization == nil {
key = packageIdentity(descriptor.Name, descriptor.Version)
} else {
key = packageIdentity(descriptor.Parameterization.Name, &descriptor.Parameterization.Version)
}
if p, ok := l.entries[key]; ok {
return p, nil
}

p, err := l.loader.LoadPackageReferenceV2(ctx, descriptor)
if err != nil {
return nil, err
}

l.entries[key] = p
return p, nil
}
79 changes: 79 additions & 0 deletions pkg/codegen/schema/loader_cached_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package schema

import (
"context"
"testing"

"github.com/blang/semver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type mockLoader struct {
GetPackageF func(context.Context, *PackageDescriptor) (PackageReference, error)
}

func (m *mockLoader) LoadPackage(pkg string, version *semver.Version) (*Package, error) {
ref, err := m.LoadPackageReference(pkg, version)
if err != nil {
return nil, err
}
return ref.Definition()
}

func (m *mockLoader) LoadPackageV2(ctx context.Context, descriptor *PackageDescriptor) (*Package, error) {
ref, err := m.LoadPackageReferenceV2(ctx, descriptor)
if err != nil {
return nil, err
}
return ref.Definition()
}

func (m *mockLoader) LoadPackageReference(pkg string, version *semver.Version) (PackageReference, error) {
return m.LoadPackageReferenceV2(context.TODO(), &PackageDescriptor{
Name: pkg,
Version: version,
})
}

func (m *mockLoader) LoadPackageReferenceV2(
ctx context.Context, descriptor *PackageDescriptor,
) (PackageReference, error) {
return m.GetPackageF(ctx, descriptor)
}

func TestCachedLoader(t *testing.T) {
t.Parallel()

calls := 0
mockLoader := &mockLoader{
GetPackageF: func(context.Context, *PackageDescriptor) (PackageReference, error) {
calls++
return nil, nil
},
}

loader := NewCachedLoader(mockLoader)

_, err := loader.LoadPackageReference("pkg", nil)
require.NoError(t, err)

_, err = loader.LoadPackageReference("pkg", nil)
require.NoError(t, err)

assert.Equal(t, 1, calls)
}

0 comments on commit d9df477

Please sign in to comment.