A
A
amalygin2016-01-21 19:09:26
go
amalygin, 2016-01-21 19:09:26

Why is a Go application gradually consuming more and more RAM?

There were problems with the backend written in Php when sending more than 50,000 push notifications. Used ApnsPhp and it took a very long time to send. There, as I understand it, the features of the implementation. Since I studied Go a little, I decided to raise a simple service on Go on another virtual machine, which sends Push notifications, receiving tokens on request to my old server. I use this to send .

func main() {
  c, err := apns.NewClientWithFiles(apns.ProductionGateway, "/root/go/src/myapp/resources/pushcert.pem", "/root/go/src/myapp/resources/apnskey.pem")
  if err != nil {
    addStringToLog(fmt.Sprintf("%s", err))
    log.Fatal("Could not create client", err.Error())
  }
  go func() {
    for f := range c.FailedNotifs {
      addStringToLog(fmt.Sprintf("Notif", f.Notif.ID, "failed with", f.Err.Error()))
      fmt.Println("Notif", f.Notif.ID, "failed with", f.Err.Error())
    }
  }()
  prepareSendingPush(c)
  tickChan := time.NewTicker(time.Second * 30).C

  for {
    select {
    case <-tickChan:
      prepareSendingPush(c)
    }
  }
}

The prepareSendingPush(c) function makes an HTTP request to my server and, if it receives tokens, it is sent.
Everything works fine, the service knocks every 30 seconds and sends notifications if necessary, by the way, sending takes 5 seconds, instead of 150 or more for ApnsPHP. I run start-stop-daemon -Sbvx /root/go/src/myapp/myapp.
As a result, I look through htop how many resources my application eats. And firstly I observe that my application is displayed as 6 running processes. What is it? And secondly, that slowly the amount of RAM consumption begins to grow and grow. What am I doing wrong? The virtual machine is running Ubuntu 14.04.
Here is the code, in the comment can not be inserted normally.
func prepareSendingPush(client apns.Client) {
  addStringToLog("Start get tokens")
  fmt.Printf("Start get tokens")
  response, err := http.Get("https://domain.com/gettokens.php")
  if err != nil {
    addStringToLog(fmt.Sprintf("%s", err))
    fmt.Printf("%s", err)
  } else {
    defer response.Body.Close()
    contents, err := ioutil.ReadAll(response.Body)
    if err != nil {
      addStringToLog(fmt.Sprintf("%s", err))
      fmt.Printf("%s", err)
    }
    type JsonObj struct {
      Status string   `json:"status"`
      Text   string   `json:"text"`
      Tokens []string `json:"tokens"`
    }
    js := new(JsonObj)
    err = json.Unmarshal(contents, &js)
    if err != nil {
      addStringToLog(fmt.Sprintf("%s", err))
      fmt.Println("error:", err)
    }
    if len(js.Tokens) > 0 {
      addStringToLog("Start sending")
      fmt.Printf("Start sending")
      for i := 0; i < len(js.Tokens); i++ {
        tok := js.Tokens[i]
        if tok != "" {
          sendPush(client, tok, js.Text, 1, i)
        }
      }
      addStringToLog("End sending")
      fmt.Printf("End sending")
    }
    addStringToLog("End get tokens")
    fmt.Printf("End get tokens")
  }
}

func sendPush(client apns.Client, token string, body string, badge uint, identifier int) {
  p := apns.NewPayload()
  p.APS.Alert.Body = body
  p.APS.Sound = "default"
  p.APS.Badge.Set(badge)
  //p.APS.ContentAvailable = 1

  m := apns.NewNotification()
  m.Payload = p
  m.DeviceToken = token
  m.Priority = apns.PriorityImmediate
  m.Identifier = uint32(identifier)

  client.Send(m)
}

Answer the question

In order to leave comments, you need to log in

2 answer(s)
A
Alexander Pavlyuk, 2016-01-21
@amalygin

Keep track of the number of goroutines. Perhaps somewhere they are implicitly created and hang on reading from a channel, for example, to which you forgot to write or which you forgot to close.
This can be done using the runtime module, getting the value runtime.NumGoroutine
The number of processes is usually taken from the number of cores, so you have 6 of them running. To put it simply, the go runtime scatters goroutines between these processes to execute.

U
uvelichitel, 2016-01-21
@uvelichitel

sendPush(), preparesendPush()You pass functions apns.Clientas a parameter. Go always passes 'copy_by_value' parameters, which means it creates and passes a copy of the client on every call.

apns.Client // contains filtered or unexported fields
, that is, it is not known what is copied and how much it lends itself to the scavenger. Try passing the same client as a reference
func prepareSendingPush(client *apns.Client), func sendPush(client *apns.Client...
causingprepareSendingPush(&c)

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question