本文整理汇总了Golang中github.com/coreos/etcd/raft/raftpb.ConfChange.Unmarshal方法的典型用法代码示例。如果您正苦于以下问题:Golang ConfChange.Unmarshal方法的具体用法?Golang ConfChange.Unmarshal怎么用?Golang ConfChange.Unmarshal使用的例子?那么恭喜您, 这里精选的方法代码示例或许可以为您提供帮助。您也可以进一步了解该方法所在类github.com/coreos/etcd/raft/raftpb.ConfChange
的用法示例。
在下文中一共展示了ConfChange.Unmarshal方法的15个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于系统推荐出更棒的Golang代码示例。
示例1: processCommitCh
func (n *node) processCommitCh() {
pending := make(chan struct{}, numPendingMutations)
for e := range n.commitCh {
if e.Data == nil {
continue
}
if e.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
cc.Unmarshal(e.Data)
if len(cc.Context) > 0 {
var rc task.RaftContext
x.Check(rc.Unmarshal(cc.Context))
n.Connect(rc.Id, rc.Addr)
}
n.raft.ApplyConfChange(cc)
} else {
go n.process(e, pending)
}
}
}
示例2: run
func (n *node) run() {
for {
select {
case <-n.ticker:
n.raft.Tick()
case rd := <-n.raft.Ready():
n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot)
n.send(rd.Messages)
if !raft.IsEmptySnap(rd.Snapshot) {
n.processSnapshot(rd.Snapshot)
}
for _, entry := range rd.CommittedEntries {
n.process(entry)
if entry.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
cc.Unmarshal(entry.Data)
n.raft.ApplyConfChange(cc)
}
}
n.raft.Advance()
case <-n.done:
return
}
}
}
示例3: tryRaftLogEntry
func tryRaftLogEntry(kv engine.MVCCKeyValue) (string, error) {
var ent raftpb.Entry
if err := maybeUnmarshalInline(kv.Value, &ent); err != nil {
return "", err
}
if ent.Type == raftpb.EntryNormal {
if len(ent.Data) > 0 {
_, cmdData := storage.DecodeRaftCommand(ent.Data)
var cmd storagebase.RaftCommand
if err := cmd.Unmarshal(cmdData); err != nil {
return "", err
}
ent.Data = nil
return fmt.Sprintf("%s by %v\n%s\n%s\n", &ent, cmd.OriginReplica, cmd.BatchRequest, &cmd), nil
}
return fmt.Sprintf("%s: EMPTY\n", &ent), nil
} else if ent.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
if err := cc.Unmarshal(ent.Data); err != nil {
return "", err
}
var ctx storage.ConfChangeContext
if err := ctx.Unmarshal(cc.Context); err != nil {
return "", err
}
var cmd storagebase.ReplicatedEvalResult
if err := cmd.Unmarshal(ctx.Payload); err != nil {
return "", err
}
ent.Data = nil
return fmt.Sprintf("%s\n%s\n", &ent, &cmd), nil
}
return "", fmt.Errorf("unknown log entry type: %s", &ent)
}
示例4: getIDs
// getIDs returns an ordered set of IDs included in the given snapshot and
// the entries. The given snapshot/entries can contain two kinds of
// ID-related entry:
// - ConfChangeAddNode, in which case the contained ID will be added into the set.
// - ConfChangeRemoveNode, in which case the contained ID will be removed from the set.
func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
ids := make(map[uint64]bool)
if snap != nil {
for _, id := range snap.Metadata.ConfState.Nodes {
ids[id] = true
}
}
for _, e := range ents {
if e.Type != raftpb.EntryConfChange {
continue
}
if snap != nil && e.Index < snap.Metadata.Index {
continue
}
var cc raftpb.ConfChange
if err := cc.Unmarshal(e.Data); err != nil {
log.L.WithError(err).Panic("unmarshal configuration change should never fail")
}
switch cc.Type {
case raftpb.ConfChangeAddNode:
ids[cc.NodeID] = true
case raftpb.ConfChangeRemoveNode:
delete(ids, cc.NodeID)
case raftpb.ConfChangeUpdateNode:
// do nothing
default:
log.L.Panic("ConfChange Type should be either ConfChangeAddNode or ConfChangeRemoveNode!")
}
}
var sids []uint64
for id := range ids {
sids = append(sids, id)
}
return sids
}
示例5: Start
// Start is the main loop for a Raft node, it
// goes along the state machine, acting on the
// messages received from other Raft nodes in
// the cluster
func (n *Node) Start() {
for {
select {
case <-n.ticker.C:
n.Tick()
case rd := <-n.Ready():
n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot)
n.send(rd.Messages)
if !raft.IsEmptySnap(rd.Snapshot) {
n.processSnapshot(rd.Snapshot)
}
for _, entry := range rd.CommittedEntries {
n.process(entry)
if entry.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
err := cc.Unmarshal(entry.Data)
if err != nil {
log.Fatal("raft: Can't unmarshal configuration change")
}
switch cc.Type {
case raftpb.ConfChangeAddNode:
n.applyAddNode(cc)
case raftpb.ConfChangeRemoveNode:
n.applyRemoveNode(cc)
}
n.ApplyConfChange(cc)
}
}
n.Advance()
case <-n.stopChan:
n.Stop()
n.Node = nil
close(n.stopChan)
return
case pause := <-n.pauseChan:
// FIXME lock hell
n.SetPaused(pause)
for n.pause {
select {
case pause = <-n.pauseChan:
n.SetPaused(pause)
}
}
n.pauseLock.Lock()
// process pending messages
for _, m := range n.rcvmsg {
err := n.Step(n.Ctx, m)
if err != nil {
log.Fatal("Something went wrong when unpausing the node")
}
}
n.rcvmsg = nil
n.pauseLock.Unlock()
}
}
}
示例6: publishEntries
// publishEntries writes committed log entries to commit channel and returns
// whether all entries could be published.
func (rc *raftNode) publishEntries(ents []raftpb.Entry) bool {
for i := range ents {
switch ents[i].Type {
case raftpb.EntryNormal:
if len(ents[i].Data) == 0 {
// ignore empty messages
break
}
s := string(ents[i].Data)
select {
case rc.commitC <- &s:
case <-rc.stopc:
return false
}
case raftpb.EntryConfChange:
var cc raftpb.ConfChange
cc.Unmarshal(ents[i].Data)
rc.node.ApplyConfChange(cc)
switch cc.Type {
case raftpb.ConfChangeAddNode:
if len(cc.Context) > 0 {
rc.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)})
}
case raftpb.ConfChangeRemoveNode:
if cc.NodeID == uint64(rc.id) {
log.Println("I've been removed from the cluster! Shutting down.")
return false
}
rc.transport.RemovePeer(types.ID(cc.NodeID))
}
}
// after commit, update appliedIndex
rc.appliedIndex = ents[i].Index
// special nil commit to signal replay has finished
if ents[i].Index == rc.lastIndex {
select {
case rc.commitC <- nil:
case <-rc.stopc:
return false
}
}
}
return true
}
示例7: TestProposeAfterRemoveLeader
// TestProposeAfterRemoveLeader ensures that we gracefully handle
// proposals that are attempted after a leader has been removed from
// the active configuration, but before that leader has called
// MultiNode.RemoveGroup.
func TestProposeAfterRemoveLeader(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mn := newMultiNode(1)
go mn.run()
defer mn.Stop()
storage := NewMemoryStorage()
if err := mn.CreateGroup(1, newTestConfig(1, nil, 10, 1, storage),
[]Peer{{ID: 1}}); err != nil {
t.Fatal(err)
}
if err := mn.Campaign(ctx, 1); err != nil {
t.Fatal(err)
}
if err := mn.ProposeConfChange(ctx, 1, raftpb.ConfChange{
Type: raftpb.ConfChangeRemoveNode,
NodeID: 1,
}); err != nil {
t.Fatal(err)
}
gs := <-mn.Ready()
g := gs[1]
if err := storage.Append(g.Entries); err != nil {
t.Fatal(err)
}
for _, e := range g.CommittedEntries {
if e.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
if err := cc.Unmarshal(e.Data); err != nil {
t.Fatal(err)
}
mn.ApplyConfChange(1, cc)
}
}
mn.Advance(gs)
if err := mn.Propose(ctx, 1, []byte("somedata")); err != nil {
t.Errorf("err = %v, want nil", err)
}
}
示例8: process
func (n *node) process(entry raftpb.Entry) {
fmt.Printf("node %v: processing entry", n.id)
if entry.Data == nil {
return
}
if entry.Type == raftpb.EntryConfChange {
fmt.Printf("Configuration change\n")
var cc raftpb.ConfChange
cc.Unmarshal(entry.Data)
n.raft.ApplyConfChange(cc)
return
}
if entry.Type == raftpb.EntryNormal {
parts := bytes.SplitN(entry.Data, []byte(":"), 2)
k := string(parts[0])
v := string(parts[1])
n.data[k] = v
fmt.Printf(" Key: %v Val: %v\n", k, v)
}
}
示例9: readyApply
func (c *ctrl) readyApply(snapshot raftpb.Snapshot, committedEntries []raftpb.Entry) error {
c.snapshotc <- snapshot
for _, committedEntry := range committedEntries {
c.entryc <- committedEntry
if committedEntry.Type == raftpb.EntryConfChange {
// See raftexample raftNode.publishEntries
var cc raftpb.ConfChange
if err := cc.Unmarshal(committedEntry.Data); err != nil {
return fmt.Errorf("unmarshal ConfChange: %v", err)
}
c.node.ApplyConfChange(cc)
if cc.Type == raftpb.ConfChangeRemoveNode && cc.NodeID == c.self.ID {
return errors.New("got ConfChange that removed me from the cluster; terminating")
}
}
}
return nil
}
示例10: publishEntries
// publishEntries writes committed log entries to commit channel and returns
// whether all entries could be published.
func (rc *raftNode) publishEntries(ents []raftpb.Entry) bool {
for i := range ents {
switch ents[i].Type {
case raftpb.EntryNormal:
if len(ents[i].Data) == 0 {
// ignore conf changes and empty messages
continue
}
s := string(ents[i].Data)
select {
case rc.commitC <- &s:
case <-rc.stopc:
return false
}
case raftpb.EntryConfChange:
var cc raftpb.ConfChange
cc.Unmarshal(ents[i].Data)
rc.node.ApplyConfChange(cc)
switch cc.Type {
case raftpb.ConfChangeAddNode:
if len(cc.Context) > 0 {
rc.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)})
}
case raftpb.ConfChangeRemoveNode:
if cc.NodeID == uint64(rc.id) {
log.Println("I've been removed from the cluster! Shutting down.")
return false
}
rc.transport.RemovePeer(types.ID(cc.NodeID))
}
}
}
return true
}
示例11: processCommittedEntry
// processCommittedEntry tells the application that a command was committed.
// Returns the commandID, or an empty string if the given entry was not a command.
func (s *state) processCommittedEntry(groupID roachpb.RangeID, g *group, entry raftpb.Entry) string {
var commandID string
switch entry.Type {
case raftpb.EntryNormal:
var command []byte
commandID, command = decodeCommand(entry.Data)
s.sendEvent(&EventCommandCommitted{
GroupID: groupID,
CommandID: commandID,
Command: command,
Index: entry.Index,
})
case raftpb.EntryConfChange:
cc := raftpb.ConfChange{}
if err := cc.Unmarshal(entry.Data); err != nil {
log.Fatalf("invalid ConfChange data: %s", err)
}
var payload []byte
if len(cc.Context) > 0 {
var ctx ConfChangeContext
if err := ctx.Unmarshal(cc.Context); err != nil {
log.Fatalf("invalid ConfChangeContext: %s", err)
}
commandID = ctx.CommandID
payload = ctx.Payload
s.CacheReplicaDescriptor(groupID, ctx.Replica)
}
replica, err := s.ReplicaDescriptor(groupID, roachpb.ReplicaID(cc.NodeID))
if err != nil {
// TODO(bdarnell): stash Replica information somewhere so we can have it here
// with no chance of failure.
log.Fatalf("could not look up replica info (node %s, group %d, replica %d): %s",
s.nodeID, groupID, cc.NodeID, err)
}
s.sendEvent(&EventMembershipChangeCommitted{
GroupID: groupID,
CommandID: commandID,
Index: entry.Index,
Replica: replica,
ChangeType: cc.Type,
Payload: payload,
Callback: func(err error) {
select {
case s.callbackChan <- func() {
gInner, ok := s.groups[groupID]
if !ok {
log.Infof("group %d no longer exists, aborting configuration change", groupID)
} else if gInner != g {
log.Infof("passed in group and fetched group objects do not match\noriginal:%+v\nfetched:%+v\n, aborting configuration change",
g, gInner)
} else if err == nil {
if log.V(3) {
log.Infof("node %v applying configuration change %v", s.nodeID, cc)
}
// TODO(bdarnell): dedupe by keeping a record of recently-applied commandIDs
switch cc.Type {
case raftpb.ConfChangeAddNode:
err = s.addNode(replica.NodeID, g)
case raftpb.ConfChangeRemoveNode:
err = s.removeNode(replica.NodeID, g)
case raftpb.ConfChangeUpdateNode:
// Updates don't concern multiraft, they are simply passed through.
}
if err != nil {
log.Errorf("error applying configuration change %v: %s", cc, err)
}
g.raftGroup.ApplyConfChange(cc)
} else {
log.Warningf("aborting configuration change: %s", err)
g.raftGroup.ApplyConfChange(raftpb.ConfChange{})
}
}:
case <-s.stopper.ShouldStop():
}
},
})
}
return commandID
}
示例12: processCommittedEntry
// processCommittedEntry tells the application that a command was committed.
// Returns the commandID, or an empty string if the given entry was not a command.
func (s *state) processCommittedEntry(groupID proto.RangeID, g *group, entry raftpb.Entry) string {
var commandID string
switch entry.Type {
case raftpb.EntryNormal:
// etcd raft occasionally adds a nil entry (e.g. upon election); ignore these.
if entry.Data != nil {
var command []byte
commandID, command = decodeCommand(entry.Data)
s.sendEvent(&EventCommandCommitted{
GroupID: groupID,
CommandID: commandID,
Command: command,
Index: entry.Index,
})
}
case raftpb.EntryConfChange:
cc := raftpb.ConfChange{}
if err := cc.Unmarshal(entry.Data); err != nil {
log.Fatalf("invalid ConfChange data: %s", err)
}
var payload []byte
if len(cc.Context) > 0 {
commandID, payload = decodeCommand(cc.Context)
}
g.waitForCallback = true
s.sendEvent(&EventMembershipChangeCommitted{
GroupID: groupID,
CommandID: commandID,
Index: entry.Index,
NodeID: proto.RaftNodeID(cc.NodeID),
ChangeType: cc.Type,
Payload: payload,
Callback: func(err error) {
select {
case s.callbackChan <- func() {
if err == nil {
if log.V(3) {
log.Infof("node %v applying configuration change %v", s.nodeID, cc)
}
// TODO(bdarnell): dedupe by keeping a record of recently-applied commandIDs
switch cc.Type {
case raftpb.ConfChangeAddNode:
err = s.addNode(proto.RaftNodeID(cc.NodeID), g)
case raftpb.ConfChangeRemoveNode:
err = s.removeNode(proto.RaftNodeID(cc.NodeID), g)
case raftpb.ConfChangeUpdateNode:
// Updates don't concern multiraft, they are simply passed through.
}
if err != nil {
log.Errorf("error applying configuration change %v: %s", cc, err)
}
s.multiNode.ApplyConfChange(uint64(groupID), cc)
} else {
log.Warningf("aborting configuration change: %s", err)
s.multiNode.ApplyConfChange(uint64(groupID),
raftpb.ConfChange{})
}
// Re-submit all pending proposals that were held
// while the config change was pending
g.waitForCallback = false
for _, prop := range g.pending {
s.propose(prop)
}
}:
case <-s.stopper.ShouldStop():
}
},
})
}
return commandID
}
示例13: processCommittedEntry
// processCommittedEntry tells the application that a command was committed.
// Returns the commandID, or an empty string if the given entry was not a command.
func (s *state) processCommittedEntry(groupID roachpb.RangeID, g *group, entry raftpb.Entry) string {
var commandID string
switch entry.Type {
case raftpb.EntryNormal:
// etcd raft occasionally adds a nil entry (e.g. upon election); ignore these.
if entry.Data != nil {
var command []byte
commandID, command = decodeCommand(entry.Data)
s.sendEvent(&EventCommandCommitted{
GroupID: groupID,
CommandID: commandID,
Command: command,
Index: entry.Index,
})
}
case raftpb.EntryConfChange:
cc := raftpb.ConfChange{}
if err := cc.Unmarshal(entry.Data); err != nil {
log.Fatalf("invalid ConfChange data: %s", err)
}
var payload []byte
if len(cc.Context) > 0 {
var ctx ConfChangeContext
if err := ctx.Unmarshal(cc.Context); err != nil {
log.Fatalf("invalid ConfChangeContext: %s", err)
}
commandID = ctx.CommandID
payload = ctx.Payload
s.CacheReplicaDescriptor(groupID, ctx.Replica)
}
replica, err := s.ReplicaDescriptor(groupID, roachpb.ReplicaID(cc.NodeID))
if err != nil {
// TODO(bdarnell): stash Replica information somewhere so we can have it here
// with no chance of failure.
log.Fatalf("could not look up replica info (node %s, group %d, replica %d): %s",
s.nodeID, groupID, cc.NodeID, err)
}
g.waitForCallback++
s.sendEvent(&EventMembershipChangeCommitted{
GroupID: groupID,
CommandID: commandID,
Index: entry.Index,
Replica: replica,
ChangeType: cc.Type,
Payload: payload,
Callback: func(err error) {
var errStr string
if err != nil {
errStr = err.Error() // can't leak err into the callback
}
select {
case s.callbackChan <- func() {
if errStr == "" {
if log.V(3) {
log.Infof("node %v applying configuration change %v", s.nodeID, cc)
}
// TODO(bdarnell): dedupe by keeping a record of recently-applied commandIDs
var err error
switch cc.Type {
case raftpb.ConfChangeAddNode:
err = s.addNode(replica.NodeID, g)
case raftpb.ConfChangeRemoveNode:
err = s.removeNode(replica.NodeID, g)
case raftpb.ConfChangeUpdateNode:
// Updates don't concern multiraft, they are simply passed through.
}
if err != nil {
log.Errorf("error applying configuration change %v: %s", cc, err)
}
s.multiNode.ApplyConfChange(uint64(groupID), cc)
} else {
log.Warningf("aborting configuration change: %s", errStr)
s.multiNode.ApplyConfChange(uint64(groupID),
raftpb.ConfChange{})
}
// Re-submit all pending proposals that were held
// while the config change was pending
g.waitForCallback--
if g.waitForCallback <= 0 {
for _, prop := range g.pending {
s.propose(prop)
}
}
}:
case <-s.stopper.ShouldStop():
}
},
})
}
return commandID
}
示例14: handleWriteResponse
func (s *state) handleWriteResponse(response *writeResponse, readyGroups map[uint64]raft.Ready) {
log.V(6).Infof("node %v got write response: %#v", s.nodeID, *response)
// Everything has been written to disk; now we can apply updates to the state machine
// and send outgoing messages.
for groupID, ready := range readyGroups {
g, ok := s.groups[groupID]
if !ok {
log.V(4).Infof("dropping stale write to group %v", groupID)
continue
}
for _, entry := range ready.CommittedEntries {
var commandID string
switch entry.Type {
case raftpb.EntryNormal:
// etcd raft occasionally adds a nil entry (e.g. upon election); ignore these.
if entry.Data != nil {
var command []byte
commandID, command = decodeCommand(entry.Data)
s.sendEvent(&EventCommandCommitted{
GroupID: groupID,
CommandID: commandID,
Command: command,
})
}
case raftpb.EntryConfChange:
cc := raftpb.ConfChange{}
err := cc.Unmarshal(entry.Data)
if err != nil {
log.Fatalf("invalid ConfChange data: %s", err)
}
var payload []byte
if len(cc.Context) > 0 {
commandID, payload = decodeCommand(cc.Context)
}
s.sendEvent(&EventMembershipChangeCommitted{
GroupID: groupID,
CommandID: commandID,
NodeID: NodeID(cc.NodeID),
ChangeType: cc.Type,
Payload: payload,
Callback: func(err error) {
s.callbackChan <- func() {
if err == nil {
log.V(3).Infof("node %v applying configuration change %v", s.nodeID, cc)
// TODO(bdarnell): dedupe by keeping a record of recently-applied commandIDs
switch cc.Type {
case raftpb.ConfChangeAddNode:
err = s.addNode(NodeID(cc.NodeID), groupID)
case raftpb.ConfChangeRemoveNode:
// TODO(bdarnell): support removing nodes; fix double-application of initial entries
case raftpb.ConfChangeUpdateNode:
// Updates don't concern multiraft, they are simply passed through.
}
if err != nil {
log.Errorf("error applying configuration change %v: %s", cc, err)
}
s.multiNode.ApplyConfChange(groupID, cc)
} else {
log.Warningf("aborting configuration change: %s", err)
s.multiNode.ApplyConfChange(groupID,
raftpb.ConfChange{})
}
// Re-submit all pending proposals, in case any of them were config changes
// that were dropped due to the one-at-a-time rule. This is a little
// redundant since most pending proposals won't benefit from this but
// config changes should be rare enough (and the size of the pending queue
// small enough) that it doesn't really matter.
for _, prop := range g.pending {
s.proposalChan <- prop
}
}
},
})
}
if p, ok := g.pending[commandID]; ok {
// TODO(bdarnell): the command is now committed, but not applied until the
// application consumes EventCommandCommitted. Is closing the channel
// at this point useful or do we need to wait for the command to be
// applied too?
// This could be done with a Callback as in EventMembershipChangeCommitted
// or perhaps we should move away from a channel to a callback-based system.
if p.ch != nil {
// Because of the way we re-queue proposals during leadership
// changes, we may close the same proposal object twice.
close(p.ch)
p.ch = nil
}
delete(g.pending, commandID)
}
}
noMoreHeartbeats := make(map[uint64]struct{})
for _, msg := range ready.Messages {
switch msg.Type {
case raftpb.MsgHeartbeat:
log.V(7).Infof("node %v dropped individual heartbeat to node %v",
s.nodeID, msg.To)
continue
//.........这里部分代码省略.........
示例15: start
func (n *node) start() {
n.stopc = make(chan struct{})
ticker := time.Tick(100 * time.Millisecond)
go func() {
for {
select {
case <-ticker:
n.Tick()
//fmt.Println("node_id:", n.id)
case rd := <-n.Ready():
if !raft.IsEmptyHardState(rd.HardState) {
n.state = rd.HardState
n.storage.SetHardState(n.state)
}
n.storage.Append(rd.Entries)
//fmt.Printf("------Node_ID:%v---------\n", n.id)
//n.storage.Dump()
time.Sleep(time.Millisecond)
//fmt.Println("rd ready")
// TODO: make send async, more like real world...
for _, m := range rd.Messages {
n.iface.send(m)
}
//Process Snapshot
if !raft.IsEmptySnap(rd.Snapshot) {
n.storage.ApplySnapshot(rd.Snapshot)
}
//
for _, entry := range rd.CommittedEntries {
//process(entry)
if entry.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
cc.Unmarshal(entry.Data)
st := n.Node.ApplyConfChange(cc)
fmt.Printf("CommittedEntries state: %v\n", st.String())
}
}
n.Advance()
case m := <-n.iface.recv():
n.Step(context.TODO(), m)
//fmt.Printf("recv:%v\n", m)
case <-n.stopc:
n.Stop()
log.Printf("raft.%d: stop\n", n.id)
n.Node = nil
close(n.stopc)
return
case p := <-n.pausec:
recvms := make([]raftpb.Message, 0)
for p {
select {
case m := <-n.iface.recv():
recvms = append(recvms, m)
case p = <-n.pausec:
}
}
// step all pending messages
for _, m := range recvms {
n.Step(context.TODO(), m)
}
}
}
}()
}