diff --git a/CHANGELOG b/CHANGELOG index e1e360e..582380b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/cmd/netris/gui.go b/cmd/netris/gui.go index 988aacc..e815eb9 100644 --- a/cmd/netris/gui.go +++ b/cmd/netris/gui.go @@ -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 } diff --git a/cmd/netris/gui_init.go b/cmd/netris/gui_init.go index b9ad3a7..29b79ae 100644 --- a/cmd/netris/gui_init.go +++ b/cmd/netris/gui_init.go @@ -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) diff --git a/go.mod b/go.mod index 08cab31..c5f2645 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 46e227b..7eadd88 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/game/conn.go b/pkg/game/conn.go index da2966b..4ed38cc 100644 --- a/pkg/game/conn.go +++ b/pkg/game/conn.go @@ -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) }() diff --git a/pkg/game/game.go b/pkg/game/game.go index d1ea0a3..56fac51 100644 --- a/pkg/game/game.go +++ b/pkg/game/game.go @@ -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() } } diff --git a/pkg/game/server.go b/pkg/game/server.go index ec5fed3..8d08e83 100644 --- a/pkg/game/server.go +++ b/pkg/game/server.go @@ -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() diff --git a/pkg/mino/bag.go b/pkg/mino/bag.go index 393146e..739c44d 100644 --- a/pkg/mino/bag.go +++ b/pkg/mino/bag.go @@ -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() diff --git a/pkg/mino/matrix.go b/pkg/mino/matrix.go index 4488aa5..fea506c 100644 --- a/pkg/mino/matrix.go +++ b/pkg/mino/matrix.go @@ -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() } }