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

View File

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

View File

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

View File

@@ -14,15 +14,16 @@ package index
import ( import (
"fmt" "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" "log/slog"
"os" "os"
"sort" "sort"
"strings" "strings"
"sync" "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 // 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. // GetAllComponentSchemas will return all schemas defined in the components section of the document.
func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { 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. // 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, ParentNode: schemasNode,
RequiredRefProperties: extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}, fullDef, index), 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) { func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, schema := range securitySchemesNode.Content { for i, schema := range securitySchemesNode.Content {
if i%2 == 0 { if i%2 == 0 {
@@ -398,8 +397,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
}) })
} else { } else {
if paramRef != nil { if paramRef != nil {
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = index.paramOpRefs[pathItemNode.Value][method][paramRefName] = append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
} }
} }
@@ -466,13 +464,11 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
Path: path, Path: path,
}) })
} else { } else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
} }
} }
} else { } else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
} }
continue continue
} }
@@ -489,7 +485,6 @@ func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
} }
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string { func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
cleanedPath := baseURL.Path // not cleaned yet! cleanedPath := baseURL.Path // not cleaned yet!
// create a slice of path segments from existing path // create a slice of path segments from existing path
@@ -533,7 +528,17 @@ func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bo
} else { } else {
p = cleanedPath p = cleanedPath
} }
} }
return strings.TrimSuffix(p, "/") 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
}