Fix server deadlock

This commit is contained in:
Trevor Slocum 2020-05-22 16:09:13 -07:00
parent 5074c6e607
commit 8ba9582ab9
10 changed files with 144 additions and 57 deletions

View file

@ -1,6 +1,7 @@
0.1.8:
- Add custom color support
- Improve SSH host key file not found error
- Fix server deadlock
0.1.7:
- Spawn pieces within view

View file

@ -429,7 +429,8 @@ func renderMatrixes(mx []*mino.Matrix) {
}
for i := range mx {
mx[i].Lock()
mx[i].Lock() // Unlocked later in this function
if mt == mino.MatrixCustom {
continue
}

View file

@ -17,7 +17,7 @@ func initGUI(skipTitle bool) (*cview.Application, error) {
cview.Styles.PrimaryTextColor = tcell.ColorDefault
cview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault
app = cview.NewApplication().EnableMouse()
app = cview.NewApplication().EnableMouse(true)
app.SetAfterResizeFunc(handleResize)

15
go.mod
View file

@ -3,15 +3,14 @@ module gitlab.com/tslocum/netris
go 1.13
require (
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/creack/pty v1.1.9
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/creack/pty v1.1.11
github.com/gdamore/tcell v1.3.0
github.com/gliderlabs/ssh v0.2.2
github.com/gliderlabs/ssh v0.3.0
github.com/mattn/go-isatty v0.0.12
gitlab.com/tslocum/cbind v0.1.1
gitlab.com/tslocum/cview v1.4.4-0.20200220174815-3bf6bb259c75
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
gopkg.in/yaml.v2 v2.2.8
gitlab.com/tslocum/cview v1.4.6
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
gopkg.in/yaml.v2 v2.3.0
)

35
go.sum
View file

@ -1,16 +1,14 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.0 h1:7GcKy4erEljCE/QeQ2jTVpu+3f3zkpZOxOJjFYkMqYU=
github.com/gliderlabs/ssh v0.3.0/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
@ -19,15 +17,17 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
gitlab.com/tslocum/cbind v0.1.1 h1:JXXtxMWHgWLvoF+QkrvcNvOQ59juy7OE1RhT7hZfdt0=
gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/qTpPqk=
gitlab.com/tslocum/cview v1.4.4-0.20200220174815-3bf6bb259c75 h1:htStKLVVrf77HtDVx0jFY4YUO45w/FaESnu2U+T2gKY=
gitlab.com/tslocum/cview v1.4.4-0.20200220174815-3bf6bb259c75/go.mod h1:+bEf1cg6IoWvL16YHJAKwGGpQf5s/nxXAA7YJr+WOHE=
gitlab.com/tslocum/cview v1.4.6 h1:dEM/aOsatoaNZOZ511n8hhZABPzTunUdi0RpUw9uXjM=
gitlab.com/tslocum/cview v1.4.6/go.mod h1:PW2Ucec7oTYOfK4N+hqm/CKEN9B1PBidq5YJ3ZaeknU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -35,15 +35,14 @@ golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -30,6 +30,7 @@ type Conn struct {
forwardOut chan GameCommandInterface
*sync.WaitGroup
sync.Mutex
}
func NewServerConn(conn net.Conn, forwardOut chan GameCommandInterface) *Conn {
@ -112,7 +113,12 @@ func (s *Conn) Write(gc GameCommandInterface) {
func (s *Conn) handleLocalWrite() {
for e := range s.out {
if s.forwardOut != nil {
s.forwardOut <- e
select {
case s.forwardOut <- e:
default:
s.Done()
s.Close()
}
}
s.Done()
@ -228,7 +234,12 @@ func (s *Conn) handleRead() {
if !processed {
s.addSourceID(gc)
s.In <- gc
select {
case s.In <- gc:
default:
s.Close()
}
}
err = s.conn.SetReadDeadline(time.Now().Add(ConnTimeout))
@ -290,16 +301,25 @@ func (s *Conn) handleWrite() {
}
func (s *Conn) Close() {
s.Lock()
if s.Terminated {
s.Unlock()
return
}
s.Terminated = true
s.Unlock()
s.conn.Close()
go func() {
s.Wait()
s.Lock()
defer s.Unlock()
close(s.In)
close(s.out)
}()

View file

@ -11,7 +11,6 @@ import (
"time"
"gitlab.com/tslocum/netris/pkg/event"
"gitlab.com/tslocum/netris/pkg/mino"
)
@ -65,7 +64,7 @@ type Game struct {
SpeedLimit int
sentPing time.Time
*sync.Mutex
sync.Mutex
}
func NewGame(rank int, out func(GameCommandInterface), logger chan string, draw chan event.DrawObject) (*Game, error) {
@ -83,7 +82,7 @@ func NewGame(rank int, out func(GameCommandInterface), logger chan string, draw
Event: make(chan interface{}, CommandQueueSize),
draw: draw,
logger: logger,
Mutex: new(sync.Mutex)}
}
if out != nil {
g.out = out
@ -385,6 +384,7 @@ func (g *Game) handleDistributeMatrixes() {
if g.Terminated {
t.Stop()
g.Unlock()
return
}
@ -470,13 +470,20 @@ func (g *Game) handleDistributeMatrixes() {
go func() {
for {
time.Sleep(7 * time.Second)
g.Lock()
if g.Terminated {
g.Unlock()
return
} else if len(g.Players) > 1 {
g.Unlock()
g.Reset()
g.Start(0)
return
}
g.Unlock()
}
}()
}
@ -648,6 +655,12 @@ func (g *Game) handleDistributeGarbage() {
g.Lock()
if g.Terminated {
t.Stop()
g.Unlock()
return
}
for i := range g.Players {
if g.Players[i].pendingGarbage > 0 {
g.Players[i].Write(&GameCommandReceiveGarbage{Lines: g.Players[i].pendingGarbage})
@ -655,6 +668,7 @@ func (g *Game) handleDistributeGarbage() {
g.Players[i].pendingGarbage = 0
}
}
g.Unlock()
}
}

View file

@ -97,6 +97,9 @@ func NewServer(si []ServerInterface, logLevel int) *Server {
}
func (s *Server) NewGame() (*Game, error) {
s.Lock()
defer s.Unlock()
gameID := 1
for {
if _, ok := s.Games[gameID]; !ok {
@ -136,27 +139,27 @@ func (s *Server) handle() {
for {
time.Sleep(1 * time.Minute)
s.Lock()
s.removeTerminatedGames()
s.Unlock()
}
}
func (s *Server) removeTerminatedGames() {
s.Lock()
defer s.Unlock()
for gameID, g := range s.Games {
if g != nil && !g.Terminated {
g.Lock()
if !g.Terminated {
g.Unlock()
continue
}
delete(s.Games, gameID)
g = nil
g.Unlock()
}
}
func (s *Server) FindGame(p *Player, gameID int, newGame ListedGame) *Game {
s.Lock()
defer s.Unlock()
var (
g *Game
err error
@ -190,20 +193,39 @@ func (s *Server) FindGame(p *Player, gameID int, newGame ListedGame) *Game {
g.Unlock()
} else if gameID > 0 {
// Join a game by its ID
if gm, ok := s.Games[gameID]; ok && !gm.Terminated && (gm.MaxPlayers == 0 || len(gm.Players) < gm.MaxPlayers) {
g = gm
s.Lock()
gm := s.Games[gameID]
s.Unlock()
if gm != nil {
gm.Lock()
canJoin := !gm.Terminated && (gm.MaxPlayers == 0 || len(gm.Players) < gm.MaxPlayers)
gm.Unlock()
if canJoin {
g = gm
} else {
p.Write(&GameCommandMessage{Message: "Failed to join game - Player limit reached"})
return nil
}
} else {
p.Write(&GameCommandMessage{Message: "Failed to join game - Player limit reached"})
p.Write(&GameCommandMessage{Message: "Failed to join game - Invalid game ID"})
return nil
}
} else if gameID == 0 {
// Join any game
s.Lock()
for _, gm := range s.Games {
if gm != nil && !gm.Terminated && (gm.MaxPlayers == 0 || len(gm.Players) < gm.MaxPlayers) {
gm.Lock()
if !gm.Terminated && (gm.MaxPlayers == 0 || len(gm.Players) < gm.MaxPlayers) {
gm.Unlock()
g = gm
break
}
gm.Unlock()
}
s.Unlock()
} else {
// Create a local game
g, err = s.NewGame()
@ -261,13 +283,18 @@ func (s *Server) handleNewPlayer(pl *Player) {
if _, ok := e.(*GameCommandListGames); ok {
var gl []*ListedGame
s.Lock()
for _, g := range s.Games {
g.Lock()
if g.Terminated {
g.Unlock()
continue
}
gl = append(gl, &ListedGame{ID: g.ID, Name: g.Name, Players: len(g.Players), MaxPlayers: g.MaxPlayers, SpeedLimit: g.SpeedLimit})
g.Unlock()
}
s.Unlock()
sort.Slice(gl, func(i, j int) bool {
if gl[i].Players == gl[j].Players {
@ -407,15 +434,19 @@ func (s *Server) handleGameCommands(pl *Player, g *Game) {
g.Players[p.SourcePlayer].totalGarbageSent += p.Lines
}
case *GameCommandStats:
players := 0
games := 0
go func(p *Player) {
players := 0
games := 0
for _, g := range s.Games {
players += len(g.Players)
games++
}
s.Lock()
for _, g := range s.Games {
players += len(g.Players)
games++
}
s.Unlock()
g.Players[p.SourcePlayer].Write(&GameCommandStats{Created: s.created, Players: players, Games: games})
p.Write(&GameCommandStats{Created: s.created, Players: players, Games: games})
}(g.Players[p.SourcePlayer])
}
g.Unlock()

View file

@ -14,13 +14,13 @@ type Bag struct {
i int
width int
*sync.Mutex
sync.Mutex
}
func NewBag(seed int64, minos []Mino, width int) (*Bag, error) {
minoSource := rand.NewSource(seed)
garbageSource := rand.NewSource(seed)
b := &Bag{Original: minos, minoRandomizer: rand.New(minoSource), garbageRandomizer: rand.New(garbageSource), width: width, Mutex: new(sync.Mutex)}
b := &Bag{Original: minos, minoRandomizer: rand.New(minoSource), garbageRandomizer: rand.New(garbageSource), width: width}
b.shuffle()

View file

@ -361,7 +361,9 @@ func (m *Matrix) Draw() {
return
}
m.draw <- event.DrawPlayerMatrix
select {
case m.draw <- event.DrawPlayerMatrix:
}
}
func (m *Matrix) ClearOverlay() {
@ -458,6 +460,10 @@ func (m *Matrix) SetGameOver() {
m.Lock()
defer m.Unlock()
m.setGameOver()
}
func (m *Matrix) setGameOver() {
if m.GameOver {
return
}
@ -682,7 +688,7 @@ LANDPIECE:
_ = score
m.Moved()
m.moved()
for i := range m.lands {
if time.Since(m.lands[i]) > 2*time.Minute {
@ -855,7 +861,7 @@ func (m *Matrix) movePiece(x int, y int) bool {
}
if y < 0 {
m.Moved()
m.moved()
}
m.Draw()
@ -864,6 +870,22 @@ func (m *Matrix) movePiece(x int, y int) bool {
}
func (m *Matrix) Moved() {
m.Lock()
if m.Move == nil {
m.Unlock()
return
}
select {
case m.Move <- 0:
m.Unlock()
default:
m.Unlock()
m.SetGameOver()
}
}
func (m *Matrix) moved() {
if m.Move == nil {
return
}
@ -871,7 +893,7 @@ func (m *Matrix) Moved() {
select {
case m.Move <- 0:
default:
m.SetGameOver()
m.setGameOver()
}
}