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