From ae316aabbea95d6c323059a548f1b0cfc743e254 Mon Sep 17 00:00:00 2001 From: Yacov Manevich Date: Wed, 4 May 2022 12:02:09 +0300 Subject: [PATCH] Properly handle concurrent building of chaincode packages The chaincode building has a mechanism that detects parallel building of the same chaincode package, and enforces mutual exclusion to prevent that from happening. Unfortunately, the mechanism had a bug because it relies on locks but returned a copy of the lock and not the real lock, thus each goroutine was locking its own lock and not a shared lock. Another issue was that the mapping that held the locks per package ID was growing indefinitely and never shrinking. Change-Id: I3d33c369487e0c81d8c64ce6f0bc833b3f2a01bc Signed-off-by: Yacov Manevich (cherry picked from commit cb98555345197a940bd6c1c862ae02941d8fb37d) --- core/chaincode/lifecycle/lifecycle.go | 31 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/core/chaincode/lifecycle/lifecycle.go b/core/chaincode/lifecycle/lifecycle.go index da507d63a2d..2db93e81c20 100644 --- a/core/chaincode/lifecycle/lifecycle.go +++ b/core/chaincode/lifecycle/lifecycle.go @@ -309,7 +309,8 @@ type ExternalFunctions struct { ChaincodeBuilder ChaincodeBuilder BuildRegistry *container.BuildRegistry mutex sync.Mutex - BuildLocks map[string]sync.Mutex + BuildLocks map[string]*sync.Mutex + concurrentInstalls uint32 } // CheckCommitReadiness takes a chaincode definition, checks that @@ -675,7 +676,9 @@ func (ef *ExternalFunctions) InstallChaincode(chaincodeInstallPackage []byte) (* return nil, errors.WithMessage(err, "could not save cc install package") } - buildLock := ef.getBuildLock(packageID) + buildLock, cleanupBuildLocks := ef.getBuildLock(packageID) + defer cleanupBuildLocks() + buildLock.Lock() defer buildLock.Unlock() @@ -709,20 +712,34 @@ func (ef *ExternalFunctions) InstallChaincode(chaincodeInstallPackage []byte) (* }, nil } -func (ef *ExternalFunctions) getBuildLock(packageID string) *sync.Mutex { +func (ef *ExternalFunctions) getBuildLock(packageID string) (*sync.Mutex, func()) { ef.mutex.Lock() defer ef.mutex.Unlock() + ef.concurrentInstalls++ + + cleanup := func() { + ef.mutex.Lock() + defer ef.mutex.Unlock() + + ef.concurrentInstalls-- + // If there are no other concurrent installs happening in parallel, + // cleanup the build lock mapping to release memory + if ef.concurrentInstalls == 0 { + ef.BuildLocks = nil + } + } + if ef.BuildLocks == nil { - ef.BuildLocks = map[string]sync.Mutex{} + ef.BuildLocks = map[string]*sync.Mutex{} } - buildLock, ok := ef.BuildLocks[packageID] + _, ok := ef.BuildLocks[packageID] if !ok { - ef.BuildLocks[packageID] = sync.Mutex{} + ef.BuildLocks[packageID] = &sync.Mutex{} } - return &buildLock + return ef.BuildLocks[packageID], cleanup } // GetInstalledChaincodePackage retrieves the installed chaincode with the given package ID