How would you cancel function call without time.AfterFunc ?

2,209 views
Skip to first unread message

Gyu-Ho Lee

unread,
Feb 7, 2014, 11:14:05 AM2/7/14
to golan...@googlegroups.com
Just curious, if I can't use condition to shut down a function, how do I end a function? Is there any way that I can cancel a function call?

time.AfterFunc(time.Millisecond, func() { ~~ }) is run on its own goroutine, and we can use Stop method to cancel the function call. But it can only be used for a function running on goroutine of time.AfterFunc.

Here's the source code

   107	// AfterFunc waits for the duration to elapse and then calls f
   108	// in its own goroutine. It returns a Timer that can
   109	// be used to cancel the call using its Stop method.
   110	func AfterFunc(d Duration, f func()) *Timer {
   111		t := &Timer{
   112			r: runtimeTimer{
   113				when: when(d),
   114				f:    goFunc,
   115				arg:  f,
   116			},
   117		}
   118		startTimer(&t.r)
   119		return t
   120	}
   121	
   122	func goFunc(now int64, arg interface{}) {
   123		go arg.(func())()
   124	}
Is there any way that I can cancel function call without it being run on goroutine? For example, time.CancelFunc(time.Millisecond, fn) will run fn immediately and cancel the function after 1ms. This is different than time.AfterFunc(0, fn) which runs fn after 0 ms but still on its own goroutine, whereas time.CancelFunc will run fn on the main, I mean not on the separate goroutine. It might be useful to cancel a function that runs forever in its recursive call.


package main

import "time"

func main() {
count := 0
t := time.AfterFunc(0, func() {
for {
count++
}
})
time.Sleep(time.Millisecond)
t.Stop()
println(count)
}

This returns [process took too long] message. How would you cancel this function call?



Thanks,

Caleb Doxsey

unread,
Feb 7, 2014, 12:39:42 PM2/7/14
to Gyu-Ho Lee, golang-nuts
I'm not sure if it's possible to kill an arbitrary goroutine or not, but this isn't how you should structure your code. If you want a goroutine to be cancelable you need to use channels to communicate between them. For example:


func main() {
ticker := time.NewTicker(time.Second)
killme := make(chan bool)
go func() {
fmt.Println("starting counter")
defer fmt.Println("all done")
cnt := 0
for {
select {
case <-killme:
return
case <-ticker.C:
cnt++
fmt.Println(cnt)
}
}
}()
time.Sleep(time.Second * 10)
killme <- true
}


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Kevin Malachowski

unread,
Feb 7, 2014, 12:48:36 PM2/7/14
to golan...@googlegroups.com
You may want to look into http://godoc.org/launchpad.net/tomb . Depending on your needs using the whole package may be overkill, but you may learn some good Go patterns from it.

Dave Cheney

unread,
Feb 7, 2014, 8:25:54 PM2/7/14
to Caleb Doxsey, Gyu-Ho Lee, golang-nuts
On Sat, Feb 8, 2014 at 4:39 AM, Caleb Doxsey <ca...@doxsey.net> wrote:
I'm not sure if it's possible to kill an arbitrary goroutine or not, but this isn't how you should structure your code. If you want a goroutine to be cancelable you need to use channels to communicate between them. For example:


func main() {
ticker := time.NewTicker(time.Second)
killme := make(chan bool)
go func() {
fmt.Println("starting counter")
defer fmt.Println("all done")
cnt := 0
for {
select {
case <-killme:
return
case <-ticker.C:
cnt++
fmt.Println(cnt)
}
}
}()
time.Sleep(time.Second * 10)
killme <- true

use close(killme) so you don't block while killing the goroutine.

Gyu-Ho Lee

unread,
Feb 8, 2014, 4:33:49 PM2/8/14
to golan...@googlegroups.com, Caleb Doxsey, Gyu-Ho Lee
Thanks, I didn't know about that. closing channel is even simpler!

Thomas Bruyelle

unread,
Feb 9, 2014, 3:20:32 AM2/9/14
to golan...@googlegroups.com, Caleb Doxsey, Gyu-Ho Lee
If your ticker is not shared with other go routines, you can achieve this without creating the extra channel. At the end you invoke ticker.Stop().


func main() {
ticker := time.NewTicker(time.Second)

go func() {
fmt.Println("starting counter")
defer fmt.Println("all done")

cnt := 0
for _ = range ticker.C {
cnt++
fmt.Println(cnt)
}
}()

time.Sleep(time.Second * 10)
ticker.Stop()

Gyu-Ho Lee

unread,
Feb 9, 2014, 3:28:06 AM2/9/14
to golan...@googlegroups.com, Caleb Doxsey, Gyu-Ho Lee
Thank you so much! I really like this pattern!

Caleb Doxsey

unread,
Feb 9, 2014, 10:31:13 AM2/9/14
to Gyu-Ho Lee, golang-nuts
Although in this case it won't matter, I think merely stopping the ticker will leave an orphaned goroutine:

Stop turns off a ticker. After Stop, no more ticks will be sent. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.

In general if a goroutine is something you intend to have run the lifetime of a program then you probably don't need to worry about it. However if it's something you kickoff in response to an http request (or similar), then you'll definitely want to make sure everything gets cleaned up when you're done with it. Otherwise you'll have a memory leak which will eventually catch up to you in a long-lived process.

My original example also seems to have a memory leak. You need to both stop the goroutine and stop the ticker. Something like this:

func example() {
t := time.NewTicker(time.Millisecond)
defer t.Stop()
s := make(chan struct{})
defer close(s)

go func() {
for {
select {
case <-t.C:
// do stuff here
case <-s:
return
}
}
}()

time.Sleep(time.Millisecond)
}

I tested with an example like this:

func main() {
for i :=0; i<100000000; i++ {
example()
time.Sleep(time.Millisecond)
}
fmt.Scanln()
}

With an orphaned goroutine memory grows very quickly. Without the t.Stop() it still grows, albeit much more slowly. By fixing both issues it seems to be stable.

Gyu-Ho Lee

unread,
Feb 9, 2014, 3:24:47 PM2/9/14
to golan...@googlegroups.com, Gyu-Ho Lee, ca...@doxsey.net
Thanks, so there is no way in Go to cancel a function call that is not on a separate goroutine? Then how do we run any function with limit, in case it runs and takes too long time to return? Other than time.AfterFunc, how do we set the lifetime of function?

Here is my try but I think it still has the "orphaned" goroutine and runs slow in playground, but this is something I want to do.


ticknr := func(limit time.Duration, f func()) bool {

t := time.NewTicker(100)
// t := time.NewTicker(1)
// If we want to run the function only once
// we do not need to put the smallest time

s := make(chan struct{})
done := false
go func() {
for {
select {
case <-t.C:
// to run only once
if !done {
f()
}
done = true
case <-s:
return
}
}
}()
time.Sleep(limit)
close(s)
t.Stop()
return done
}
ticktime1 := time.Now()
tickdone1 := ticknr(5*time.Millisecond, func() {
time.Sleep(time.Millisecond)
})
fmt.Println("#1 Done:", tickdone1, "/ #1 Took:", time.Since(ticktime1))
// #1 Done: true / #1 Took: 5.046722ms

ticktime2 := time.Now()
tickdone2 := ticknr(time.Millisecond, func() {
time.Sleep(5 * time.Millisecond)
})
fmt.Println("#2 Done:", tickdone2, "/ #1 Took:", time.Since(ticktime2))
// #2 Done: false / #1 Took: 1.034584ms

Does this still have the orphaned goroutine? Or with return, the goroutine also returns?

Thomas Bruyelle

unread,
Feb 9, 2014, 5:03:40 PM2/9/14
to golan...@googlegroups.com
Thx Caleb I didn't realize the missing orphaned routine.

m...@carlmjohnson.net

unread,
Feb 9, 2014, 7:36:35 PM2/9/14
to golan...@googlegroups.com
Allow me to promote my blog post, which addresses the issue of Go-routine leaks and quit channels: http://blog.carlsensei.com/post/72359081647

Gyu-Ho Lee

unread,
Feb 9, 2014, 9:23:24 PM2/9/14
to golan...@googlegroups.com, m...@carlmjohnson.net
I am reading it right now. That's great, but how do we know when we return inside goroutine, the goroutine gets cancelled?

func Odds(from, to int, quit <-chan struct{}) <-chan int {
    c := make(chan int)
    go func() {
        defer close(c)
        if from%2 == 0 {
            from += 1
        }
        for i := from; i < to; i += 2 {
            select {
            case <-quit:
                return
            case c <- i:
            }
        }
    }()
    return c
}
So when you do this   case <-quit: return , does the goroutine not exist anymore or we only end the Odds function? 

Matt Harden

unread,
Feb 9, 2014, 10:46:08 PM2/9/14
to Gyu-Ho Lee, golang-nuts, Caleb Doxsey
In Go there is no way to "cancel" a function running in either the same or another goroutine. The function would have to be written to check whether it should be cancelled from time to time.


--

Caleb Doxsey

unread,
Feb 10, 2014, 12:50:17 AM2/10/14
to Matt Harden, Gyu-Ho Lee, golang-nuts
It's hard to come up with a good solution without knowing what you're trying to do. For example if you're concerned about network stuff taking too long there's SetReadDeadline and SetWriteDeadline (http://godoc.org/net#TCPConn.SetReadDeadline).

If you want to pull from a channel for up to a certain amount of time you can use a pattern like this: (http://play.golang.org/p/HBZ2wovLOA)

func somethingSlow(c chan bool) {
time.Sleep(time.Second)
c <- true
}

func main() {
c := make(chan bool, 1) // use a buffer here so the send can happen even if no one is receiving
go somethingSlow(c)
select {
case value := <-c:
fmt.Println("no timeout. got:", value)
case <-time.After(time.Second):
fmt.Println("timeout")
}
}

Though keep in mind that even if the timeout happens the somethingSlow goroutine will continue running. (until either the program as a whole terminates, or it finishes)

Gyu-Ho Lee

unread,
Feb 10, 2014, 1:12:39 AM2/10/14
to golan...@googlegroups.com, Gyu-Ho Lee, ca...@doxsey.net
Thanks, I just wanted to know if Go can cancel the function call whether it is run on other goroutines or in the main goroutine. Something like time.AfterFunc that runs a function after input duration on its own goroutine, so that the function call can be stopped from Stop method. So I was wondering if I can do the similar thing to any other function. Let's say I have a recursive function that is to run forever because I forgot the condition to end recursion. And I want to run this function, not on a different goroutine, but in the same lifetime of main program so that I can check if the function takes too long or if the function returns within limited time. If the function does not return within the time limit, rather than let the function's memory infinitely, I want to simply cancel the function call and print out the error message.

That's it. Just out of curiosity, not for practical use. Thanks a lot anyway. I learned a lot.

Thomas Bruyelle

unread,
Feb 10, 2014, 3:27:56 AM2/10/14
to golan...@googlegroups.com, m...@carlmjohnson.net
Very interesting thanks
Reply all
Reply to author
Forward
0 new messages