/* Parse a gilmour *Message and for subscribers of this topic do the following: * If subscriber is one shot, unsubscribe the subscriber to prevent subscribers from re-execution. * If subscriber belongs to a group, try acquiring a lock via backend to ensure group exclusivity. * If all conditions suffice spin up a new goroutine for each subscription. */ func (g *Gilmour) processMessage(msg *proto.Packet) { subs, ok := g.getSubscribers(msg.GetPattern()) if !ok || len(subs) == 0 { ui.Warn("*Message cannot be processed. No subs found for key %v", msg.GetPattern()) return } m, err := parseMessage(msg.GetData()) if err != nil { ui.Alert(err.Error()) return } for _, s := range subs { opts := s.GetOpts() if opts != nil && opts.isOneShot() { ui.Message("Unsubscribing one shot response topic %v", msg.GetTopic()) go g.UnsubscribeReply(msg.GetPattern(), s) } if opts.GetGroup() != "" && opts.shouldSendResponse() { if !g.backend.AcquireGroupLock(opts.GetGroup(), m.GetSender()) { ui.Warn( "Unable to acquire Lock. Topic %v Group %v Sender %v", msg.GetTopic(), opts.GetGroup(), m.GetSender(), ) continue } } go g.handleRequest(s, msg.GetTopic(), m) } }
func (g *Gilmour) sendTimeout(senderId, channel string) { msg := g.timeoutMessage(senderId) if err := g.publish(channel, msg); err != nil { ui.Alert(err.Error()) } }
func (g *Gilmour) handleRequest(s *Subscription, topic string, m *Message) { senderId := m.GetSender() req := &Request{topic, m} res := NewMessage() res.setSender(proto.ResponseTopic(senderId)) done := make(chan bool, 1) //Executing Request go func(done chan<- bool) { // Schedule a function to recover in case handler runs into an error. // Read more: https://gist.github.com/meson10/d56eface6f87c664d07d defer func() { err := recover() if err == nil { return } const size = 4096 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] buffer := string(buf) res.SetData(buffer).SetCode(500) done <- true }() s.GetHandler()(req, res) done <- true }(done) // Start a timeout handler, which writes on the Done channel, ahead of the // handler. This might result in a RACE condition, as there is no way to // kill a goroutine, since they are not preemptive. timeout := s.GetOpts().GetTimeout() time.AfterFunc(time.Duration(timeout)*time.Second, func() { done <- false }) status := <-done if s.GetOpts().shouldSendResponse() { if status == false { g.sendTimeout(senderId, res.GetSender()) } else { if res.GetCode() == 0 { res.SetCode(200) } if err := g.publish(res.GetSender(), res); err != nil { ui.Alert(err.Error()) } } } else if status == false { // Inform the error catcher, If there is no handler for this Request // but the request had failed. This is automatically handled in case // of a response being written via Publisher. request := string(req.bytes()) errMsg := proto.MakeError(499, topic, request, "", req.Sender(), "") g.reportError(errMsg) } }