Skip to content

Commit 3dcc32f

Browse files
bviswana101ghaskins
authored andcommitted
FAB-2724: Fix CouchDB max open connections
Client and Transport are reusable and thread safe, so initialize once and reuse across requests. Also close the response body properly by consuming it fully before closing it to allow the underlying RoundTripper to reuse the transport for subsequent requests. Change-Id: I1d1692ee96db1ed91a0eaccbe81439312c6935ac Signed-off-by: Balaji Viswanathan <[email protected]> Signed-off-by: denyeart <[email protected]>
1 parent 8ce1073 commit 3dcc32f

File tree

2 files changed

+40
-27
lines changed

2 files changed

+40
-27
lines changed

core/ledger/util/couchdb/couchdb.go

+29-26
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ type CouchConnectionDef struct {
135135

136136
//CouchInstance represents a CouchDB instance
137137
type CouchInstance struct {
138-
conf CouchConnectionDef //connection configuration
138+
conf CouchConnectionDef //connection configuration
139+
client *http.Client // a client to connect to this instance
139140
}
140141

141142
//CouchDatabase represents a database within a CouchDB instance
@@ -206,6 +207,13 @@ type Base64Attachment struct {
206207
AttachmentData string `json:"data"`
207208
}
208209

210+
// closeResponseBody discards the body and then closes it to enable returning it to
211+
// connection pool
212+
func closeResponseBody(resp *http.Response) {
213+
io.Copy(ioutil.Discard, resp.Body) // discard whatever is remaining of body
214+
resp.Body.Close()
215+
}
216+
209217
//CreateConnectionDefinition for a new client connection
210218
func CreateConnectionDefinition(couchDBAddress, username, password string, maxRetries,
211219
maxRetriesOnStartup int, requestTimeout time.Duration) (*CouchConnectionDef, error) {
@@ -264,7 +272,7 @@ func (dbclient *CouchDatabase) CreateDatabaseIfNotExist() (*DBOperationResponse,
264272
if err != nil {
265273
return nil, err
266274
}
267-
defer resp.Body.Close()
275+
defer closeResponseBody(resp)
268276

269277
//Get the response from the create REST call
270278
dbResponse := &DBOperationResponse{}
@@ -305,7 +313,7 @@ func (dbclient *CouchDatabase) GetDatabaseInfo() (*DBInfo, *DBReturn, error) {
305313
if err != nil {
306314
return nil, couchDBReturn, err
307315
}
308-
defer resp.Body.Close()
316+
defer closeResponseBody(resp)
309317

310318
dbResponse := &DBInfo{}
311319
json.NewDecoder(resp.Body).Decode(&dbResponse)
@@ -344,7 +352,7 @@ func (couchInstance *CouchInstance) VerifyCouchConfig() (*ConnectionInfo, *DBRet
344352
if err != nil {
345353
return nil, couchDBReturn, fmt.Errorf("Unable to connect to CouchDB, check the hostname and port: %s", err.Error())
346354
}
347-
defer resp.Body.Close()
355+
defer closeResponseBody(resp)
348356

349357
dbResponse := &ConnectionInfo{}
350358
errJSON := json.NewDecoder(resp.Body).Decode(&dbResponse)
@@ -371,7 +379,6 @@ func (couchInstance *CouchInstance) VerifyCouchConfig() (*ConnectionInfo, *DBRet
371379
}
372380

373381
return dbResponse, couchDBReturn, nil
374-
375382
}
376383

377384
//DropDatabase provides method to drop an existing database
@@ -393,7 +400,7 @@ func (dbclient *CouchDatabase) DropDatabase() (*DBOperationResponse, error) {
393400
if err != nil {
394401
return nil, err
395402
}
396-
defer resp.Body.Close()
403+
defer closeResponseBody(resp)
397404

398405
dbResponse := &DBOperationResponse{}
399406
json.NewDecoder(resp.Body).Decode(&dbResponse)
@@ -434,7 +441,7 @@ func (dbclient *CouchDatabase) EnsureFullCommit() (*DBOperationResponse, error)
434441
logger.Errorf("Failed to invoke _ensure_full_commit Error: %s\n", err.Error())
435442
return nil, err
436443
}
437-
defer resp.Body.Close()
444+
defer closeResponseBody(resp)
438445

439446
dbResponse := &DBOperationResponse{}
440447
json.NewDecoder(resp.Body).Decode(&dbResponse)
@@ -528,7 +535,7 @@ func (dbclient *CouchDatabase) SaveDoc(id string, rev string, couchDoc *CouchDoc
528535
if err != nil {
529536
return "", err
530537
}
531-
defer resp.Body.Close()
538+
defer closeResponseBody(resp)
532539

533540
//get the revision and return
534541
revision, err := getRevisionHeader(resp)
@@ -666,7 +673,7 @@ func (dbclient *CouchDatabase) ReadDoc(id string) (*CouchDoc, string, error) {
666673
logger.Debugf("couchDBReturn=%v\n", couchDBReturn)
667674
return nil, "", err
668675
}
669-
defer resp.Body.Close()
676+
defer closeResponseBody(resp)
670677

671678
//Get the media type from the Content-Type header
672679
mediaType, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
@@ -813,7 +820,7 @@ func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip
813820
if err != nil {
814821
return nil, err
815822
}
816-
defer resp.Body.Close()
823+
defer closeResponseBody(resp)
817824

818825
if logger.IsEnabledFor(logging.DEBUG) {
819826
dump, err2 := httputil.DumpResponse(resp, true)
@@ -918,7 +925,7 @@ func (dbclient *CouchDatabase) DeleteDoc(id, rev string) error {
918925
}
919926
return err
920927
}
921-
defer resp.Body.Close()
928+
defer closeResponseBody(resp)
922929

923930
logger.Debugf("Exiting DeleteDoc()")
924931

@@ -948,7 +955,7 @@ func (dbclient *CouchDatabase) QueryDocuments(query string) (*[]QueryResult, err
948955
if err != nil {
949956
return nil, err
950957
}
951-
defer resp.Body.Close()
958+
defer closeResponseBody(resp)
952959

953960
if logger.IsEnabledFor(logging.DEBUG) {
954961
dump, err2 := httputil.DumpResponse(resp, true)
@@ -1034,7 +1041,7 @@ func (dbclient *CouchDatabase) BatchRetrieveIDRevision(keys []string) ([]*DocMet
10341041
if err != nil {
10351042
return nil, err
10361043
}
1037-
defer resp.Body.Close()
1044+
defer closeResponseBody(resp)
10381045

10391046
if logger.IsEnabledFor(logging.DEBUG) {
10401047
dump, _ := httputil.DumpResponse(resp, false)
@@ -1129,7 +1136,7 @@ func (dbclient *CouchDatabase) BatchUpdateDocuments(documents []*CouchDoc) ([]*B
11291136
if err != nil {
11301137
return nil, err
11311138
}
1132-
defer resp.Body.Close()
1139+
defer closeResponseBody(resp)
11331140

11341141
if logger.IsEnabledFor(logging.DEBUG) {
11351142
dump, _ := httputil.DumpResponse(resp, false)
@@ -1156,7 +1163,9 @@ func (dbclient *CouchDatabase) BatchUpdateDocuments(documents []*CouchDoc) ([]*B
11561163

11571164
}
11581165

1159-
//handleRequest method is a generic http request handler
1166+
//handleRequest method is a generic http request handler.
1167+
// if it returns an error, it ensures that the response body is closed, else it is the
1168+
// callee's responsibility to close response correctly
11601169
func (couchInstance *CouchInstance) handleRequest(method, connectURL string, data []byte, rev string,
11611170
multipartBoundary string, maxRetries int) (*http.Response, *DBReturn, error) {
11621171

@@ -1170,9 +1179,6 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
11701179
//set initial wait duration for retries
11711180
waitDuration := retryWaitTime * time.Millisecond
11721181

1173-
//get the connection timeout
1174-
requestTimeout := couchInstance.conf.RequestTimeout
1175-
11761182
//attempt the http request for the max number of retries
11771183
for attempts := 0; attempts < maxRetries; attempts++ {
11781184

@@ -1225,14 +1231,8 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
12251231
logger.Debugf("HTTP Request: %s", bytes.Replace(dump, []byte{0x0d, 0x0a}, []byte{0x20, 0x7c, 0x20}, -1))
12261232
}
12271233

1228-
//Create the http client
1229-
client := &http.Client{Timeout: requestTimeout}
1230-
1231-
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
1232-
transport.DisableCompression = false
1233-
client.Transport = transport
12341234
//Execute http request
1235-
resp, errResp = client.Do(req)
1235+
resp, errResp = couchInstance.client.Do(req)
12361236

12371237
//if an error is not detected then drop out of the retry
12381238
if errResp == nil && resp != nil && resp.StatusCode < 500 {
@@ -1248,8 +1248,9 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
12481248

12491249
} else {
12501250

1251-
//Read the response body
1251+
//Read the response body and close it for next attempt
12521252
jsonError, err := ioutil.ReadAll(resp.Body)
1253+
closeResponseBody(resp)
12531254
if err != nil {
12541255
return nil, nil, err
12551256
}
@@ -1283,6 +1284,8 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
12831284
//check to see if the status code is 400 or higher
12841285
//response codes 4XX and 500 will be treated as errors
12851286
if resp.StatusCode >= 400 {
1287+
// close the response before returning error
1288+
defer closeResponseBody(resp)
12861289

12871290
//Read the response body
12881291
jsonError, err := ioutil.ReadAll(resp.Body)

core/ledger/util/couchdb/couchdbutil.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package couchdb
1818

1919
import (
2020
"fmt"
21+
"net/http"
2122
"regexp"
2223
"strconv"
2324
"strings"
@@ -38,8 +39,17 @@ func CreateCouchInstance(couchDBConnectURL, id, pw string, maxRetries,
3839
return nil, err
3940
}
4041

42+
// Create the http client once
43+
// Clients and Transports are safe for concurrent use by multiple goroutines
44+
// and for efficiency should only be created once and re-used.
45+
client := &http.Client{Timeout: couchConf.RequestTimeout}
46+
47+
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
48+
transport.DisableCompression = false
49+
client.Transport = transport
50+
4151
//Create the CouchDB instance
42-
couchInstance := &CouchInstance{conf: *couchConf}
52+
couchInstance := &CouchInstance{conf: *couchConf, client: client}
4353

4454
connectInfo, retVal, verifyErr := couchInstance.VerifyCouchConfig()
4555
if verifyErr != nil {

0 commit comments

Comments
 (0)