Timeout factories

Timeout factories

Suppose you want to execute a function, and you expect it to complete in a pre defined amount of time..

Maybe you just don’t care about the result if you can’t get it quickly.

Timeout patterns in golang are very useful if you want to fail quickly. Particularly in regard to web programming or socket programming.

The idea behind a timeout is handy, but a pain to code over and over. Here is a clever example of a concurrent timeout implementation, as well as an example on channel factories.

Timeout

Timeouts are the idea that the code should move forward based on an arbitrary defined amount of time, if another task has not completed.

Concurrent Factories

Concurrent factories are ways of generating channels in go, that look and feel the same, but can have different implementations.

In the case of the code below, the getTimeoutChannel function behaves as a concurrent factory.


package main

import (
	"time"
	"fmt"
)

// Will call the getTimeoutChannel factory function, passing in different sleep times for each channel
func main() {
	ch_a := getTimeoutChannel(1) // 1 sec
	ch_b := getTimeoutChannel(2) // 2 sec
	ch_c := getTimeoutChannel(5) // 5 sec
	switch {
	case <-ch_a:
		fmt.Println("Channel A")
		break
	case <-ch_b:
		fmt.Println("Channel B")
		break
	case <-ch_c:
		fmt.Println("Channel C")
		break
	}
}

// Will generate a new channel, and concurrently run a sleep based on the input
// Will return true after the sleep is over
func getTimeoutChannel(N int) chan bool {
	ch := make(chan bool)
	go func() {
		time.Sleep(time.Second * time.Duration(N))
		ch <- true
	}()
	return ch
}

Follow @kris-nova

What happened?

We started 3 concurrent timeout factories, each with a unique channel. Each of the channels will timeout and return a value, after the defined sleep. In this example ch_a will obviously timeout first, as it only sleeps for 1 second.

The switch statement will hang until one of the 3 channels returns a value. After the switch statement detects a value from a channel, it performs the logic for the corresponding case. This allows us to easily pick and chose which avenue the code should progress with further.

When is this useful?

Imagine if ch_a and ch_b were not timeout channels but rather actual logic in your program. Imagine if ch_a was actually a read from a cache, and ch_b was actually a read from a database.

Let’s say the 2 second timeout, was actually a 2 second cache read. The program shouldn’t really care if the cache read is successful or not. If it is taking 2 seconds, then it is hardly doing it’s job as a quick cache anyway. So the program should be smart enough to use whatever value is returned first and not whatever value should be returned first. In this case, the database read.

Now we are in a situation where we implemented a cache, and for whatever reason the cache doesn’t seem to want to return a value. Perhaps updating the cache would be in order?

We can take our example one step further and keep the 5 second timeout on ch_c. For the sake of our experimental program, 5 seconds should be more than enough time for any of the supported avenues to return a meaningful value. If after the 5 seconds has elapsed and the first two channels haven’t reported any valuable data we should consider the system in a state of catastrophic failure, and report back accordingly. Simply add the failure path to the program, and rest assured that the program will handle even the most unexpected of edge cases quickly, and meaningfully.

Now, doesn’t that seem like a great way to structure a program?

LEAVE A COMMENT

0 comment