Structs and GoRoutines, switches instances?

Issue

I have this function:

func New(config *WatcherConfig) *Watcher {
    instance := &Watcher{
        config: config,
        connections: make(map[string]ConnectionInfo),
        lock: sync.Mutex{},
    }

    go instance.Start()

    return instance
}

It creates a new instance of Watcher, currently there are two WatcherConfig. As you can see I start another function called Start() using a Go routine.

func (w *Watcher) Start() {
    fmt.Println(w.config.PathToLogFile)
}

One WatcherConfig has a value of /var/log/openvpn.log for PathToLogFile and another WatcherConfig has a value of /var/log/postfix.log for PathToLogFile. However when I call the Start() function using a GoRoutine, it prints /var/log/postfix.log twice. If I remove the go routine, so like this:

func New(config *WatcherConfig) *Watcher {
    instance := &Watcher{
        config: config,
        connections: make(map[string]ConnectionInfo),
        lock: sync.Mutex{},
    }

    instance.Start()

    return instance
}

It will now print /var/log/openvpn.log and /var/log/postfix.log correctly.

Code that calls New()

/// ---------
    /// Parse config file
    /// ---------
    configuration, err := config.ParseConfig(*configFileLocation)

    if err != nil {
        log.Fatalln(err)
    }


var watchers []*watcher.Watcher

for _, watcherConfig := range configuration.Watchers {
    watchers = append(watchers, watcher.New(&watcherConfig))
}

Why does the go routine "switch" to another instance?

Solution

How to fix:

for _, watcherConfig := range configuration.Watchers {
    watcherConfig := watcherConfig // copy
    watchers = append(watchers, watcher.New(&watcherConfig))
}

Range loops in Go are re-using the iteration variable, i.e. they are re-using the allocated memory to reduce allocations, in your case it’s the watcherConfig variable. So the pointer you are passing to New with New(&watcherConfig) will point to the same memory (since it is re-used). Hence all watchers end up with the same config at the end.

The version without a goroutine in New only looks to be working correctly because the print statement outputs the value currently stored in the shared memory, but once the loop ends, the watchers will, nevertheless, have a reference to the same, the last, config.


There’s this FAQ entry that discusses the problem.

Answered By – mkopriva

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.