Skip to content

Commit 5bdca86

Browse files
author
Srinivasan Muralidharan
committed
fab-1475 make CC fmk allow concurrent invokes
https://jira.hyperledger.org/browse/FAB-1475 Summary ======= With pre-consensus simulation, multiple chains and relaxation by the ledger to simulate versions of chaincode state concurrently, we can now allow chaincode framework to execute invokes concurrently. This CR enables this. This CR enables concurrency basically by removing the FSM states that enforced serialization (so basically all the FSM changes in chaincode/hander.go and chaincode/shim/handler.go). The CR also has a "Chaincode Checker" program which has the potential for much bigger things . the tooling test their chaincodes for consistency . the tooling for stressing the fabric The concurrency enablement was tested with the "ccchecker". Details ======= The submit will basically have 4 things . changes to 3 chaincode framework files handler.go files to enable concurrency . concurrency_test.go to run 100 concurrent invokes followed by 100 concurrent queries . a complete "ccchecker" example framework for testing and validating chaincodes . exports some functions under fabric/peer/chaincode CLI for use by the above ccchecker example framework "ccchecker" comes with a sample "newkeyperinvoke" chaincode that should NEVER fail ledger consistency checks. To test simply follow these steps . vagrant window 1 - start orderer ./orderer . vagrant window 2 - start peer peer node start . vagrant window 3 - bring up chaincode for test cd peer //deploy the chaincode used by ccchecker out of the box peer chaincode deploy -n mycc -p github.com/hyperledger/fabric/examples/ccchecker/chaincodes/newkeyperinvoke -c '{"Args":[""]}' //wait for commit say for about 10 secs and then issue a query to bring the CC up peer chaincode query -n mycc -c '{"Args":["get","a"]}' //verify the chaincode is up docker ps . vagrant window 4 - run test cd examples/ccchecker go build ./ccchecker The above reads from ccchecker.json and executes tests concurrently. Change-Id: I5267b19f03ed10003eb28facf87693525f0dcd1a Signed-off-by: Srinivasan Muralidharan <[email protected]>
1 parent defb65b commit 5bdca86

19 files changed

+1345
-291
lines changed

core/chaincode/chaincode_support.go

+11-12
Original file line numberDiff line numberDiff line change
@@ -397,30 +397,29 @@ func (chaincodeSupport *ChaincodeSupport) getArgsAndEnv(cccid *CCContext, cLang
397397
}
398398

399399
// launchAndWaitForRegister will launch container if not already running. Use the targz to create the image if not found
400-
func (chaincodeSupport *ChaincodeSupport) launchAndWaitForRegister(ctxt context.Context, cccid *CCContext, cds *pb.ChaincodeDeploymentSpec, cLang pb.ChaincodeSpec_Type, targz io.Reader) (bool, error) {
400+
func (chaincodeSupport *ChaincodeSupport) launchAndWaitForRegister(ctxt context.Context, cccid *CCContext, cds *pb.ChaincodeDeploymentSpec, cLang pb.ChaincodeSpec_Type, targz io.Reader) error {
401401
canName := cccid.GetCanonicalName()
402402
if canName == "" {
403-
return false, fmt.Errorf("chaincode name not set")
403+
return fmt.Errorf("chaincode name not set")
404404
}
405405

406406
chaincodeSupport.runningChaincodes.Lock()
407-
var ok bool
408-
//if its in the map, there must be a connected stream...nothing to do
409-
if _, ok = chaincodeSupport.chaincodeHasBeenLaunched(canName); ok {
410-
chaincodeLogger.Debugf("chaincode is running and ready: %s", canName)
407+
//if its in the map, its either up or being launched. Either case break the
408+
//multiple launch by failing
409+
if _, hasBeenLaunched := chaincodeSupport.chaincodeHasBeenLaunched(canName); hasBeenLaunched {
411410
chaincodeSupport.runningChaincodes.Unlock()
412-
return true, nil
411+
return fmt.Errorf("Error chaincode is being launched: %s", canName)
413412
}
414-
alreadyRunning := false
415413

414+
//chaincodeHasBeenLaunch false... its not in the map, add it and proceed to launch
416415
notfy := chaincodeSupport.preLaunchSetup(canName)
417416
chaincodeSupport.runningChaincodes.Unlock()
418417

419418
//launch the chaincode
420419

421420
args, env, err := chaincodeSupport.getArgsAndEnv(cccid, cLang)
422421
if err != nil {
423-
return alreadyRunning, err
422+
return err
424423
}
425424

426425
chaincodeLogger.Debugf("start container: %s(networkid:%s,peerid:%s)", canName, chaincodeSupport.peerNetworkID, chaincodeSupport.peerID)
@@ -440,7 +439,7 @@ func (chaincodeSupport *ChaincodeSupport) launchAndWaitForRegister(ctxt context.
440439
chaincodeSupport.runningChaincodes.Lock()
441440
delete(chaincodeSupport.runningChaincodes.chaincodeMap, canName)
442441
chaincodeSupport.runningChaincodes.Unlock()
443-
return alreadyRunning, err
442+
return err
444443
}
445444

446445
//wait for REGISTER state
@@ -459,7 +458,7 @@ func (chaincodeSupport *ChaincodeSupport) launchAndWaitForRegister(ctxt context.
459458
chaincodeLogger.Debugf("error on stop %s(%s)", errIgnore, err)
460459
}
461460
}
462-
return alreadyRunning, err
461+
return err
463462
}
464463

465464
//Stop stops a chaincode if running
@@ -577,7 +576,7 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid
577576
//launch container if it is a System container or not in dev mode
578577
if (!chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) && (chrte == nil || chrte.handler == nil) {
579578
var targz io.Reader = bytes.NewBuffer(cds.CodePackage)
580-
_, err = chaincodeSupport.launchAndWaitForRegister(context, cccid, cds, cLang, targz)
579+
err = chaincodeSupport.launchAndWaitForRegister(context, cccid, cds, cLang, targz)
581580
if err != nil {
582581
chaincodeLogger.Errorf("launchAndWaitForRegister failed %s", err)
583582
return cID, cMsg, err

core/chaincode/concurrency_test.go

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package chaincode
18+
19+
import (
20+
"fmt"
21+
"sync"
22+
"testing"
23+
24+
"github.com/hyperledger/fabric/core/util"
25+
pb "github.com/hyperledger/fabric/protos/peer"
26+
27+
"golang.org/x/net/context"
28+
)
29+
30+
//TestExecuteConcurrentInvokes deploys newkeyperinvoke and runs 100 concurrent invokes
31+
//followed by concurrent 100 queries to validate
32+
func TestExecuteConcurrentInvokes(t *testing.T) {
33+
chainID := util.GetTestChainID()
34+
35+
lis, err := initPeer(chainID)
36+
if err != nil {
37+
t.Fail()
38+
t.Logf("Error creating peer: %s", err)
39+
}
40+
41+
defer finitPeer(lis, chainID)
42+
43+
var ctxt = context.Background()
44+
45+
url := "github.com/hyperledger/fabric/examples/ccchecker/chaincodes/newkeyperinvoke"
46+
47+
chaincodeID := &pb.ChaincodeID{Name: "nkpi", Path: url}
48+
49+
args := util.ToChaincodeArgs("init", "")
50+
51+
spec := &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID, CtorMsg: &pb.ChaincodeInput{Args: args}}
52+
53+
cccid := NewCCContext(chainID, "nkpi", "0", "", false, nil)
54+
55+
defer theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec})
56+
57+
_, err = deploy(ctxt, cccid, spec)
58+
if err != nil {
59+
t.Fail()
60+
t.Logf("Error initializing chaincode %s(%s)", chaincodeID, err)
61+
return
62+
}
63+
64+
var wg sync.WaitGroup
65+
66+
//run 100 invokes in parallel
67+
numTrans := 100
68+
69+
results := make([][]byte, numTrans)
70+
errs := make([]error, numTrans)
71+
72+
e := func(inv bool, qnum int) {
73+
defer wg.Done()
74+
75+
newkey := fmt.Sprintf("%d", qnum)
76+
77+
var args [][]byte
78+
if inv {
79+
args = util.ToChaincodeArgs("put", newkey, newkey)
80+
} else {
81+
args = util.ToChaincodeArgs("get", newkey)
82+
}
83+
84+
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID, CtorMsg: &pb.ChaincodeInput{Args: args}}
85+
86+
//start with a new background
87+
_, _, results[qnum], err = invoke(context.Background(), chainID, spec)
88+
89+
if err != nil {
90+
errs[qnum] = fmt.Errorf("Error executing <%s>: %s", chaincodeID.Name, err)
91+
return
92+
}
93+
}
94+
95+
wg.Add(numTrans)
96+
97+
//execute transactions concurrently.
98+
for i := 0; i < numTrans; i++ {
99+
go e(true, i)
100+
}
101+
102+
wg.Wait()
103+
104+
for i := 0; i < numTrans; i++ {
105+
if errs[i] != nil {
106+
t.Fail()
107+
t.Logf("Error invoking chaincode iter %d %s(%s)", i, chaincodeID.Name, errs[i])
108+
}
109+
if results[i] == nil || string(results[i]) != "OK" {
110+
t.Fail()
111+
t.Logf("Error concurrent invoke %d %s", i, chaincodeID.Name)
112+
return
113+
}
114+
}
115+
116+
wg.Add(numTrans)
117+
118+
//execute queries concurrently.
119+
for i := 0; i < numTrans; i++ {
120+
go e(false, i)
121+
}
122+
123+
wg.Wait()
124+
125+
for i := 0; i < numTrans; i++ {
126+
if errs[i] != nil {
127+
t.Fail()
128+
t.Logf("Error querying chaincode iter %d %s(%s)", i, chaincodeID.Name, errs[i])
129+
return
130+
}
131+
if results[i] == nil || string(results[i]) != fmt.Sprintf("%d", i) {
132+
t.Fail()
133+
if results[i] == nil {
134+
t.Logf("Error concurrent query %d(%s)", i, chaincodeID.Name)
135+
} else {
136+
t.Logf("Error concurrent query %d(%s, %s, %v)", i, chaincodeID.Name, string(results[i]), results[i])
137+
}
138+
return
139+
}
140+
}
141+
}

core/chaincode/exectransaction_test.go

+14-32
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ func endTxSimulationCIS(chainID string, txid string, txsim ledger.TxSimulator, p
161161
return endTxSimulation(chainID, txsim, payload, commit, prop)
162162
}
163163

164+
//getting a crash from ledger.Commit when doing concurrent invokes
165+
//It is likely intentional that ledger.Commit is serial (ie, the real
166+
//Committer will invoke this serially on each block). Mimic that here
167+
//by forcing serialization of the ledger.Commit call.
168+
//
169+
//NOTE-this should NOT have any effect on the older serial tests.
170+
//This affects only the tests in concurrent_test.go which call these
171+
//concurrently (100 concurrent invokes followed by 100 concurrent queries)
172+
var _commitLock_ sync.Mutex
173+
164174
func endTxSimulation(chainID string, txsim ledger.TxSimulator, payload []byte, commit bool, prop *pb.Proposal) error {
165175
txsim.Done()
166176
if lgr := peer.GetLedger(chainID); lgr != nil {
@@ -194,6 +204,10 @@ func endTxSimulation(chainID string, txsim ledger.TxSimulator, payload []byte, c
194204
block := common.NewBlock(1, []byte{})
195205
block.Data.Data = [][]byte{envBytes}
196206
//commit the block
207+
208+
//see comment on _commitLock_
209+
_commitLock_.Lock()
210+
defer _commitLock_.Unlock()
197211
if err := lgr.Commit(block); err != nil {
198212
return err
199213
}
@@ -601,38 +615,6 @@ func TestExecuteInvokeTransaction(t *testing.T) {
601615
theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeID: chaincodeID}})
602616
}
603617

604-
// Execute multiple transactions and queries.
605-
func exec(ctxt context.Context, chainID string, chaincodeID string, numTrans int, numQueries int) []error {
606-
var wg sync.WaitGroup
607-
errs := make([]error, numTrans+numQueries)
608-
609-
e := func(qnum int) {
610-
defer wg.Done()
611-
var spec *pb.ChaincodeSpec
612-
args := util.ToChaincodeArgs("invoke", "a", "b", "10")
613-
614-
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeID: &pb.ChaincodeID{Name: chaincodeID}, CtorMsg: &pb.ChaincodeInput{Args: args}}
615-
616-
_, _, _, err := invoke(ctxt, chainID, spec)
617-
618-
if err != nil {
619-
errs[qnum] = fmt.Errorf("Error executing <%s>: %s", chaincodeID, err)
620-
return
621-
}
622-
}
623-
wg.Add(numTrans + numQueries)
624-
625-
//execute transactions sequentially..
626-
go func() {
627-
for i := 0; i < numTrans; i++ {
628-
e(i)
629-
}
630-
}()
631-
632-
wg.Wait()
633-
return errs
634-
}
635-
636618
// Test the execution of an invalid transaction.
637619
func TestExecuteInvokeInvalidTransaction(t *testing.T) {
638620
chainID := util.GetTestChainID()

0 commit comments

Comments
 (0)