mattermost/app/plugin_deadlock_test.go
Claudio Costa 1e53fe85ad
[MM-21378] Add mutex to model.Post to guard against race conditions on Post.Props (#13884)
* Add mutex to model.Post to guard against race conditions on Post.Props

* Rename mutex

* Add GetProp() method to Post

* Fix more tests

* Fix flaky test

Benchmarks:

BenchmarkPostPropsGet_indirect
BenchmarkPostPropsGet_indirect-2     	85026746	        13.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_indirect-4     	90273747	        13.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_indirect-8     	88324293	        13.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_indirect-16    	91427720	        13.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_direct
BenchmarkPostPropsGet_direct-2       	1000000000	         0.242 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_direct-4       	1000000000	         0.241 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_direct-8       	1000000000	         0.240 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsGet_direct-16      	1000000000	         0.241 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsAdd_indirect
BenchmarkPostPropsAdd_indirect-2     	 5602224	       203 ns/op	     336 B/op	       2 allocs/op
BenchmarkPostPropsAdd_indirect-4     	 5959496	       206 ns/op	     336 B/op	       2 allocs/op
BenchmarkPostPropsAdd_indirect-8     	 5833999	       205 ns/op	     336 B/op	       2 allocs/op
BenchmarkPostPropsAdd_indirect-16    	 5802493	       225 ns/op	     336 B/op	       2 allocs/op
BenchmarkPostPropsAdd_direct
BenchmarkPostPropsAdd_direct-2       	100000000	        11.3 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsAdd_direct-4       	100000000	        11.3 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsAdd_direct-8       	100000000	        11.6 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsAdd_direct-16      	99840794	        11.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsDel_indirect
BenchmarkPostPropsDel_indirect-2     	18824002	        61.9 ns/op	      48 B/op	       1 allocs/op
BenchmarkPostPropsDel_indirect-4     	19470736	        63.8 ns/op	      48 B/op	       1 allocs/op
BenchmarkPostPropsDel_indirect-8     	17640460	        65.3 ns/op	      48 B/op	       1 allocs/op
BenchmarkPostPropsDel_indirect-16    	18692962	        65.4 ns/op	      48 B/op	       1 allocs/op
BenchmarkPostPropsDel_direct
BenchmarkPostPropsDel_direct-2       	516257440	         2.34 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsDel_direct-4       	514865216	         2.43 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsDel_direct-8       	511330477	         2.37 ns/op	       0 B/op	       0 allocs/op
BenchmarkPostPropsDel_direct-16      	499504010	         2.38 ns/op	       0 B/op	       0 allocs/op
2020-03-13 21:12:20 +01:00

321 lines
6.9 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"os"
"strings"
"testing"
"text/template"
"time"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v5/model"
)
func TestPluginDeadlock(t *testing.T) {
t.Run("Single Plugin", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(`
package main
import (
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) OnActivate() error {
_, err := p.API.CreatePost(&model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "message",
})
if err != nil {
panic(err.Error())
}
return nil
}
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
if _, from_plugin := post.GetProps()["from_plugin"]; from_plugin {
return nil, ""
}
p.API.CreatePost(&model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "message",
Props: map[string]interface{}{
"from_plugin": true,
},
})
return nil, ""
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`,
))
templateData := struct {
User *model.User
Channel *model.Channel
}{
th.BasicUser,
th.BasicChannel,
}
plugins := []string{}
pluginTemplates := []*template.Template{
pluginPostOnActivate,
}
for _, pluginTemplate := range pluginTemplates {
b := &strings.Builder{}
pluginTemplate.Execute(b, templateData)
plugins = append(plugins, b.String())
}
done := make(chan bool)
go func() {
SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI)
close(done)
}()
select {
case <-done:
case <-time.After(30 * time.Second):
require.Fail(t, "plugin failed to activate: likely deadlocked")
go func() {
time.Sleep(5 * time.Second)
os.Exit(1)
}()
}
})
t.Run("Multiple Plugins", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
pluginPostOnHasBeenPosted := template.Must(template.New("pluginPostOnHasBeenPosted").Parse(`
package main
import (
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
if _, from_plugin := post.GetProps()["from_plugin"]; from_plugin {
return nil, ""
}
p.API.CreatePost(&model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "message",
Props: map[string]interface{}{
"from_plugin": true,
},
})
return nil, ""
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`,
))
pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(`
package main
import (
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) OnActivate() error {
_, err := p.API.CreatePost(&model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "message",
})
if err != nil {
panic(err.Error())
}
return nil
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`,
))
templateData := struct {
User *model.User
Channel *model.Channel
}{
th.BasicUser,
th.BasicChannel,
}
plugins := []string{}
pluginTemplates := []*template.Template{
pluginPostOnHasBeenPosted,
pluginPostOnActivate,
}
for _, pluginTemplate := range pluginTemplates {
b := &strings.Builder{}
pluginTemplate.Execute(b, templateData)
plugins = append(plugins, b.String())
}
done := make(chan bool)
go func() {
SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI)
close(done)
}()
select {
case <-done:
case <-time.After(30 * time.Second):
require.Fail(t, "plugin failed to activate: likely deadlocked")
go func() {
time.Sleep(5 * time.Second)
os.Exit(1)
}()
}
})
t.Run("CreatePost on OnDeactivate Plugin", func(t *testing.T) {
th := Setup(t).InitBasic()
pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(`
package main
import (
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) OnDeactivate() error {
_, err := p.API.CreatePost(&model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "OnDeactivate",
})
if err != nil {
panic(err.Error())
}
return nil
}
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
updatedPost := &model.Post{
UserId: "{{.User.Id}}",
ChannelId: "{{.Channel.Id}}",
Message: "messageUpdated",
Props: map[string]interface{}{
"from_plugin": true,
},
}
return updatedPost, ""
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`,
))
templateData := struct {
User *model.User
Channel *model.Channel
}{
th.BasicUser,
th.BasicChannel,
}
plugins := []string{}
pluginTemplates := []*template.Template{
pluginPostOnActivate,
}
for _, pluginTemplate := range pluginTemplates {
b := &strings.Builder{}
pluginTemplate.Execute(b, templateData)
plugins = append(plugins, b.String())
}
done := make(chan bool)
go func() {
posts, appErr := th.App.GetPosts(th.BasicChannel.Id, 0, 2)
require.Nil(t, appErr)
require.NotNil(t, posts)
messageWillBePostedCalled := false
for _, p := range posts.Posts {
if p.Message == "messageUpdated" {
messageWillBePostedCalled = true
}
}
require.False(t, messageWillBePostedCalled, "MessageWillBePosted should not have been called")
SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI)
th.TearDown()
posts, appErr = th.App.GetPosts(th.BasicChannel.Id, 0, 2)
require.Nil(t, appErr)
require.NotNil(t, posts)
messageWillBePostedCalled = false
for _, p := range posts.Posts {
if p.Message == "messageUpdated" {
messageWillBePostedCalled = true
}
}
require.True(t, messageWillBePostedCalled, "MessageWillBePosted was not called on deactivate")
close(done)
}()
select {
case <-done:
case <-time.After(30 * time.Second):
require.Fail(t, "plugin failed to activate: likely deadlocked")
go func() {
time.Sleep(5 * time.Second)
os.Exit(1)
}()
}
})
}