Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RW Lock to allComponentSchemas to Prevent Data Race Error #344

Merged
merged 5 commits into from
Oct 29, 2024
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
3 changes: 2 additions & 1 deletion index/index_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ type SpecIndex struct {
allComponentSchemaDefinitions *sync.Map // all schemas found in components (openapi) or definitions (swagger).
securitySchemesNode *yaml.Node // components/securitySchemes node
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
allComponentSchemas map[string]*Reference // all component schemas
allComponentSchemas map[string]*Reference // all component schema definitions
allComponentSchemasLock sync.RWMutex // prevent concurrent read writes to the schema file which causes a race condition
requestBodiesNode *yaml.Node // components/requestBodies node
allRequestBodies map[string]*Reference // all request bodies
responsesNode *yaml.Node // components/responses node
Expand Down
24 changes: 21 additions & 3 deletions index/spec_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,29 @@ 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 {
if index != nil && index.allComponentSchemas != nil {
if index == nil {
return nil
}

// Acquire read lock
index.allComponentSchemasLock.RLock()
if index.allComponentSchemas != nil {
defer index.allComponentSchemasLock.RUnlock()
return index.allComponentSchemas
}
schemaMap := syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions)
index.allComponentSchemas = schemaMap
// Release the read lock before acquiring write lock
index.allComponentSchemasLock.RUnlock()

// Acquire write lock to initialize the map
index.allComponentSchemasLock.Lock()
defer index.allComponentSchemasLock.Unlock()

// Double-check if another goroutine initialized it
if index.allComponentSchemas == nil {
schemaMap := syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions)
index.allComponentSchemas = schemaMap
}

return index.allComponentSchemas
}

Expand Down
56 changes: 56 additions & 0 deletions index/spec_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,62 @@ func Test_GetAllComponentSchemas(t *testing.T) {
assert.Nil(t, index.GetAllComponentSchemas())
}

func TestSpecIndex_GetAllComponentSchemas_ConcurrentAccess(t *testing.T) {
yml := `
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Pet:
type: object
properties:
name:
type: string
age:
type: integer
Cat:
type: object
properties:
lives:
type: integer
`

// Parse the YAML into a yaml.Node
var rootNode yaml.Node
err := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, err)

index := NewSpecIndex(&rootNode)

callGetAllComponentSchemas := func(wg *sync.WaitGroup) {
defer wg.Done()
schemas := index.GetAllComponentSchemas()
assert.Equal(t, 2, len(schemas))
}

const numGoroutines = 10

// WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
wg.Add(numGoroutines)

// Start multiple goroutines
for i := 0; i < numGoroutines; i++ {
go callGetAllComponentSchemas(&wg)
}

// Wait for all goroutines to complete
wg.Wait()
}

func TestSpecIndex_GetAllComponentSchemas_NilIndex(t *testing.T) {
var index *SpecIndex
schemas := index.GetAllComponentSchemas()
assert.Nil(t, schemas, "Expected GetAllComponentSchemas to return nil when index is nil")
}

func TestSpecIndex_Cache(t *testing.T) {

idx := new(SpecIndex)
Expand Down
Loading