-
Notifications
You must be signed in to change notification settings - Fork 225
enable shrink the socket pool size #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,8 @@ type mongoServer struct { | |
| pingCount uint32 | ||
| closed bool | ||
| abended bool | ||
| minPoolSize int | ||
| maxIdleTimeMS int | ||
| } | ||
|
|
||
| type dialer struct { | ||
|
|
@@ -76,17 +78,22 @@ type mongoServerInfo struct { | |
|
|
||
| var defaultServerInfo mongoServerInfo | ||
|
|
||
| func newServer(addr string, tcpaddr *net.TCPAddr, sync chan bool, dial dialer) *mongoServer { | ||
| func newServer(addr string, tcpaddr *net.TCPAddr, sync chan bool, dial dialer, minPoolSize, maxIdleTimeMS int) *mongoServer { | ||
| server := &mongoServer{ | ||
| Addr: addr, | ||
| ResolvedAddr: tcpaddr.String(), | ||
| tcpaddr: tcpaddr, | ||
| sync: sync, | ||
| dial: dial, | ||
| info: &defaultServerInfo, | ||
| pingValue: time.Hour, // Push it back before an actual ping. | ||
| Addr: addr, | ||
| ResolvedAddr: tcpaddr.String(), | ||
| tcpaddr: tcpaddr, | ||
| sync: sync, | ||
| dial: dial, | ||
| info: &defaultServerInfo, | ||
| pingValue: time.Hour, // Push it back before an actual ping. | ||
| minPoolSize: minPoolSize, | ||
| maxIdleTimeMS: maxIdleTimeMS, | ||
| } | ||
| go server.pinger(true) | ||
| if maxIdleTimeMS != 0 { | ||
| go server.poolShrinker() | ||
| } | ||
| return server | ||
| } | ||
|
|
||
|
|
@@ -221,6 +228,7 @@ func (server *mongoServer) close(waitForIdle bool) { | |
| func (server *mongoServer) RecycleSocket(socket *mongoSocket) { | ||
| server.Lock() | ||
| if !server.closed { | ||
| socket.lastTimeUsed = time.Now() | ||
| server.unusedSockets = append(server.unusedSockets, socket) | ||
| } | ||
| server.Unlock() | ||
|
|
@@ -346,6 +354,53 @@ func (server *mongoServer) pinger(loop bool) { | |
| } | ||
| } | ||
|
|
||
| func (server *mongoServer) poolShrinker() { | ||
| ticker := time.NewTicker(1 * time.Minute) | ||
| for _ = range ticker.C { | ||
| if server.closed { | ||
| ticker.Stop() | ||
| return | ||
| } | ||
| server.Lock() | ||
| unused := len(server.unusedSockets) | ||
| if unused < server.minPoolSize { | ||
| server.Unlock() | ||
| continue | ||
| } | ||
| now := time.Now() | ||
| end := 0 | ||
| reclaimMap := map[*mongoSocket]bool{} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a minor nitpick, but |
||
| // Because the acquirision and recycle are done at the tail of array, | ||
| // the head is always the oldest unused socket. | ||
| for _, s := range server.unusedSockets[:unused-server.minPoolSize] { | ||
| if s.lastTimeUsed.Add(time.Duration(server.maxIdleTimeMS) * time.Millisecond).After(now) { | ||
| break | ||
| } | ||
| end++ | ||
| reclaimMap[s] = true | ||
| } | ||
| tbr := server.unusedSockets[:end] | ||
| if end > 0 { | ||
| next := make([]*mongoSocket, unused-end) | ||
| copy(next, server.unusedSockets[end:]) | ||
| server.unusedSockets = next | ||
| remainSockets := []*mongoSocket{} | ||
| for _, s := range server.liveSockets { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is late, and I might be missing something here, but it looks like you populate the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, thanks for the clarification :). |
||
| if !reclaimMap[s] { | ||
| remainSockets = append(remainSockets, s) | ||
| } | ||
| } | ||
| server.liveSockets = remainSockets | ||
| stats.conn(-1*end, server.info.Master) | ||
| } | ||
| server.Unlock() | ||
|
|
||
| for _, s := range tbr { | ||
| s.Close() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| type mongoServerSlice []*mongoServer | ||
|
|
||
| func (s mongoServerSlice) Len() int { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,6 +269,15 @@ const ( | |
| // Defines the per-server socket pool limit. Defaults to 4096. | ||
| // See Session.SetPoolLimit for details. | ||
| // | ||
| // minPoolSize=<limit> | ||
| // | ||
| // Defines the per-server socket pool minium size. Defaults to 0. | ||
| // | ||
| // maxIdleTimeMS=<millisecond> | ||
| // | ||
| // The maximum number of milliseconds that a connection can remain idle in the pool | ||
| // before being removed and closed. | ||
| // | ||
| // appName=<appName> | ||
| // | ||
| // The identifier of this client application. This parameter is used to | ||
|
|
@@ -320,6 +329,8 @@ func ParseURL(url string) (*DialInfo, error) { | |
| appName := "" | ||
| readPreferenceMode := Primary | ||
| var readPreferenceTagSets []bson.D | ||
| minPoolSize := 0 | ||
| maxIdleTimeMS := 0 | ||
| for _, opt := range uinfo.options { | ||
| switch opt.key { | ||
| case "authSource": | ||
|
|
@@ -366,6 +377,16 @@ func ParseURL(url string) (*DialInfo, error) { | |
| doc = append(doc, bson.DocElem{Name: strings.TrimSpace(kvp[0]), Value: strings.TrimSpace(kvp[1])}) | ||
| } | ||
| readPreferenceTagSets = append(readPreferenceTagSets, doc) | ||
| case "minPoolSize": | ||
| minPoolSize, err = strconv.Atoi(opt.value) | ||
| if err != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please return an error if the value is <0?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will update |
||
| return nil, errors.New("bad value for minPoolSize: " + opt.value) | ||
| } | ||
| case "maxIdleTimeMS": | ||
| maxIdleTimeMS, err = strconv.Atoi(opt.value) | ||
| if err != nil { | ||
| return nil, errors.New("bad value for maxIdleTimeMS: " + opt.value) | ||
| } | ||
| case "connect": | ||
| if opt.value == "direct" { | ||
| direct = true | ||
|
|
@@ -400,6 +421,8 @@ func ParseURL(url string) (*DialInfo, error) { | |
| TagSets: readPreferenceTagSets, | ||
| }, | ||
| ReplicaSetName: setName, | ||
| MinPoolSize: minPoolSize, | ||
| MaxIdleTimeMS: maxIdleTimeMS, | ||
| } | ||
| return &info, nil | ||
| } | ||
|
|
@@ -473,6 +496,14 @@ type DialInfo struct { | |
| // cluster and establish connections with further servers too. | ||
| Direct bool | ||
|
|
||
| // MinPoolSize defines The minimum number of connections in the connection pool. | ||
| // Defaults to 0. | ||
| MinPoolSize int | ||
|
|
||
| //The maximum number of milliseconds that a connection can remain idle in the pool | ||
| // before being removed and closed. | ||
| MaxIdleTimeMS int | ||
|
|
||
| // DialServer optionally specifies the dial function for establishing | ||
| // connections with the MongoDB servers. | ||
| DialServer func(addr *ServerAddr) (net.Conn, error) | ||
|
|
@@ -552,6 +583,15 @@ func DialWithInfo(info *DialInfo) (*Session, error) { | |
| if info.PoolLimit > 0 { | ||
| session.poolLimit = info.PoolLimit | ||
| } | ||
|
|
||
| if info.MinPoolSize > 0 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this checks should be reversed to return an error if MinPoolSize or MaxIdleTimeMS are <0 |
||
| cluster.minPoolSize = info.MinPoolSize | ||
| } | ||
|
|
||
| if info.MaxIdleTimeMS > 0 { | ||
| cluster.maxIdleTimeMS = info.MaxIdleTimeMS | ||
| } | ||
|
|
||
| cluster.Release() | ||
|
|
||
| // People get confused when we return a session that is not actually | ||
|
|
@@ -5262,7 +5302,7 @@ func getRFC2253NameString(RDNElements *pkix.RDNSequence) string { | |
| var replacer = strings.NewReplacer(",", "\\,", "=", "\\=", "+", "\\+", "<", "\\<", ">", "\\>", ";", "\\;") | ||
| //The elements in the sequence needs to be reversed when converting them | ||
| for i := len(*RDNElements) - 1; i >= 0; i-- { | ||
| var nameAndValueList = make([]string,len((*RDNElements)[i])) | ||
| var nameAndValueList = make([]string, len((*RDNElements)[i])) | ||
| for j, attribute := range (*RDNElements)[i] { | ||
| var shortAttributeName = rdnOIDToShortName(attribute.Type) | ||
| if len(shortAttributeName) <= 0 { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit late, and I might be wrong here, but aren't you reading the cluster fields (minPoolSize, maxIdleTimeMs) outside of the read lock?
I assume they stay constant during the run of the application, but stil I would appreciate a comment here stating tha this is safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do, thanks @szank