Skip to content

Commit 0067149

Browse files
authored
perf: cache deprecated fields schema parse to fix YAMLGeneration regression (#21264)
1 parent 5e60adb commit 0067149

1 file changed

Lines changed: 45 additions & 27 deletions

File tree

pkg/parser/schema_deprecation.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"regexp"
77
"sort"
8+
"sync"
89
)
910

1011
// DeprecatedField represents a deprecated field with its replacement information
@@ -14,21 +15,37 @@ type DeprecatedField struct {
1415
Description string // Description from the schema
1516
}
1617

17-
// GetMainWorkflowDeprecatedFields returns a list of deprecated fields from the main workflow schema
18-
func GetMainWorkflowDeprecatedFields() ([]DeprecatedField, error) {
19-
log.Print("Getting deprecated fields from main workflow schema")
20-
// Parse the schema JSON
21-
var schemaDoc map[string]any
22-
if err := json.Unmarshal([]byte(mainWorkflowSchema), &schemaDoc); err != nil {
23-
return nil, fmt.Errorf("failed to parse main workflow schema: %w", err)
24-
}
18+
// deprecatedFieldsCache caches the result of parsing the main workflow schema so that
19+
// the expensive 414KB JSON unmarshal is only performed once per process lifetime.
20+
// Both the result and any error are cached permanently: since mainWorkflowSchema is an
21+
// embedded compile-time constant, a parse failure is always a programming error (not
22+
// transient), so re-parsing on subsequent calls would produce the same failure.
23+
var (
24+
deprecatedFieldsOnce sync.Once
25+
deprecatedFieldsCache []DeprecatedField
26+
deprecatedFieldsErr error
27+
)
2528

26-
fields, err := extractDeprecatedFields(schemaDoc)
27-
if err != nil {
28-
return nil, err
29-
}
30-
log.Printf("Found %d deprecated fields in main workflow schema", len(fields))
31-
return fields, nil
29+
// GetMainWorkflowDeprecatedFields returns a list of deprecated fields from the main workflow schema.
30+
// The result is cached after the first call so the schema is only parsed once per process.
31+
// Callers must not modify the returned slice.
32+
func GetMainWorkflowDeprecatedFields() ([]DeprecatedField, error) {
33+
deprecatedFieldsOnce.Do(func() {
34+
log.Print("Getting deprecated fields from main workflow schema")
35+
var schemaDoc map[string]any
36+
if err := json.Unmarshal([]byte(mainWorkflowSchema), &schemaDoc); err != nil {
37+
deprecatedFieldsErr = fmt.Errorf("failed to parse main workflow schema: %w", err)
38+
return
39+
}
40+
fields, err := extractDeprecatedFields(schemaDoc)
41+
if err != nil {
42+
deprecatedFieldsErr = err
43+
return
44+
}
45+
deprecatedFieldsCache = fields
46+
log.Printf("Found %d deprecated fields in main workflow schema", len(fields))
47+
})
48+
return deprecatedFieldsCache, deprecatedFieldsErr
3249
}
3350

3451
// extractDeprecatedFields extracts deprecated fields from a schema document
@@ -75,20 +92,21 @@ func extractDeprecatedFields(schemaDoc map[string]any) ([]DeprecatedField, error
7592
return deprecated, nil
7693
}
7794

78-
// extractReplacementFromDescription extracts the replacement field name from a description
79-
// It looks for patterns like "Use 'field-name' instead" or "Deprecated: Use 'field-name'"
80-
func extractReplacementFromDescription(description string) string {
81-
// Common patterns in deprecation messages
82-
patterns := []string{
83-
`[Uu]se '([^']+)' instead`,
84-
`[Uu]se "([^"]+)" instead`,
85-
`[Uu]se ` + "`" + `([^` + "`" + `]+)` + "`" + ` instead`,
86-
`[Rr]eplace(?:d)? (?:with|by) '([^']+)'`,
87-
`[Rr]eplace(?:d)? (?:with|by) "([^"]+)"`,
88-
}
95+
// replacementPatterns are pre-compiled regexes used by extractReplacementFromDescription.
96+
// Pre-compiling avoids repeated compilation overhead when extracting replacements from
97+
// many deprecated field descriptions.
98+
var replacementPatterns = []*regexp.Regexp{
99+
regexp.MustCompile(`[Uu]se '([^']+)' instead`),
100+
regexp.MustCompile(`[Uu]se "([^"]+)" instead`),
101+
regexp.MustCompile("[Uu]se `([^`]+)` instead"),
102+
regexp.MustCompile(`[Rr]eplace(?:d)? (?:with|by) '([^']+)'`),
103+
regexp.MustCompile(`[Rr]eplace(?:d)? (?:with|by) "([^"]+)"`),
104+
}
89105

90-
for _, pattern := range patterns {
91-
re := regexp.MustCompile(pattern)
106+
// extractReplacementFromDescription extracts the replacement field name from a description.
107+
// It looks for patterns like "Use 'field-name' instead" or "Deprecated: Use 'field-name'".
108+
func extractReplacementFromDescription(description string) string {
109+
for _, re := range replacementPatterns {
92110
if match := re.FindStringSubmatch(description); len(match) >= 2 {
93111
return match[1]
94112
}

0 commit comments

Comments
 (0)