* what architecture are you using? amd64 or 386
* what operating system are you using?
* how do you run the client program?
* what kind of memory usage do you see?
I ran the server using 6g (amd64) on OS X
and then ran the client repeatedly with -maxRichieste 1000.
After the first three runs the server was using 5160K
according to ps.
After 20 more runs the server was using 5292K.
After 20 more runs the server was using 5316K.
After 20 more runs the server was using 5372K.
That seems more like fragmentation than a memory
leak to me. If it's a leak it's very slow one.
I'm guessing you are seeing different behavior.
Russ
Is that the memory usage after all the requests have been made, and
the server is sitting idle?
Memory is not garbage collected immediately, so it's not unusual for a
server's memory use to grow substantially under heavy load.
Are you keeping old references around? The code you have posted is too
long for me to check through. Try cutting it down to the smallest
possible example of code that leaks. (You'll probably find the problem
in the process.)
Andrew
I can't give the specifics - it's not documented at the moment. The
code is in pkg/runtime if you want to take a look.
> I will be glad if you can confirm your explanation (memory is not
> garbage collected immediately but it will be in the future or when
> it's necessary) or help me understand what else is going on.
>
> func stringToByteSlice(s string) []byte {
> buf := make([]byte, len(s))
> copyString(buf, 0, s)
> return buf
> }
> // Copy from string to byte array at offset doff. Assume there's
> room.
> func copyString(dst []byte, doff int, str string) {
> for soff := 0; soff < len(str); soff++ {
> dst[doff] = str[soff]
> doff++
> }
> }
If this code is being executed on every request, that's a lot of
allocation (every call to make). I don't see a leak here, though.
Could be memory fragmentation? Maybe rsc could provide a better
analysis.
I've got to ask - why do this instead of just performing the
conversion: []bytes(s) ?
Andrew
I might be missing something, but can't you replace
stringToByteSlice(s) with just []byte(s)? Does that affect the memory
usage?
Oops, adg already said that 5 hours ago...
Unfortunately not. However there are some tangentially related
articles at Russ' blog:
http://www.google.com.au/search?&q=site:swtch.com+garbage+collector
Andrew
Russ
-rob
>
> Another one: As it stands todays, allocated memory is not reclaimed to
> the OS, right? So long running processes with dynamic allocation habit
> as this somehow contrived example would'nt be a desirble deployment
> sceanrio.
That has not been true for quite some time. The malloc that comes with
gnu libc uses mmap to acquire memory and munmap to free it when done.
ron
2010/11/6 peterGo <go.pe...@gmail.com>:
> I thought that this message is still true:
> groups.google.com/group/golang-nuts/msg/6b7b0b92a0dcfdc5
> Must have missed the news that this shortcoming has been fixed!
no, you are right; I just misunderstood your comment.
Thanks
ron
It's not actually a shortcoming, it's really a feature.
If a Go program needs memory and can't allocate it then it's fatal.
In fact this is true of all programs on a modern unix like system.
If a long running program has required an amount of memory in the
past, then it's fair to assume it will require that amount of memory
again sometime in the future.
Returning that memory to the OS is dangerous because there is no
guarantee that it will get that memory back.
Keeping the memory doesn't have much of a downside anyway, if you
don't end up using it then it will be swapped out to disk and will
just cost you a small amount of disk space.
I don't think the jvm or .net vm returns memory to the OS either.
- jessta
--
=====================
http://jessta.id.au
Usage:
clirunnert [-threadNum=# of client sessions to execute] (default 5)
*/
package main
import (
"os"
"bytes"
"fmt"
"flag"
"strings"
"io"
"http"
"net")
// Server configuration
var srvAddr *string = flag.String("srv", "localhost:6060", "Server
address \"host:port\"")
var reqNum int = 5
var threadNum *int = flag.Int("threadNum", 5, "# of client sessions to execute")
var connType *string = flag.String("conn", "HTTP", "Connection type
(HTTP | SOCKET)")
/* --------------------------------------------------------------
Client Definition
-------------------------------------------------------------- */
type NoCloser struct {
io.Reader
}
func (NoCloser) Close() os.Error {return nil}
type HttpclientHttp struct {
conn *http.ClientConn
resp http.Response
}
func (cli *HttpclientHttp) OpenConnection(url string) (err os.Error) {
var conn net.Conn
conn, err = net.Dial("tcp", "", url)
if err == nil {
httpConn := http.NewClientConn(conn, nil)
cli.conn = httpConn
}
return err
}
func (cli *HttpclientHttp) CloseConnection() {
c, _ := cli.conn.Close()
if c != nil {
// Closes underlying connection, discards unread data
c.Close()
}
}
func (cli *HttpclientHttp) MakeRequest(url string, method string,
body io.Reader) (err os.Error) {
var req http.Request
// Create and send default request
req.Method = strings.ToUpper(method)
req.ProtoMajor = 1
req.ProtoMinor = 1
req.Close = false
req.Body = NoCloser{body}
req.URL, err = http.ParseURL(url)
if err != nil {
cli.CloseConnection()
return os.NewError("MakeRequest()-error in ParseURL:\t" + err.String())
}
cli.conn.Write(&req)
if err != nil {
cli.CloseConnection()
return os.NewError("MakeRequest()-error in Write:\t" + err.String())
}
// Get response
cli.resp = *new(http.Response)
var resp *http.Response
resp, err = cli.conn.Read()
if err != nil {
cli.CloseConnection()
return os.NewError("MakeRequest()-error in Read:\t" + err.String())
}
cli.resp = *resp
return err
}
func (cli *HttpclientHttp) GetResponse() (http.Response) {
return cli.resp
}
/* --------------------------------------------------------------
Client execution
-------------------------------------------------------------- */
func clientSession(connType string, id, reqNum int) (err os.Error) {
var client *HttpclientHttp
var bodyStr string
var path string
client = new(HttpclientHttp)
err = client.OpenConnection(*srvAddr)
if err != nil {
return os.NewError(fmt.Sprintf("Conn. %s, thread %d/%d. Error in
OpenConnection: \t%s", connType, id, *threadNum, err.String()))
}
defer client.CloseConnection()
for i:= 1; i <= reqNum; i++ {
path = fmt.Sprintf("http://%s", *srvAddr) + "/crono/"
err = client.MakeRequest(path, "GET", bytes.NewBufferString(bodyStr))
if err != nil {
client.CloseConnection()
return os.NewError(fmt.Sprintf("Conn. %s, thread %d/%d, request
%d/%d. Error during server communication: %s", connType, id,
*threadNum, i, reqNum, err.String()))
}
}
return err
}
func main() {
flag.Parse()
var done = make(chan os.Error)
var start = make(chan bool)
for i := 1; i <= *threadNum; i++ {
go func(start chan bool, done chan os.Error, i int) {
<- start
err := clientSession(*connType, i, reqNum)
done <- err
return
}(start, done, i)
}
for i := 1; i <= *threadNum; i++ {
start <- true
}
for i := 1; i <= *threadNum; i++ {
err := <-done
if err != nil {
fmt.Printf("ABORT-%s\n", err)
os.Exit(1)
}
}
fmt.Printf("clirunnert END\n")
}
----------------------
SERVER
/*
Test HTTP server.
*/
package main
import (
"http"
"log"
"flag"
)
import _ "http/pprof"
// Server configuration
var srvAddr *string = flag.String("srv", "localhost:6060", "Server
address \"host:port\"")
// Server handler function
func cronoServer(w http.ResponseWriter, req *http.Request) {
}
func main() {
log.Stdout("\n====> httpservt <====\n\n")
flag.Parse()
http.HandleFunc("/crono/", cronoServer)
log.Stdoutf("HTTP server listening on %s\n", *srvAddr)
err := http.ListenAndServe(*srvAddr, nil)
if err != nil {
log.Crash("ListenAndServe error: ", err)
}
log.Stdout("HTTP server: END")
}
Then I've started the server and run 60 times the client with 1000
threads (total 60000) and taken its heap profile "pprof_heap-after 60
x 1000 thread.svg".
Restarted the server and run 10 times the client with 6000 threads
(total 60000) and taken its heap profile "pprof_heap-after 10 x 6000
thread.svg". In this case on my PC I have an error in every run.
As you can see, the buffer allocation and the malg are different. It
seems something isn't released in case of error on the client side.
Can anybody help me to understand and correct the code?
Thanks
Silvia