fix(server): avoid deadlock when changing equipped main options
This commit is contained in:
@@ -670,7 +670,12 @@ func (s *EquipmentInventory) mainOptionChange(request []byte) (int, []byte, bool
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
locked := true
|
||||
defer func() {
|
||||
if locked {
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
if s.optionReroll == nil {
|
||||
return 0, nil, true, errors.New("player: equipment option design unavailable")
|
||||
}
|
||||
@@ -709,6 +714,10 @@ func (s *EquipmentInventory) mainOptionChange(request []byte) (int, []byte, bool
|
||||
if pendingDirty {
|
||||
s.pendingReroll = pending
|
||||
}
|
||||
// Character health reads account equipment through pictorial ownership.
|
||||
// Release the equipment lock before resolving the equipped character.
|
||||
s.mu.Unlock()
|
||||
locked = false
|
||||
var response []byte
|
||||
if character, ok := s.equippedCharacter(current); ok {
|
||||
response = wire.AppendBytes(response, 1, CharacterWire(character))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bd2server/internal/server/gamedata"
|
||||
"bd2server/internal/server/wire"
|
||||
@@ -227,6 +229,54 @@ func TestEquipmentMainOptionChangeRejectsForgedChoice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquipmentMainOptionChangeEquippedCharacterHealthReadsEquipment(t *testing.T) {
|
||||
fixture := newEquipmentOptionRerollFixture(t)
|
||||
const characterIndex = 920000001
|
||||
characters := &CharacterStore{characters: []Character{{InvenIndex: characterIndex, ID: 50, Level: 1}}}
|
||||
if err := characters.AttachMaxHealth(func(Character) (uint64, error) {
|
||||
// The production max-health provider obtains pictorial equipment via All.
|
||||
if got := fixture.equipment.All()[0].MainOption[0].ID; got != 9 {
|
||||
return 0, fmt.Errorf("health provider observed stale main option %d", got)
|
||||
}
|
||||
return 123, nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.equipment.characters = characters
|
||||
fixture.equipment.owned.Equipment[0].UseChar = characterIndex
|
||||
request := wire.AppendVarint(nil, 1, 60)
|
||||
request = wire.AppendVarint(request, 2, fixture.original.InvenIndex)
|
||||
request = wire.AppendVarint(request, 3, 100)
|
||||
request = wire.AppendVarint(request, 4, 9)
|
||||
type result struct {
|
||||
code int
|
||||
body []byte
|
||||
handled bool
|
||||
err error
|
||||
}
|
||||
done := make(chan result, 1)
|
||||
go func() {
|
||||
code, body, handled, err := fixture.equipment.Handle("/EquipMainOptChange", request)
|
||||
done <- result{code, body, handled, err}
|
||||
}()
|
||||
select {
|
||||
case got := <-done:
|
||||
if got.err != nil || !got.handled || got.code != 537 {
|
||||
t.Fatalf("change result=%+v", got)
|
||||
}
|
||||
character, found, err := wire.Bytes(got.body, 1)
|
||||
if err != nil || !found || len(character) == 0 {
|
||||
t.Fatalf("equipped character response missing: found=%v err=%v", found, err)
|
||||
}
|
||||
hp, found, err := wire.Varint(character, 3)
|
||||
if err != nil || !found || hp != 123 {
|
||||
t.Fatalf("updated health=%d found=%v err=%v", hp, found, err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("equipped main option change deadlocked while resolving character health")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquipmentOptionRerollRejectsForgedMaterialsWithoutCharge(t *testing.T) {
|
||||
fixture := newEquipmentOptionRerollFixture(t)
|
||||
fixture.equipment.BeginSession("option-reroll-forged-material")
|
||||
|
||||
Reference in New Issue
Block a user