I'm sending and receiving data over an interface (serial in this case) with the following behaviour:
- The receiver sends back an Ack message if a message is delivered successfully.
- If an Ack is not received within a timeout, the command should be re-sent limited a number of times.
- Multiple clients may request sending at the same time, but no new commands should be sent until an Ack for the last command is received or it times out. Other sending commands shall wait until the channel is unreserved or they time out.
The algorithm to do this is fairly simple. (Feel free to say so if you think it can be improved):
The implementation in Go works as expected. I just feel there might be a smarter way to do it:
package main import ( "fmt" "math/rand" "time" ) /****** Simulating the receiver behaviour ******/ var receiveChannel = make(chan string, 1) // Used to read messages from the interface func Read() string { return <-receiveChannel } // Used to write messages to the interface func Write(data string) { // Randomly drop 50% of packets if n := rand.Intn(100); n < 50 { receiveChannel <- "ACK" } } /*******************************************/ var ackChannel = make(chan bool, 1) var sendChannel = make(chan string, 1) func run() { for { if Read() == "ACK" { ackChannel <- true } } } func sendWrapper(data string) error { // Reserve the sending channel timeoutTimer := time.NewTimer(1 * time.Second) select { // This should block until sendChannel has a free spot or times out case sendChannel <- data: timeoutTimer.Stop() fmt.Printf("Send chan reserved for command id=%v\n", data) case <-timeoutTimer.C: return fmt.Errorf("timed out while waiting for send channel to be free. id=%v", data) } attempts := 2 err := send(data, attempts) // Free up the sending channel select { case x := <-sendChannel: fmt.Printf("Send channel cleared %v\n", x) default: fmt.Printf("Send channel is empty. This should never happen!\n") } return err } func send(data string, attempts int) error { // Send data Write(data) // Wait for an ACK to be received ackTimeoutTimer := time.NewTimer(time.Millisecond * 100) select { case <-ackChannel: ackTimeoutTimer.Stop() case <-ackTimeoutTimer.C: // Retry again if attempts > 1 { return send(data, attempts-1) } return fmt.Errorf("Timed out while waiting for ack. id=%v", data) } fmt.Printf("Frame sent and acked id=%v\n", data) return nil } /****** Testing ******/ func main() { go run() // Multiple goroutines sending data for i := 0; i < 7; i++ { go func() { x := i if err := sendWrapper(fmt.Sprint(x)); err != nil { fmt.Println(err.Error()) } else { fmt.Printf("Sending successful. i=%v\n", x) } }() time.Sleep(10 * time.Millisecond) } time.Sleep(4 * time.Second) }