From aaebc3f64f882a3b512f7d1ae8fb6e86e633cd49 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Tue, 15 Nov 2022 19:01:11 +0100 Subject: [PATCH 1/4] feat(concurrency): adding lo.WaitFor --- README.md | 25 +++++++++++++++++++++++++ concurrency.go | 37 ++++++++++++++++++++++++++++++++++++- concurrency_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d37ed23..e67696ee 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ Concurrency helpers: - [Synchronize](#synchronize) - [Async](#async) - [Transaction](#transaction) +- [WaitFor](#waitfor) Error handling: @@ -2837,6 +2838,30 @@ _, _ = transaction.Process(-5) // rollback 1 ``` +### WaitFor + +Runs periodically until a condition is validated. + +```go +alwaysTrue := func(i int) bool { return true } +alwaysFalse := func(i int) bool { return false } +laterTrue := func(i int) bool { + return i > 5 +} + +ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) +// true + +ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) +// false + +ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) +// true + +ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) +// false +``` + ### Validate Helper function that creates an error when a condition is not met. diff --git a/concurrency.go b/concurrency.go index 31a62dde..95580661 100644 --- a/concurrency.go +++ b/concurrency.go @@ -1,6 +1,9 @@ package lo -import "sync" +import ( + "sync" + "time" +) type synchronize struct { locker sync.Locker @@ -93,3 +96,35 @@ func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, }() return ch } + +// WaitFor runs periodically until a condition is validated. +func WaitFor(condition func(i int) bool, maxDuration time.Duration, tick time.Duration) (int, time.Duration, bool) { + if condition(0) { + return 1, 0, true + } + + start := time.Now() + + timer := time.NewTimer(maxDuration) + ticker := time.NewTicker(tick) + + defer func() { + timer.Stop() + ticker.Stop() + }() + + i := 1 + + for { + select { + case <-timer.C: + return i, time.Since(start), false + case <-ticker.C: + if condition(i) { + return i + 1, time.Since(start), true + } + + i++ + } + } +} diff --git a/concurrency_test.go b/concurrency_test.go index ae65efdd..a75f7f52 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -212,3 +212,47 @@ func TestAsyncX(t *testing.T) { } } } + +func TestWaitFor(t *testing.T) { + t.Parallel() + testWithTimeout(t, 100*time.Millisecond) + is := assert.New(t) + + alwaysTrue := func(i int) bool { return true } + alwaysFalse := func(i int) bool { return false } + + iter, duration, ok := WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) + is.Equal(1, iter) + is.Equal(time.Duration(0), duration) + is.True(ok) + iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) + is.Equal(10, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) + + laterTrue := func(i int) bool { + return i >= 5 + } + + iter, duration, ok = WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) + is.Equal(6, iter) + is.InEpsilon(6*time.Millisecond, duration, float64(500*time.Microsecond)) + is.True(ok) + iter, duration, ok = WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) + is.Equal(2, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) + + counter := 0 + + alwaysFalse = func(i int) bool { + is.Equal(counter, i) + counter++ + return false + } + + iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) + is.Equal(10, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) +} From 31e7f691836caf6ab04d4cbca86548f16707f2d1 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Fri, 28 Jun 2024 21:07:23 +0200 Subject: [PATCH 2/4] doc --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e67696ee..e4869dc4 100644 --- a/README.md +++ b/README.md @@ -2849,16 +2849,24 @@ laterTrue := func(i int) bool { return i > 5 } -ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) +iterations, duration, ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) +// 1 +// 0ms // true ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) +// 10 +// 10ms // false ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) +// 7 +// 7ms // true ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) +// 2 +// 10ms // false ``` From 1880550a8f4bcc2ae31242d10a4e0c3c01bf55fd Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Fri, 28 Jun 2024 21:43:18 +0200 Subject: [PATCH 3/4] more accurate tests --- concurrency_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concurrency_test.go b/concurrency_test.go index a75f7f52..6c65b5ad 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -225,8 +225,8 @@ func TestWaitFor(t *testing.T) { is.Equal(1, iter) is.Equal(time.Duration(0), duration) is.True(ok) - iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) - is.Equal(10, iter) + iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, 4*time.Millisecond) + is.Equal(3, iter) is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) is.False(ok) From 173088f6a4f6ce88b1019296c16cf8cf6a993372 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Fri, 28 Jun 2024 21:55:20 +0200 Subject: [PATCH 4/4] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4869dc4..e975267e 100644 --- a/README.md +++ b/README.md @@ -2854,17 +2854,17 @@ iterations, duration, ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Mil // 0ms // true -ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) +iterations, duration, ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) // 10 // 10ms // false -ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) // 7 // 7ms // true -ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) // 2 // 10ms // false