Reputation: 387
I work the first time with goroutines and channels in go and do not come any further.
I have a websocket connection where every time a user connect a new goroutine is spawned. Now I want to stop this goroutine if the user disconnect from the websocket connection.
To manage the stop signale I have create a map of channels. Each entry can be identified by the users websocket connection. I pass the websocket connection, the map of channels for the stop signal and two other parameters to the goroutine. But the goroutine doesn't receive any values from the quit channel and I don't know why.
Here is the relevant code for the main
package:
package main
import (
"net/http"
"time"
"github.com/gorilla/websocket"
)
func wsHandler(w http.ResponseWriter, r *http.Request) {
...
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer ws.Close()
data.Quit[ws] = make(chan bool)
data.DB.ListenToTable(data.GetTableName(source), channel, data.Quit, ws)
for {
if _, _, err := ws.NextReader(); err != nil {
data.Quit[ws] <- true
ws.Close()
break
}
}
}
And the code of the data
package where the goroutine is created:
package data
var Quit = make(map[*websocket.Conn](chan bool))
func (db *rethinkDB) ListenToTable(name string, ch chan Data, quit map[*websocket.Conn](chan bool), ws *websocket.Conn) {
go func(name string, ws *websocket.Conn) {
for {
select {
case <-quit[ws]:
fmt.Println("QUIT GOROUTINE")
break
default:
res, err := r.Table(name).Changes().Run(db.session)
if err != nil {
log.Fatalln(err)
}
var response DataFeed
for res.Next(&response) {
response.NewVal.WS = ws
ch <- response.NewVal
}
if res.Err() != nil {
log.Println(res.Err())
}
}
}
}(name, ws)
}
I have also tried buffered channels or pass the channel instead of the map of channels to the goroutine but without success. The fmt.Println("QUIT GOROUTINE")
command is never called and the goroutine isn't sopped.
I hope someone can help me and sorry if this question was already ask but I haven't found a solution that solves my problem.
Upvotes: 1
Views: 1981
Reputation: 7430
First to make things easier:
As far as I can see you do not need a global register for the quit channels. Just create a ch := make(chan bool)
in main, pass it to ListenToTable
(instead of the whole map of channels) and use it in the select. In main close(ch)
it if you want to exit. But as you said, that doesn't solve your problem.
Theoretically you are on the right track with closing the go routine. I took your sample code and made the following runnable code from it:
package main
import (
"fmt"
"time"
)
func main() {
chClose := make(chan bool)
channel := make(chan string)
ListenToTable("somestring", channel, chClose)
time.Sleep(3 * time.Second)
chClose <- true
time.Sleep(1 * time.Second)
}
func ListenToTable(name string, ch chan string, chClose chan bool) {
go func(name string) {
for {
select {
case <-chClose:
fmt.Println("QUIT GOROUTINE")
return // VERY IMPORTANT: not break!
default:
}
}
}(name)
}
The problem must be with something else in you code, probably blocked by something in the default
section and not even executing the select. Try printing fmt.Println("something")
before the select {
. If that is not printed regularly then you have your answer.
One more thing: As commented in the code above you cannot break out of a for { select { ... } }
with a single break
. You need to use a return
(to exit the function) or another tactic (like a break with a label as Adrian suggested in the comments). The break
will only exit the select
, but not the for
loop.
Upvotes: 1