Blocking send and receive algorithm in Go 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 (
    &quot;fmt&quot;
    &quot;math/rand&quot;
    &quot;time&quot;
)

/****** Simulating the receiver behaviour ******/

var receiveChannel = make(chan string, 1)

// Used to read messages from the interface
func Read() string {
    return &lt;-receiveChannel
}

// Used to write messages to the interface
func Write(data string) {
    // Randomly drop 50% of packets
    if n := rand.Intn(100); n &lt; 50 {
        receiveChannel &lt;- &quot;ACK&quot;
    }
}

/*******************************************/

var ackChannel = make(chan bool, 1)
var sendChannel = make(chan string, 1)

func run() {
    for {
        if Read() == &quot;ACK&quot; {
            ackChannel &lt;- 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 &lt;- data:
        timeoutTimer.Stop()
        fmt.Printf(&quot;Send chan reserved for command id=%v\n&quot;, data)
    case &lt;-timeoutTimer.C:
        return fmt.Errorf(&quot;timed out while waiting for send channel to be free. id=%v&quot;, data)
    }

    attempts := 2
    err := send(data, attempts)

    // Free up the sending channel
    select {
    case x := &lt;-sendChannel:
        fmt.Printf(&quot;Send channel cleared %v\n&quot;, x)
    default:
        fmt.Printf(&quot;Send channel is empty. This should never happen!\n&quot;)
    }

    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 &lt;-ackChannel:
        ackTimeoutTimer.Stop()
    case &lt;-ackTimeoutTimer.C:
        // Retry again
        if attempts &gt; 1 {
            return send(data, attempts-1)
        }
        return fmt.Errorf(&quot;Timed out while waiting for ack. id=%v&quot;, data)
    }
    fmt.Printf(&quot;Frame sent and acked id=%v\n&quot;, data)
    return nil
}

/****** Testing ******/

func main() {
    go run()
    
    // Multiple goroutines sending data
    for i := 0; i &lt; 7; i++ {
        go func() {
            x := i
            if err := sendWrapper(fmt.Sprint(x)); err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Printf(&quot;Sending successful. i=%v\n&quot;, x)
            }
        }()
        time.Sleep(10 * time.Millisecond)
    }
    time.Sleep(4 * time.Second)
}
You have at least one bug.



The closure from your code:

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i &lt; 7; i++ {
        go func() {
            x := i
            fmt.Printf("Sending successful. i=%v\n", x)
        }()
    }
    time.Sleep(4 * time.Second)
}


Output:

$ go run closure.go
Sending successful. i=7
Sending successful. i=7
Sending successful. i=7
Sending successful. i=7
Sending successful. i=7
Sending successful. i=7
Sending successful. i=7
$


The x := i statement is in the wrong place. It should be:

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i &lt; 7; i++ {
        x := i
        go func() {
            fmt.Printf("Sending successful. i=%v\n", x)
        }()
    }
    time.Sleep(4 * time.Second)
}


Output:

$ go run closure.go
Sending successful. i=4
Sending successful. i=5
Sending successful. i=1
Sending successful. i=3
Sending successful. i=2
Sending successful. i=0
Sending successful. i=6
$




Reference:

Go: Frequently Asked Questions (FAQ)

What happens with closures running as
goroutines?