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 }