Ignore invalid message ID if last request timed out
[govpp.git] / api / api.go
index 783f97a..60508cd 100644 (file)
@@ -18,6 +18,8 @@ import (
        "errors"
        "fmt"
        "time"
+
+       "github.com/sirupsen/logrus"
 )
 
 // MessageType represents the type of a VPP message.
@@ -28,11 +30,13 @@ const (
        RequestMessage MessageType = iota
        // ReplyMessage represents a VPP reply message
        ReplyMessage
+       // EventMessage represents a VPP notification event message
+       EventMessage
        // OtherMessage represents other VPP message (e.g. counters)
        OtherMessage
 )
 
-// Message is an interface that is implemented by all VPP Binary API messages generated by the binapi_generator.
+// Message is an interface that is implemented by all VPP Binary API messages generated by the binapigenerator.
 type Message interface {
        // GetMessageName returns the original VPP name of the message, as defined in the VPP API.
        GetMessageName() string
@@ -59,7 +63,7 @@ type ChannelProvider interface {
        // It uses default buffer sizes for the request and reply Go channels.
        NewAPIChannel() (*Channel, error)
 
-       // NewAPIChannel returns a new channel for communication with VPP via govpp core.
+       // NewAPIChannelBuffered returns a new channel for communication with VPP via govpp core.
        // It allows to specify custom buffer sizes for the request and reply Go channels.
        NewAPIChannelBuffered() (*Channel, error)
 }
@@ -78,7 +82,8 @@ type MessageIdentifier interface {
 
 // Channel is the main communication interface with govpp core. It contains two Go channels, one for sending the requests
 // to VPP and one for receiving the replies from it. The user can access the Go channels directly, or use the helper
-// methods  provided inside of this package.
+// methods  provided inside of this package. Do not use the same channel from multiple goroutines concurrently,
+// otherwise the responses could mix! Use multiple channels instead.
 type Channel struct {
        ReqChan   chan *VppRequest // channel for sending the requests to VPP, closing this channel releases all resources in the ChannelProvider
        ReplyChan chan *VppReply   // channel where VPP replies are delivered to
@@ -91,6 +96,7 @@ type Channel struct {
 
        replyTimeout time.Duration // maximum time that the API waits for a reply from VPP before returning an error, can be set with SetReplyTimeout
        metadata     interface{}   // opaque metadata of the API channel
+       lastTimedOut bool          // wether last request timed out
 }
 
 // VppRequest is a request that will be sent to VPP.
@@ -211,33 +217,46 @@ func (ch *Channel) receiveReplyInternal(msg Message) (LastReplyReceived bool, er
        if msg == nil {
                return false, errors.New("nil message passed in")
        }
-       select {
-       // blocks until a reply comes to ReplyChan or until timeout expires
-       case vppReply := <-ch.ReplyChan:
-               if vppReply.Error != nil {
-                       err = vppReply.Error
-                       return
-               }
-               if vppReply.LastReplyReceived {
-                       LastReplyReceived = true
-                       return
-               }
-               // message checks
-               expMsgID, err := ch.MsgIdentifier.GetMessageID(msg)
-               if err != nil {
-                       err = fmt.Errorf("message %s with CRC %s is not compatible with the VPP we are connected to",
-                               msg.GetMessageName(), msg.GetCrcString())
+
+       timer := time.NewTimer(ch.replyTimeout)
+       for {
+               select {
+               // blocks until a reply comes to ReplyChan or until timeout expires
+               case vppReply := <-ch.ReplyChan:
+                       if vppReply.Error != nil {
+                               err = vppReply.Error
+                               return
+                       }
+                       if vppReply.LastReplyReceived {
+                               LastReplyReceived = true
+                               return
+                       }
+                       // message checks
+                       expMsgID, err := ch.MsgIdentifier.GetMessageID(msg)
+                       if err != nil {
+                               err = fmt.Errorf("message %s with CRC %s is not compatible with the VPP we are connected to",
+                                       msg.GetMessageName(), msg.GetCrcString())
+                               return false, err
+                       }
+                       if vppReply.MessageID != expMsgID {
+                               if ch.lastTimedOut {
+                                       logrus.Warnf("received invalid message ID, expected %d (%s), but got %d (probably timed out reply from previous request)",
+                                               expMsgID, msg.GetMessageName(), vppReply.MessageID)
+                                       continue
+                               }
+                               err = fmt.Errorf("received invalid message ID, expected %d (%s), but got %d (check if multiple goroutines are not sharing single GoVPP channel)",
+                                       expMsgID, msg.GetMessageName(), vppReply.MessageID)
+                               return false, err
+                       }
+                       ch.lastTimedOut = false
+                       // decode the message
+                       err = ch.MsgDecoder.DecodeMsg(vppReply.Data, msg)
                        return false, err
-               }
-               if vppReply.MessageID != expMsgID {
-                       err = fmt.Errorf("invalid message ID %d, expected %d", vppReply.MessageID, expMsgID)
+               case <-timer.C:
+                       ch.lastTimedOut = true
+                       err = fmt.Errorf("no reply received within the timeout period %s", ch.replyTimeout)
                        return false, err
                }
-               // decode the message
-               err = ch.MsgDecoder.DecodeMsg(vppReply.Data, msg)
-
-       case <-time.After(ch.replyTimeout):
-               err = fmt.Errorf("no reply received within the timeout period %ds", ch.replyTimeout/time.Second)
        }
        return
 }