Golang Context Timeout Not Working Via Test

2k views Asked by At

I have some async calls that runs and I'm setting a timeout for all via the context.

ctxWithTimeout, cancel := context.WithTimeout(ctx, getTimeoutDuration())
defer cancel()

go func1(ctxWithTimeout, outputChan1, param1)
go func2(ctxWithTimeout, outputChan2, param2)
go func3(ctxWithTimeout, outputChan3)

outputChan1Result := <-outputChan1
Some logic...
outputChan2Result := <-outputChan2
Some logic...
outputChan3Result := <-outputChan3
Some logic...

getTimeoutDuration will return "1" (nanosecond for tests) and 60 seconds for other cases. I made sure I have the correct values when running the tests.

func1 and the other 2 have some logic inside and some other calls that I'm using mocks for.

The code works when I run my service and execute the call via a postman call, if I hit the timeout I see the correct code is being executed and I get the correct response in postman. The timeout can be identified by

ctx.Err() == context.DeadlineExceeded

I wrote a test and wanted to reach timeout. The execution of the 3 functions takes ~130µs and the code runs without hitting the 1 nanosecond timeout. The result is also as if I just managed to run and execute all the code under the time limitation.

Any idea why the timeout is not being triggered or how to make sure it will be triggered?

2

There are 2 answers

0
Jonathan Hall On

You have not included your complete code, but I'm guessing the function you wish to test looks something like this:

func foo(ctx context.Context) {
    ctxWithTimeout, cancel := context.WithTimeout(ctx, getTimeoutDuration())
    defer cancel()

    go func1(ctxWithTimeout, outputChan1, param1)
    go func2(ctxWithTimeout, outputChan2, param2)
    go func3(ctxWithTimeout, outputChan3)
}

And your test call looks someting like:

    foo(context.Background())`

Change your test to:

    ctx, cancel := context.WithCancel(context.Background())
    cancel()
    foo(ctx)
1
isavinof On

According to your example, the code does not work as you expect. And you should wait channels and context deadline simultaneously in the select.

ctxWithTimeout, cancel := context.WithTimeout(ctx, getTimeoutDuration())

go func1(ctxWithTimeout, outputChan1, param1)
go func2(ctxWithTimeout, outputChan2, param2)
go func3(ctxWithTimeout, outputChan3)
select {
    case outputChan1Result := <-outputChan1:
    case outputChan2Result := <-outputChan2:
    case outputChan3Result := <-outputChan3:
    case <-ctxWithTimeout.Done():
}

Furthermore, you should wait all results somehow: waitGroup or counter e.g.

Dirty example: https://go.dev/play/p/-eJcjAItMMu