| 1 | import sync |
| 2 | import time |
| 3 | |
| 4 | // Note: this is the same test as `vlib/sync/once_test.v`, but |
| 5 | // it uses an explicit passing of the voidptr parameter in |
| 6 | // once.do_with_param/2, instead of passing a closure of it |
| 7 | // in once.do/1. |
| 8 | |
| 9 | struct One { |
| 10 | pub mut: |
| 11 | i int |
| 12 | } |
| 13 | |
| 14 | fn (mut o One) add(i int) { |
| 15 | o.i = o.i + i |
| 16 | } |
| 17 | |
| 18 | fn run(mut once sync.Once, mut o One, c chan bool) { |
| 19 | once.do_with_param(fn (mut o One) { |
| 20 | o.add(5) |
| 21 | }, o) |
| 22 | c <- true |
| 23 | } |
| 24 | |
| 25 | fn test_once() { |
| 26 | mut o := &One{} |
| 27 | mut once := sync.new_once() |
| 28 | c := chan bool{} |
| 29 | n := 10 |
| 30 | |
| 31 | // It is executed 10 times, but only once actually. |
| 32 | for i := 0; i < n; i++ { |
| 33 | spawn run(mut once, mut o, c) |
| 34 | } |
| 35 | for i := 0; i < n; i++ { |
| 36 | <-c |
| 37 | } |
| 38 | assert o.i == 5 |
| 39 | } |
| 40 | |
| 41 | // Ordering test: do_with_param must not return until f() has completed. |
| 42 | // The bug was that count was set to 1 *before* f() ran, so a concurrent |
| 43 | // caller could observe count==1 and return on the fast path while f() was |
| 44 | // still executing (or hadn't started). |
| 45 | |
| 46 | struct OrderingState { |
| 47 | pub mut: |
| 48 | value int |
| 49 | ready chan bool |
| 50 | } |
| 51 | |
| 52 | fn slow_init(p voidptr) { |
| 53 | mut s := unsafe { &OrderingState(p) } |
| 54 | // Signal that we are inside f() — count has been stored (buggy) or not (fixed). |
| 55 | s.ready <- true |
| 56 | // Sleep long enough for the concurrent do_with_param call to observe the state. |
| 57 | time.sleep(50 * time.millisecond) |
| 58 | s.value = 99 |
| 59 | } |
| 60 | |
| 61 | fn run_ordering(mut once sync.Once, s &OrderingState, c chan bool) { |
| 62 | once.do_with_param(slow_init, s) |
| 63 | c <- true |
| 64 | } |
| 65 | |
| 66 | fn test_once_with_param_ordering() { |
| 67 | mut s := &OrderingState{ |
| 68 | ready: chan bool{cap: 1} |
| 69 | } |
| 70 | mut once := sync.new_once() |
| 71 | c := chan bool{} |
| 72 | |
| 73 | spawn run_ordering(mut once, s, c) |
| 74 | |
| 75 | // Wait until the goroutine is inside f() before letting the second |
| 76 | // caller proceed. With the old buggy code count==1 is already visible |
| 77 | // here, so the second do_with_param returns immediately — before |
| 78 | // s.value is set. With the fix, count is still 0, so the second |
| 79 | // caller blocks on the mutex and waits for f() to complete. |
| 80 | _ := <-s.ready |
| 81 | once.do_with_param(fn (p voidptr) {}, unsafe { nil }) |
| 82 | |
| 83 | assert s.value == 99, 'do_with_param must not return before f() completes' |
| 84 | _ := <-c |
| 85 | } |
| 86 | |