mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-27 07:28:27 +00:00
Add a Linearizer impl
This commit is contained in:
parent
27c335438f
commit
25f9fda0bb
2 changed files with 292 additions and 0 deletions
96
src/github.com/matrix-org/dendrite/common/linearizer.go
Normal file
96
src/github.com/matrix-org/dendrite/common/linearizer.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2017 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import "sync"
|
||||
|
||||
// Linearizer allows different goroutines to serialize execution of functions
|
||||
// based on a string key.
|
||||
type Linearizer struct {
|
||||
// protects lastMutex
|
||||
mutex sync.Mutex
|
||||
//
|
||||
lastMutex map[string]<-chan struct{}
|
||||
}
|
||||
|
||||
// NewLinearizer creates a new Linearizer
|
||||
func NewLinearizer() Linearizer {
|
||||
return Linearizer{
|
||||
lastMutex: make(map[string]<-chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Await schedules the callback to run once all previous callbacks for the given
|
||||
// key have finished executing, returning once callback has completed.
|
||||
func (l *Linearizer) Await(key string, callback func()) {
|
||||
l.AwaitWithHook(key, callback, nil)
|
||||
}
|
||||
|
||||
// AwaitWithHook is the same as Await, but with an added hook channel gets
|
||||
// closed once the callback has been scheduled. This is mainly useful for
|
||||
// testing as any functions scheduled after hook has been closed are guaranteed
|
||||
// to be run after this callback has finished. If hook is nil then it is
|
||||
// ignored.
|
||||
func (l *Linearizer) AwaitWithHook(key string, callback func(), hook chan<- struct{}) {
|
||||
closeChannel := make(chan struct{})
|
||||
defer close(closeChannel)
|
||||
|
||||
awaitChannel := l.getAndSetLastMutex(key, closeChannel)
|
||||
|
||||
if hook != nil {
|
||||
close(hook)
|
||||
}
|
||||
|
||||
if awaitChannel != nil {
|
||||
<-awaitChannel
|
||||
}
|
||||
|
||||
callback()
|
||||
|
||||
l.cleanupKey(key, closeChannel)
|
||||
}
|
||||
|
||||
// NumberOfActiveKeys returns the number of keys that have callbacks currently
|
||||
// scheduled to be run (or are running). Used mostly in tests to ensure that map
|
||||
// entries are cleaned up.
|
||||
func (l *Linearizer) NumberOfActiveKeys() int {
|
||||
return len(l.lastMutex)
|
||||
}
|
||||
|
||||
// getAndSetLastMutex replaces the current entry in lastMutex with the given
|
||||
// channel, while locking the mutex. The existing entry is returned if it
|
||||
// exists, otherwise returns nil.
|
||||
func (l *Linearizer) getAndSetLastMutex(key string, closeChannel <-chan struct{}) <-chan struct{} {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
awaitChannel := l.lastMutex[key]
|
||||
l.lastMutex[key] = closeChannel
|
||||
|
||||
return awaitChannel
|
||||
}
|
||||
|
||||
// cleanupKey deletes the entry in the lastMutex map if the value of key in the
|
||||
// map is currentChannel. If they match then its safe to delete because all
|
||||
// scheduled callbacks have been run for that key.
|
||||
func (l *Linearizer) cleanupKey(key string, currentChannel <-chan struct{}) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
entry, ok := l.lastMutex[key]
|
||||
if ok && entry == currentChannel {
|
||||
delete(l.lastMutex, key)
|
||||
}
|
||||
}
|
196
src/github.com/matrix-org/dendrite/common/linearizer_test.go
Normal file
196
src/github.com/matrix-org/dendrite/common/linearizer_test.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
// Copyright 2017 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleLinearizer(t *testing.T) {
|
||||
l := NewLinearizer()
|
||||
|
||||
numCalls := 0
|
||||
l.Await("foo", func() {
|
||||
numCalls++
|
||||
})
|
||||
|
||||
if numCalls != 1 {
|
||||
t.Fatalf("Expected function to be called once, called %d times", numCalls)
|
||||
}
|
||||
|
||||
activeKeys := l.NumberOfActiveKeys()
|
||||
if activeKeys != 0 {
|
||||
t.Fatalf("Expected no active keys, got %d active keys", activeKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleAfterLinearizer(t *testing.T) {
|
||||
l := NewLinearizer()
|
||||
|
||||
numFirstCalls := 0
|
||||
l.Await("foo", func() {
|
||||
numFirstCalls++
|
||||
})
|
||||
|
||||
numSecondCalls := 0
|
||||
l.Await("foo", func() {
|
||||
numSecondCalls++
|
||||
})
|
||||
|
||||
if numFirstCalls != 1 {
|
||||
t.Fatalf("Expected first function to be called once, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 1 {
|
||||
t.Fatalf("Expected second function to be called once, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
activeKeys := l.NumberOfActiveKeys()
|
||||
if activeKeys != 0 {
|
||||
t.Fatalf("Expected no active keys, got %d active keys", activeKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleConcurrentLinearizer(t *testing.T) { // nolint: gocyclo
|
||||
l := NewLinearizer()
|
||||
|
||||
waitGroup := sync.WaitGroup{}
|
||||
waitGroup.Add(3)
|
||||
|
||||
numFirstCalls := 0
|
||||
numSecondCalls := 0
|
||||
numThirdCalls := 0
|
||||
|
||||
startSignal := make(chan struct{})
|
||||
|
||||
setupAwait2 := make(chan struct{})
|
||||
setupAwait3 := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
l.AwaitWithHook("foo", func() {
|
||||
<-startSignal
|
||||
|
||||
numFirstCalls++
|
||||
|
||||
if numFirstCalls != 1 {
|
||||
t.Fatalf("Expected first function to be called once, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 0 {
|
||||
t.Fatalf("Expected second function to not be called, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
if numThirdCalls != 0 {
|
||||
t.Fatalf("Expected third function to not be called, called %d times", numThirdCalls)
|
||||
}
|
||||
}, setupAwait2)
|
||||
|
||||
t.Log("Finished waiting on w1")
|
||||
waitGroup.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-setupAwait2
|
||||
l.AwaitWithHook("foo", func() {
|
||||
numSecondCalls++
|
||||
|
||||
if numFirstCalls != 1 {
|
||||
t.Fatalf("Expected first function to be called once, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 1 {
|
||||
t.Fatalf("Expected second function to be called once, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
if numThirdCalls != 0 {
|
||||
t.Fatalf("Expected third function to not be called, called %d times", numThirdCalls)
|
||||
}
|
||||
}, setupAwait3)
|
||||
|
||||
t.Log("Finished waiting on w2")
|
||||
|
||||
waitGroup.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-setupAwait3
|
||||
l.AwaitWithHook("foo", func() {
|
||||
numThirdCalls++
|
||||
|
||||
if numFirstCalls != 1 {
|
||||
t.Fatalf("Expected first function to be called once, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 1 {
|
||||
t.Fatalf("Expected second function to be called once, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
if numThirdCalls != 1 {
|
||||
t.Fatalf("Expected third function to be called once, called %d times", numThirdCalls)
|
||||
}
|
||||
}, startSignal)
|
||||
|
||||
t.Log("Finished waiting on w3")
|
||||
waitGroup.Done()
|
||||
}()
|
||||
|
||||
waitGroup.Wait()
|
||||
|
||||
activeKeys := l.NumberOfActiveKeys()
|
||||
if activeKeys != 0 {
|
||||
t.Fatalf("Expected no active keys, got %d active keys", activeKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferentKeysLiniearizer(t *testing.T) {
|
||||
l := NewLinearizer()
|
||||
|
||||
waitChan := make(chan struct{})
|
||||
|
||||
numFirstCalls := 0
|
||||
go l.Await("foo", func() {
|
||||
<-waitChan
|
||||
numFirstCalls++
|
||||
})
|
||||
|
||||
numSecondCalls := 0
|
||||
l.Await("bar", func() {
|
||||
numSecondCalls++
|
||||
})
|
||||
|
||||
if numFirstCalls != 0 {
|
||||
t.Fatalf("Expected first function to not be called, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 1 {
|
||||
t.Fatalf("Expected second function to be called once, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
waitChan <- struct{}{}
|
||||
|
||||
if numFirstCalls != 1 {
|
||||
t.Fatalf("Expected first function to be called once, called %d times", numFirstCalls)
|
||||
}
|
||||
|
||||
if numSecondCalls != 1 {
|
||||
t.Fatalf("Expected second function to be called once, called %d times", numSecondCalls)
|
||||
}
|
||||
|
||||
activeKeys := l.NumberOfActiveKeys()
|
||||
if activeKeys != 0 {
|
||||
t.Fatalf("Expected no active keys, got %d active keys", activeKeys)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue