samol
samol

Reputation: 20590

How to go about testing go routines?

An example of this problem is when a user creates a resource and deletes a resource. We will perform the operation and also increment (decrement) a counter cache.

In testing, there is sometimes a race condition where the counter cache has not been updated by the go routine.

EDIT: Sorry about the confusion, to clarify: the counter cache is not in memory, it is actually a field in the database. The race condition is not to a variable in memory, it is actually that the goroutine might be slow to write into the database itself!

I currently use a 1 second sleep after the operation to ensure that the counter cache has been updated before testing the counter cache. Is there another way to test go routine without the arbitrary 1 second sleep to wait for the go routine to finish?

Cheers

Upvotes: 2

Views: 1868

Answers (2)

nemo
nemo

Reputation: 57659

One solution is to make to let your counter offer a channel which is updated as soon as the value changes. In go it is common practice to synchronize by communicating the result. For example your Couter could look like this:

type Counter struct {
   value int
   ValueChange chan int
}

func (c *Counter) Change(n int) {
    c.value += n
    c.ValueChange <- c.value
}

Whenever Change is called, the new value is passed through the channel and whoever is waiting for the value unblocks and continues execution, therefore synchronizing with the counter. With this code you can listen on ValueChange for changes like this:

v := <-c.ValueChange

Concurrently calling c.Change is no problem anymore.

There is a runnable example on play.

Upvotes: 0

BraveNewCurrency
BraveNewCurrency

Reputation: 13065

In testing, there is sometimes a race condition where the counter cache has not been updated by the go routine. I currently use a 1 second sleep after the operation to ensure that the counter cache has been updated before testing the counter cache.

Yikes, I hate to say it, but you're doing it wrong. Go has first-class features to make concurrency easy! If you use them correctly, it's impossible to have race conditions.

In fact, there's a tool that will detect races for you. I'll bet it complains about your program.

One simple solution:

  • Have the main routine create a goroutine for keeping track of the counter.
  • the goroutine will just do a select and get a message to increment/decrement or read the counter. (If reading, it will be passed in a channel to return the number)
  • when you create/delete resources, send an appropriate message to the goroutine counter via it's channel.
  • when you want to read the counter, send a message for read, and then read the return channel.

(Another alternative would be to use locks. It would be a tiny bit more performant, but much more cumbersome to write and ensure it's correct.)

Upvotes: 3

Related Questions