Skip to content

Commit 2e0a61f

Browse files
author
Chris Elder
committed
[FAB-3131] Peer hangs when CouchDB unresponsive
The peer hangs when the CouchDB network connection is unresponsive. This can be corrected by adding a timeout to the network client connection used for connecting to CouchDB. If the connection times out, then the request to CouchDB will return an error and the retry logic will take over. A new entry is added for CouchDB in core.yaml named connectionTimeout couchDBConfig: couchDBAddress: 127.0.0.1:5984 username: password: # Number of retries for CouchDB errors maxRetries: 3 # Number of retries for CouchDB errors during peer startup maxRetriesOnStartup: 10 # CouchDB connection timeout (unit: duration, e.g. 60s) connectionTimeout: 60s This is a duration property configurable by the peer. Change-Id: I5028029e7f303144465c2bfeebf540c5d8e64d82 Signed-off-by: Chris Elder <[email protected]>
1 parent 987496f commit 2e0a61f

File tree

11 files changed

+88
-32
lines changed

11 files changed

+88
-32
lines changed

core/chaincode/chaincodetest.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,13 @@ ledger:
428428
couchDBAddress: 127.0.0.1:5984
429429
username:
430430
password:
431+
# Number of retries for CouchDB errors
431432
maxRetries: 3
433+
# Number of retries for CouchDB errors during peer startup
432434
maxRetriesOnStartup: 10
435+
# CouchDB request timeout (unit: duration, e.g. 20s)
436+
requestTimeout: 20s
437+
433438

434439
# historyDatabase - options are true or false
435440
# Indicates if the history of key updates should be stored in goleveldb

core/chaincode/exectransaction_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ func finitPeer(lis net.Listener, chainIDs ...string) {
126126
password := viper.GetString("ledger.state.couchDBConfig.password")
127127
maxRetries := viper.GetInt("ledger.state.couchDBConfig.maxRetries")
128128
maxRetriesOnStartup := viper.GetInt("ledger.state.couchDBConfig.maxRetriesOnStartup")
129+
requestTimeout := viper.GetDuration("ledger.state.couchDBConfig.requestTimeout")
129130

130-
couchInstance, _ := couchdb.CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
131+
couchInstance, _ := couchdb.CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
131132
db, _ := couchdb.CreateCouchDatabase(*couchInstance, chainID)
132133
//drop the test database
133134
db.DropDatabase()

core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewVersionedDBProvider() (*VersionedDBProvider, error) {
5656
logger.Debugf("constructing CouchDB VersionedDBProvider")
5757
couchDBDef := ledgerconfig.GetCouchDBDefinition()
5858
couchInstance, err := couchdb.CreateCouchInstance(couchDBDef.URL, couchDBDef.Username, couchDBDef.Password,
59-
couchDBDef.MaxRetries, couchDBDef.MaxRetriesOnStartup)
59+
couchDBDef.MaxRetries, couchDBDef.MaxRetriesOnStartup, couchDBDef.RequestTimeout)
6060
if err != nil {
6161
return nil, err
6262
}

core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb_test_export.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package statecouchdb
1919
import (
2020
"strings"
2121
"testing"
22+
"time"
2223

2324
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
2425
"github.com/hyperledger/fabric/core/ledger/util/couchdb"
@@ -31,6 +32,7 @@ var username = ""
3132
var password = ""
3233
var maxRetries = 3
3334
var maxRetriesOnStartup = 10
35+
var connectionTimeout = time.Second * 60
3436

3537
// TestVDBEnv provides a couch db backed versioned db for testing
3638
type TestVDBEnv struct {
@@ -57,7 +59,7 @@ func (env *TestVDBEnv) Cleanup(dbName string) {
5759
}
5860
func cleanupDB(dbName string) {
5961
//create a new connection
60-
couchInstance, _ := couchdb.CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
62+
couchInstance, _ := couchdb.CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, connectionTimeout)
6163
db := couchdb.CouchDatabase{CouchInstance: *couchInstance, DBName: dbName}
6264
//drop the test database
6365
db.DropDatabase()

core/ledger/ledgerconfig/ledger_config.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package ledgerconfig
1818

1919
import (
2020
"path/filepath"
21+
"time"
2122

2223
"github.com/spf13/viper"
2324
)
@@ -29,6 +30,7 @@ type CouchDBDef struct {
2930
Password string
3031
MaxRetries int
3132
MaxRetriesOnStartup int
33+
RequestTimeout time.Duration
3234
}
3335

3436
//IsCouchDBEnabled exposes the useCouchDB variable
@@ -80,8 +82,9 @@ func GetCouchDBDefinition() *CouchDBDef {
8082
password := viper.GetString("ledger.state.couchDBConfig.password")
8183
maxRetries := viper.GetInt("ledger.state.couchDBConfig.maxRetries")
8284
maxRetriesOnStartup := viper.GetInt("ledger.state.couchDBConfig.maxRetriesOnStartup")
85+
requestTimeout := viper.GetDuration("ledger.state.couchDBConfig.requestTimeout")
8386

84-
return &CouchDBDef{couchDBAddress, username, password, maxRetries, maxRetriesOnStartup}
87+
return &CouchDBDef{couchDBAddress, username, password, maxRetries, maxRetriesOnStartup, requestTimeout}
8588
}
8689

8790
//GetQueryLimit exposes the queryLimit variable

core/ledger/ledgerconfig/ledger_config_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package ledgerconfig
1818

1919
import (
2020
"testing"
21+
"time"
2122

2223
"github.com/hyperledger/fabric/common/ledger/testutil"
2324
ledgertestutil "github.com/hyperledger/fabric/core/ledger/testutil"
@@ -54,6 +55,9 @@ func TestGetCouchDBDefinition(t *testing.T) {
5455
testutil.AssertEquals(t, couchDBDef.URL, "127.0.0.1:5984")
5556
testutil.AssertEquals(t, couchDBDef.Username, "")
5657
testutil.AssertEquals(t, couchDBDef.Password, "")
58+
testutil.AssertEquals(t, couchDBDef.MaxRetries, 3)
59+
testutil.AssertEquals(t, couchDBDef.MaxRetriesOnStartup, 10)
60+
testutil.AssertEquals(t, couchDBDef.RequestTimeout, time.Second*20)
5761
}
5862

5963
func TestIsHistoryDBEnabledDefault(t *testing.T) {

core/ledger/util/couchdb/couchdb.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ type CouchConnectionDef struct {
129129
Password string
130130
MaxRetries int
131131
MaxRetriesOnStartup int
132+
RequestTimeout time.Duration
132133
}
133134

134135
//CouchInstance represents a CouchDB instance
@@ -205,13 +206,11 @@ type Base64Attachment struct {
205206
}
206207

207208
//CreateConnectionDefinition for a new client connection
208-
func CreateConnectionDefinition(couchDBAddress, username, password string, maxRetries, maxRetriesOnStartup int) (*CouchConnectionDef, error) {
209+
func CreateConnectionDefinition(couchDBAddress, username, password string, maxRetries,
210+
maxRetriesOnStartup int, requestTimeout time.Duration) (*CouchConnectionDef, error) {
209211

210212
logger.Debugf("Entering CreateConnectionDefinition()")
211213

212-
//connectURL := fmt.Sprintf("%s//%s", "http:", couchDBAddress)
213-
//connectURL := couchDBAddress
214-
215214
connectURL := &url.URL{
216215
Host: couchDBAddress,
217216
Scheme: "http",
@@ -228,7 +227,9 @@ func CreateConnectionDefinition(couchDBAddress, username, password string, maxRe
228227
logger.Debugf("Exiting CreateConnectionDefinition()")
229228

230229
//return an object containing the connection information
231-
return &CouchConnectionDef{finalURL.String(), username, password, maxRetries, maxRetriesOnStartup}, nil
230+
return &CouchConnectionDef{finalURL.String(), username, password, maxRetries,
231+
maxRetriesOnStartup, requestTimeout}, nil
232+
232233
}
233234

234235
//CreateDatabaseIfNotExist method provides function to create database
@@ -1140,7 +1141,8 @@ func (dbclient *CouchDatabase) BatchUpdateDocuments(documents []*CouchDoc) ([]*B
11401141
}
11411142

11421143
//handleRequest method is a generic http request handler
1143-
func (couchInstance *CouchInstance) handleRequest(method, connectURL string, data []byte, rev string, multipartBoundary string, maxRetries int) (*http.Response, *DBReturn, error) {
1144+
func (couchInstance *CouchInstance) handleRequest(method, connectURL string, data []byte, rev string,
1145+
multipartBoundary string, maxRetries int) (*http.Response, *DBReturn, error) {
11441146

11451147
logger.Debugf("Entering handleRequest() method=%s url=%v", method, connectURL)
11461148

@@ -1152,6 +1154,9 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
11521154
//set initial wait duration for retries
11531155
waitDuration := retryWaitTime * time.Millisecond
11541156

1157+
//get the connection timeout
1158+
requestTimeout := couchInstance.conf.RequestTimeout
1159+
11551160
//attempt the http request for the max number of retries
11561161
for attempts := 0; attempts < maxRetries; attempts++ {
11571162

@@ -1205,7 +1210,7 @@ func (couchInstance *CouchInstance) handleRequest(method, connectURL string, dat
12051210
}
12061211

12071212
//Create the http client
1208-
client := &http.Client{}
1213+
client := &http.Client{Timeout: requestTimeout}
12091214

12101215
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
12111216
transport.DisableCompression = false

core/ledger/util/couchdb/couchdb_test.go

+47-18
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"os"
23+
"strings"
2324
"testing"
25+
"time"
2426
"unicode/utf8"
2527

2628
"github.com/hyperledger/fabric/common/ledger/testutil"
@@ -36,13 +38,14 @@ var username = ""
3638
var password = ""
3739
var maxRetries = 3
3840
var maxRetriesOnStartup = 10
41+
var requestTimeout = time.Second * 20
3942

4043
const updateDocumentConflictError = "conflict"
4144
const updateDocumentConflictReason = "Document update conflict."
4245

4346
func cleanup(database string) error {
4447
//create a new connection
45-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
48+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
4649
if err != nil {
4750
fmt.Println("Unexpected error", err)
4851
return err
@@ -78,15 +81,15 @@ func TestDBConnectionDef(t *testing.T) {
7881
ledgertestutil.SetupCoreYAMLConfig("./../../../../peer")
7982

8083
//create a new connection
81-
_, err := CreateConnectionDefinition(connectURL, "", "", maxRetries, maxRetriesOnStartup)
84+
_, err := CreateConnectionDefinition(connectURL, "", "", maxRetries, maxRetriesOnStartup, requestTimeout)
8285
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create database connection definition"))
8386

8487
}
8588

8689
func TestDBBadConnectionDef(t *testing.T) {
8790

8891
//create a new connection
89-
_, err := CreateConnectionDefinition("^^^localhost:5984", "", "", maxRetries, maxRetriesOnStartup)
92+
_, err := CreateConnectionDefinition("^^^localhost:5984", "", "", maxRetries, maxRetriesOnStartup, requestTimeout)
9093
testutil.AssertError(t, err, fmt.Sprintf("Did not receive error when trying to create database connection definition with a bad hostname"))
9194

9295
}
@@ -102,7 +105,7 @@ func TestDBCreateSaveWithoutRevision(t *testing.T) {
102105

103106
if err == nil {
104107
//create a new instance and database object
105-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
108+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
106109
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
107110
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
108111

@@ -128,7 +131,7 @@ func TestDBCreateEnsureFullCommit(t *testing.T) {
128131

129132
if err == nil {
130133
//create a new instance and database object
131-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
134+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
132135
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
133136
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
134137

@@ -153,25 +156,25 @@ func TestDBBadDatabaseName(t *testing.T) {
153156
if ledgerconfig.IsCouchDBEnabled() {
154157

155158
//create a new instance and database object using a valid database name mixed case
156-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
159+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
157160
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
158161
_, dberr := CreateCouchDatabase(*couchInstance, "testDB")
159162
testutil.AssertNoError(t, dberr, fmt.Sprintf("Error when testing a valid database name"))
160163

161164
//create a new instance and database object using a valid database name letters and numbers
162-
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
165+
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
163166
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
164167
_, dberr = CreateCouchDatabase(*couchInstance, "test132")
165168
testutil.AssertNoError(t, dberr, fmt.Sprintf("Error when testing a valid database name"))
166169

167170
//create a new instance and database object using a valid database name - special characters
168-
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
171+
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
169172
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
170173
_, dberr = CreateCouchDatabase(*couchInstance, "test1234~!@#$%^&*()[]{}.")
171174
testutil.AssertNoError(t, dberr, fmt.Sprintf("Error when testing a valid database name"))
172175

173176
//create a new instance and database object using a invalid database name - too long /*
174-
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
177+
couchInstance, err = CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
175178
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
176179
_, dberr = CreateCouchDatabase(*couchInstance, "A12345678901234567890123456789012345678901234"+
177180
"56789012345678901234567890123456789012345678901234567890123456789012345678901234567890"+
@@ -192,7 +195,7 @@ func TestDBBadConnection(t *testing.T) {
192195
if ledgerconfig.IsCouchDBEnabled() {
193196

194197
//create a new instance and database object
195-
_, err := CreateCouchInstance(badConnectURL, username, password, maxRetries, maxRetriesOnStartup)
198+
_, err := CreateCouchInstance(badConnectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
196199
testutil.AssertError(t, err, fmt.Sprintf("Error should have been thrown for a bad connection"))
197200
}
198201
}
@@ -208,7 +211,7 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) {
208211

209212
if err == nil {
210213
//create a new instance and database object
211-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
214+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
212215
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
213216
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
214217

@@ -286,6 +289,32 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) {
286289

287290
}
288291

292+
func TestDBRequestTimeout(t *testing.T) {
293+
294+
if ledgerconfig.IsCouchDBEnabled() {
295+
296+
database := "testdbrequesttimeout"
297+
err := cleanup(database)
298+
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to cleanup Error: %s", err))
299+
defer cleanup(database)
300+
301+
if err == nil {
302+
303+
//create an impossibly short timeout
304+
impossibleTimeout := time.Microsecond * 1
305+
306+
//create a new instance and database object with a timeout that will fail
307+
//Also use a maxRetriesOnStartup=3 to reduce the number of retries
308+
_, err := CreateCouchInstance(connectURL, username, password, maxRetries, 3, impossibleTimeout)
309+
testutil.AssertError(t, err, fmt.Sprintf("Error should have been thown while trying to create a couchdb instance with a connection timeout"))
310+
311+
//see if the error message contains the timeout error
312+
testutil.AssertEquals(t, strings.Count(err.Error(), "Client.Timeout exceeded while awaiting headers"), 1)
313+
314+
}
315+
}
316+
}
317+
289318
func TestDBBadJSON(t *testing.T) {
290319

291320
if ledgerconfig.IsCouchDBEnabled() {
@@ -298,7 +327,7 @@ func TestDBBadJSON(t *testing.T) {
298327
if err == nil {
299328

300329
//create a new instance and database object
301-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
330+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
302331
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
303332
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
304333

@@ -334,7 +363,7 @@ func TestPrefixScan(t *testing.T) {
334363

335364
if err == nil {
336365
//create a new instance and database object
337-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
366+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
338367
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
339368
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
340369

@@ -406,7 +435,7 @@ func TestDBSaveAttachment(t *testing.T) {
406435
attachments = append(attachments, attachment)
407436

408437
//create a new instance and database object
409-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
438+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
410439
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
411440
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
412441

@@ -439,7 +468,7 @@ func TestDBDeleteDocument(t *testing.T) {
439468

440469
if err == nil {
441470
//create a new instance and database object
442-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
471+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
443472
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
444473
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
445474

@@ -477,7 +506,7 @@ func TestDBDeleteNonExistingDocument(t *testing.T) {
477506

478507
if err == nil {
479508
//create a new instance and database object
480-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
509+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
481510
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
482511
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
483512

@@ -616,7 +645,7 @@ func TestRichQuery(t *testing.T) {
616645

617646
if err == nil {
618647
//create a new instance and database object --------------------------------------------------------
619-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
648+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
620649
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
621650
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
622651

@@ -830,7 +859,7 @@ func TestBatchBatchOperations(t *testing.T) {
830859
defer cleanup(database)
831860

832861
//create a new instance and database object --------------------------------------------------------
833-
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup)
862+
couchInstance, err := CreateCouchInstance(connectURL, username, password, maxRetries, maxRetriesOnStartup, requestTimeout)
834863
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
835864
db := CouchDatabase{CouchInstance: *couchInstance, DBName: database}
836865

core/ledger/util/couchdb/couchdbutil.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ import (
2121
"regexp"
2222
"strconv"
2323
"strings"
24+
"time"
2425
)
2526

2627
var validNamePattern = `^[a-z][a-z0-9_$(),+/-]+`
2728
var maxLength = 249
2829

2930
//CreateCouchInstance creates a CouchDB instance
30-
func CreateCouchInstance(couchDBConnectURL, id, pw string, maxRetries, maxRetriesOnStartup int) (*CouchInstance, error) {
31+
func CreateCouchInstance(couchDBConnectURL, id, pw string, maxRetries,
32+
maxRetriesOnStartup int, connectionTimeout time.Duration) (*CouchInstance, error) {
33+
3134
couchConf, err := CreateConnectionDefinition(couchDBConnectURL,
32-
id, pw, maxRetries, maxRetriesOnStartup)
35+
id, pw, maxRetries, maxRetriesOnStartup, connectionTimeout)
3336
if err != nil {
3437
logger.Errorf("Error during CouchDB CreateConnectionDefinition(): %s\n", err.Error())
3538
return nil, err

0 commit comments

Comments
 (0)