diff --git a/backoff.go b/backoff.go new file mode 100644 index 0000000..5b49521 --- /dev/null +++ b/backoff.go @@ -0,0 +1,76 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package signaling + +import ( + "context" + "fmt" + "time" +) + +type Backoff interface { + Reset() + NextWait() time.Duration + Wait(context.Context) +} + +type exponentialBackoff struct { + initial time.Duration + maxWait time.Duration + nextWait time.Duration +} + +func NewExponentialBackoff(initial time.Duration, maxWait time.Duration) (Backoff, error) { + if initial <= 0 { + return nil, fmt.Errorf("initial must be larger than 0") + } + if maxWait < initial { + return nil, fmt.Errorf("maxWait must be larger or equal to initial") + } + + return &exponentialBackoff{ + initial: initial, + maxWait: maxWait, + + nextWait: initial, + }, nil +} + +func (b *exponentialBackoff) Reset() { + b.nextWait = b.initial +} + +func (b *exponentialBackoff) NextWait() time.Duration { + return b.nextWait +} + +func (b *exponentialBackoff) Wait(ctx context.Context) { + waiter, cancel := context.WithTimeout(ctx, b.nextWait) + defer cancel() + + b.nextWait = b.nextWait * 2 + if b.nextWait > b.maxWait { + b.nextWait = b.maxWait + } + + <-waiter.Done() +} diff --git a/backoff_test.go b/backoff_test.go new file mode 100644 index 0000000..95c9294 --- /dev/null +++ b/backoff_test.go @@ -0,0 +1,64 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package signaling + +import ( + "context" + "testing" + "time" +) + +func TestBackoff_Exponential(t *testing.T) { + backoff, err := NewExponentialBackoff(100*time.Millisecond, 500*time.Millisecond) + if err != nil { + t.Fatal(err) + } + + waitTimes := []time.Duration{ + 100 * time.Millisecond, + 200 * time.Millisecond, + 400 * time.Millisecond, + 500 * time.Millisecond, + 500 * time.Millisecond, + } + + for _, wait := range waitTimes { + if backoff.NextWait() != wait { + t.Errorf("Wait time should be %s, got %s", wait, backoff.NextWait()) + } + + a := time.Now() + backoff.Wait(context.Background()) + b := time.Now() + if b.Sub(a) < wait { + t.Errorf("Should have waited %s, got %s", wait, b.Sub(a)) + } + } + + backoff.Reset() + a := time.Now() + backoff.Wait(context.Background()) + b := time.Now() + if b.Sub(a) < 100*time.Millisecond { + t.Errorf("Should have waited %s, got %s", 100*time.Millisecond, b.Sub(a)) + } +}