fix: data race

This commit is contained in:
Tristan Cartledge
2023-12-04 18:08:02 +00:00
parent eb0b10cbb6
commit 2b128cb465
6 changed files with 95 additions and 28 deletions

View File

@@ -1191,3 +1191,61 @@ func TestDocument_AdvanceCallbackReferences(t *testing.T) {
assert.Empty(t, buf.String())
}
func BenchmarkLoadDocTwice(b *testing.B) {
for i := 0; i < b.N; i++ {
spec, err := os.ReadFile("test_specs/speakeasy-test.yaml")
require.NoError(b, err)
doc, err := NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
BasePath: "./test_specs",
IgnorePolymorphicCircularReferences: true,
IgnoreArrayCircularReferences: true,
AllowFileReferences: true,
})
require.NoError(b, err)
_, errs := doc.BuildV3Model()
require.Empty(b, errs)
doc, err = NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
BasePath: "./test_specs",
IgnorePolymorphicCircularReferences: true,
IgnoreArrayCircularReferences: true,
AllowFileReferences: true,
})
require.NoError(b, err)
_, errs = doc.BuildV3Model()
require.Empty(b, errs)
}
}
func TestDocument_LoadDocTwice(t *testing.T) {
spec, err := os.ReadFile("test_specs/speakeasy-test.yaml")
require.NoError(t, err)
doc, err := NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
BasePath: "./test_specs",
IgnorePolymorphicCircularReferences: true,
IgnoreArrayCircularReferences: true,
AllowFileReferences: true,
})
require.NoError(t, err)
_, errs := doc.BuildV3Model()
require.Empty(t, errs)
doc, err = NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
BasePath: "./test_specs",
IgnorePolymorphicCircularReferences: true,
IgnoreArrayCircularReferences: true,
AllowFileReferences: true,
})
require.NoError(t, err)
_, errs = doc.BuildV3Model()
require.Empty(t, errs)
}

View File

@@ -4,14 +4,15 @@
package index
import (
"github.com/pb33f/libopenapi/datamodel"
"golang.org/x/sync/syncmap"
"io/fs"
"log/slog"
"net/http"
"net/url"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
)
@@ -235,7 +236,7 @@ type SpecIndex struct {
allRefSchemaDefinitions []*Reference // all schemas found that are references.
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger).
allComponentSchemaDefinitions *syncmap.Map // all schemas found in components (openapi) or definitions (swagger).
securitySchemesNode *yaml.Node // components/securitySchemes node
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
requestBodiesNode *yaml.Node // components/requestBodies node

View File

@@ -4,8 +4,10 @@
package index
import (
"gopkg.in/yaml.v3"
"strings"
"sync"
"gopkg.in/yaml.v3"
)
func isHttpMethod(val string) bool {
@@ -52,7 +54,7 @@ func boostrapIndexCollections(rootNode *yaml.Node, index *SpecIndex) {
index.linksRefs = make(map[string]map[string][]*Reference)
index.callbackRefs = make(map[string]*Reference)
index.externalSpecIndex = make(map[string]*SpecIndex)
index.allComponentSchemaDefinitions = make(map[string]*Reference)
index.allComponentSchemaDefinitions = &sync.Map{}
index.allParameters = make(map[string]*Reference)
index.allSecuritySchemes = make(map[string]*Reference)
index.allRequestBodies = make(map[string]*Reference)

View File

@@ -13,8 +13,10 @@ import (
type ContextKey string
const CurrentPathKey ContextKey = "currentPath"
const FoundIndexKey ContextKey = "foundIndex"
const (
CurrentPathKey ContextKey = "currentPath"
FoundIndexKey ContextKey = "foundIndex"
)
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef)
@@ -33,7 +35,6 @@ func (index *SpecIndex) SearchIndexForReferenceWithContext(ctx context.Context,
}
func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *Reference) (*Reference, *SpecIndex, context.Context) {
if index.cache != nil {
if v, ok := index.cache.Load(searchRef.FullDefinition); ok {
return v.(*Reference), v.(*Reference).Index, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation)
@@ -74,12 +75,10 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
refAlt = fmt.Sprintf("%s#/%s", absPath, uri[1])
}
} else {
if filepath.IsAbs(uri[0]) {
roloLookup = uri[0]
} else {
if strings.HasPrefix(uri[0], "http") {
roloLookup = ref
} else {
@@ -107,9 +106,11 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
if r, ok := index.allComponentSchemaDefinitions[refAlt]; ok {
if r, ok := index.allComponentSchemaDefinitions.Load(refAlt); ok {
ref := r.(*Reference)
index.cache.Store(refAlt, r)
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
return ref, ref.Index, context.WithValue(ctx, CurrentPathKey, ref.RemoteLocation)
}
// check the rolodex for the reference.
@@ -184,5 +185,4 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
}
return nil, index, ctx
}

View File

@@ -14,15 +14,16 @@ package index
import (
"fmt"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
"log/slog"
"os"
"sort"
"strings"
"sync"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
)
// NewSpecIndexWithConfig will create a new index of an OpenAPI or Swagger spec. It uses the same logic as NewSpecIndex
@@ -284,7 +285,7 @@ func (index *SpecIndex) GetAllReferenceSchemas() []*Reference {
// GetAllComponentSchemas will return all schemas defined in the components section of the document.
func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference {
return index.allComponentSchemaDefinitions
return syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions)
}
// GetAllSecuritySchemes will return all security schemes / definitions found in the document.

View File

@@ -34,7 +34,7 @@ func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pat
ParentNode: schemasNode,
RequiredRefProperties: extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}, fullDef, index),
}
index.allComponentSchemaDefinitions[def] = ref
index.allComponentSchemaDefinitions.Store(def, ref)
}
}
@@ -308,7 +308,6 @@ func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPr
}
func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
var name string
for i, schema := range securitySchemesNode.Content {
if i%2 == 0 {
@@ -398,8 +397,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
})
} else {
if paramRef != nil {
index.paramOpRefs[pathItemNode.Value][method][paramRefName] =
append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
}
}
@@ -466,13 +464,11 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
Path: path,
})
} else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
}
}
} else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
}
continue
}
@@ -489,7 +485,6 @@ func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
}
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
cleanedPath := baseURL.Path // not cleaned yet!
// create a slice of path segments from existing path
@@ -533,7 +528,17 @@ func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bo
} else {
p = cleanedPath
}
}
return strings.TrimSuffix(p, "/")
}
func syncMapToMap[K comparable, V any](sm *sync.Map) map[K]V {
m := make(map[K]V)
sm.Range(func(key, value interface{}) bool {
m[key.(K)] = value.(V)
return true
})
return m
}