Search code examples
gotimer

Timer implementation for rpc method


I have a Go RPC server that serves client requests. A client requests work (or task) from the server and the server assigns a task to the client. The server expects workers (or clients) to finish any task within a time limit. Therefore a timeout event callback mechanism is required on the server-side.

Here is what I tried so far.

func (l *Listener) RequestHandler(request string, reply string) error {
    // some other work
    // ....
    _timer := time.NewTimer(time.Second * 5) // timer for 2 seconds
    go func() {
        // simulates a client not replying case, with timeout of 2 sec
        y := <-_timer.C
        fmt.Println("TimeOut for client")
        // revert state changes becasue of client fail
    }()

    // set reply
    // update some states
    return nil
}

In the above snippet for each request from a worker (or a client) the handler in the server-side starts a timer and a goroutine. The goroutine reverts the changes done by the handler function before sending a reply to the client.

Is there any way of creating a "set of timers" and blocking wait on the "set of timers" ? Further, whenever a timer expires the blocking wait wakes up and provides us with the timer handles. Depending on the timer type we can perform different expiry handler functions in the runtime.

I am trying to implement a similar mechanism in Go that we can implement in C++ with timerfd with epoll.

Full code for the sample implementation of timers in Go. server.go and client.go.


Solution

  • I suggest you to explored the context package

    it can be be done like this:

    func main() {
        c := context.Background()
        wg := &sync.WaitGroup{}
        f(c, wg)
        wg.Wait()
    }
    
    func f(c context.Context, wg *sync.WaitGroup) {
        c, _ = context.WithTimeout(c, 3*time.Second)
        wg.Add(1)
    
        go func(c context.Context) {
            defer wg.Done()
            select {
            case <-c.Done():
                fmt.Println("f() Done:", c.Err())
                return
            case r := <-time.After(5 * time.Second):
                fmt.Println("f():", r)
            }
        }(c)
    }
    

    basically you initiate a base context and then derive other contexts from it, when a context is terminated, either by passing the time or a call to its close, it closes its Done channel and the Done channel of all the contexts that are derived from it.