mirror of
https://github.com/elyby/chrly.git
synced 2024-12-23 21:50:03 +05:30
#1: Add broadcaster structure to broadcast results of the same usernames
This commit is contained in:
parent
8244351bb5
commit
abea94a41f
53
api/mojang/queue/broadcast.go
Normal file
53
api/mojang/queue/broadcast.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
)
|
||||||
|
|
||||||
|
type broadcastMap struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
listeners map[string][]chan *mojang.SignedTexturesResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBroadcaster() *broadcastMap {
|
||||||
|
return &broadcastMap{
|
||||||
|
listeners: make(map[string][]chan *mojang.SignedTexturesResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean value, which will be true if the username passed didn't exist before
|
||||||
|
func (c *broadcastMap) AddListener(username string, resultChan chan *mojang.SignedTexturesResponse) bool {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
val, alreadyHasSource := c.listeners[username]
|
||||||
|
if alreadyHasSource {
|
||||||
|
c.listeners[username] = append(val, resultChan)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.listeners[username] = []chan *mojang.SignedTexturesResponse{resultChan}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *broadcastMap) BroadcastAndRemove(username string, result *mojang.SignedTexturesResponse) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
val, ok := c.listeners[username]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, channel := range val {
|
||||||
|
go func(channel chan *mojang.SignedTexturesResponse) {
|
||||||
|
channel <- result
|
||||||
|
close(channel)
|
||||||
|
}(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(c.listeners, username)
|
||||||
|
}
|
75
api/mojang/queue/broadcast_test.go
Normal file
75
api/mojang/queue/broadcast_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBroadcastMap_GetOrAppend(t *testing.T) {
|
||||||
|
t.Run("first call when username didn't exist before should return true", func(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
broadcaster := newBroadcaster()
|
||||||
|
channel := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
isFirstListener := broadcaster.AddListener("mock", channel)
|
||||||
|
|
||||||
|
assert.True(isFirstListener)
|
||||||
|
listeners, ok := broadcaster.listeners["mock"]
|
||||||
|
assert.True(ok)
|
||||||
|
assert.Len(listeners, 1)
|
||||||
|
assert.Equal(channel, listeners[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("subsequent calls should return false", func(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
broadcaster := newBroadcaster()
|
||||||
|
channel1 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
isFirstListener := broadcaster.AddListener("mock", channel1)
|
||||||
|
|
||||||
|
assert.True(isFirstListener)
|
||||||
|
|
||||||
|
channel2 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
isFirstListener = broadcaster.AddListener("mock", channel2)
|
||||||
|
|
||||||
|
assert.False(isFirstListener)
|
||||||
|
|
||||||
|
channel3 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
isFirstListener = broadcaster.AddListener("mock", channel3)
|
||||||
|
|
||||||
|
assert.False(isFirstListener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBroadcastMap_BroadcastAndRemove(t *testing.T) {
|
||||||
|
t.Run("should broadcast to all listeners and remove the key", func(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
broadcaster := newBroadcaster()
|
||||||
|
channel1 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
channel2 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
broadcaster.AddListener("mock", channel1)
|
||||||
|
broadcaster.AddListener("mock", channel2)
|
||||||
|
|
||||||
|
result := &mojang.SignedTexturesResponse{Id: "mockUuid"}
|
||||||
|
broadcaster.BroadcastAndRemove("mock", result)
|
||||||
|
|
||||||
|
assert.Equal(result, <-channel1)
|
||||||
|
assert.Equal(result, <-channel2)
|
||||||
|
|
||||||
|
channel3 := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
isFirstListener := broadcaster.AddListener("mock", channel3)
|
||||||
|
assert.True(isFirstListener)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("call on not exists username", func(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
assert.NotPanics(func() {
|
||||||
|
broadcaster := newBroadcaster()
|
||||||
|
broadcaster.BroadcastAndRemove("mock", &mojang.SignedTexturesResponse{})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -14,8 +14,8 @@ type jobItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type jobsQueue struct {
|
type jobsQueue struct {
|
||||||
|
lock sync.Mutex
|
||||||
items []*jobItem
|
items []*jobItem
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *jobsQueue) New() *jobsQueue {
|
func (s *jobsQueue) New() *jobsQueue {
|
||||||
|
@ -20,20 +20,30 @@ type JobsQueue struct {
|
|||||||
|
|
||||||
onFirstCall sync.Once
|
onFirstCall sync.Once
|
||||||
queue jobsQueue
|
queue jobsQueue
|
||||||
|
broadcast *broadcastMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse {
|
func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse {
|
||||||
ctx.onFirstCall.Do(func() {
|
ctx.onFirstCall.Do(func() {
|
||||||
ctx.queue.New()
|
ctx.queue.New()
|
||||||
|
ctx.broadcast = newBroadcaster()
|
||||||
ctx.startQueue()
|
ctx.startQueue()
|
||||||
})
|
})
|
||||||
|
|
||||||
resultChan := make(chan *mojang.SignedTexturesResponse)
|
responseChan := make(chan *mojang.SignedTexturesResponse)
|
||||||
// TODO: prevent of adding the same username more than once
|
isFirstListener := ctx.broadcast.AddListener(username, responseChan)
|
||||||
ctx.queue.Enqueue(&jobItem{username, resultChan})
|
if isFirstListener {
|
||||||
// TODO: return nil if processing takes more than 5 seconds
|
resultChan := make(chan *mojang.SignedTexturesResponse)
|
||||||
|
ctx.queue.Enqueue(&jobItem{username, resultChan})
|
||||||
|
// TODO: return nil if processing takes more than 5 seconds
|
||||||
|
|
||||||
return resultChan
|
go func() {
|
||||||
|
result := <-resultChan
|
||||||
|
ctx.broadcast.BroadcastAndRemove(username, result)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *JobsQueue) startQueue() {
|
func (ctx *JobsQueue) startQueue() {
|
||||||
|
@ -3,6 +3,7 @@ package queue
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/elyby/chrly/api/mojang"
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
|
||||||
@ -135,6 +136,64 @@ func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
|
|||||||
suite.Iterate()
|
suite.Iterate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *QueueTestSuite) TestReceiveTexturesForTheSameUsernames() {
|
||||||
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
||||||
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
||||||
|
}, nil)
|
||||||
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(
|
||||||
|
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
||||||
|
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
||||||
|
|
||||||
|
suite.Iterate()
|
||||||
|
|
||||||
|
result1 := <-resultChan1
|
||||||
|
result2 := <-resultChan2
|
||||||
|
|
||||||
|
if suite.Assert().NotNil(result1) {
|
||||||
|
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
|
||||||
|
suite.Assert().Equal("maksimkurb", result1.Name)
|
||||||
|
|
||||||
|
suite.Assert().Equal(result1, result2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *QueueTestSuite) TestReceiveTexturesForUsernameThatAlreadyProcessing() {
|
||||||
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
||||||
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
||||||
|
}, nil)
|
||||||
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).
|
||||||
|
Once().
|
||||||
|
After(10*time.Millisecond). // Simulate long round trip
|
||||||
|
Return(
|
||||||
|
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
||||||
|
|
||||||
|
// Note that for entire test there is only one iteration
|
||||||
|
suite.Iterate()
|
||||||
|
|
||||||
|
// Let it meet delayed UuidToTextures request
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
||||||
|
|
||||||
|
result1 := <-resultChan1
|
||||||
|
result2 := <-resultChan2
|
||||||
|
|
||||||
|
if suite.Assert().NotNil(result1) {
|
||||||
|
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
|
||||||
|
suite.Assert().Equal("maksimkurb", result1.Name)
|
||||||
|
|
||||||
|
suite.Assert().Equal(result1, result2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
|
func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
|
||||||
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{}, nil)
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{}, nil)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user