You can run the long-running operation asynchronously and have it send on a channel to signal completion.
Then you block on that completion channel and c.Request.Context().Done()
with a select
statement:
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
signal := make(chan struct{}, 1)
go longRunningOperation(signal)
select {
case <-signal:
close(signal) // remember to clean up after yourself
// move on, will print "Processing"
case <-c.Request.Context().Done():
// abort
return
}
log.Print("Processing")
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
func longRunningOperation(signal chan<- struct{}) {
time.Sleep(10 * time.Second)
signal <- struct{}{} // signal that this operation has finished
}
The disadvantage with this approach is that, as-is, the long-running operation itself would keep executing.
Goroutines exit when the main
function of your program returns, which is not the case in an actual gin
server. So this might not be what you want.
In the case of a database operation, most APIs require a context.Context
parameter, which can be used to detect request cancellation. So you could pass the c.Request.Context()
down the call chain to make sure the async long-running operation also terminates when the client disconnects.
func Handler(c *gin.Context) {
signal := make(chan struct{}, 1)
go longRunningOperation(c.Request.Context(), signal)
...
}
func longRunningOperation(ctx context.Context, signal chan<- struct{}) {
if err := doSomethingContext(ctx); err != nil {
return
}
signal <- struct{}{} // signal that this operation has finished (successfully)
}