Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
138
server/boards/utils/callbackqueue.go
Обычный файл
138
server/boards/utils/callbackqueue.go
Обычный файл
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
// CallbackFunc is a func that can enqueued in the callback queue and will be
|
||||
// called when dequeued.
|
||||
type CallbackFunc func() error
|
||||
|
||||
// CallbackQueue provides a simple thread pool for processing callbacks. Callbacks will
|
||||
// be executed in the order in which they are enqueued, but no guarantees are provided
|
||||
// regarding the order in which they finish (unless poolSize == 1).
|
||||
type CallbackQueue struct {
|
||||
name string
|
||||
poolSize int
|
||||
|
||||
queue chan CallbackFunc
|
||||
done chan struct{}
|
||||
alive chan int
|
||||
|
||||
idone uint32
|
||||
|
||||
logger mlog.LoggerIFace
|
||||
}
|
||||
|
||||
// NewCallbackQueue creates a new CallbackQueue and starts a thread pool to service it.
|
||||
func NewCallbackQueue(name string, queueSize int, poolSize int, logger mlog.LoggerIFace) *CallbackQueue {
|
||||
cn := &CallbackQueue{
|
||||
name: name,
|
||||
poolSize: poolSize,
|
||||
queue: make(chan CallbackFunc, queueSize),
|
||||
done: make(chan struct{}),
|
||||
alive: make(chan int, poolSize),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
for i := 0; i < poolSize; i++ {
|
||||
go cn.loop(i)
|
||||
}
|
||||
|
||||
return cn
|
||||
}
|
||||
|
||||
// Shutdown stops accepting enqueues and exits all pool threads. This method waits
|
||||
// as long as the context allows for the threads to exit.
|
||||
// Returns true if the pool exited, false on timeout.
|
||||
func (cn *CallbackQueue) Shutdown(context context.Context) bool {
|
||||
if !atomic.CompareAndSwapUint32(&cn.idone, 0, 1) {
|
||||
// already shutdown
|
||||
return true
|
||||
}
|
||||
|
||||
// signal threads to exit
|
||||
close(cn.done)
|
||||
|
||||
// wait for the threads to exit or timeout
|
||||
count := 0
|
||||
for count < cn.poolSize {
|
||||
select {
|
||||
case <-cn.alive:
|
||||
count++
|
||||
case <-context.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// try to drain any remaining callbacks
|
||||
for {
|
||||
select {
|
||||
case f := <-cn.queue:
|
||||
cn.exec(f)
|
||||
case <-context.Done():
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue adds a callback to the queue.
|
||||
func (cn *CallbackQueue) Enqueue(f CallbackFunc) {
|
||||
if atomic.LoadUint32(&cn.idone) != 0 {
|
||||
cn.logger.Debug("CallbackQueue skipping enqueue, notifier is shutdown", mlog.String("name", cn.name))
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cn.queue <- f:
|
||||
default:
|
||||
start := time.Now()
|
||||
cn.queue <- f
|
||||
dur := time.Since(start)
|
||||
cn.logger.Warn("CallbackQueue queue backlog", mlog.String("name", cn.name), mlog.Duration("wait_time", dur))
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *CallbackQueue) loop(id int) {
|
||||
defer func() {
|
||||
cn.logger.Trace("CallbackQueue thread exited", mlog.String("name", cn.name), mlog.Int("id", id))
|
||||
cn.alive <- id
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case f := <-cn.queue:
|
||||
cn.exec(f)
|
||||
case <-cn.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *CallbackQueue) exec(f CallbackFunc) {
|
||||
// don't let a panic in the callback exit the thread.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
stack := debug.Stack()
|
||||
cn.logger.Error("CallbackQueue callback panic",
|
||||
mlog.String("name", cn.name),
|
||||
mlog.Any("panic", r),
|
||||
mlog.String("stack", string(stack)),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := f(); err != nil {
|
||||
cn.logger.Error("CallbackQueue callback error", mlog.String("name", cn.name), mlog.Err(err))
|
||||
}
|
||||
}
|
||||
67
server/boards/utils/callbackqueue_test.go
Обычный файл
67
server/boards/utils/callbackqueue_test.go
Обычный файл
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
func Test_newChangeNotifier(t *testing.T) {
|
||||
logger := mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)
|
||||
|
||||
t.Run("startup, shutdown", func(t *testing.T) {
|
||||
cn := NewCallbackQueue("test1", 100, 5, logger)
|
||||
|
||||
var callbackCount int32
|
||||
callback := func() error {
|
||||
atomic.AddInt32(&callbackCount, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
const loops = 500
|
||||
for i := 0; i < loops; i++ {
|
||||
cn.Enqueue(callback)
|
||||
// don't peg the cpu
|
||||
if i%20 == 0 {
|
||||
time.Sleep(time.Millisecond * 1)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
ok := cn.Shutdown(ctx)
|
||||
assert.True(t, ok, "shutdown should return true (no timeout)")
|
||||
|
||||
assert.Equal(t, int32(loops), atomic.LoadInt32(&callbackCount))
|
||||
})
|
||||
|
||||
t.Run("handle panic", func(t *testing.T) {
|
||||
cn := NewCallbackQueue("test2", 100, 5, logger)
|
||||
|
||||
var callbackCount int32
|
||||
callback := func() error {
|
||||
atomic.AddInt32(&callbackCount, 1)
|
||||
panic("oh no!")
|
||||
}
|
||||
|
||||
const loops = 5
|
||||
for i := 0; i < loops; i++ {
|
||||
cn.Enqueue(callback)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
ok := cn.Shutdown(ctx)
|
||||
assert.True(t, ok, "shutdown should return true (no timeout)")
|
||||
|
||||
assert.Equal(t, int32(loops), atomic.LoadInt32(&callbackCount))
|
||||
})
|
||||
}
|
||||
23
server/boards/utils/debug.go
Обычный файл
23
server/boards/utils/debug.go
Обычный файл
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsRunningUnitTests returns true if this instance of FocalBoard is running unit or integration tests.
|
||||
func IsRunningUnitTests() bool {
|
||||
testing := os.Getenv("FOCALBOARD_UNIT_TESTING")
|
||||
if testing == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
switch strings.ToLower(testing) {
|
||||
case "1", "t", "y", "true", "yes":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
15
server/boards/utils/links.go
Обычный файл
15
server/boards/utils/links.go
Обычный файл
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MakeCardLink creates fully qualified card links based on card id and parents.
|
||||
func MakeCardLink(serverRoot string, teamID string, boardID string, cardID string) string {
|
||||
return fmt.Sprintf("%s/team/%s/%s/0/%s", serverRoot, teamID, boardID, cardID)
|
||||
}
|
||||
|
||||
func MakeBoardLink(serverRoot string, teamID string, board string) string {
|
||||
return fmt.Sprintf("%s/team/%s/%s", serverRoot, teamID, board)
|
||||
}
|
||||
8
server/boards/utils/testUtils.go
Обычный файл
8
server/boards/utils/testUtils.go
Обычный файл
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import "github.com/stretchr/testify/mock"
|
||||
|
||||
var Anything = mock.MatchedBy(func(interface{}) bool { return true })
|
||||
130
server/boards/utils/utils.go
Обычный файл
130
server/boards/utils/utils.go
Обычный файл
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
type IDType byte
|
||||
|
||||
const (
|
||||
IDTypeNone IDType = '7'
|
||||
IDTypeTeam IDType = 't'
|
||||
IDTypeBoard IDType = 'b'
|
||||
IDTypeCard IDType = 'c'
|
||||
IDTypeView IDType = 'v'
|
||||
IDTypeSession IDType = 's'
|
||||
IDTypeUser IDType = 'u'
|
||||
IDTypeToken IDType = 'k'
|
||||
IDTypeBlock IDType = 'a'
|
||||
IDTypeAttachment IDType = 'i'
|
||||
)
|
||||
|
||||
// NewId is a globally unique identifier. It is a [A-Z0-9] string 27
|
||||
// characters long. It is a UUID version 4 Guid that is zbased32 encoded
|
||||
// with the padding stripped off, and a one character alpha prefix indicating the
|
||||
// type of entity or a `7` if unknown type.
|
||||
func NewID(idType IDType) string {
|
||||
return string(idType) + mm_model.NewId()
|
||||
}
|
||||
|
||||
// GetMillis is a convenience method to get milliseconds since epoch.
|
||||
func GetMillis() int64 {
|
||||
return mm_model.GetMillis()
|
||||
}
|
||||
|
||||
// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
|
||||
func GetMillisForTime(thisTime time.Time) int64 {
|
||||
return mm_model.GetMillisForTime(thisTime)
|
||||
}
|
||||
|
||||
// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
|
||||
func GetTimeForMillis(millis int64) time.Time {
|
||||
return mm_model.GetTimeForMillis(millis)
|
||||
}
|
||||
|
||||
// SecondsToMillis is a convenience method to convert seconds to milliseconds.
|
||||
func SecondsToMillis(seconds int64) int64 {
|
||||
return seconds * 1000
|
||||
}
|
||||
|
||||
func StructToMap(v interface{}) (m map[string]interface{}) {
|
||||
b, _ := json.Marshal(v)
|
||||
_ = json.Unmarshal(b, &m)
|
||||
return
|
||||
}
|
||||
|
||||
func intersection(a []interface{}, b []interface{}) []interface{} {
|
||||
set := make([]interface{}, 0)
|
||||
hash := make(map[interface{}]bool)
|
||||
av := reflect.ValueOf(a)
|
||||
bv := reflect.ValueOf(b)
|
||||
|
||||
for i := 0; i < av.Len(); i++ {
|
||||
el := av.Index(i).Interface()
|
||||
hash[el] = true
|
||||
}
|
||||
|
||||
for i := 0; i < bv.Len(); i++ {
|
||||
el := bv.Index(i).Interface()
|
||||
if _, found := hash[el]; found {
|
||||
set = append(set, el)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func Intersection(x ...[]interface{}) []interface{} {
|
||||
if len(x) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(x) == 1 {
|
||||
return x[0]
|
||||
}
|
||||
|
||||
result := x[0]
|
||||
i := 1
|
||||
for i < len(x) {
|
||||
result = intersection(result, x[i])
|
||||
i++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func IsCloudLicense(license *mm_model.License) bool {
|
||||
return license != nil &&
|
||||
license.Features != nil &&
|
||||
license.Features.Cloud != nil &&
|
||||
*license.Features.Cloud
|
||||
}
|
||||
|
||||
func DedupeStringArr(arr []string) []string {
|
||||
hashMap := map[string]bool{}
|
||||
|
||||
for _, item := range arr {
|
||||
hashMap[item] = true
|
||||
}
|
||||
|
||||
dedupedArr := make([]string, len(hashMap))
|
||||
i := 0
|
||||
for key := range hashMap {
|
||||
dedupedArr[i] = key
|
||||
i++
|
||||
}
|
||||
|
||||
return dedupedArr
|
||||
}
|
||||
|
||||
func GetBaseFilePath() string {
|
||||
return path.Join("boards", time.Now().Format("20060102"))
|
||||
}
|
||||
Ссылка в новой задаче
Block a user