коммит произвёл
GitHub
родитель
9449d0e302
Коммит
d8622d1b07
@@ -34,6 +34,7 @@ type PushNotificationsHub struct {
|
|||||||
sema chan struct{}
|
sema chan struct{}
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
|
semaWg *sync.WaitGroup
|
||||||
buffer int
|
buffer int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +143,8 @@ func (a *App) sendPushNotification(notification *PostNotification, user *model.U
|
|||||||
channelName := notification.GetChannelName(nameFormat, user.Id)
|
channelName := notification.GetChannelName(nameFormat, user.Id)
|
||||||
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
|
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
|
||||||
|
|
||||||
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
select {
|
||||||
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
||||||
notificationType: notificationTypeMessage,
|
notificationType: notificationTypeMessage,
|
||||||
post: post,
|
post: post,
|
||||||
user: user,
|
user: user,
|
||||||
@@ -152,6 +154,9 @@ func (a *App) sendPushNotification(notification *PostNotification, user *model.U
|
|||||||
explicitMention: explicitMention,
|
explicitMention: explicitMention,
|
||||||
channelWideMention: channelWideMention,
|
channelWideMention: channelWideMention,
|
||||||
replyToThreadType: replyToThreadType,
|
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) {
|
func (a *App) clearPushNotification(currentSessionId, userId, channelId string) {
|
||||||
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
select {
|
||||||
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
||||||
notificationType: notificationTypeClear,
|
notificationType: notificationTypeClear,
|
||||||
currentSessionId: currentSessionId,
|
currentSessionId: currentSessionId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
channelId: channelId,
|
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) {
|
func (a *App) UpdateMobileAppBadge(userId string) {
|
||||||
a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
select {
|
||||||
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
||||||
notificationType: notificationTypeUpdateBadge,
|
notificationType: notificationTypeUpdateBadge,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
}:
|
||||||
|
case <-a.Srv().PushNotificationsHub.stopChan:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +271,7 @@ func (s *Server) createPushNotificationsHub() {
|
|||||||
notificationsChan: make(chan PushNotification, buffer),
|
notificationsChan: make(chan PushNotification, buffer),
|
||||||
app: fakeApp,
|
app: fakeApp,
|
||||||
wg: new(sync.WaitGroup),
|
wg: new(sync.WaitGroup),
|
||||||
|
semaWg: new(sync.WaitGroup),
|
||||||
sema: make(chan struct{}, runtime.NumCPU()*8), // numCPU * 8 is a good amount of concurrency.
|
sema: make(chan struct{}, runtime.NumCPU()*8), // numCPU * 8 is a good amount of concurrency.
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
buffer: buffer,
|
buffer: buffer,
|
||||||
@@ -267,11 +281,19 @@ func (s *Server) createPushNotificationsHub() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hub *PushNotificationsHub) start() {
|
func (hub *PushNotificationsHub) start() {
|
||||||
|
hub.wg.Add(1)
|
||||||
|
defer hub.wg.Done()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case notification := <-hub.notificationsChan:
|
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.
|
// Adding to the waitgroup first.
|
||||||
hub.wg.Add(1)
|
hub.semaWg.Add(1)
|
||||||
// Get token.
|
// Get token.
|
||||||
hub.sema <- struct{}{}
|
hub.sema <- struct{}{}
|
||||||
go func(notification PushNotification) {
|
go func(notification PushNotification) {
|
||||||
@@ -279,7 +301,7 @@ func (hub *PushNotificationsHub) start() {
|
|||||||
// Release token.
|
// Release token.
|
||||||
<-hub.sema
|
<-hub.sema
|
||||||
// Now marking waitgroup as done.
|
// Now marking waitgroup as done.
|
||||||
hub.wg.Done()
|
hub.semaWg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var err *model.AppError
|
var err *model.AppError
|
||||||
@@ -299,8 +321,6 @@ func (hub *PushNotificationsHub) start() {
|
|||||||
)
|
)
|
||||||
case notificationTypeUpdateBadge:
|
case notificationTypeUpdateBadge:
|
||||||
err = hub.app.updateMobileAppBadgeSync(notification.userId)
|
err = hub.app.updateMobileAppBadgeSync(notification.userId)
|
||||||
case notificationTypeDummy:
|
|
||||||
return
|
|
||||||
default:
|
default:
|
||||||
mlog.Error("Invalid notification type", mlog.String("notification_type", string(notification.notificationType)))
|
mlog.Error("Invalid notification type", mlog.String("notification_type", string(notification.notificationType)))
|
||||||
}
|
}
|
||||||
@@ -322,9 +342,14 @@ func (hub *PushNotificationsHub) stop() {
|
|||||||
notificationType: notificationTypeDummy,
|
notificationType: notificationTypeDummy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hub.stopChan <- struct{}{}
|
close(hub.stopChan)
|
||||||
close(hub.notificationsChan)
|
// 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()
|
hub.wg.Wait()
|
||||||
|
// And then we wait for the semaphore to finish.
|
||||||
|
hub.semaWg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) StopPushNotificationsHubWorkers() {
|
func (s *Server) StopPushNotificationsHubWorkers() {
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/config"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/store/storetest/mocks"
|
"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/mattermost/mattermost-server/v5/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@@ -1362,6 +1364,49 @@ func TestAllPushNotifications(t *testing.T) {
|
|||||||
assert.Equal(t, 6, numUpdateBadges)
|
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.
|
// Run it with | grep -v '{"level"' to prevent spamming the console.
|
||||||
func BenchmarkPushNotificationThroughput(b *testing.B) {
|
func BenchmarkPushNotificationThroughput(b *testing.B) {
|
||||||
th := SetupWithStoreMock(b)
|
th := SetupWithStoreMock(b)
|
||||||
|
|||||||
@@ -716,6 +716,7 @@ func (s *Server) Shutdown() error {
|
|||||||
s.StopHTTPServer()
|
s.StopHTTPServer()
|
||||||
s.stopLocalModeServer()
|
s.stopLocalModeServer()
|
||||||
// Push notification hub needs to be shutdown after HTTP server
|
// 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.StopPushNotificationsHubWorkers()
|
||||||
|
|
||||||
s.WaitForGoroutines()
|
s.WaitForGoroutines()
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user