Working with files

Files are a great example of areas where data consistency issues such as race conditions can lead to more permanent and catastrophic problems. Let's look at a piece of code that might continuously attempt to update a file to see where we could run into race conditions, which in turn could lead to bigger problems such as an application failing or losing data consistency:

package main

import(
  "fmt"
  "io/ioutil"
  "strconv"
  "sync"
)


func writeFile(i int) {

  rwLock.RLock();
  ioutil.WriteFile("test.txt", 
    []byte(strconv.FormatInt(int64(i),10)), 0x777)
  rwLock.RUnlock();

  writer<-true

}

var writer chan bool
var rwLock sync.RWMutex

func main() {
  
  writer = make(chan bool)

  for i:=0;i<10;i++ {
    go writeFile(i)
  }


  <-writer
  fmt.Println("Done!")
}

Code involving file operations are rife for these sorts of potential issues, as mistakes are specifically not ephemeral and can be locked in time forever.

If our goroutines block at some critical point or the application fails midway through, we could end up with a file that has invalid data in it. In this case, we're simply iterating through some numbers, but you can also apply this situation to one involving database or datastore writes—the potential exists for persistent bad data instead of temporary bad data.

This is not a problem that is exclusively solved by channels or mutual exclusions; rather, it requires some sort of sanity check at every step to make certain that data is where you and the application expect it to be at every step in the execution. Any operation involving io.Writer relies on primitives, which Go's documentation explicitly notes that we should not assume they are safe for parallel execution. In this case, we have wrapped the file writing in a mutex.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.93.169