func (suite *errorSetSuite) SetupTest() { suite.rawErrs = map[string]*terrors.Error{} suite.errs = nil err := terrors.InternalService("", "uid1", nil) err.Params[errUidField] = "uid1" err.Params[errServiceField] = "service.uid1" err.Params[errEndpointField] = "uid1" suite.errs = append(suite.errs, err) suite.rawErrs["uid1"] = err err = terrors.InternalService("", "uid2", nil) err.Params[errUidField] = "uid2" err.Params[errServiceField] = "service.uid2" err.Params[errEndpointField] = "uid2" suite.errs = append(suite.errs, err) suite.rawErrs["uid2"] = err err = terrors.InternalService("", "uid3", nil) err.Params[errUidField] = "uid3" err.Params[errServiceField] = "service.uid2" // Same service as uid2 err.Params[errEndpointField] = "uid3" suite.errs = append(suite.errs, err) suite.rawErrs["uid3"] = err }
// exec actually executes the requests; called by Go() within a sync.Once. func (c *client) exec() { defer close(c.doneC) c.RLock() timeout := c.timeout calls := c.calls // We don't need to make a copy as calls cannot be mutated once execution begins trans := c.transport() middleware := c.middleware c.RUnlock() completedCallsC := make(chan clientCall, len(calls)) for _, call := range calls { if call.err != nil { completedCallsC <- call continue } else if trans == nil { call.err = terrors.InternalService("no_transport", "Client has no transport", nil) completedCallsC <- call continue } go c.performCall(call, middleware, trans, timeout, completedCallsC) } // Collect completed calls into a new map completedCalls := make(map[string]clientCall, cap(completedCallsC)) for i := 0; i < cap(completedCallsC); i++ { call := <-completedCallsC completedCalls[call.uid] = call } close(completedCallsC) c.Lock() defer c.Unlock() c.calls = completedCalls }
// Handle takes an inbound Request, unmarshals it, dispatches it to the handler, and serialises the result as a // Response. Note that the response may be nil. func (e Endpoint) Handle(req mercury.Request) (rsp mercury.Response, err error) { // Unmarshal the request body (unless there already is one) if req.Body() == nil && e.Request != nil { if um := e.unmarshaler(req); um != nil { if werr := terrors.Wrap(um.UnmarshalPayload(req), nil); werr != nil { log.Warnf("[Mercury:Server] Cannot unmarshal request payload: %v", werr) terr := werr.(*terrors.Error) terr.Code = terrors.ErrBadRequest rsp, err = nil, terr return } } } defer func() { if v := recover(); v != nil { traceVerbose := make([]byte, 1024) runtime.Stack(traceVerbose, true) log.Criticalf("[Mercury:Server] Recovered from handler panic for request %s:\n%v\n%s", req.Id(), v, string(traceVerbose)) rsp, err = nil, terrors.InternalService("panic", fmt.Sprintf("Panic in handler %s:\n%s", req.Endpoint(), string(traceVerbose)), nil) } }() rsp, err = e.Handler(req) return }
func (suite *errorSetSuite) TestMultiErrorPriority() { br := terrors.BadRequest("missing_param", "foo bar", nil) is := terrors.InternalService("something_broke", "hello world", nil) suite.Assert().True(higherPriority(is.Code, br.Code)) se := terrors.New("something_else", "baz", nil) suite.Assert().True(higherPriority(is.Code, se.Code)) suite.Assert().True(higherPriority(br.Code, se.Code)) es := ErrorSet{se, is, br} suite.Assert().Equal(is.Code, es.Combined().(*terrors.Error).Code) }
"golang.org/x/net/context" "gopkg.in/tomb.v2" "github.com/mondough/mercury" "github.com/mondough/mercury/transport" terrors "github.com/mondough/typhon/errors" tmsg "github.com/mondough/typhon/message" ttrans "github.com/mondough/typhon/transport" ) const ( connectTimeout = 30 * time.Second ) var ( ErrAlreadyRunning error = terrors.InternalService("", "Server is already running", nil) // empty dotted code so impl details don't leak outside ErrTransportClosed error = terrors.InternalService("", "Transport closed", nil) errEndpointNotFound = terrors.BadRequest("endpoint_not_found", "Endpoint not found", nil) defaultMiddleware []ServerMiddleware defaultMiddlewareM sync.RWMutex ) func NewServer(name string) Server { defaultMiddlewareM.RLock() middleware := defaultMiddleware defaultMiddlewareM.RUnlock() return &server{ name: name, middleware: middleware, }
"github.com/mondough/typhon/errors" "github.com/mondough/typhon/message" "github.com/mondough/typhon/transport" ) const ( DirectReplyQueue = "amq.rabbitmq.reply-to" connectTimeout = 30 * time.Second chanSendTimeout = 10 * time.Second respondTimeout = 10 * time.Second ) var ( ErrCouldntConnect = errors.InternalService("", "Could not connect to RabbitMQ", nil) ErrDeliveriesClosed = errors.InternalService("", "Delivery channel closed", nil) ErrNoReplyTo = errors.BadRequest("", "Request does not have appropriate X-Rabbit-ReplyTo header", nil) ) type rabbitTransport struct { tomb *tomb.Tomb connM sync.RWMutex // protects conn + connReady conn *RabbitConnection // underlying connection connReady chan struct{} // swapped along with conn (reconnecting) replyQueue string // message reply queue name inflightReqs map[string]chan<- message.Response // correlation id: response chan inflightReqsM sync.Mutex // protects inflightReqs listeners map[string]*tomb.Tomb // service name: tomb listenersM sync.RWMutex // protects listeners }
package transport import ( "time" "github.com/mondough/typhon/errors" "github.com/mondough/typhon/message" "gopkg.in/tomb.v2" ) var ( // ErrAlreadyListening indicates a listener channel is already active for a given service ErrAlreadyListening = errors.InternalService("", "Listener already registered for service", nil) // ErrTimeout indicates a timeout was exceeded ErrTimeout = errors.Timeout("", "Timed out", nil) ) // A Transport provides a persistent interface to a transport layer. It is capable of sending and receiving Messages // on behalf of multiple services in parallel. type Transport interface { // A Tomb tracking the lifecycle of the Transport. Tomb() *tomb.Tomb // Ready vends a channel to wait on until the Transport is ready for use. Note that this will block indefinitely if // the Transport never reaches readiness. When ready, the channel is closed (so it's safe to listen in many // goroutines) Ready() <-chan struct{} // Listen for requests destined for a specific service, forwarding them down the passed channel. If another listener // is already listening, returns ErrAlreadyListening. Listen(serviceName string, inboundChan chan<- message.Request) error // StopListening terminates a listener for the passed service, returning whether successful. StopListening(serviceName string) bool