Clarification related to HTTP request context cancellations and goroutines

127 views
Skip to first unread message

Amit Saha

unread,
Jan 7, 2021, 9:55:09 PM1/7/21
to golang-nuts
Hi all, I want to confirm whether my understanding related to handling
HTTP client disconnections is correct or not.

Consider this:

func longRunningProcessing(ctx context.Context, w http.ResponseWriter) {
done := make(chan bool)
go func() {
expensiveWork() # This is a blocking expensive processing call
done <- true
}()
select {
case <-ctx.Done():
log.Printf("Client disconnected. Cancelling expensive operation.")
return
case <- done:
fmt.Fprintf(w, "Done!")
}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
longRunningProcessing(ctx)
}

When a client connection close is detected, the Done() call in select
unblocks and the call goes out of the longRunningProcessing()
function.
However, the goroutine with the expensiveWork() is actually still running?

It seems like there is *no* way there is *not* going to be a goroutine
leak (and resources associated with it) in a pattern like the above
unless
I have control over expensiveWork() and can instrument it with a child
context. I am also thinking if there is a way to obtain the behavior
similar to exec.CommandContext() command
which does a hard kill of the external process when the context is cancelled.

Thanks for any insights and clarifications.

Robert Engels

unread,
Jan 7, 2021, 10:00:21 PM1/7/21
to Amit Saha, golang-nuts
You need to pass the context to the expensive work a periodically check if it has been cancelled.

> On Jan 7, 2021, at 8:55 PM, Amit Saha <amits...@gmail.com> wrote:
>
> Hi all, I want to confirm whether my understanding related to handling
> --
> 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.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CANODV3k9gF0gLWB1Z1OGaH-ArPbq8mNPKx9mzrTOTCa-kZZmvg%40mail.gmail.com.

Amit Saha

unread,
Jan 8, 2021, 10:17:38 PM1/8/21
to Robert Engels, golang-nuts


> On 8 Jan 2021, at 1:59 pm, Robert Engels <ren...@ix.netcom.com> wrote:
>
> You need to pass the context to the expensive work a periodically check if it has been cancelled.

Thanks. I was thinking how to implement this. Is this a nice way to do it?

func clientDisconnected(ctx context.Context, done chan bool) bool {
select {
case <-done:
return false
case <-ctx.Done():
log.Printf("api: client disconnected.")
return true
}
}


func apiHandlerFunction(w http.ResponseWriter, r *http.Request) {
done := make(chan bool)
go func() {
log.Println("First expensive operation")
time.Sleep(5 * time.Second)
done <- true
}()

if clientDisconnected(r.Context(), done) {
return
}

go func() {
log.Println("Second expensive operation")
time.Sleep(5 * time.Second)
done <- true
}()

if clientDisconnected(r.Context(), done) {
return
}

fmt.Fprintf(w, "All operations done")

Robert Engels

unread,
Jan 8, 2021, 11:41:27 PM1/8/21
to Amit Saha, golang-nuts
Depends on how quickly you want the expensive operation to terminate. Unless it is cpu bound the expensive operation probably is making IO calls - pass the context to those so the cancel will propagate. If it doesn’t I think a periodic poll/check of the context is simpler.

> On Jan 8, 2021, at 9:17 PM, Amit Saha <amits...@gmail.com> wrote:
>
> 
Reply all
Reply to author
Forward
0 new messages