Example #1
0
func NewMmuxConnection() *MmuxConnection {
	conn := &MmuxConnection{
		OutGoingTable:    util.NewMessageTable(),
		SubscribeMap:     map[string]bool{},
		MaxOfflineQueue:  1000,
		Created:          time.Now(),
		Identifier:       "",
		SubscribedTopics: make(map[string]*SubscribeSet),
		CleanSession:     true,
	}

	return conn
}
Example #2
0
// QoS 1, 2 are available. but really suck implementation.
// reconsider qos design later.
func NewMomonga(config *configuration.Config) *Momonga {
	engine := &Momonga{
		OutGoingTable: util.NewMessageTable(),
		TopicMatcher:  util.NewQlobber(),
		Connections:   map[uint32]map[string]*MmuxConnection{},
		RetryMap:      map[string][]*Retryable{},
		Started:       time.Now(),
		EnableSys:     false,
		DataStore:     datastore.NewMemstore(),
		LockPool:      map[uint32]*sync.RWMutex{},
		config:        config,
		InflightTable: map[string]*util.MessageTable{},
	}

	// initialize lock pool
	for i := 0; i < config.GetLockPoolSize(); i++ {
		engine.LockPool[uint32(i)] = &sync.RWMutex{}
		engine.Connections[uint32(i)] = make(map[string]*MmuxConnection)
	}

	auth := config.GetAuthenticators()
	if len(auth) > 0 {
		for i := 0; i < len(auth); i++ {
			var authenticator Authenticator
			switch auth[i].Type {
			case "empty":
				authenticator = &EmptyAuthenticator{}
				authenticator.Init(config)
			default:
				panic(fmt.Sprintf("Unsupported type specified: [%s]", auth[i].Type))
			}
			engine.registerAuthenticator(authenticator)
		}
	} else {
		authenticator := &EmptyAuthenticator{}
		authenticator.Init(config)
		engine.registerAuthenticator(authenticator)
	}

	engine.setupCallback()
	return engine
}
Example #3
0
func (self *Momonga) SendPublishMessage(msg *codec.PublishMessage, client_id string, is_bridged bool) {
	// Don't pass wrong message here. user should validate the message be ore using this API.
	if len(msg.TopicName) < 1 {
		return
	}

	// TODO: Have to persist retain message.
	if msg.Retain > 0 {
		if len(msg.Payload) == 0 {
			log.Debug("[DELETE RETAIN: %s]\n%s", msg.TopicName, hex.Dump([]byte(msg.TopicName)))

			err := self.DataStore.Del([]byte(msg.TopicName), []byte(msg.TopicName))
			if err != nil {
				log.Error("Error: %s\n", err)
			}
			// これ配送したらおかしいべ
			log.Debug("Deleted retain: %s", msg.TopicName)
			// あれ、ackとかかえすんだっけ?
			Metrics.System.Broker.Messages.RetainedCount.Add(-1)
			return
		} else {
			buffer := bytes.NewBuffer(nil)
			codec.WriteMessageTo(msg, buffer)
			self.DataStore.Put([]byte(msg.TopicName), buffer.Bytes())
			Metrics.System.Broker.Messages.RetainedCount.Add(1)
		}
	}

	if Mflags["experimental.qos1"] {
		if msg.QosLevel == 1 {
			targets := self.TopicMatcher.Match(msg.TopicName)

			go func(msg *codec.PublishMessage, set []interface{}) {
				p := make(chan string, 1000)
				wg := sync.WaitGroup{}
				wg.Add(3) // bulk sernder, retry kun, receive kun

				mchan := make(chan string, 256)
				term := make(chan bool, 1)
				cnt := len(set)
				mng := make(map[string]*codec.PublishMessage)

				// retry kun。こういう実装だととても楽
				go func(term chan bool, mchan chan string, mng map[string]*codec.PublishMessage) {
					for {
						select {
						case m := <-mchan:
							if msg, ok := mng[m]; ok {
								conn, err := self.GetConnectionByClientId(m)
								if err != nil {
									fmt.Printf("something wrong: %s %s", m, err)
									continue
								}

								if err == nil {
									log.Debug("sending a retry msg: %s", msg)
									conn.WriteMessageQueue(msg)
								} else {
									log.Debug("connection not exist. next retry")
								}
							}
						case <-term:
							log.Debug("  retry finished.")

							wg.Done()
							return
						}
					}
				}(term, mchan, mng)

				// reader
				go func(p chan string, term chan bool, cnt int, mng map[string]*codec.PublishMessage, mchan chan string) {
					limit := time.After(time.Second * 60)
					for {
						select {
						case id := <-p:
							cnt--
							delete(mng, id)
							// これはcallbackでやってもいいようなきもするけど、wait groupとかもろもろ渡すの面倒くさい

							if cnt < 1 {
								log.Debug("  all delivery finished.")
								term <- true

								wg.Done()
								return
							}
						case <-time.After(time.Second * 20):
							// 終わってないやつをなめてリトライさせる
							for cid, m := range mng {
								m.Dupe = true
								mchan <- cid
							}
						case <-limit:
							log.Debug("  gave up retry.")
							term <- true
							wg.Done()
							return
						}
					}
				}(p, term, cnt, mng, mchan)

				// sender. これは勝手に終わる
				go func(msg *codec.PublishMessage, set []interface{}, p chan string, mng map[string]*codec.PublishMessage) {
					dp := make(map[string]bool)
					for i := range targets {
						var tbl *util.MessageTable
						var ok bool

						myset := targets[i].(*SubscribeSet)
						fmt.Printf("myset: %s", myset)

						// NOTE (from interoperability/client_test.py):
						//
						//   overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
						//   the server may send back one message with the highest QoS of any matching subscription, or one message for
						//   each subscription with a matching QoS.
						//
						// Currently, We choose one message for each subscription with a matching QoS.
						//
						if _, ok := dp[myset.ClientId]; ok {
							continue
						}
						dp[myset.ClientId] = true

						x, _ := codec.CopyPublishMessage(msg)
						x.QosLevel = myset.QoS
						conn, err := self.GetConnectionByClientId(myset.ClientId)
						// これは面倒臭い。clean sessionがtrueで再接続した時はもはや別人として扱わなければならない

						if x.QosLevel == 0 {
							// QoS 0にダウングレードしたらそのまま終わらせる
							conn.WriteMessageQueue(x)
							p <- myset.ClientId
							continue
						}

						if tbl, ok = self.InflightTable[myset.ClientId]; !ok {
							self.InflightTable[myset.ClientId] = util.NewMessageTable()
							// callback仕込めるんだよなー。QoS1なら使わなくてもいいかなー。とかおもったり

							tbl = self.InflightTable[myset.ClientId]
						}

						id := tbl.NewId()
						x.PacketIdentifier = id
						x.Opaque = p
						tbl.Register2(id, x, 1, x)

						if err != nil {
							continue
						}
						mng[myset.ClientId] = x
						conn.WriteMessageQueue(x)
					}
					log.Debug("  all fisrt delivery finished.")

					wg.Done()
				}(msg, targets, p, mng)

				wg.Wait()
				close(p)
				close(mchan)
				close(term)
				mng = nil
				log.Debug("  okay, cleanup qos1 sending thread.")
			}(msg, targets)
			return
		}
	}

	// Publishで受け取ったMessageIdのやつのCountをとっておく
	// で、Pubackが帰ってきたらrefcountを下げて0になったらMessageを消す
	//log.Debug("TopicName: %s %s", m.TopicName, m.Payload)
	targets := self.TopicMatcher.Match(msg.TopicName)
	if msg.TopicName[0:1] == "#" {
		// TODO:  [MQTT-4.7.2-1] The Server MUST NOT match Topic Filters starting with a wildcard character
		// (# or +) with Topic Names beginning with a $ character
	}

	// list つくってからとって、だとタイミング的に居ない奴も出てくるんだよな。マジカオス
	// ここで必要なのは, connection(clientId), subscribed qosがわかればあとは投げるだけ
	// なんで、Qlobberがかえすのはであるべきなんだけど。これすっげー消しづらいのよね・・・
	// {
	//    Connection: Connection or client id
	//    QoS:
	// }
	// いやまぁエラーハンドリングちゃんとやってれば問題ない。
	// client idのほうがベターだな。Connectionを無駄に参照つけると後が辛い
	dp := make(map[string]bool)
	count := 0

	for i := range targets {
		var cn Connection
		var ok error

		myset := targets[i].(*SubscribeSet)
		clientId := myset.ClientId
		//clientId := targets[i].(string)

		// NOTE (from interoperability/client_test.py):
		//
		//   overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
		//   the server may send back one message with the highest QoS of any matching subscription, or one message for
		//   each subscription with a matching QoS.
		//
		// Currently, We choose one message for each subscription with a matching QoS.
		//
		if _, ok := dp[clientId]; ok {
			continue
		}
		dp[clientId] = true

		cn, ok = self.GetConnectionByClientId(clientId)
		if ok != nil {
			// どちらかというとClientが悪いと思うよ!
			// リスト拾った瞬間にはいたけど、その後いなくなったんだから配送リストからこれらは無視すべき
			log.Info("(can't fetch %s. already disconnected, or unsubscribed?)", clientId)
			continue
		}

		if cn.IsBridge() && clientId == client_id {
			// Don't send message to same bridge
			continue
		}

		var x *codec.PublishMessage

		if msg.QosLevel == 0 {
			// we don't need to copy message on QoS 0
			x = msg
		} else {
			x = codec.MustCopyPublishMessage(msg)
		}

		subscriberQos := myset.QoS
		// Downgrade QoS
		if subscriberQos < x.QosLevel {
			x.QosLevel = subscriberQos
		}

		if x.QosLevel > 0 {
			// TODO: ClientごとにInflightTableを持つ
			// engineのOutGoingTableなのはとりあえず、ということ
			id := self.OutGoingTable.NewId()
			x.PacketIdentifier = id
			if sender, ok := x.Opaque.(Connection); ok {
				// TODO: ここ(でなにをするつもりだったのか・・w)
				self.OutGoingTable.Register2(x.PacketIdentifier, x, len(targets), sender)
			}
		}

		cn.WriteMessageQueue(x)
		count++
	}

	Metrics.System.Broker.Messages.Sent.Add(int64(count))
}
Example #4
0
// TODO: どっかで綺麗にしたい
func NewMyConnection(conf *MyConfig) *MyConnection {
	if conf == nil {
		conf = &defaultConfig
	}

	c := &MyConnection{
		Events:           make(map[string]interface{}),
		Queue:            make(chan codec.Message, conf.QueueSize),
		OfflineQueue:     make([]codec.Message, 0),
		MaxOfflineQueue:  conf.OfflineQueueSize,
		InflightTable:    util.NewMessageTable(),
		SubscribeHistory: make(map[string]int),
		Mutex:            sync.RWMutex{},
		Qlobber:          util.NewQlobber(),
		SubscribedTopics: make(map[string]int),
		Last:             time.Now(),
		CleanSession:     true,
		Keepalive:        conf.Keepalive,
		State:            STATE_INIT,
		Closed:           make(chan bool),
		MaxMessageSize:   conf.MaxMessageSize,
		fch:              make(chan bool, 1),
	}

	c.t = time.AfterFunc(time.Second*300, func() {
		// NOTE: assume flush
		if c.State == STATE_CONNECTED {
			c.kickFlusher()
		}
	})

	c.logger = log.Global
	if conf.Logger != nil {
		c.logger = conf.Logger
	}

	if conf.WritePerSec > 0 {
		c.balancer = &util.Balancer{
			PerSec: conf.WritePerSec,
		}
	}
	if conf.MaxMessageSize > 0 {
		c.MaxMessageSize = conf.MaxMessageSize
	}

	c.Events["connected"] = func() {
		c.State = STATE_CONNECTED
	}

	c.Events["connack"] = func(result uint8) {
		if result == 0 {
			c.SetState(STATE_CONNECTED)
			if c.Reconnect {
				for key, qos := range c.SubscribeHistory {
					c.Subscribe(key, qos)
				}
			}

			//TODO: このアホっぽい実装はあとでちゃんとなおす。なおしたい
			if len(c.OfflineQueue) > 0 {
				c.Mutex.Lock()
				var targets []codec.Message
				for len(c.OfflineQueue) > 0 {
					targets = append(targets, c.OfflineQueue[0])
					c.OfflineQueue = c.OfflineQueue[1:]
				}
				c.Mutex.Unlock()

				for i := 0; i < len(targets); i++ {
					c.Queue <- targets[i]
				}
			}
			c.setupKicker()
		} else {
			c.State = STATE_CLOSED
		}
	}

	// for Wait API
	c.InflightTable.SetOnFinish(func(id uint16, message codec.Message, opaque interface{}) {
		if m, ok := message.(*codec.PublishMessage); ok {
			if m.QosLevel == 1 {
				if b, ok := opaque.(chan bool); ok {
					close(b)
				}
			} else if m.QosLevel == 2 {
				if b, ok := opaque.(chan bool); ok {
					close(b)
				}
			}
		}
	})

	// こっちに集約できるとClientが薄くなれる
	c.Events["publish"] = func(msg *codec.PublishMessage) {
		if msg.QosLevel == 1 {
			ack := codec.NewPubackMessage()
			ack.PacketIdentifier = msg.PacketIdentifier
			c.WriteMessageQueue(ack)
			c.logger.Debug("Send puback message to sender. [%s: %d]", c.GetId(), ack.PacketIdentifier)
		} else if msg.QosLevel == 2 {
			ack := codec.NewPubrecMessage()
			ack.PacketIdentifier = msg.PacketIdentifier
			c.WriteMessageQueue(ack)
			c.logger.Debug("Send pubrec message to sender. [%s: %d]", c.GetId(), ack.PacketIdentifier)
		}
	}

	c.Events["puback"] = func(messageId uint16) {
		c.InflightTable.Unref(messageId)
	}

	c.Events["pubrec"] = func(messageId uint16) {
		ack := codec.NewPubrelMessage()
		ack.PacketIdentifier = messageId
		c.Queue <- ack
	}

	c.Events["pubrel"] = func(messageId uint16) {
		ack := codec.NewPubcompMessage()
		ack.PacketIdentifier = messageId
		c.Queue <- ack

		c.InflightTable.Unref(ack.PacketIdentifier) // Unackknowleged
	}

	c.Events["pubcomp"] = func(messageId uint16) {
		c.InflightTable.Unref(messageId)
	}

	c.Events["unsuback"] = func(messageId uint16) {
		mm, err := c.InflightTable.Get(messageId)
		if err == nil {
			if v, ok := mm.(*codec.UnsubscribeMessage); ok {
				delete(c.SubscribeHistory, v.TopicName)
			}
		}

		c.InflightTable.Remove(messageId)
	}

	c.Events["subscribe"] = func(p *codec.SubscribeMessage) {
	}

	c.Events["suback"] = func(messageId uint16, grunted int) {
		c.InflightTable.Remove(messageId)
	}

	c.Events["unsubscribe"] = func(messageId uint16, granted int, payload []codec.SubscribePayload) {
		for i := 0; i < len(payload); i++ {
			delete(c.SubscribeHistory, payload[i].TopicPath)
		}
	}

	// これはコネクション渡したほうがいいんではないだろうか。
	c.Events["pingreq"] = func() {
		// TODO: check Ping count periodically, abort MyConnection when the counter exceeded.
		c.PingCounter++
	}

	c.Events["pingresp"] = func() {
		// nothing to do.
		c.PingCounter--
	}

	c.Events["disconnect"] = func() {
		// nothing to do ?
		c.State = STATE_CLOSED
	}

	c.Events["error"] = func(err error) {
		//fmt.Printf("Error: %s\n", err)
	}

	c.Events["connect"] = func(msg *codec.ConnectMessage) {
	}

	c.Events["parsed"] = func() {
	}

	// Write Queue
	go func() {
		for {
			select {
			case msg := <-c.Queue:
				state := c.GetState()
				if state == STATE_CONNECTED || state == STATE_CONNECTING {
					if msg.GetType() == codec.PACKET_TYPE_PUBLISH {
						sb := msg.(*codec.PublishMessage)
						if sb.QosLevel < 0 {
							c.logger.Error("QoS under zero. %s: %#v", c.Id, sb)
							break
						}

						if sb.QosLevel > 0 {
							id := c.InflightTable.NewId()
							sb.PacketIdentifier = id
							c.InflightTable.Register(id, sb, nil)
						}
					}

					e := c.writeMessage(msg)
					if e != nil {
						if v, ok := c.Events["error"].(func(error)); ok {
							v(e)
						}
					}
					c.invalidateTimer()
				} else {
					c.OfflineQueue = append(c.OfflineQueue, msg)
				}
			case <-c.Closed:
				if c.KeepLoop {
					time.Sleep(time.Second)
				} else {
					return
				}
			}
		}
	}()
	go c.flusher()
	return c
}