Skip to content

Commit 682e8d8

Browse files
committed
follow-up for 6849858
It is expected that the DurationExpr.s contains valid duration. That's why DurationExpr.Duration() shouldn't return an error. Instead, it must return the maximum possible value (or the minimum possible value) if the provided `step` arg is too big for the stored duration ending with `i` such as `1234567i`. Returning the error from DurationExpr.Duration() may require too many changes at the caller side, which may introduce new errors if they weren't verified carefully. See, for example, VictoriaMetrics/VictoriaMetrics#8487 . That's why it is better to do not return an error from DurationExpr.Duration(), and returning the clamped duration instead. Updates VictoriaMetrics/VictoriaMetrics#8447
1 parent 6849858 commit 682e8d8

File tree

4 files changed

+45
-57
lines changed

4 files changed

+45
-57
lines changed

lexer.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,13 @@ func DurationValue(s string, step int64) (int64, error) {
598598
isMinus = true
599599
}
600600
}
601-
if math.Abs(d) > 1<<63-1 {
602-
return 0, fmt.Errorf("too big duration %.0fms", d)
601+
if d > math.MaxInt64 {
602+
// Truncate too big durations. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8447
603+
return math.MaxInt64, nil
604+
}
605+
if d < math.MinInt64 {
606+
// Truncate too small durations. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8447
607+
return math.MinInt64, nil
603608
}
604609
return int64(d), nil
605610
}

lexer_test.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -504,14 +504,14 @@ func testLexerError(t *testing.T, s string) {
504504
}
505505

506506
func TestPositiveDurationSuccess(t *testing.T) {
507-
f := func(s string, step, expectedD int64) {
507+
f := func(s string, step, dExpected int64) {
508508
t.Helper()
509509
d, err := PositiveDurationValue(s, step)
510510
if err != nil {
511511
t.Fatalf("unexpected error: %s", err)
512512
}
513-
if d != expectedD {
514-
t.Fatalf("unexpected duration; got %d; want %d", d, expectedD)
513+
if d != dExpected {
514+
t.Fatalf("unexpected duration; got %d; want %d", d, dExpected)
515515
}
516516
}
517517

@@ -549,6 +549,10 @@ func TestPositiveDurationSuccess(t *testing.T) {
549549
f("1H", 45, 1*60*60*1000)
550550
f("1D", 45, 1*24*60*60*1000)
551551
f("1Y", 45, 1*365*24*60*60*1000)
552+
553+
// Too big duration
554+
f("10000000000y", 0, math.MaxInt64)
555+
f("922335359011637780i", 5*3600*1000, math.MaxInt64)
552556
}
553557

554558
func TestPositiveDurationError(t *testing.T) {
@@ -572,23 +576,20 @@ func TestPositiveDurationError(t *testing.T) {
572576
f("1mi")
573577
f("1mb")
574578

575-
// Too big duration
576-
f("10000000000y")
577-
578579
// Uppercase M isn't a duration, but a 1e6 multiplier.
579580
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3664
580581
f("1M")
581582
}
582583

583584
func TestDurationSuccess(t *testing.T) {
584-
f := func(s string, step, expectedD int64) {
585+
f := func(s string, step, dExpected int64) {
585586
t.Helper()
586587
d, err := DurationValue(s, step)
587588
if err != nil {
588589
t.Fatalf("unexpected error: %s", err)
589590
}
590-
if d != expectedD {
591-
t.Fatalf("unexpected duration; got %d; want %d", d, expectedD)
591+
if d != dExpected {
592+
t.Fatalf("unexpected duration; got %d; want %d", d, dExpected)
592593
}
593594
}
594595

@@ -641,6 +642,14 @@ func TestDurationSuccess(t *testing.T) {
641642
f("-3.H", 10, -3*60*60*1000)
642643
f("1D", 10, 1*24*60*60*1000)
643644
f("-.1Y", 10, -0.1*365*24*60*60*1000)
645+
646+
// Too big duration
647+
f("10000000000y", 0, math.MaxInt64)
648+
f("922335359011637780i", 5*3600*1000, math.MaxInt64)
649+
650+
// Too small duration
651+
f("-10000000000y", 0, math.MinInt64)
652+
f("-922335359011637780i", 5*3600*1000, math.MinInt64)
644653
}
645654

646655
func TestDurationError(t *testing.T) {

parser.go

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,10 +1030,7 @@ func expandDuration(was []*withArgExpr, d *DurationExpr) (*DurationExpr, error)
10301030
return t, nil
10311031
case *NumberExpr:
10321032
// Convert number of seconds to DurationExpr
1033-
de := &DurationExpr{
1034-
s: t.s,
1035-
}
1036-
return de, nil
1033+
return newDurationExpr(t.s)
10371034
default:
10381035
return nil, fmt.Errorf("unexpected value for WITH template %q; got %s; want duration", d.s, e.AppendString(nil))
10391036
}
@@ -1631,26 +1628,33 @@ func (p *parser) parsePositiveDuration() (*DurationExpr, error) {
16311628
}
16321629
}
16331630
// Verify duration value.
1634-
if _, err := DurationValue(s, 0); err != nil {
1635-
return nil, fmt.Errorf(`duration: parse value error: %q: %w`, s, err)
1636-
}
16371631
if s == "$__interval" {
16381632
s = "1i"
16391633
}
1640-
de := &DurationExpr{
1641-
s: s,
1642-
}
1643-
return de, nil
1634+
return newDurationExpr(s)
16441635
}
16451636

16461637
// DurationExpr contains the duration
16471638
type DurationExpr struct {
1639+
// s is a string representation of the duration.
1640+
//
1641+
// it must contain valid duration if needsParsing is set to false.
16481642
s string
16491643

16501644
// needsParsing is set to true if s isn't parsed yet with expandWithExpr()
16511645
needsParsing bool
16521646
}
16531647

1648+
func newDurationExpr(s string) (*DurationExpr, error) {
1649+
if _, err := DurationValue(s, 0); err != nil {
1650+
return nil, fmt.Errorf(`cannot parse duration %q: %w`, s, err)
1651+
}
1652+
de := &DurationExpr{
1653+
s: s,
1654+
}
1655+
return de, nil
1656+
}
1657+
16541658
// AppendString appends string representation of de to dst and returns the result.
16551659
func (de *DurationExpr) AppendString(dst []byte) []byte {
16561660
if de == nil {
@@ -1663,29 +1667,26 @@ func (de *DurationExpr) AppendString(dst []byte) []byte {
16631667
//
16641668
// Error is returned if the duration is negative.
16651669
func (de *DurationExpr) NonNegativeDuration(step int64) (int64, error) {
1666-
d, err := de.Duration(step)
1667-
if err != nil {
1668-
return 0, err
1669-
}
1670+
d := de.Duration(step)
16701671
if d < 0 {
16711672
return 0, fmt.Errorf("unexpected negative duration %dms", d)
16721673
}
16731674
return d, nil
16741675
}
16751676

16761677
// Duration returns the duration from de in milliseconds.
1677-
func (de *DurationExpr) Duration(step int64) (int64, error) {
1678+
func (de *DurationExpr) Duration(step int64) int64 {
16781679
if de == nil {
1679-
return 0, nil
1680+
return 0
16801681
}
16811682
if de.needsParsing {
16821683
panic(fmt.Errorf("BUG: duration %q must be already parsed", de.s))
16831684
}
16841685
d, err := DurationValue(de.s, step)
16851686
if err != nil {
1686-
return 0, fmt.Errorf("cannot parse duration %q: %w", de.s, err)
1687+
panic(fmt.Errorf("BUG: cannot parse duration %q: %s", de.s, err))
16871688
}
1688-
return d, nil
1689+
return d
16891690
}
16901691

16911692
// parseIdentExpr parses expressions starting with `ident` token.

parser_test.go

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,30 +1008,3 @@ func TestParseError(t *testing.T) {
10081008
f(`with (x={a="b" or c="d"}) {x,d="e"}`)
10091009
f(`with (x={a="b" or c="d"}) {x,d="e" or z="c"}`)
10101010
}
1011-
1012-
func TestParseDuration(t *testing.T) {
1013-
s := func(v string, step, expected int64) {
1014-
de := DurationExpr{s: v}
1015-
result, err := de.NonNegativeDuration(step)
1016-
if err != nil {
1017-
t.Fatalf("unexpected error: %s", err)
1018-
}
1019-
if result != expected {
1020-
t.Fatalf("unexpected result; got %d, expected %d", result, expected)
1021-
}
1022-
}
1023-
1024-
f := func(v string, step int64) {
1025-
de := DurationExpr{s: v}
1026-
_, err := de.NonNegativeDuration(step)
1027-
if err == nil {
1028-
t.Fatalf("expecting error, got nil")
1029-
}
1030-
}
1031-
1032-
s("1i", 10, 10)
1033-
s("10s", 300, 10*1000)
1034-
1035-
// too large resulting value
1036-
f("99999999999999999999i", 10)
1037-
}

0 commit comments

Comments
 (0)