Skip to content

Commit 8039329

Browse files
FMLShakunaliuCorey Daley
authored
Correct way to save memory using write buffer pool and freeing net.http default buffers (#761)
**Summary of Changes** 1. Add an example that uses the write buffer pool The loop process of the websocket connection is inner the http handler at existing examples, This usage will cause the 8k buffer(4k read buffer + 4k write buffer) allocated by net.http can't be GC(Observed by heap profiling, see picture below) . The purpose of saving memory is not achieved even if the WriteBufferPool is used. In example bufferpool, server process websocket connection in a new goroutine, and the goroutine created by the net.http will exit, then the 8k buffer will be GC. ![heap](https://user-images.githubusercontent.com/12793501/148676918-872d1a6d-ce10-4146-ba01-7de114db09f5.png) Co-authored-by: hakunaliu <hakunaliu@tencent.com> Co-authored-by: Corey Daley <cdaley@redhat.com>
1 parent 8983b96 commit 8039329

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
1414
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
1515
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
1616
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
17+
* [Write buffer pool example](https://github.com/gorilla/websocket/tree/master/examples/bufferpool)
1718

1819
### Status
1920

examples/bufferpool/client.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//go:build ignore
2+
// +build ignore
3+
4+
package main
5+
6+
import (
7+
"flag"
8+
"log"
9+
"net/url"
10+
"os"
11+
"os/signal"
12+
"sync"
13+
"time"
14+
15+
"github.com/gorilla/websocket"
16+
)
17+
18+
var addr = flag.String("addr", "localhost:8080", "http service address")
19+
20+
func runNewConn(wg *sync.WaitGroup) {
21+
defer wg.Done()
22+
23+
interrupt := make(chan os.Signal, 1)
24+
signal.Notify(interrupt, os.Interrupt)
25+
26+
u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"}
27+
log.Printf("connecting to %s", u.String())
28+
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
29+
if err != nil {
30+
log.Fatal("dial:", err)
31+
}
32+
defer c.Close()
33+
34+
done := make(chan struct{})
35+
36+
go func() {
37+
defer close(done)
38+
for {
39+
_, message, err := c.ReadMessage()
40+
if err != nil {
41+
log.Println("read:", err)
42+
return
43+
}
44+
log.Printf("recv: %s", message)
45+
}
46+
}()
47+
48+
ticker := time.NewTicker(time.Minute * 5)
49+
defer ticker.Stop()
50+
51+
for {
52+
select {
53+
case <-done:
54+
return
55+
case t := <-ticker.C:
56+
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
57+
if err != nil {
58+
log.Println("write:", err)
59+
return
60+
}
61+
case <-interrupt:
62+
log.Println("interrupt")
63+
64+
// Cleanly close the connection by sending a close message and then
65+
// waiting (with timeout) for the server to close the connection.
66+
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
67+
if err != nil {
68+
log.Println("write close:", err)
69+
return
70+
}
71+
select {
72+
case <-done:
73+
case <-time.After(time.Second):
74+
}
75+
return
76+
}
77+
}
78+
}
79+
80+
func main() {
81+
flag.Parse()
82+
log.SetFlags(0)
83+
wg := &sync.WaitGroup{}
84+
for i := 0; i < 1000; i++ {
85+
wg.Add(1)
86+
go runNewConn(wg)
87+
}
88+
wg.Wait()
89+
}

examples/bufferpool/server.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//go:build ignore
2+
// +build ignore
3+
4+
package main
5+
6+
import (
7+
"flag"
8+
"log"
9+
"net/http"
10+
"sync"
11+
12+
_ "net/http/pprof"
13+
14+
"github.com/gorilla/websocket"
15+
)
16+
17+
var addr = flag.String("addr", "localhost:8080", "http service address")
18+
19+
var upgrader = websocket.Upgrader{
20+
ReadBufferSize: 256,
21+
WriteBufferSize: 256,
22+
WriteBufferPool: &sync.Pool{},
23+
}
24+
25+
func process(c *websocket.Conn) {
26+
defer c.Close()
27+
for {
28+
_, message, err := c.ReadMessage()
29+
if err != nil {
30+
log.Println("read:", err)
31+
break
32+
}
33+
log.Printf("recv: %s", message)
34+
}
35+
}
36+
37+
func handler(w http.ResponseWriter, r *http.Request) {
38+
c, err := upgrader.Upgrade(w, r, nil)
39+
if err != nil {
40+
log.Print("upgrade:", err)
41+
return
42+
}
43+
44+
// Process connection in a new goroutine
45+
go process(c)
46+
47+
// Let the http handler return, the 8k buffer created by it will be garbage collected
48+
}
49+
50+
func main() {
51+
flag.Parse()
52+
log.SetFlags(0)
53+
http.HandleFunc("/ws", handler)
54+
log.Fatal(http.ListenAndServe(*addr, nil))
55+
}

0 commit comments

Comments
 (0)