Как избежать двойного соединения при восстановлении соединения?

В этих условиях:

  • Клиент теряет сетевое подключение к zk.
  • Проходит минута.
  • Клиент восстанавливает сетевое подключение к zk.

Я получаю следующую панику:

panic: close of closed channel

goroutine 2849 [running]:
github.com/samuel/go-zookeeper/zk.(*Conn).Close(0xc420795180)
  github.com/samuel/go-zookeeper/zk/conn.go:253 47
github.com/curator-go/curator.(*handleHolder).internalClose(0xc4203058f0, 0xc420302470, 0x0)
  github.com/curator-go/curator/state.go:136 +0x8d
github.com/curator-go/curator.(*handleHolder).closeAndReset(0xc4203058f0, 0xc42587cd00, 0x1e)
  github.com/curator-go/curator/state.go:122 +0x2f
github.com/curator-go/curator.(*connectionState).reset(0xc420302420, 0x1b71d87, 0xf)
  github.com/curator-go/curator/state.go:234 +0x55
github.com/curator-go/curator.(*connectionState).handleExpiredSession(0xc420302420)
  github.com/curator-go/curator/state.go:351 +0xd9
github.com/curator-go/curator.(*connectionState).checkState(0xc420302420, 0xffffff90, 0x0, 0x0, 0xc425ed2600, 0xed0e5250a)
  github.com/curator-go/curator/state.go:318 +0x9c
github.com/curator-go/curator.(*connectionState).process(0xc420302420, 0xc425ed2680)
  github.com/curator-go/curator/state.go:299 +0x16d
created by github.com/curator-go/curator.(*Watchers).Fire
  github.com/curator-go/curator/watcher.go:64 +0x96

Это подробная последовательность событий:

  • Клиент теряет сетевое подключение к zk.
  • Проходит минута.
  • Клиент восстанавливает сетевое подключение к zk.
  • goroutine A называет s.ReregisterAll()Conn()checkTimeout()reset (bc 1 минута истекло) → closeAndReset()conn.Close() , который может блокировать секунду
  • goroutine B обрабатывает zk.StateExpired (кластер zk отправляет этот bc, он считает этот клиент мертвым, так как он не выполнял ping во время 2.) → resetcloseAndReset()conn.Close(), что вызывает панику потому что conn.Close() уже закрыл канал соединения c.shouldQuit. И s.zooKeeper.getZookeeperConnection никогда не вызывался goroutine A, потому что он блокировал второй, поэтому нет нового соединения.

Наивное решение, которое я пробовал, - просто использовать мьютексы на reset, но теперь я получаю helper.GetConnectionString() равным пустой строке. Какой лучший способ избежать этого сбоя и попытаться войти в хорошее состояние, когда клиент потеряет, а затем восстановит сетевое подключение? Должно ли исправление быть в github.com/samuel/go-zookeeper, не позволяя закрыть уже закрытое соединение?

(Я написал этот вопрос здесь, но, похоже, проект не хватает в плане обсуждения, поэтому я спрашиваю о SO. )

Ответ 1

zk.Conn имеет метод State(), который возвращает перечисление "Состояние", которое является одним из следующих:

type State int32
const (
    StateUnknown           State = -1
    StateDisconnected      State = 0
    StateConnecting        State = 1
    StateAuthFailed        State = 4
    StateConnectedReadOnly State = 5
    StateSaslAuthenticated State = 6
    StateExpired           State = -112

    StateConnected  = State(100)
    StateHasSession = State(101)
)

Какое состояние "подключено", когда goroutine B вызывает conn.Close()?

Возможным решением было бы добавить переключатель в goroutine B, в котором вы не вызываете conn.Close(), если вы находитесь в conn.StateConnecting.