diff --git a/core/chaincode/handler.go b/core/chaincode/handler.go index a270cca7a20..c147cb0e0b0 100644 --- a/core/chaincode/handler.go +++ b/core/chaincode/handler.go @@ -733,6 +733,16 @@ func (h *Handler) HandleGetStateMetadata(msg *pb.ChaincodeMessage, txContext *Tr // Handles query to ledger to rage query state func (h *Handler) HandleGetStateByRange(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) { + iterID := h.UUIDGenerator.New() + defer func() { + if r := recover(); r != nil { + chaincodeLogger.Errorf("Panic error in HandleGetStateByRange: %v", r) + // Clean up any query context that may have been created + if txContext != nil { + txContext.CleanupQueryContext(iterID) + } + } + }() getStateByRange := &pb.GetStateByRange{} err := proto.Unmarshal(msg.Payload, getStateByRange) if err != nil { @@ -745,7 +755,6 @@ func (h *Handler) HandleGetStateByRange(msg *pb.ChaincodeMessage, txContext *Tra } totalReturnLimit := h.calculateTotalReturnLimit(metadata) - iterID := h.UUIDGenerator.New() var rangeIter commonledger.ResultsIterator isPaginated := false namespaceID := txContext.NamespaceID diff --git a/core/chaincode/handler_test.go b/core/chaincode/handler_test.go index 5aed680678e..30f67002871 100644 --- a/core/chaincode/handler_test.go +++ b/core/chaincode/handler_test.go @@ -12,6 +12,7 @@ import ( "github.com/hyperledger/fabric-lib-go/common/metrics/metricsfakes" pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" + commonledger "github.com/hyperledger/fabric/common/ledger" "github.com/hyperledger/fabric/common/util" ar "github.com/hyperledger/fabric/core/aclmgmt/resources" "github.com/hyperledger/fabric/core/chaincode" @@ -1601,6 +1602,54 @@ var _ = Describe("Handler", func() { Expect(retCount).To(BeNil()) }) }) + + Context("when a panic occurs during handling", func() { + var ( + fakeIterator *mock.QueryResultsIterator + request *pb.GetStateByRange + incomingMessage *pb.ChaincodeMessage + ) + + BeforeEach(func() { + request = &pb.GetStateByRange{ + StartKey: "start-key", + EndKey: "end-key", + } + payload, err := proto.Marshal(request) + Expect(err).NotTo(HaveOccurred()) + + incomingMessage = &pb.ChaincodeMessage{ + Type: pb.ChaincodeMessage_GET_STATE_BY_RANGE, + Payload: payload, + Txid: "tx-id", + ChannelId: "channel-id", + } + + fakeIterator = &mock.QueryResultsIterator{} + fakeTxSimulator.GetStateRangeScanIteratorReturns(fakeIterator, nil) + + // Set up QueryResponseBuilder to panic + fakeQueryResponseBuilder.BuildQueryResponseStub = func(*chaincode.TransactionContext, commonledger.ResultsIterator, string, bool, int32) (*pb.QueryResponse, error) { + panic("boom!") + } + }) + + It("recovers from the panic and cleans up the query context", func() { + handler.HandleGetStateByRange(incomingMessage, txContext) + + // Verify the query iterator was cleaned up + iterator := txContext.GetQueryIterator("generated-query-id") + Expect(iterator).To(BeNil()) + + // Verify the pending query result was cleaned up + pqr := txContext.GetPendingQueryResult("generated-query-id") + Expect(pqr).To(BeNil()) + + // Verify the total return count was cleaned up + retCount := txContext.GetTotalReturnCount("generated-query-id") + Expect(retCount).To(BeNil()) + }) + }) }) Describe("HandleQueryStateNext", func() {