Search code examples
gotimer

Is it possible to stop timer gracefully without additional channel in golang?


Here is my test code:

package main

import (
    "runtime"
    "testing"
    "time"
)

func testTimerRoutine(expire bool) bool {
    dur := 10 * time.Millisecond
    timer := time.NewTimer(dur)
    leak := true
    go func() {
        defer func() {
            println("timer routine quit")
            leak = false
        }()
        <-timer.C
        println("timer expired")
    }()

    if expire {
        time.Sleep(2 * dur)
    }

    if !timer.Stop() {
        println("timer stop false")
        <-timer.C
    } else {
        println("timer stop true")
    }

    runtime.GC()
    time.Sleep(10 * dur)
    return leak
}

func TestTimerRoutineDeadlock(t *testing.T) {
    leak := testTimerRoutine(true)
    if leak {
        t.Errorf("routine leak")
    }
}

func TestTimerRoutineLeak(t *testing.T) {
    leak := testTimerRoutine(false)
    if leak {
        t.Errorf("routine leak")
    }
}

I meant to think that without other channels, just using time.Timer.C.

I now start a go routine dedicated to reading timer.C in response to a timer timeout after the timer is started.

But I found two problems with this approach:

  1. there seems to be a problem with the go routine not exiting if the timer is stopped before it has timed out;
  2. If the timer has already timed out and the call to Stop() returns false, there will be a deadlock at this point to drain timer.C as required by the API.

I wonder if there is a way, without using an additional Channel, to achieve the functionality I require to stop the timer early in response to a timer timeout.


Solution

  • Here's a quote from the documentation of timer:

    This (Stop) cannot be done concurrent to other receives from the Timer's channel or other calls to the Timer's Stop method.

    Yet, this is exactly what you are doing. You have a goroutine receiving from a timer channel concurrently with the Stop call.

    In general, if you need to stop a timer because of some sort of an event, send a request to the listening goroutine to stop the timer.

    You don't have to drain the channel as it is a channel with capacity 1, so if nothing is listening from it, it will be garbage collected.