1

I was trying to insert documents in the mongoDB using go-client (mgo). I create a new mongo session, and two channels for communcaition b/w go-routines, channel is used to sync b/w readFile and main, while other is to pass data read from file in readFile to db write routine insertTxn.

type Txn struct {
    Date time.Time
    Amt  float64
}



func main() {
    session, err := mgo.Dial("localhost")
    if err != nil {
        panic(err)
    }
    defer session.Close()

    channel := make(chan bool)
    txnChannel := make(chan Txn, 1e4)

    go readFile(txnChannel, channel)
    go insertTxn(session, txnChannel)

    <-channel
    time.Sleep(time.Second * 10) // waiting for insertTxn to finish

    defer func() {
        if r := recover(); r != nil {
            fmt.Println(r)
        }
    }()
}

Then, goroutine readFile is started which starts reading from an input file, and writing the data to txnChannel channel. After completion, it marks the completion by writing to channel channel.

func readFile(txnChannel chan<- Txn, channel chan<- bool) { // write only channel
        txnFile, err := os.Open("path/to/dir/txns.txt")
        if err != nil {
            panic(err)
        }
        txnReader := bufio.NewReader(txnFile)
        defer func() {
            txnFile.Close()
            channel <- true
        }()
        var data []string
        var txn Txn
        var dur int
        var str string
        for i := 0; i < 25*1e2; i++ {
            str, err = txnReader.ReadString('\n')
            str = strings.TrimSuffix(str, "\n")
            if err != nil {
                panic(err)
            }
            data = strings.Split(str, " ")
            txn.Amt, err = strconv.ParseFloat(data[1], 64)
            if err != nil {
                panic(err)
            }
            if err != nil {
                panic(err)
            }
            dur, err = strconv.Atoi(data[0])
            if err != nil {
                panic(err)
            }
            txn.Date = time.Now().Add(-time.Second * time.Duration(dur))
            txnChannel <- txn
        }
}

The other goroutine insertTxn, creates a reference to txn collection, and keep listening on the txnChannel channel, and writes the data it receives to the mongo collection.

func insertTxn(session *mgo.Session, txnChannel <-chan Txn) {
        txnColl := session.DB("happay").C("txns")
        for {
            select {
            case txn := <-txnChannel:
                if err := txnColl.Insert(txn).Error; err != nil {
                    panic(err)
                }
            }
        }
}

Now, the program panics with following reason:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1151f27]

I believe the issue might be that I am using the same struct again and again, but as I read that writing to a channel does copy by value, rather than by reference. So, it should work. Any help would be highly appreciated.

ks2bmallik
  • 184
  • 3
  • 18

1 Answers1

1

The source of the problem is in insertTxn() function:

if err := txnColl.Insert(txn).Error; err != nil {
    panic(err)
}

Collection.Insert() returns a value of type error, and you refer to its Error method, but you don't call it (calling it would look like Error() which would result in a value of type string which cannot be compared to nil anyway...), so your err variable will be a function value, which panics if Collection.Insert() returns an explicit nil error value.

If you want to check if some function returns an error, don't call its error.Error() method, simply check the error value itself like this:

if err := txnColl.Insert(txn); err != nil {
    panic(err)
}

Some other notes:

Use sync.WaitGroup to wait for goroutines, time.Sleep() may be ok for a demo, but it's really bad for "production" code. For an example, see Prevent the main() function from terminating before goroutines finish in Golang and Solving goroutines deadlock.

Also registering a defer function at the end of main() to "catch" panics has no effect:

   defer func() {
        if r := recover(); r != nil {
            fmt.Println(r)
        }
    }()

This should be the first (or an earlier but definitely not the last) call in main(). Also if you just print the error, it's needless as a panic not recovered from will also be printed. Use recover() if you intend to handle the situation.

Also in readFile() when reading from the file, you should also check for io.EOF, as the file may not have as many lines as you expect it to, in which case you may break early.

icza
  • 389,944
  • 63
  • 907
  • 827