[MM-19914] Fix data races in WebSocketEvent (#13039)

* Make WebSocketEvent type immutable

* Update code to use updated immutable WebSocketEvent type

* Export WebSocketEvent fields and mark them as deprecated
Этот коммит содержится в:
Claudio Costa
2019-12-24 09:32:11 +01:00
коммит произвёл GitHub
родитель 9ab7bee0a6
Коммит 80dd2915db
16 изменённых файлов: 216 добавлений и 94 удалений

Просмотреть файл

@@ -1367,7 +1367,7 @@ func TestConvertChannelToPrivate(t *testing.T) {
for { for {
select { select {
case resp := <-WebSocketClient.EventChannel: case resp := <-WebSocketClient.EventChannel:
if resp.Event == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.Data["channel_id"].(string) == publicChannel2.Id { if resp.EventType() == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.GetData()["channel_id"].(string) == publicChannel2.Id {
eventHit = true eventHit = true
} }
case <-stop: case <-stop:
@@ -2426,7 +2426,7 @@ func TestRemoveChannelMember(t *testing.T) {
}) })
wsr := <-wsClient.EventChannel wsr := <-wsClient.EventChannel
require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.Event) require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.EventType())
// requirePost listens for websocket events and tries to find the post matching // requirePost listens for websocket events and tries to find the post matching
// the expected post's channel and message. // the expected post's channel and message.
@@ -2435,7 +2435,7 @@ func TestRemoveChannelMember(t *testing.T) {
for { for {
select { select {
case event := <-wsClient.EventChannel: case event := <-wsClient.EventChannel:
postData, ok := event.Data["post"] postData, ok := event.GetData()["post"]
if !ok { if !ok {
continue continue
} }

Просмотреть файл

@@ -315,7 +315,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
for { for {
select { select {
case resp := <-webSocketClient.EventChannel: case resp := <-webSocketClient.EventChannel:
if resp.Event == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.Data["plugin_statuses"].([]interface{})) == 0 { if resp.EventType() == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.GetData()["plugin_statuses"].([]interface{})) == 0 {
done <- true done <- true
return return
} }

Просмотреть файл

@@ -487,7 +487,7 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
for waiting { for waiting {
select { select {
case event := <-WebSocketClient.EventChannel: case event := <-WebSocketClient.EventChannel:
require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.Event, "should not have ephemeral message event") require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.EventType(), "should not have ephemeral message event")
case <-timeout: case <-timeout:
waiting = false waiting = false
} }
@@ -506,12 +506,12 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
for waiting { for waiting {
select { select {
case event := <-WebSocketClient.EventChannel: case event := <-WebSocketClient.EventChannel:
if event.Event != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE { if event.EventType() != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
// Ignore any other events // Ignore any other events
continue continue
} }
wpost := model.PostFromJson(strings.NewReader(event.Data["post"].(string))) wpost := model.PostFromJson(strings.NewReader(event.GetData()["post"].(string)))
acm, ok := wpost.Props[model.PROPS_ADD_CHANNEL_MEMBER].(map[string]interface{}) acm, ok := wpost.Props[model.PROPS_ADD_CHANNEL_MEMBER].(map[string]interface{})
require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props") require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props")

Просмотреть файл

@@ -259,6 +259,7 @@ func TestUpdatePreferencesWebsocket(t *testing.T) {
Name: model.NewId(), Name: model.NewId(),
}, },
} }
_, resp := th.Client.UpdatePreferences(userId, preferences) _, resp := th.Client.UpdatePreferences(userId, preferences)
CheckNoError(t, resp) CheckNoError(t, resp)
@@ -268,12 +269,12 @@ func TestUpdatePreferencesWebsocket(t *testing.T) {
for waiting { for waiting {
select { select {
case event := <-WebSocketClient.EventChannel: case event := <-WebSocketClient.EventChannel:
if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED { if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED {
// Ignore any other events // Ignore any other events
continue continue
} }
received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string))) received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string)))
require.NoError(t, err) require.NoError(t, err)
for i, p := range *preferences { for i, p := range *preferences {
@@ -376,12 +377,12 @@ func TestDeletePreferencesWebsocket(t *testing.T) {
for waiting { for waiting {
select { select {
case event := <-WebSocketClient.EventChannel: case event := <-WebSocketClient.EventChannel:
if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_DELETED { if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_DELETED {
// Ignore any other events // Ignore any other events
continue continue
} }
received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string))) received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string)))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

Просмотреть файл

@@ -323,11 +323,11 @@ func TestCreateUserWebSocketEvent(t *testing.T) {
for { for {
select { select {
case ev := <-userWSClient.EventChannel: case ev := <-userWSClient.EventChannel:
if ev.Event == model.WEBSOCKET_EVENT_NEW_USER { if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER {
userHasReceived = true userHasReceived = true
} }
case ev := <-guestWSClient.EventChannel: case ev := <-guestWSClient.EventChannel:
if ev.Event == model.WEBSOCKET_EVENT_NEW_USER { if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER {
guestHasReceived = true guestHasReceived = true
} }
case <-time.After(2 * time.Second): case <-time.After(2 * time.Second):
@@ -1675,7 +1675,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e
select { select {
case resp, ok := <-client.EventChannel: case resp, ok := <-client.EventChannel:
require.Truef(t, ok, "channel closed before receiving expected event %s", model.WEBSOCKET_EVENT_USER_UPDATED) require.Truef(t, ok, "channel closed before receiving expected event %s", model.WEBSOCKET_EVENT_USER_UPDATED)
if resp.Event == model.WEBSOCKET_EVENT_USER_UPDATED { if resp.EventType() == model.WEBSOCKET_EVENT_USER_UPDATED {
test(resp) test(resp)
return return
} }
@@ -1687,7 +1687,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e
func assertWebsocketEventUserUpdatedWithEmail(t *testing.T, client *model.WebSocketClient, email string) { func assertWebsocketEventUserUpdatedWithEmail(t *testing.T, client *model.WebSocketClient, email string) {
assertExpectedWebsocketEvent(t, client, model.WEBSOCKET_EVENT_USER_UPDATED, func(event *model.WebSocketEvent) { assertExpectedWebsocketEvent(t, client, model.WEBSOCKET_EVENT_USER_UPDATED, func(event *model.WebSocketEvent) {
eventUser, ok := event.Data["user"].(map[string]interface{}) eventUser, ok := event.GetData()["user"].(map[string]interface{})
require.True(t, ok, "expected user") require.True(t, ok, "expected user")
userEmail, ok := eventUser["email"].(string) userEmail, ok := eventUser["email"].(string)
require.Truef(t, ok, "expected email %s, but got nil", email) require.Truef(t, ok, "expected email %s, but got nil", email)

Просмотреть файл

@@ -115,7 +115,7 @@ func TestWebSocketEvent(t *testing.T) {
for { for {
select { select {
case resp := <-WebSocketClient.EventChannel: case resp := <-WebSocketClient.EventChannel:
if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { if resp.EventType() == model.WEBSOCKET_EVENT_TYPING && resp.GetData()["user_id"].(string) == "somerandomid" {
eventHit = true eventHit = true
} }
case <-stop: case <-stop:
@@ -140,7 +140,7 @@ func TestWebSocketEvent(t *testing.T) {
for { for {
select { select {
case resp := <-WebSocketClient.EventChannel: case resp := <-WebSocketClient.EventChannel:
if resp.Event == model.WEBSOCKET_EVENT_TYPING { if resp.EventType() == model.WEBSOCKET_EVENT_TYPING {
eventHit = true eventHit = true
} }
case <-stop: case <-stop:
@@ -180,7 +180,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) {
require.Equal(t, resp.Status, model.STATUS_OK, "should have responded OK to authentication challenge") require.Equal(t, resp.Status, model.STATUS_OK, "should have responded OK to authentication challenge")
wsr := <-WebSocketClient.EventChannel wsr := <-WebSocketClient.EventChannel
require.Equal(t, wsr.Event, model.WEBSOCKET_EVENT_HELLO, "missing hello") require.Equal(t, wsr.EventType(), model.WEBSOCKET_EVENT_HELLO, "missing hello")
stop := make(chan bool) stop := make(chan bool)
count := 0 count := 0
@@ -189,7 +189,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) {
for { for {
select { select {
case wsr := <-WebSocketClient.EventChannel: case wsr := <-WebSocketClient.EventChannel:
if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { if wsr != nil && wsr.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED {
count = count + 1 count = count + 1
} }
@@ -378,8 +378,8 @@ func TestWebSocketStatuses(t *testing.T) {
for { for {
select { select {
case resp := <-WebSocketClient.EventChannel: case resp := <-WebSocketClient.EventChannel:
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { if resp.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.GetData()["user_id"].(string) == th.BasicUser.Id {
status := resp.Data["status"].(string) status := resp.GetData()["status"].(string)
if status == model.STATUS_ONLINE { if status == model.STATUS_ONLINE {
onlineHit = true onlineHit = true
} else if status == model.STATUS_AWAY { } else if status == model.STATUS_AWAY {

Просмотреть файл

@@ -734,11 +734,9 @@ func (api *PluginAPI) KVList(page, perPage int) ([]string, *model.AppError) {
} }
func (api *PluginAPI) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) { func (api *PluginAPI) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
api.app.Publish(&model.WebSocketEvent{ ev := model.NewWebSocketEvent(fmt.Sprintf("custom_%v_%v", api.id, event), "", "", "", nil)
Event: fmt.Sprintf("custom_%v_%v", api.id, event), ev = ev.SetBroadcast(broadcast).SetData(payload)
Data: payload, api.app.Publish(ev)
Broadcast: broadcast,
})
} }
func (api *PluginAPI) HasPermissionTo(userId string, permission *model.Permission) bool { func (api *PluginAPI) HasPermissionTo(userId string, permission *model.Permission) bool {

Просмотреть файл

@@ -79,7 +79,7 @@ func (a *App) notifyPluginStatusesChanged() error {
// Notify any system admins. // Notify any system admins.
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil)
message.Add("plugin_statuses", pluginStatuses) message.Add("plugin_statuses", pluginStatuses)
message.Broadcast.ContainsSensitiveData = true message.GetBroadcast().ContainsSensitiveData = true
a.Publish(message) a.Publish(message)
return nil return nil

Просмотреть файл

@@ -1112,13 +1112,13 @@ func (a *App) sendUpdatedUserEvent(user model.User) {
a.SanitizeProfile(adminCopyOfUser, true) a.SanitizeProfile(adminCopyOfUser, true)
adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
adminMessage.Add("user", &adminCopyOfUser) adminMessage.Add("user", &adminCopyOfUser)
adminMessage.Broadcast.ContainsSensitiveData = true adminMessage.GetBroadcast().ContainsSensitiveData = true
a.Publish(adminMessage) a.Publish(adminMessage)
a.SanitizeProfile(&user, false) a.SanitizeProfile(&user, false)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", &user) message.Add("user", &user)
message.Broadcast.ContainsSanitizedData = true message.GetBroadcast().ContainsSanitizedData = true
a.Publish(message) a.Publish(message)
} }

Просмотреть файл

@@ -185,7 +185,7 @@ func (c *WebConn) writePump() {
"websocket.slow: dropping message", "websocket.slow: dropping message",
mlog.String("user_id", c.UserId), mlog.String("user_id", c.UserId),
mlog.String("type", msg.EventType()), mlog.String("type", msg.EventType()),
mlog.String("channel_id", evt.Broadcast.ChannelId), mlog.String("channel_id", evt.GetBroadcast().ChannelId),
) )
skipSend = true skipSend = true
} }
@@ -194,9 +194,7 @@ func (c *WebConn) writePump() {
if !skipSend { if !skipSend {
var msgBytes []byte var msgBytes []byte
if evtOk { if evtOk {
cpyEvt := &model.WebSocketEvent{} cpyEvt := evt.SetSequence(c.Sequence)
*cpyEvt = *evt
cpyEvt.Sequence = c.Sequence
msgBytes = []byte(cpyEvt.ToJson()) msgBytes = []byte(cpyEvt.ToJson())
c.Sequence++ c.Sequence++
} else { } else {
@@ -209,7 +207,7 @@ func (c *WebConn) writePump() {
"websocket.full", "websocket.full",
mlog.String("user_id", c.UserId), mlog.String("user_id", c.UserId),
mlog.String("type", msg.EventType()), mlog.String("type", msg.EventType()),
mlog.String("channel_id", evt.Broadcast.ChannelId), mlog.String("channel_id", evt.GetBroadcast().ChannelId),
mlog.Int("size", len(msg.ToJson())), mlog.Int("size", len(msg.ToJson())),
) )
} else { } else {
@@ -305,11 +303,11 @@ func (webCon *WebConn) shouldSendEventToGuest(msg *model.WebSocketEvent) bool {
var userId string var userId string
var canSee bool var canSee bool
switch msg.Event { switch msg.EventType() {
case model.WEBSOCKET_EVENT_USER_UPDATED: case model.WEBSOCKET_EVENT_USER_UPDATED:
userId = msg.Data["user"].(*model.User).Id userId = msg.GetData()["user"].(*model.User).Id
case model.WEBSOCKET_EVENT_NEW_USER: case model.WEBSOCKET_EVENT_NEW_USER:
userId = msg.Data["user_id"].(string) userId = msg.GetData()["user_id"].(string)
default: default:
return true return true
} }
@@ -332,7 +330,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
// If the event contains sanitized data, only send to users that don't have permission to // If the event contains sanitized data, only send to users that don't have permission to
// see sensitive data. Prevents admin clients from receiving events with bad data // see sensitive data. Prevents admin clients from receiving events with bad data
var hasReadPrivateDataPermission *bool var hasReadPrivateDataPermission *bool
if msg.Broadcast.ContainsSanitizedData { if msg.GetBroadcast().ContainsSanitizedData {
hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id)) hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
if *hasReadPrivateDataPermission { if *hasReadPrivateDataPermission {
@@ -341,7 +339,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
} }
// If the event contains sensitive data, only send to users with permission to see it // If the event contains sensitive data, only send to users with permission to see it
if msg.Broadcast.ContainsSensitiveData { if msg.GetBroadcast().ContainsSensitiveData {
if hasReadPrivateDataPermission == nil { if hasReadPrivateDataPermission == nil {
hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id)) hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
} }
@@ -352,19 +350,19 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
} }
// If the event is destined to a specific user // If the event is destined to a specific user
if len(msg.Broadcast.UserId) > 0 { if len(msg.GetBroadcast().UserId) > 0 {
return webCon.UserId == msg.Broadcast.UserId return webCon.UserId == msg.GetBroadcast().UserId
} }
// if the user is omitted don't send the message // if the user is omitted don't send the message
if len(msg.Broadcast.OmitUsers) > 0 { if len(msg.GetBroadcast().OmitUsers) > 0 {
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok { if _, ok := msg.GetBroadcast().OmitUsers[webCon.UserId]; ok {
return false return false
} }
} }
// Only report events to users who are in the channel for the event // Only report events to users who are in the channel for the event
if len(msg.Broadcast.ChannelId) > 0 { if len(msg.GetBroadcast().ChannelId) > 0 {
if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME { if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME {
webCon.AllChannelMembers = nil webCon.AllChannelMembers = nil
webCon.LastAllChannelMembersTime = 0 webCon.LastAllChannelMembersTime = 0
@@ -380,15 +378,15 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
webCon.LastAllChannelMembersTime = model.GetMillis() webCon.LastAllChannelMembersTime = model.GetMillis()
} }
if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok { if _, ok := webCon.AllChannelMembers[msg.GetBroadcast().ChannelId]; ok {
return true return true
} }
return false return false
} }
// Only report events to users who are in the team for the event // Only report events to users who are in the team for the event
if len(msg.Broadcast.TeamId) > 0 { if len(msg.GetBroadcast().TeamId) > 0 {
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId) return webCon.IsMemberOfTeam(msg.GetBroadcast().TeamId)
} }
if webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" { if webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" {

Просмотреть файл

@@ -72,9 +72,9 @@ func TestWebConnShouldSendEvent(t *testing.T) {
// needs more cases to get full coverage // needs more cases to get full coverage
} }
event := &model.WebSocketEvent{Event: "some_event"} event := model.NewWebSocketEvent("some_event", "", "", "", nil)
for _, c := range cases { for _, c := range cases {
event.Broadcast = c.Broadcast event = event.SetBroadcast(c.Broadcast)
assert.Equal(t, c.User1Expected, basicUserWc.ShouldSendEvent(event), c.Description) assert.Equal(t, c.User1Expected, basicUserWc.ShouldSendEvent(event), c.Description)
assert.Equal(t, c.User2Expected, basicUser2Wc.ShouldSendEvent(event), c.Description) assert.Equal(t, c.User2Expected, basicUser2Wc.ShouldSendEvent(event), c.Description)
assert.Equal(t, c.AdminExpected, adminUserWc.ShouldSendEvent(event), c.Description) assert.Equal(t, c.AdminExpected, adminUserWc.ShouldSendEvent(event), c.Description)

Просмотреть файл

@@ -164,7 +164,7 @@ func (a *App) HubUnregister(webConn *WebConn) {
func (a *App) Publish(message *model.WebSocketEvent) { func (a *App) Publish(message *model.WebSocketEvent) {
if metrics := a.Metrics; metrics != nil { if metrics := a.Metrics; metrics != nil {
metrics.IncrementWebsocketEvent(message.Event) metrics.IncrementWebsocketEvent(message.EventType())
} }
a.PublishSkipClusterSend(message) a.PublishSkipClusterSend(message)
@@ -176,11 +176,11 @@ func (a *App) Publish(message *model.WebSocketEvent) {
Data: message.ToJson(), Data: message.ToJson(),
} }
if message.Event == model.WEBSOCKET_EVENT_POSTED || if message.EventType() == model.WEBSOCKET_EVENT_POSTED ||
message.Event == model.WEBSOCKET_EVENT_POST_EDITED || message.EventType() == model.WEBSOCKET_EVENT_POST_EDITED ||
message.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED || message.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED ||
message.Event == model.WEBSOCKET_EVENT_GROUP_ADDED || message.EventType() == model.WEBSOCKET_EVENT_GROUP_ADDED ||
message.Event == model.WEBSOCKET_EVENT_ADDED_TO_TEAM { message.EventType() == model.WEBSOCKET_EVENT_ADDED_TO_TEAM {
cm.SendType = model.CLUSTER_SEND_RELIABLE cm.SendType = model.CLUSTER_SEND_RELIABLE
} }
@@ -189,8 +189,8 @@ func (a *App) Publish(message *model.WebSocketEvent) {
} }
func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) { func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) {
if message.Broadcast.UserId != "" { if message.GetBroadcast().UserId != "" {
hub := a.GetHubForUserId(message.Broadcast.UserId) hub := a.GetHubForUserId(message.GetBroadcast().UserId)
if hub != nil { if hub != nil {
hub.Broadcast(message) hub.Broadcast(message)
} }
@@ -485,10 +485,10 @@ func (h *Hub) Start() {
} }
case msg := <-h.broadcast: case msg := <-h.broadcast:
candidates := connections.All() candidates := connections.All()
if msg.Broadcast.UserId != "" { if msg.GetBroadcast().UserId != "" {
candidates = connections.ForUser(msg.Broadcast.UserId) candidates = connections.ForUser(msg.GetBroadcast().UserId)
} }
msg.PrecomputeJSON() msg = msg.PrecomputeJSON()
for _, webCon := range candidates { for _, webCon := range candidates {
if webCon.ShouldSendEvent(msg) { if webCon.ShouldSendEvent(msg) {
select { select {

Просмотреть файл

@@ -91,7 +91,7 @@ func TestHubStopRaceCondition(t *testing.T) {
hub.UpdateActivity("userId", "sessionToken", 0) hub.UpdateActivity("userId", "sessionToken", 0)
for i := 0; i <= BROADCAST_QUEUE_SIZE; i++ { for i := 0; i <= BROADCAST_QUEUE_SIZE; i++ {
hub.Broadcast(&model.WebSocketEvent{}) hub.Broadcast(model.NewWebSocketEvent("", "", "", "", nil))
} }
hub.InvalidateUser("userId") hub.InvalidateUser("userId")

Просмотреть файл

@@ -4,6 +4,7 @@
package model package model
import ( import (
"bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"time" "time"
@@ -121,9 +122,9 @@ func (wsc *WebSocketClient) Listen() {
return return
} }
var event WebSocketEvent event := WebSocketEventFromJson(bytes.NewReader(rawMsg))
if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { if event.IsValid() {
wsc.EventChannel <- &event wsc.EventChannel <- event
continue continue
} }

Просмотреть файл

@@ -76,30 +76,41 @@ type precomputedWebSocketEventJSON struct {
Broadcast json.RawMessage Broadcast json.RawMessage
} }
type WebSocketEvent struct { // webSocketEventJSON mirrors WebSocketEvent to make some of its unexported fields serializable
type webSocketEventJSON struct {
Event string `json:"event"` Event string `json:"event"`
Data map[string]interface{} `json:"data"` Data map[string]interface{} `json:"data"`
Broadcast *WebsocketBroadcast `json:"broadcast"` Broadcast *WebsocketBroadcast `json:"broadcast"`
Sequence int64 `json:"seq"` Sequence int64 `json:"seq"`
}
// **NOTE**: Direct access to WebSocketEvent fields is deprecated. They will be
// made unexported in next major version release. Provided getter functions should be used instead.
type WebSocketEvent struct {
Event string // Deprecated: use EventType()
Data map[string]interface{} // Deprecated: use GetData()
Broadcast *WebsocketBroadcast // Deprecated: use GetBroadcast()
Sequence int64 // Deprecated: use GetSequence()
precomputedJSON *precomputedWebSocketEventJSON precomputedJSON *precomputedWebSocketEventJSON
} }
// PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence. // PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence.
// This makes ToJson much more efficient when sending the same event to multiple connections. // This makes ToJson much more efficient when sending the same event to multiple connections.
func (m *WebSocketEvent) PrecomputeJSON() { func (ev *WebSocketEvent) PrecomputeJSON() *WebSocketEvent {
event, _ := json.Marshal(m.Event) copy := ev.Copy()
data, _ := json.Marshal(m.Data) event, _ := json.Marshal(copy.Event)
broadcast, _ := json.Marshal(m.Broadcast) data, _ := json.Marshal(copy.Data)
m.precomputedJSON = &precomputedWebSocketEventJSON{ broadcast, _ := json.Marshal(copy.Broadcast)
copy.precomputedJSON = &precomputedWebSocketEventJSON{
Event: json.RawMessage(event), Event: json.RawMessage(event),
Data: json.RawMessage(data), Data: json.RawMessage(data),
Broadcast: json.RawMessage(broadcast), Broadcast: json.RawMessage(broadcast),
} }
return copy
} }
func (m *WebSocketEvent) Add(key string, value interface{}) { func (ev *WebSocketEvent) Add(key string, value interface{}) {
m.Data[key] = value ev.Data[key] = value
} }
func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent {
@@ -107,26 +118,85 @@ func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[st
Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}}
} }
func (o *WebSocketEvent) IsValid() bool { func (ev *WebSocketEvent) Copy() *WebSocketEvent {
return o.Event != "" copy := &WebSocketEvent{
} Event: ev.Event,
Data: ev.Data,
func (o *WebSocketEvent) EventType() string { Broadcast: ev.Broadcast,
return o.Event Sequence: ev.Sequence,
} precomputedJSON: ev.precomputedJSON,
func (o *WebSocketEvent) ToJson() string {
if o.precomputedJSON != nil {
return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, o.precomputedJSON.Event, o.precomputedJSON.Data, o.precomputedJSON.Broadcast, o.Sequence)
} }
b, _ := json.Marshal(o) return copy
}
func (ev *WebSocketEvent) GetData() map[string]interface{} {
return ev.Data
}
func (ev *WebSocketEvent) GetBroadcast() *WebsocketBroadcast {
return ev.Broadcast
}
func (ev *WebSocketEvent) GetSequence() int64 {
return ev.Sequence
}
func (ev *WebSocketEvent) SetEvent(event string) *WebSocketEvent {
copy := ev.Copy()
copy.Event = event
return copy
}
func (ev *WebSocketEvent) SetData(data map[string]interface{}) *WebSocketEvent {
copy := ev.Copy()
copy.Data = data
return copy
}
func (ev *WebSocketEvent) SetBroadcast(broadcast *WebsocketBroadcast) *WebSocketEvent {
copy := ev.Copy()
copy.Broadcast = broadcast
return copy
}
func (ev *WebSocketEvent) SetSequence(seq int64) *WebSocketEvent {
copy := ev.Copy()
copy.Sequence = seq
return copy
}
func (ev *WebSocketEvent) IsValid() bool {
return ev.Event != ""
}
func (ev *WebSocketEvent) EventType() string {
return ev.Event
}
func (ev *WebSocketEvent) ToJson() string {
if ev.precomputedJSON != nil {
return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.Sequence)
}
b, _ := json.Marshal(webSocketEventJSON{
ev.Event,
ev.Data,
ev.Broadcast,
ev.Sequence,
})
return string(b) return string(b)
} }
func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
var o *WebSocketEvent var ev WebSocketEvent
json.NewDecoder(data).Decode(&o) var o webSocketEventJSON
return o if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
}
ev.Event = o.Event
ev.Data = o.Data
ev.Broadcast = o.Broadcast
ev.Sequence = o.Sequence
return &ev
} }
type WebSocketResponse struct { type WebSocketResponse struct {

Просмотреть файл

@@ -17,14 +17,68 @@ func TestWebSocketEvent(t *testing.T) {
json := m.ToJson() json := m.ToJson()
result := WebSocketEventFromJson(strings.NewReader(json)) result := WebSocketEventFromJson(strings.NewReader(json))
badresult := WebSocketEventFromJson(strings.NewReader("junk"))
require.Nil(t, badresult, "should not have parsed")
require.True(t, m.IsValid(), "should be valid") require.True(t, m.IsValid(), "should be valid")
require.Equal(t, m.GetBroadcast().TeamId, result.GetBroadcast().TeamId, "Ids do not match")
require.Equal(t, m.GetData()["RootId"], result.GetData()["RootId"], "Ids do not match")
}
require.Equal(t, m.Broadcast.TeamId, result.Broadcast.TeamId, "Ids do not match") func TestWebSocketEventImmutable(t *testing.T) {
m := NewWebSocketEvent("some_event", NewId(), NewId(), NewId(), nil)
require.Equal(t, m.Data["RootId"], result.Data["RootId"], "Ids do not match") new := m.SetEvent("new_event")
if new == m {
require.Fail(t, "pointers should not be the same")
}
require.NotEqual(t, m.Event, new.Event)
require.Equal(t, new.Event, "new_event")
require.Equal(t, new.Event, new.EventType())
new = m.SetSequence(45)
if new == m {
require.Fail(t, "pointers should not be the same")
}
require.NotEqual(t, m.Sequence, new.Sequence)
require.Equal(t, new.Sequence, int64(45))
require.Equal(t, new.Sequence, new.GetSequence())
broadcast := &WebsocketBroadcast{}
new = m.SetBroadcast(broadcast)
if new == m {
require.Fail(t, "pointers should not be the same")
}
require.NotEqual(t, m.Broadcast, new.Broadcast)
require.Equal(t, new.Broadcast, broadcast)
require.Equal(t, new.Broadcast, new.GetBroadcast())
data := map[string]interface{}{
"key": "val",
"key2": "val2",
}
new = m.SetData(data)
if new == m {
require.Fail(t, "pointers should not be the same")
}
require.NotEqual(t, m, new)
require.Equal(t, new.Data, data)
require.Equal(t, new.Data, new.GetData())
copy := m.Copy()
if copy == m {
require.Fail(t, "pointers should not be the same")
}
require.Equal(t, m, copy)
}
func TestWebSocketEventFromJson(t *testing.T) {
ev := WebSocketEventFromJson(strings.NewReader("junk"))
require.Nil(t, ev, "should not have parsed")
data := `{"event": "test", "data": {"key": "val"}, "seq": 45, "broadcast": {"user_id": "userid"}}`
ev = WebSocketEventFromJson(strings.NewReader(data))
require.NotNil(t, ev, "should have parsed")
require.Equal(t, ev.Event, "test")
require.Equal(t, ev.Sequence, int64(45))
require.Equal(t, ev.Data, map[string]interface{}{"key": "val"})
require.Equal(t, ev.Broadcast, &WebsocketBroadcast{UserId: "userid"})
} }
func TestWebSocketResponse(t *testing.T) { func TestWebSocketResponse(t *testing.T) {
@@ -46,7 +100,7 @@ func TestWebSocketResponse(t *testing.T) {
func TestWebSocketEvent_PrecomputeJSON(t *testing.T) { func TestWebSocketEvent_PrecomputeJSON(t *testing.T) {
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil) event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
event.Sequence = 7 event = event.SetSequence(7)
before := event.ToJson() before := event.ToJson()
event.PrecomputeJSON() event.PrecomputeJSON()
@@ -60,7 +114,7 @@ var stringSink string
func BenchmarkWebSocketEvent_ToJson(b *testing.B) { func BenchmarkWebSocketEvent_ToJson(b *testing.B) {
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil) event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
event.Data[NewId()] = NewId() event.GetData()[NewId()] = NewId()
} }
b.Run("SerializedNTimes", func(b *testing.B) { b.Run("SerializedNTimes", func(b *testing.B) {