I ended up creating an issue on this:
https://github.com/golang/go/issues/44026

On Sat, Jan 30, 2021 at 9:12 PM Christian Worm Mortensen <c...@epust.dk>
wrote:

> Hi Mike,
>
> Thank you for your consideration. I think you exactly got the essence of
> my question: How do I wait on all go routines to finish (or be blocked on
> one or more channels) before advancing time.
>
> A key thing I would like from such a solution is that it does not require
> too heavy modifications to the code to be tested or put restrictions on how
> it can do things.
>
> I think it may be possible to solve it with some explicit check in / check
> out as I think you also suggest. I guess in essence you will check out
> before you call select and check in again after select is done waiting. I
> think this will still not work if buffered channels are used. But maybe if
> buffered channels are mocked, it may be doable.
>
> I think I may want to make a feature request on this. I see several
> options:
>
> * Make a version of runtime.gosched that only returns when no other go
> routines can run
> * Make it possible to read the number of go routines that are ready to
> run. You could then make a loop where you call runtime.gosched until that
> value is 0.
> * Make it possible to start a special go routine when the system is
> deadlocked.
>
> One problem is what to do if the program is waiting on external IO such as
> the completion of an HTTP request. I guess in an ideal solution it would be
> possible for the program to decide if it will advance time in that
> situation or not.
>
> Please let me know if you have any ideas of other things to put into the
> feature request.
>
> Thanks,
>
> Christian
>
> On Fri, Jan 29, 2021 at 9:25 PM mspr...@us.ibm.com <mspre...@us.ibm.com>
> wrote:
>
>> Volker: injecting sleep is a nice idea, in the general vein that Jesper
>> said of injecting time.  However, as soon as we zoom out a step and need to
>> test both that generator and the goroutine(s) consuming and acting upon
>> that channel activity, we get back to the essence of the original question:
>> how to test when we have a bunch of goroutines doing stuff and the test
>> needs to wait for them all to finish before advancing time?
>>
>> FYI, in Kubernetes we have done something similar to the Facebook clock
>> package --- but recently we have called out the narrower interface used by
>> code that only reads time.  See PassiveClock in
>> https://github.com/kubernetes/utils/blob/master/clock/clock.go and
>> https://github.com/kubernetes/apimachinery/blob/master/pkg/util/clock/clock.go
>> (yeah, we have two forked lines of development of this clock thing, sigh).
>>
>> The pattern of using channel activity to coordinate asynchronous activity
>> is inherently inimical to what the original poster asked for.  An
>> alternative is to define clocks that run procedures rather than do channel
>> sends.  See the EventClock in
>> https://github.com/kubernetes/apiserver/blob/master/pkg/util/flowcontrol/fairqueuing/testing/clock/event_clock.go
>> .  A mocked one of those could know when all the timed activities have
>> completed --- if all the timed activities were synchronously contained in
>> EventFuncs.  Sadly this is too restrictive a pattern for a lot of real
>> code.  You will see in that package an additional idea: explicitly tracking
>> (at "user level") when the goroutines in question block/unblock.  This is
>> painful, but I see no better way (given the golang runtime interface as it
>> is defined today).
>>
>> Regards,
>> Mike
>>
>> On Friday, January 29, 2021 at 10:11:34 AM UTC-5 Volker Dobler wrote:
>>
>>> One way to do this is have an internal implementation like
>>> func generatorImpl(sleep func(time.Duration)) <-chan int
>>> and func generator just calls that one with time.Sleep.
>>> Tests are done against generatorImpl where you know have
>>> detailed control of how much (typically none) time is
>>> actually slept.
>>>
>>> Expiration of cookies is tested in that way, see e.g.
>>> https://golang.org/src/net/http/cookiejar/jar.go#L159
>>> So while technically Jar.Cookies is never tested the
>>> risk is basically nil.
>>>
>>> V.
>>> On Thursday, 28 January 2021 at 22:15:50 UTC+1 Christian Worm Mortensen
>>> wrote:
>>>
>>>> Hi!
>>>>
>>>> Suppose I want to unit test this function:
>>>>
>>>> func generator() <-chan int {
>>>> ret := make(chan int)
>>>> go func() {
>>>> for i := 0; i < 10; i++ {
>>>> ret <- i
>>>> time.Sleep(time.Second)
>>>> }
>>>> }()
>>>> return ret
>>>> }
>>>>
>>>> What is a good way to do that? One way is to do it is like this:
>>>>
>>>> func testGenerator() {
>>>> start := time.Now()
>>>> g := generator()
>>>> for i := 0; i < 10; i++ {
>>>> v := <-g
>>>> if v != i {
>>>> panic("Wrong value")
>>>> }
>>>> }
>>>> elapsed := time.Now().Sub(start)
>>>> if elapsed < 9*time.Second || elapsed > 11*time.Second {
>>>> panic("Wrong execution time")
>>>> }
>>>> }
>>>>
>>>> However there are several issues with this:
>>>>
>>>> 1) The unit test takes a long time to run - 10 seconds.
>>>> 2) The unit test is fragile to fluctuations in CPU availability
>>>> 3) The unit test is not very accurate
>>>>
>>>> Of course this is a simple example. But what if I want to test a
>>>> complicated piece of code with many go routines interacting in complicated
>>>> ways and with long timeouts?
>>>>
>>>> In other programming languages, I have been able to implement a form of
>>>> virtual time which increases only when all threads are waiting for time to
>>>> increase. This allows functions like generator above to be tested basically
>>>> instantly and this has been extremely useful for me in many projects over
>>>> the years.
>>>>
>>>> Can I do something similar in Go? I would expect I would need to wrap
>>>> time.Now, time.Sleep and time.After which I will be happy to do.
>>>>
>>>> I can see that Go has a deadlock detector. If somehow it was possible
>>>> to have Go start a new Go routine when a deadlock was detected, I think it
>>>> would be pretty straight forward to implement virtual time as described. I
>>>> could then do something like:
>>>>
>>>> runtime.registerDeadlockCallback(func () {
>>>>   // Increase virtual time and by that:
>>>>   //  * Make one or more wrapped time.Sleep calls return or
>>>>   //  * Write to one or more channels returned by wrapped time.After.
>>>> })
>>>>
>>>> Obviously this would only be needed for test code, not production code.
>>>>
>>>> Thanks,
>>>>
>>>> Christian
>>>>
>>> --
>> You received this message because you are subscribed to a topic in the
>> Google Groups "golang-nuts" group.
>> To unsubscribe from this topic, visit
>> https://groups.google.com/d/topic/golang-nuts/Y9Ccen0uMcs/unsubscribe.
>> To unsubscribe from this group and all its topics, send an email to
>> golang-nuts+unsubscr...@googlegroups.com.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/golang-nuts/9f5fa53a-64ca-483a-8e63-bae5c061e569n%40googlegroups.com
>> <https://groups.google.com/d/msgid/golang-nuts/9f5fa53a-64ca-483a-8e63-bae5c061e569n%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CABTkUoa%2Bt5QWXzNmC_du7DzRswczLGiURq_5iOYGywxMqC%3Ddxg%40mail.gmail.com.

Reply via email to