Rico Berger
Rico Berger

Reputation: 387

Golang non blocking channel doesn't work

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

Answers (1)

TehSphinX
TehSphinX

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

Related Questions