From 3462c6a1532fb014c2ca7a942d6b359c1d2afa7d Mon Sep 17 00:00:00 2001 From: xiaofan Date: Mon, 28 Nov 2016 20:30:52 +0800 Subject: [PATCH] fix concurrent map write issue by init all regexp when startup --- cronexpr_parse.go | 93 +++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/cronexpr_parse.go b/cronexpr_parse.go index aeb8296..6672177 100644 --- a/cronexpr_parse.go +++ b/cronexpr_parse.go @@ -19,6 +19,7 @@ import ( "regexp" "sort" "strings" + "sync" ) /******************************************************************************/ @@ -107,15 +108,17 @@ func atoi(s string) int { } type fieldDescriptor struct { - name string - min, max int - defaultList []int - valuePattern string - atoi func(string) int + name string + min, max int + defaultList []int + valuePattern string + atoi func(string) int + layoutInitOnce sync.Once + layoutMap map[string]*regexp.Regexp } var ( - secondDescriptor = fieldDescriptor{ + secondDescriptor = &fieldDescriptor{ name: "second", min: 0, max: 59, @@ -123,7 +126,7 @@ var ( valuePattern: `0?[0-9]|[1-5][0-9]`, atoi: atoi, } - minuteDescriptor = fieldDescriptor{ + minuteDescriptor = &fieldDescriptor{ name: "minute", min: 0, max: 59, @@ -131,7 +134,7 @@ var ( valuePattern: `0?[0-9]|[1-5][0-9]`, atoi: atoi, } - hourDescriptor = fieldDescriptor{ + hourDescriptor = &fieldDescriptor{ name: "hour", min: 0, max: 23, @@ -139,7 +142,7 @@ var ( valuePattern: `0?[0-9]|1[0-9]|2[0-3]`, atoi: atoi, } - domDescriptor = fieldDescriptor{ + domDescriptor = &fieldDescriptor{ name: "day-of-month", min: 1, max: 31, @@ -147,7 +150,7 @@ var ( valuePattern: `0?[1-9]|[12][0-9]|3[01]`, atoi: atoi, } - monthDescriptor = fieldDescriptor{ + monthDescriptor = &fieldDescriptor{ name: "month", min: 1, max: 12, @@ -157,7 +160,7 @@ var ( return monthTokens[s] }, } - dowDescriptor = fieldDescriptor{ + dowDescriptor = &fieldDescriptor{ name: "day-of-week", min: 0, max: 6, @@ -167,7 +170,7 @@ var ( return dowTokens[s] }, } - yearDescriptor = fieldDescriptor{ + yearDescriptor = &fieldDescriptor{ name: "year", min: 1970, max: 2099, @@ -177,6 +180,17 @@ var ( } ) +func (f *fieldDescriptor) makeLayoutRegexp(layout string) *regexp.Regexp { + f.layoutInitOnce.Do(func() { + f.layoutMap = make(map[string]*regexp.Regexp) + for _, l := range allLayouts { + realLayoutString := strings.Replace(l, `%value%`, f.valuePattern, -1) + f.layoutMap[l] = regexp.MustCompile(realLayoutString) + } + }) + return f.layoutMap[layout] +} + /******************************************************************************/ var ( @@ -193,9 +207,22 @@ var ( layoutDowOfSpecificWeek = `^(%value%)#([1-5])$` fieldFinder = regexp.MustCompile(`\S+`) entryFinder = regexp.MustCompile(`[^,]+`) - layoutRegexp = make(map[string]*regexp.Regexp) ) +var allLayouts = []string{ + layoutWildcard, + layoutValue, + layoutRange, + layoutWildcardAndInterval, + layoutValueAndInterval, + layoutRangeAndInterval, + layoutLastDom, + layoutWorkdom, + layoutLastWorkdom, + layoutDowOfLastWeek, + layoutDowOfSpecificWeek, +} + /******************************************************************************/ var cronNormalizer = strings.NewReplacer( @@ -264,7 +291,7 @@ type cronDirective struct { send int } -func genericFieldHandler(s string, desc fieldDescriptor) ([]int, error) { +func genericFieldHandler(s string, desc *fieldDescriptor) ([]int, error) { directives, err := genericFieldParse(s, desc) if err != nil { return nil, err @@ -302,12 +329,12 @@ func (expr *Expression) dowFieldHandler(s string) error { sdirective := s[directive.sbeg:directive.send] snormal := strings.ToLower(sdirective) // `5L` - pairs := makeLayoutRegexp(layoutDowOfLastWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal) + pairs := dowDescriptor.makeLayoutRegexp(layoutDowOfLastWeek).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { populateOne(expr.lastWeekDaysOfWeek, dowDescriptor.atoi(snormal[pairs[2]:pairs[3]])) } else { // `5#3` - pairs := makeLayoutRegexp(layoutDowOfSpecificWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal) + pairs := dowDescriptor.makeLayoutRegexp(layoutDowOfSpecificWeek).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { populateOne(expr.specificWeekDaysOfWeek, (dowDescriptor.atoi(snormal[pairs[4]:pairs[5]])-1)*7+(dowDescriptor.atoi(snormal[pairs[2]:pairs[3]])%7)) } else { @@ -344,15 +371,15 @@ func (expr *Expression) domFieldHandler(s string) error { sdirective := s[directive.sbeg:directive.send] snormal := strings.ToLower(sdirective) // `L` - if makeLayoutRegexp(layoutLastDom, domDescriptor.valuePattern).MatchString(snormal) { + if domDescriptor.makeLayoutRegexp(layoutLastDom).MatchString(snormal) { expr.lastDayOfMonth = true } else { // `LW` - if makeLayoutRegexp(layoutLastWorkdom, domDescriptor.valuePattern).MatchString(snormal) { + if domDescriptor.makeLayoutRegexp(layoutLastWorkdom).MatchString(snormal) { expr.lastWorkdayOfMonth = true } else { // `15W` - pairs := makeLayoutRegexp(layoutWorkdom, domDescriptor.valuePattern).FindStringSubmatchIndex(snormal) + pairs := domDescriptor.makeLayoutRegexp(layoutWorkdom).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { populateOne(expr.workdaysOfMonth, domDescriptor.atoi(snormal[pairs[2]:pairs[3]])) } else { @@ -397,7 +424,7 @@ func toList(set map[int]bool) []int { /******************************************************************************/ -func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) { +func genericFieldParse(s string, desc *fieldDescriptor) ([]*cronDirective, error) { // At least one entry must be present indices := entryFinder.FindAllStringIndex(s, -1) if len(indices) == 0 { @@ -414,7 +441,7 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) snormal := strings.ToLower(s[indices[i][0]:indices[i][1]]) // `*` - if makeLayoutRegexp(layoutWildcard, desc.valuePattern).MatchString(snormal) { + if desc.makeLayoutRegexp(layoutWildcard).MatchString(snormal) { directive.kind = all directive.first = desc.min directive.last = desc.max @@ -423,14 +450,14 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) continue } // `5` - if makeLayoutRegexp(layoutValue, desc.valuePattern).MatchString(snormal) { + if desc.makeLayoutRegexp(layoutValue).MatchString(snormal) { directive.kind = one directive.first = desc.atoi(snormal) directives = append(directives, &directive) continue } // `5-20` - pairs := makeLayoutRegexp(layoutRange, desc.valuePattern).FindStringSubmatchIndex(snormal) + pairs := desc.makeLayoutRegexp(layoutRange).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { directive.kind = span directive.first = desc.atoi(snormal[pairs[2]:pairs[3]]) @@ -440,7 +467,7 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) continue } // `*/2` - pairs = makeLayoutRegexp(layoutWildcardAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal) + pairs = desc.makeLayoutRegexp(layoutWildcardAndInterval).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { directive.kind = span directive.first = desc.min @@ -450,7 +477,7 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) continue } // `5/2` - pairs = makeLayoutRegexp(layoutValueAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal) + pairs = desc.makeLayoutRegexp(layoutValueAndInterval).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { directive.kind = span directive.first = desc.atoi(snormal[pairs[2]:pairs[3]]) @@ -460,7 +487,7 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) continue } // `5-20/2` - pairs = makeLayoutRegexp(layoutRangeAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal) + pairs = desc.makeLayoutRegexp(layoutRangeAndInterval).FindStringSubmatchIndex(snormal) if len(pairs) > 0 { directive.kind = span directive.first = desc.atoi(snormal[pairs[2]:pairs[3]]) @@ -474,16 +501,4 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) directives = append(directives, &directive) } return directives, nil -} - -/******************************************************************************/ - -func makeLayoutRegexp(layout, value string) *regexp.Regexp { - layout = strings.Replace(layout, `%value%`, value, -1) - re := layoutRegexp[layout] - if re == nil { - re = regexp.MustCompile(layout) - layoutRegexp[layout] = re - } - return re -} +} \ No newline at end of file