Search code examples
loopsgochannelgoroutinego-gorm

Why my go coroutines is stuck after processing?


I am new to Golang. I have been using GORM and concurrency of go to read a SQLite database and write it into a CSV file. It is working smooth but when the processing is done it is not ending the main program and exiting. I have to print command+c to exit. I don't know what I am doing wrong. May be it is entering into some blocking or deadlock mode or something. Moreover it is not printing a bye message too. Which means it is still trying to read the data from the channel. Please help. Here is the code.

package main

import (
    "fmt"
    "reflect"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

type AirQuality struct {
    // gorm.Model
    // ID      uint   `gorm:"column:id"`
    Index   string `gorm:"column:index"`
    BEN     string `gorm:"column:BEN"`
    CH4     string `gorm:"column:CH4"`
    CO      string `gorm:"column:CO"`
    EBE     string `gorm:"column:EBE"`
    MXY     string `gorm:"column:MXY"`
    NMHC    string `gorm:"column:NMHC"`
    NO      string `gorm:"column:NO"`
    NO2     string `gorm:"column:NO_2"`
    NOX     string `gorm:"column:NOx"`
    OXY     string `gorm:"column:OXY"`
    O3      string `gorm:"column:O_3"`
    PM10    string `gorm:"column:PM10"`
    PM25    string `gorm:"column:PM25"`
    PXY     string `gorm:"column:PXY"`
    SO2     string `gorm:"column:SO_2"`
    TCH     string `gorm:"column:TCH"`
    TOL     string `gorm:"column:TOL"`
    Time    string `gorm:"column:date; type:timestamp"`
    Station string `gorm:"column:station"`
}

func (AirQuality) TableName() string {
    return "AQ"
}

func main() {
    c := generateRows("boring!!")
    for {
        fmt.Println(<-c)
        if c == nil {
            fmt.Println("Bye")
            break
        }
    }
}

func generateRows(msg string) <-chan []string {
    c := make(chan []string)
    go func() {
        db, err := gorm.Open("sqlite3", "./load_testing_7.6m.db")
        if err != nil {
            panic("failed to connect database")
        }
        defer db.Close()
        rows, err := db.Model(&AirQuality{}).Limit(20).Rows()
        defer rows.Close()
        if err != nil {
            panic(err)
        }
        for rows.Next() {
            var aq AirQuality
            db.ScanRows(rows, &aq)
            v := reflect.Indirect(reflect.ValueOf(aq))
            var buf []string
            for i := 0; i < v.NumField(); i++ {
                buf = append(buf, v.Field(i).String())
            }
            c <- buf
        }
    }()
    return c
}

Solution

  • Receiving from an unbuffered channel (such as yours) where no one is ready to send a value blocks. This is what you experience. Spec: Receive operator:

    The expression [<-c] blocks until a value is available.

    The common way to signal "EOF" in case of channels is to close the channel from the sender's side when there are no more values to send, using the builtin close() function.

    Attempting to receive from a closed channel can proceed immediately, yielding the zero value of the element type of the channel. To detect this "closed" state, use the special comma-ok idiom:

    value, ok := <- c
    

    If the channel is closed, ok will be false (otherwise it's true).

    The easy and proper way to "drain" a channel until it is closed is to use the for range loop, like this:

    for value := range c {
        fmt.Println("Received:", value)
    }
    

    The for range terminates once all values have been received from channel c that were sent on it before it was closed.

    So inside generateRows(), do this:

    go func() {
        // // Use defer so it will be closed no matter how this function call ends
        defer close(c)
        // ...
    }()
    

    And your main():

    func main() {
        c := generateRows("boring!!")
        for v := range c {
            fmt.Println(v)
        }
        fmt.Println("Bye")
    }