Implementing more granular control over goroutines with tomb

As with many such problems—both niche and common—there exists a third-party utility for grabbing your goroutines by the horns.

Tomb is a library that provides diagnostics to go along with any goroutine and channel—it can tell a master channel if another goroutine is dead or dying.

In addition, it allows you to explicitly kill a goroutine, which is a bit more nuanced than simply closing the channel it is attached to. As previously mentioned, closing the channel is effectively neutering a goroutine, although it could ultimately still be active.

You are about to find a simple fetch-and-grab body script that takes a slice of URL structs (with status and URI) and attempts to grab the HTTP response for each and apply it to the struct. But instead of just reporting information from the goroutines, we'll have the ability to send "kill messages" to each of a "master" struct's child goroutines.

In this example, we'll run the script for 10 seconds, and if any of the goroutines fail to do their job in that allotted time, it will respond that it was unable to get the URL's body due to a kill send from the master struct that invoked it:

package main

import (
  "fmt"
  "io/ioutil"
  "launchpad.net/tomb"
  "net/http"
  "strconv"
  "sync"
  "time"
)

var URLS []URL

type GoTomb struct {
  tomb tomb.Tomb
}

This is the minimum necessary structure required to create a parent or a master struct for all of your spawned goroutines. The tomb.Tomb struct is simply a mutex, two channels (one for dead and dying), and a reason error struct. The structure of the URL struct looks like the following code:

type URL struct {
  Status bool
  URI    string
  Body   string
}

Our URL struct is fairly basic—Status, set to false by default and true when the body has been retrieved. It consists of the URI variable—which is the reference to the URL—and the Body variable for storing the retrieved data. The following function allows us to execute a "kill" on a GoTomb struct:

func (gt GoTomb) Kill() {

  gt.tomb.Kill(nil)

}

The preceding method invokes tomb.Kill on our GoTomb struct. Here, we have set the sole parameter to nil, but this can easily be changed to a more descriptive error, such as errors.New("Time to die, goroutine"). Here, we'll show the listener for the GoTomb struct:

func (gt *GoTomb) TombListen(i int) {

  for {
    select {
    case <-gt.tomb.Dying():
      fmt.Println("Got kill command from tomb!")
      if URLS[i].Status == false {
        fmt.Println("Never got data for", URLS[i].URI)
      }
      return
    }
  }
}

We invoke TombListen attached to our GoTomb, which sets a select that listens for the Dying() channel, as shown in the following code:

func (gt *GoTomb) Fetch() {
  for i := range URLS {
    go gt.TombListen(i)

    go func(ii int) {

      timeDelay := 5 * ii
      fmt.Println("Waiting ", strconv.FormatInt(int64(timeDelay), 10), " seconds to get", URLS[ii].URI)
      time.Sleep(time.Duration(timeDelay) * time.Second)
      response, _ := http.Get(URLS[ii].URI)
      URLS[ii].Status = true
      fmt.Println("Got body for ", URLS[ii].URI)
      responseBody, _ := ioutil.ReadAll(response.Body)
      URLS[ii].Body = string(responseBody)
    }(i)
  }
}

When we invoke Fetch(), we also set the tomb to TombListen(), which receives those "master" messages across all spawned goroutines. We impose an intentionally long wait to ensure that our last few attempts to Fetch() will come after the Kill() command. Finally, our main() function, which handles the overall setup:

func main() {

  done := make(chan int)

  URLS = []URL{{Status: false, URI: "http://www.google.com", Body: ""}, {Status: false, URI: "http://www.amazon.com", Body: ""}, {Status: false, URI: "http://www.ubuntu.com", Body: ""}}

  var MasterChannel GoTomb
  MasterChannel.Fetch()

  go func() {

    time.Sleep(10 * time.Second)
    MasterChannel.Kill()
    done <- 1
  }()

  for {
    select {
    case <-done:
      fmt.Println("")
      return
    default:
    }
  }
}

By setting time.Sleep to 10 seconds and then killing our goroutines, we guarantee that the 5 second delays between Fetch() prevent the last of our goroutines from successfully finishing before being killed.

Tip

For the tomb package, go to http://godoc.org/launchpad.net/tomb and install it using the go get launchpad.net/tomb command.

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

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