MM-28370: Fix send on closed channel (#15508)

Automatic Merge
Этот коммит содержится в:
Agniva De Sarker
2020-09-25 09:23:43 +05:30
коммит произвёл GitHub
родитель 9449d0e302
Коммит d8622d1b07
3 изменённых файлов: 80 добавлений и 9 удалений

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

@@ -34,6 +34,7 @@ type PushNotificationsHub struct {
sema chan struct{}
stopChan chan struct{}
wg *sync.WaitGroup
semaWg *sync.WaitGroup
buffer int
}
@@ -142,7 +143,8 @@ func (a *App) sendPushNotification(notification *PostNotification, user *model.U
channelName := notification.GetChannelName(nameFormat, user.Id)
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
select {
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
notificationType: notificationTypeMessage,
post: post,
user: user,
@@ -152,6 +154,9 @@ func (a *App) sendPushNotification(notification *PostNotification, user *model.U
explicitMention: explicitMention,
channelWideMention: channelWideMention,
replyToThreadType: replyToThreadType,
}:
case <-a.Srv().PushNotificationsHub.stopChan:
return
}
}
@@ -215,11 +220,15 @@ func (a *App) clearPushNotificationSync(currentSessionId, userId, channelId stri
}
func (a *App) clearPushNotification(currentSessionId, userId, channelId string) {
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
select {
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
notificationType: notificationTypeClear,
currentSessionId: currentSessionId,
userId: userId,
channelId: channelId,
}:
case <-a.Srv().PushNotificationsHub.stopChan:
return
}
}
@@ -242,9 +251,13 @@ func (a *App) updateMobileAppBadgeSync(userId string) *model.AppError {
}
func (a *App) UpdateMobileAppBadge(userId string) {
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
select {
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
notificationType: notificationTypeUpdateBadge,
userId: userId,
}:
case <-a.Srv().PushNotificationsHub.stopChan:
return
}
}
@@ -258,6 +271,7 @@ func (s *Server) createPushNotificationsHub() {
notificationsChan: make(chan PushNotification, buffer),
app: fakeApp,
wg: new(sync.WaitGroup),
semaWg: new(sync.WaitGroup),
sema: make(chan struct{}, runtime.NumCPU()*8), // numCPU * 8 is a good amount of concurrency.
stopChan: make(chan struct{}),
buffer: buffer,
@@ -267,11 +281,19 @@ func (s *Server) createPushNotificationsHub() {
}
func (hub *PushNotificationsHub) start() {
hub.wg.Add(1)
defer hub.wg.Done()
for {
select {
case notification := <-hub.notificationsChan:
// We just ignore dummy notifications.
// These are used to pump out any remaining notifications
// before we stop the hub.
if notification.notificationType == notificationTypeDummy {
continue
}
// Adding to the waitgroup first.
hub.wg.Add(1)
hub.semaWg.Add(1)
// Get token.
hub.sema <- struct{}{}
go func(notification PushNotification) {
@@ -279,7 +301,7 @@ func (hub *PushNotificationsHub) start() {
// Release token.
<-hub.sema
// Now marking waitgroup as done.
hub.wg.Done()
hub.semaWg.Done()
}()
var err *model.AppError
@@ -299,8 +321,6 @@ func (hub *PushNotificationsHub) start() {
)
case notificationTypeUpdateBadge:
err = hub.app.updateMobileAppBadgeSync(notification.userId)
case notificationTypeDummy:
return
default:
mlog.Error("Invalid notification type", mlog.String("notification_type", string(notification.notificationType)))
}
@@ -322,9 +342,14 @@ func (hub *PushNotificationsHub) stop() {
notificationType: notificationTypeDummy,
}
}
hub.stopChan <- struct{}{}
close(hub.notificationsChan)
close(hub.stopChan)
// We need to wait for the outer for loop to exit first.
// We cannot just send struct{}{} to stopChan because there are
// other listeners to the channel. And sending just once
// will cause a race.
hub.wg.Wait()
// And then we wait for the semaphore to finish.
hub.semaWg.Wait()
}
func (s *Server) StopPushNotificationsHubWorkers() {

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

@@ -11,8 +11,10 @@ import (
"testing"
"time"
"github.com/mattermost/mattermost-server/v5/config"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v5/testlib"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -1362,6 +1364,49 @@ func TestAllPushNotifications(t *testing.T) {
assert.Equal(t, 6, numUpdateBadges)
}
func TestPushNotificationRace(t *testing.T) {
memoryStore, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{
IgnoreEnvironmentOverrides: true,
})
require.NoError(t, err, "failed to initialize memory store")
defer memoryStore.Close()
mockStore := testlib.GetMockStoreForSetupFunctions()
mockPreferenceStore := mocks.PreferenceStore{}
mockPreferenceStore.On("Get",
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string")).
Return(&model.Preference{Value: "test"}, nil)
mockStore.On("Preference").Return(&mockPreferenceStore)
s := &Server{
configStore: memoryStore,
Store: mockStore,
}
app := New(ServerConnector(s))
require.NotPanics(t, func() {
s.createPushNotificationsHub()
s.StopPushNotificationsHubWorkers()
// Now we start sending messages after the PN hub is shut down.
// We test all 3 notification types.
app.clearPushNotification("currentSessionId", "userId", "channelId")
app.UpdateMobileAppBadge("userId")
notification := &PostNotification{
Post: &model.Post{},
Channel: &model.Channel{},
ProfileMap: map[string]*model.User{
"userId": {},
},
Sender: &model.User{},
}
app.sendPushNotification(notification, &model.User{}, true, false, model.COMMENTS_NOTIFY_ANY)
})
}
// Run it with | grep -v '{"level"' to prevent spamming the console.
func BenchmarkPushNotificationThroughput(b *testing.B) {
th := SetupWithStoreMock(b)

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

@@ -716,6 +716,7 @@ func (s *Server) Shutdown() error {
s.StopHTTPServer()
s.stopLocalModeServer()
// Push notification hub needs to be shutdown after HTTP server
// to prevent stray requests from generating a push notification after it's shut down.
s.StopPushNotificationsHubWorkers()
s.WaitForGoroutines()