github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/app/web_hub.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"fmt"
     8  	"hash/fnv"
     9  	"runtime"
    10  	"runtime/debug"
    11  	"strconv"
    12  	"strings"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/mattermost/mattermost-server/mlog"
    17  	"github.com/mattermost/mattermost-server/model"
    18  )
    19  
    20  const (
    21  	BROADCAST_QUEUE_SIZE = 4096
    22  	DEADLOCK_TICKER      = 15 * time.Second                  // check every 15 seconds
    23  	DEADLOCK_WARN        = (BROADCAST_QUEUE_SIZE * 99) / 100 // number of buffered messages before printing stack trace
    24  )
    25  
    26  type Hub struct {
    27  	// connectionCount should be kept first.
    28  	// See https://github.com/mattermost/mattermost-server/pull/7281
    29  	connectionCount int64
    30  	app             *App
    31  	connectionIndex int
    32  	register        chan *WebConn
    33  	unregister      chan *WebConn
    34  	broadcast       chan *model.WebSocketEvent
    35  	stop            chan struct{}
    36  	didStop         chan struct{}
    37  	invalidateUser  chan string
    38  	ExplicitStop    bool
    39  	goroutineId     int
    40  }
    41  
    42  func (a *App) NewWebHub() *Hub {
    43  	return &Hub{
    44  		app:            a,
    45  		register:       make(chan *WebConn, 1),
    46  		unregister:     make(chan *WebConn, 1),
    47  		broadcast:      make(chan *model.WebSocketEvent, BROADCAST_QUEUE_SIZE),
    48  		stop:           make(chan struct{}),
    49  		didStop:        make(chan struct{}),
    50  		invalidateUser: make(chan string),
    51  		ExplicitStop:   false,
    52  	}
    53  }
    54  
    55  func (a *App) TotalWebsocketConnections() int {
    56  	count := int64(0)
    57  	for _, hub := range a.Hubs {
    58  		count = count + atomic.LoadInt64(&hub.connectionCount)
    59  	}
    60  
    61  	return int(count)
    62  }
    63  
    64  func (a *App) HubStart() {
    65  	// Total number of hubs is twice the number of CPUs.
    66  	numberOfHubs := runtime.NumCPU() * 2
    67  	mlog.Info(fmt.Sprintf("Starting %v websocket hubs", numberOfHubs))
    68  
    69  	a.Hubs = make([]*Hub, numberOfHubs)
    70  	a.HubsStopCheckingForDeadlock = make(chan bool, 1)
    71  
    72  	for i := 0; i < len(a.Hubs); i++ {
    73  		a.Hubs[i] = a.NewWebHub()
    74  		a.Hubs[i].connectionIndex = i
    75  		a.Hubs[i].Start()
    76  	}
    77  
    78  	go func() {
    79  		ticker := time.NewTicker(DEADLOCK_TICKER)
    80  
    81  		defer func() {
    82  			ticker.Stop()
    83  		}()
    84  
    85  		for {
    86  			select {
    87  			case <-ticker.C:
    88  				for _, hub := range a.Hubs {
    89  					if len(hub.broadcast) >= DEADLOCK_WARN {
    90  						mlog.Error(fmt.Sprintf("Hub processing might be deadlock on hub %v goroutine %v with %v events in the buffer", hub.connectionIndex, hub.goroutineId, len(hub.broadcast)))
    91  						buf := make([]byte, 1<<16)
    92  						runtime.Stack(buf, true)
    93  						output := fmt.Sprintf("%s", buf)
    94  						splits := strings.Split(output, "goroutine ")
    95  
    96  						for _, part := range splits {
    97  							if strings.Contains(part, fmt.Sprintf("%v", hub.goroutineId)) {
    98  								mlog.Error(fmt.Sprintf("Trace for possible deadlock goroutine %v", part))
    99  							}
   100  						}
   101  					}
   102  				}
   103  
   104  			case <-a.HubsStopCheckingForDeadlock:
   105  				return
   106  			}
   107  		}
   108  	}()
   109  }
   110  
   111  func (a *App) HubStop() {
   112  	mlog.Info("stopping websocket hub connections")
   113  
   114  	select {
   115  	case a.HubsStopCheckingForDeadlock <- true:
   116  	default:
   117  		mlog.Warn("We appear to have already sent the stop checking for deadlocks command")
   118  	}
   119  
   120  	for _, hub := range a.Hubs {
   121  		hub.Stop()
   122  	}
   123  
   124  	a.Hubs = []*Hub{}
   125  }
   126  
   127  func (a *App) GetHubForUserId(userId string) *Hub {
   128  	if len(a.Hubs) == 0 {
   129  		return nil
   130  	}
   131  
   132  	hash := fnv.New32a()
   133  	hash.Write([]byte(userId))
   134  	index := hash.Sum32() % uint32(len(a.Hubs))
   135  	return a.Hubs[index]
   136  }
   137  
   138  func (a *App) HubRegister(webConn *WebConn) {
   139  	hub := a.GetHubForUserId(webConn.UserId)
   140  	if hub != nil {
   141  		hub.Register(webConn)
   142  	}
   143  }
   144  
   145  func (a *App) HubUnregister(webConn *WebConn) {
   146  	hub := a.GetHubForUserId(webConn.UserId)
   147  	if hub != nil {
   148  		hub.Unregister(webConn)
   149  	}
   150  }
   151  
   152  func (a *App) Publish(message *model.WebSocketEvent) {
   153  	if metrics := a.Metrics; metrics != nil {
   154  		metrics.IncrementWebsocketEvent(message.Event)
   155  	}
   156  
   157  	a.PublishSkipClusterSend(message)
   158  
   159  	if a.Cluster != nil {
   160  		cm := &model.ClusterMessage{
   161  			Event:    model.CLUSTER_EVENT_PUBLISH,
   162  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   163  			Data:     message.ToJson(),
   164  		}
   165  
   166  		if message.Event == model.WEBSOCKET_EVENT_POSTED ||
   167  			message.Event == model.WEBSOCKET_EVENT_POST_EDITED ||
   168  			message.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED ||
   169  			message.Event == model.WEBSOCKET_EVENT_GROUP_ADDED ||
   170  			message.Event == model.WEBSOCKET_EVENT_ADDED_TO_TEAM {
   171  			cm.SendType = model.CLUSTER_SEND_RELIABLE
   172  		}
   173  
   174  		a.Cluster.SendClusterMessage(cm)
   175  	}
   176  }
   177  
   178  func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) {
   179  	if message.Broadcast.UserId != "" {
   180  		if len(a.Hubs) != 0 {
   181  			a.GetHubForUserId(message.Broadcast.UserId).Broadcast(message)
   182  		}
   183  	} else {
   184  		for _, hub := range a.Hubs {
   185  			hub.Broadcast(message)
   186  		}
   187  	}
   188  }
   189  
   190  func (a *App) InvalidateCacheForChannel(channel *model.Channel) {
   191  	a.InvalidateCacheForChannelSkipClusterSend(channel.Id)
   192  	a.InvalidateCacheForChannelByNameSkipClusterSend(channel.TeamId, channel.Name)
   193  
   194  	if a.Cluster != nil {
   195  		msg := &model.ClusterMessage{
   196  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL,
   197  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   198  			Data:     channel.Id,
   199  		}
   200  
   201  		a.Cluster.SendClusterMessage(msg)
   202  
   203  		nameMsg := &model.ClusterMessage{
   204  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME,
   205  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   206  			Props:    make(map[string]string),
   207  		}
   208  
   209  		nameMsg.Props["name"] = channel.Name
   210  		if channel.TeamId == "" {
   211  			nameMsg.Props["id"] = "dm"
   212  		} else {
   213  			nameMsg.Props["id"] = channel.TeamId
   214  		}
   215  
   216  		a.Cluster.SendClusterMessage(nameMsg)
   217  	}
   218  }
   219  
   220  func (a *App) InvalidateCacheForChannelSkipClusterSend(channelId string) {
   221  	a.Srv.Store.Channel().InvalidateChannel(channelId)
   222  }
   223  
   224  func (a *App) InvalidateCacheForChannelMembers(channelId string) {
   225  	a.InvalidateCacheForChannelMembersSkipClusterSend(channelId)
   226  
   227  	if a.Cluster != nil {
   228  		msg := &model.ClusterMessage{
   229  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS,
   230  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   231  			Data:     channelId,
   232  		}
   233  		a.Cluster.SendClusterMessage(msg)
   234  	}
   235  }
   236  
   237  func (a *App) InvalidateCacheForChannelMembersSkipClusterSend(channelId string) {
   238  	a.Srv.Store.User().InvalidateProfilesInChannelCache(channelId)
   239  	a.Srv.Store.Channel().InvalidateMemberCount(channelId)
   240  }
   241  
   242  func (a *App) InvalidateCacheForChannelMembersNotifyProps(channelId string) {
   243  	a.InvalidateCacheForChannelMembersNotifyPropsSkipClusterSend(channelId)
   244  
   245  	if a.Cluster != nil {
   246  		msg := &model.ClusterMessage{
   247  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS,
   248  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   249  			Data:     channelId,
   250  		}
   251  		a.Cluster.SendClusterMessage(msg)
   252  	}
   253  }
   254  
   255  func (a *App) InvalidateCacheForChannelMembersNotifyPropsSkipClusterSend(channelId string) {
   256  	a.Srv.Store.Channel().InvalidateCacheForChannelMembersNotifyProps(channelId)
   257  }
   258  
   259  func (a *App) InvalidateCacheForChannelByNameSkipClusterSend(teamId, name string) {
   260  	if teamId == "" {
   261  		teamId = "dm"
   262  	}
   263  
   264  	a.Srv.Store.Channel().InvalidateChannelByName(teamId, name)
   265  }
   266  
   267  func (a *App) InvalidateCacheForChannelPosts(channelId string) {
   268  	a.InvalidateCacheForChannelPostsSkipClusterSend(channelId)
   269  
   270  	if a.Cluster != nil {
   271  		msg := &model.ClusterMessage{
   272  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS,
   273  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   274  			Data:     channelId,
   275  		}
   276  		a.Cluster.SendClusterMessage(msg)
   277  	}
   278  }
   279  
   280  func (a *App) InvalidateCacheForChannelPostsSkipClusterSend(channelId string) {
   281  	a.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
   282  }
   283  
   284  func (a *App) InvalidateCacheForUser(userId string) {
   285  	a.InvalidateCacheForUserSkipClusterSend(userId)
   286  
   287  	if a.Cluster != nil {
   288  		msg := &model.ClusterMessage{
   289  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER,
   290  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   291  			Data:     userId,
   292  		}
   293  		a.Cluster.SendClusterMessage(msg)
   294  	}
   295  }
   296  
   297  func (a *App) InvalidateCacheForUserSkipClusterSend(userId string) {
   298  	a.Srv.Store.Channel().InvalidateAllChannelMembersForUser(userId)
   299  	a.Srv.Store.User().InvalidateProfilesInChannelCacheByUser(userId)
   300  	a.Srv.Store.User().InvalidatProfileCacheForUser(userId)
   301  
   302  	if len(a.Hubs) != 0 {
   303  		a.GetHubForUserId(userId).InvalidateUser(userId)
   304  	}
   305  }
   306  
   307  func (a *App) InvalidateCacheForWebhook(webhookId string) {
   308  	a.InvalidateCacheForWebhookSkipClusterSend(webhookId)
   309  
   310  	if a.Cluster != nil {
   311  		msg := &model.ClusterMessage{
   312  			Event:    model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK,
   313  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
   314  			Data:     webhookId,
   315  		}
   316  		a.Cluster.SendClusterMessage(msg)
   317  	}
   318  }
   319  
   320  func (a *App) InvalidateCacheForWebhookSkipClusterSend(webhookId string) {
   321  	a.Srv.Store.Webhook().InvalidateWebhookCache(webhookId)
   322  }
   323  
   324  func (a *App) InvalidateWebConnSessionCacheForUser(userId string) {
   325  	if len(a.Hubs) != 0 {
   326  		a.GetHubForUserId(userId).InvalidateUser(userId)
   327  	}
   328  }
   329  
   330  func (h *Hub) Register(webConn *WebConn) {
   331  	h.register <- webConn
   332  
   333  	if webConn.IsAuthenticated() {
   334  		webConn.SendHello()
   335  	}
   336  }
   337  
   338  func (h *Hub) Unregister(webConn *WebConn) {
   339  	select {
   340  	case h.unregister <- webConn:
   341  	case <-h.stop:
   342  	}
   343  }
   344  
   345  func (h *Hub) Broadcast(message *model.WebSocketEvent) {
   346  	if h != nil && h.broadcast != nil && message != nil {
   347  		h.broadcast <- message
   348  	}
   349  }
   350  
   351  func (h *Hub) InvalidateUser(userId string) {
   352  	h.invalidateUser <- userId
   353  }
   354  
   355  func getGoroutineId() int {
   356  	var buf [64]byte
   357  	n := runtime.Stack(buf[:], false)
   358  	idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
   359  	id, err := strconv.Atoi(idField)
   360  	if err != nil {
   361  		id = -1
   362  	}
   363  	return id
   364  }
   365  
   366  func (h *Hub) Stop() {
   367  	close(h.stop)
   368  	<-h.didStop
   369  }
   370  
   371  func (h *Hub) Start() {
   372  	var doStart func()
   373  	var doRecoverableStart func()
   374  	var doRecover func()
   375  
   376  	doStart = func() {
   377  		h.goroutineId = getGoroutineId()
   378  		mlog.Debug(fmt.Sprintf("Hub for index %v is starting with goroutine %v", h.connectionIndex, h.goroutineId))
   379  
   380  		connections := newHubConnectionIndex()
   381  
   382  		for {
   383  			select {
   384  			case webCon := <-h.register:
   385  				connections.Add(webCon)
   386  				atomic.StoreInt64(&h.connectionCount, int64(len(connections.All())))
   387  			case webCon := <-h.unregister:
   388  				connections.Remove(webCon)
   389  				atomic.StoreInt64(&h.connectionCount, int64(len(connections.All())))
   390  
   391  				if len(webCon.UserId) == 0 {
   392  					continue
   393  				}
   394  
   395  				if len(connections.ForUser(webCon.UserId)) == 0 {
   396  					h.app.Go(func() {
   397  						h.app.SetStatusOffline(webCon.UserId, false)
   398  					})
   399  				}
   400  			case userId := <-h.invalidateUser:
   401  				for _, webCon := range connections.ForUser(userId) {
   402  					webCon.InvalidateCache()
   403  				}
   404  			case msg := <-h.broadcast:
   405  				candidates := connections.All()
   406  				if msg.Broadcast.UserId != "" {
   407  					candidates = connections.ForUser(msg.Broadcast.UserId)
   408  				}
   409  				msg.PrecomputeJSON()
   410  				for _, webCon := range candidates {
   411  					if webCon.ShouldSendEvent(msg) {
   412  						select {
   413  						case webCon.Send <- msg:
   414  						default:
   415  							mlog.Error(fmt.Sprintf("webhub.broadcast: cannot send, closing websocket for userId=%v", webCon.UserId))
   416  							close(webCon.Send)
   417  							connections.Remove(webCon)
   418  						}
   419  					}
   420  				}
   421  			case <-h.stop:
   422  				userIds := make(map[string]bool)
   423  
   424  				for _, webCon := range connections.All() {
   425  					userIds[webCon.UserId] = true
   426  					webCon.Close()
   427  				}
   428  
   429  				for userId := range userIds {
   430  					h.app.SetStatusOffline(userId, false)
   431  				}
   432  
   433  				h.ExplicitStop = true
   434  				close(h.didStop)
   435  
   436  				return
   437  			}
   438  		}
   439  	}
   440  
   441  	doRecoverableStart = func() {
   442  		defer doRecover()
   443  		doStart()
   444  	}
   445  
   446  	doRecover = func() {
   447  		if !h.ExplicitStop {
   448  			if r := recover(); r != nil {
   449  				mlog.Error(fmt.Sprintf("Recovering from Hub panic. Panic was: %v", r))
   450  			} else {
   451  				mlog.Error("Webhub stopped unexpectedly. Recovering.")
   452  			}
   453  
   454  			mlog.Error(string(debug.Stack()))
   455  
   456  			go doRecoverableStart()
   457  		}
   458  	}
   459  
   460  	go doRecoverableStart()
   461  }
   462  
   463  type hubConnectionIndexIndexes struct {
   464  	connections         int
   465  	connectionsByUserId int
   466  }
   467  
   468  // hubConnectionIndex provides fast addition, removal, and iteration of web connections.
   469  type hubConnectionIndex struct {
   470  	connections         []*WebConn
   471  	connectionsByUserId map[string][]*WebConn
   472  	connectionIndexes   map[*WebConn]*hubConnectionIndexIndexes
   473  }
   474  
   475  func newHubConnectionIndex() *hubConnectionIndex {
   476  	return &hubConnectionIndex{
   477  		connections:         make([]*WebConn, 0, model.SESSION_CACHE_SIZE),
   478  		connectionsByUserId: make(map[string][]*WebConn),
   479  		connectionIndexes:   make(map[*WebConn]*hubConnectionIndexIndexes),
   480  	}
   481  }
   482  
   483  func (i *hubConnectionIndex) Add(wc *WebConn) {
   484  	i.connections = append(i.connections, wc)
   485  	i.connectionsByUserId[wc.UserId] = append(i.connectionsByUserId[wc.UserId], wc)
   486  	i.connectionIndexes[wc] = &hubConnectionIndexIndexes{
   487  		connections:         len(i.connections) - 1,
   488  		connectionsByUserId: len(i.connectionsByUserId[wc.UserId]) - 1,
   489  	}
   490  }
   491  
   492  func (i *hubConnectionIndex) Remove(wc *WebConn) {
   493  	indexes, ok := i.connectionIndexes[wc]
   494  	if !ok {
   495  		return
   496  	}
   497  
   498  	last := i.connections[len(i.connections)-1]
   499  	i.connections[indexes.connections] = last
   500  	i.connections = i.connections[:len(i.connections)-1]
   501  	i.connectionIndexes[last].connections = indexes.connections
   502  
   503  	userConnections := i.connectionsByUserId[wc.UserId]
   504  	last = userConnections[len(userConnections)-1]
   505  	userConnections[indexes.connectionsByUserId] = last
   506  	i.connectionsByUserId[wc.UserId] = userConnections[:len(userConnections)-1]
   507  	i.connectionIndexes[last].connectionsByUserId = indexes.connectionsByUserId
   508  
   509  	delete(i.connectionIndexes, wc)
   510  }
   511  
   512  func (i *hubConnectionIndex) ForUser(id string) []*WebConn {
   513  	return i.connectionsByUserId[id]
   514  }
   515  
   516  func (i *hubConnectionIndex) All() []*WebConn {
   517  	return i.connections
   518  }