Use tomb v2, update deps, fix hang on stale connection
This commit is contained in:
@@ -516,7 +516,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
} else {
|
} else {
|
||||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||||
v.Type, v.From, v.Show, v.Status)
|
v.Type, v.From, v.Show, v.Status)
|
||||||
log.Errorf(spew.Sdump(v))
|
log.Error(spew.Sdump(v))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/cockroachdb/pebble"
|
"github.com/cockroachdb/pebble"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/eleksir/go-xmpp"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"gopkg.in/tomb.v1"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config - это у нас глобальная штука :) .
|
// Config - это у нас глобальная штука :) .
|
||||||
@@ -33,7 +33,7 @@ var connecting = false
|
|||||||
var isConnected = false
|
var isConnected = false
|
||||||
|
|
||||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||||
var sigChan = make(chan os.Signal, 1)
|
var sigChan chan os.Signal
|
||||||
|
|
||||||
// Основной инстанс xmpp-клиента.
|
// Основной инстанс xmpp-клиента.
|
||||||
var talk *xmpp.Client
|
var talk *xmpp.Client
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/hjson/hjson-go v3.3.0+incompatible
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -37,6 +37,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.26.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -127,6 +127,8 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||||
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
332
lib.go
332
lib.go
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// sigHandler Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
// sigHandler Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
||||||
func sigHandler() {
|
func sigHandler() error {
|
||||||
log.Debug("Installing signal handler")
|
log.Debug("Installing signal handler")
|
||||||
|
|
||||||
for s := range sigChan {
|
for s := range sigChan {
|
||||||
@@ -86,14 +86,16 @@ func sigHandler() {
|
|||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// establishConnection устанавливает соединение с jabber-сервером.
|
// establishConnection устанавливает соединение с jabber-сервером.
|
||||||
func establishConnection() {
|
func establishConnection() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if connecting && !isConnected {
|
if connecting && !isConnected {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проставляем глобальные переменные.
|
// Проставляем глобальные переменные.
|
||||||
@@ -104,26 +106,22 @@ func establishConnection() {
|
|||||||
talk, err = options.NewClient()
|
talk, err = options.NewClient()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gTomb.Kill(err)
|
return fmt.Errorf("Unable to connect to %s: %w", options.Host, err)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
log.Errorf("Try to send initial KeepAlive, got error: %s", err)
|
return fmt.Errorf("Try to send initial KeepAlive, got error: %w", err)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Connected")
|
log.Info("Connected")
|
||||||
|
|
||||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
||||||
for _, room := range config.Jabber.Channels {
|
for _, room := range config.Jabber.Channels {
|
||||||
go joinMuc(room.Name)
|
gTomb.Go(func() error { return joinMuc(room.Name) })
|
||||||
}
|
}
|
||||||
|
|
||||||
go RotateStatus("")
|
gTomb.Go(func() error { return RotateStatus("") })
|
||||||
|
|
||||||
lastActivity = time.Now().Unix()
|
lastActivity = time.Now().Unix()
|
||||||
connecting = false
|
connecting = false
|
||||||
@@ -134,21 +132,18 @@ func establishConnection() {
|
|||||||
_, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server)
|
_, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("Unable to send disco#info to jabber server: %s", err)
|
return fmt.Errorf("Unable to send disco#info to jabber server: %s", err)
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinMu джойнится к конференциям/каналам/комнатам в джаббере.
|
// joinMu джойнится к конференциям/каналам/комнатам в джаббере.
|
||||||
func joinMuc(room string) {
|
func joinMuc(room string) error {
|
||||||
log.Debugf("Sending disco#info from %s to %s", talk.JID(), room)
|
log.Debugf("Sending disco#info from %s to %s", talk.JID(), room)
|
||||||
|
|
||||||
if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil {
|
if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil {
|
||||||
log.Infof("Unable to send disco#info to MUC %s: %s", room, err)
|
return fmt.Errorf("Unable to send disco#info to MUC %s: %s", room, err)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ждём, пока muc нам вернёт список фичей.
|
// Ждём, пока muc нам вернёт список фичей.
|
||||||
@@ -173,15 +168,13 @@ func joinMuc(room string) {
|
|||||||
|
|
||||||
log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)")
|
log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)")
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Пытаемся зайти в комнату
|
// Пытаемся зайти в комнату
|
||||||
if _, err := talk.JoinMUCNoHistory(room, getBotNickFromRoomConfig(room)); err != nil {
|
if _, err := talk.JoinMUCNoHistory(room, getBotNickFromRoomConfig(room)); err != nil {
|
||||||
log.Errorf("Unable to join to MUC: %s", room)
|
return fmt.Errorf("Unable to join to MUC: %s, %w", room, err)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Joining to MUC: %s", room)
|
log.Infof("Joining to MUC: %s", room)
|
||||||
@@ -206,172 +199,152 @@ func joinMuc(room string) {
|
|||||||
20*int(config.Jabber.ConnectionTimeout)+1,
|
20*int(config.Jabber.ConnectionTimeout)+1,
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вот теперь точно можно слать статус.
|
// Вот теперь точно можно слать статус.
|
||||||
log.Infof("Joined to MUC: %s", room)
|
log.Infof("Joined to MUC: %s", room)
|
||||||
|
|
||||||
go RotateStatus(room)
|
gTomb.Go(func() error { return RotateStatus(room) })
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
||||||
// они выкидывают клиента через некоторое время неактивности.
|
// они выкидывают клиента через некоторое время неактивности.
|
||||||
func probeServerLiveness() { //nolint:gocognit
|
func probeServerLiveness() error { //nolint:gocognit
|
||||||
for {
|
for {
|
||||||
select {
|
for {
|
||||||
case <-gTomb.Dying():
|
if shutdown {
|
||||||
gTomb.Done()
|
return nil
|
||||||
return
|
}
|
||||||
|
|
||||||
default:
|
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||||
for {
|
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
if shutdown {
|
time.Sleep(sleepTime)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
if !isConnected {
|
||||||
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
continue
|
||||||
time.Sleep(sleepTime)
|
}
|
||||||
|
|
||||||
if !isConnected {
|
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||||
continue
|
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||||
}
|
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
if serverCapsQueried { // Сервер ответил на disco#info
|
||||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
var (
|
||||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
value interface{}
|
||||||
// continue
|
exist bool
|
||||||
// }
|
)
|
||||||
|
|
||||||
if serverCapsQueried { // Сервер ответил на disco#info
|
value, exist = serverCapsList.Get("urn:xmpp:ping")
|
||||||
var (
|
|
||||||
value interface{}
|
|
||||||
exist bool
|
|
||||||
)
|
|
||||||
|
|
||||||
value, exist = serverCapsList.Get("urn:xmpp:ping")
|
switch {
|
||||||
|
// Сервер анонсировал, что умеет в c2s пинги
|
||||||
|
case exist && value.(bool):
|
||||||
|
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||||
|
// максимальную корректировку разброса.
|
||||||
|
txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout
|
||||||
|
txTimeout += config.Jabber.PingSplayDelay
|
||||||
|
rxTimeout := txTimeout
|
||||||
|
|
||||||
switch {
|
rxTimeAgo := time.Now().Unix() - serverPingTimestampRx
|
||||||
// Сервер анонсировал, что умеет в c2s пинги
|
|
||||||
case exist && value.(bool):
|
|
||||||
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
|
||||||
// максимальную корректировку разброса.
|
|
||||||
txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout
|
|
||||||
txTimeout += config.Jabber.PingSplayDelay
|
|
||||||
rxTimeout := txTimeout
|
|
||||||
|
|
||||||
rxTimeAgo := time.Now().Unix() - serverPingTimestampRx
|
if serverPingTimestampTx > 0 { // Первая пуля от нас ушла...
|
||||||
|
switch {
|
||||||
|
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
||||||
|
case rxTimeAgo > (rxTimeout * 2):
|
||||||
|
err := fmt.Errorf( //nolint:goerr113
|
||||||
|
"stall connection detected. No c2s pong for %d seconds",
|
||||||
|
rxTimeAgo,
|
||||||
|
)
|
||||||
|
|
||||||
if serverPingTimestampTx > 0 { // Первая пуля от нас ушла...
|
return err
|
||||||
switch {
|
|
||||||
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
|
||||||
case rxTimeAgo > (rxTimeout * 2):
|
|
||||||
err := fmt.Errorf( //nolint:goerr113
|
|
||||||
"stall connection detected. No c2s pong for %d seconds",
|
|
||||||
rxTimeAgo,
|
|
||||||
)
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
// По-умолчанию, мы отправляем c2s пинг
|
||||||
|
default:
|
||||||
continue
|
log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
// По-умолчанию, мы отправляем c2s пинг
|
|
||||||
default:
|
|
||||||
log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
|
||||||
|
|
||||||
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
serverPingTimestampTx = time.Now().Unix()
|
|
||||||
}
|
|
||||||
} else { // Первая пуля пока не вылетела, отправляем
|
|
||||||
log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
|
||||||
|
|
||||||
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
||||||
gTomb.Kill(err)
|
return err
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverPingTimestampTx = time.Now().Unix()
|
serverPingTimestampTx = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
} else { // Первая пуля пока не вылетела, отправляем
|
||||||
|
log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
// Сервер не анонсировал, что умеет в c2s пинги
|
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
||||||
default:
|
return err
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
|
||||||
|
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverPingTimestampTx = time.Now().Unix()
|
||||||
}
|
}
|
||||||
} else { // Сервер не ответил на disco#info
|
|
||||||
|
// Сервер не анонсировал, что умеет в c2s пинги
|
||||||
|
default:
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
gTomb.Kill(err)
|
return err
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else { // Сервер не ответил на disco#info
|
||||||
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// probeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е.
|
// probeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е.
|
||||||
func probeMUCLiveness() { //nolint:gocognit
|
func probeMUCLiveness() error { //nolint:gocognit
|
||||||
for {
|
for {
|
||||||
select {
|
for {
|
||||||
case <-gTomb.Dying():
|
for _, room := range roomsConnected {
|
||||||
gTomb.Done()
|
var (
|
||||||
return
|
exist bool
|
||||||
|
lastActivityTS interface{}
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||||
for {
|
if lastActivityTS, exist = lastMucActivity.Get(room); !exist {
|
||||||
for _, room := range roomsConnected {
|
continue
|
||||||
var (
|
}
|
||||||
exist bool
|
|
||||||
lastActivityTS interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
// Если время последней активности в чятике не превысило
|
||||||
if lastActivityTS, exist = lastMucActivity.Get(room); !exist {
|
// config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем.
|
||||||
|
if (time.Now().Unix() - lastActivityTS.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
||||||
|
go func(room string) {
|
||||||
|
// Небольшая рандомная задержка перед пингом комнаты
|
||||||
|
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if err := talk.PingS2S(talk.JID(), room+"/"+getBotNickFromRoomConfig(room)); err != nil {
|
||||||
|
gTomb.Kill(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}(room)
|
||||||
|
*/
|
||||||
|
|
||||||
// Если время последней активности в чятике не превысило
|
var roomMap interface{}
|
||||||
// config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем.
|
|
||||||
if (time.Now().Unix() - lastActivityTS.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
roomMap, exist = mucCapsList.Get(room)
|
||||||
go func(room string) {
|
|
||||||
// Небольшая рандомная задержка перед пингом комнаты
|
|
||||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
|
|
||||||
if err := talk.PingS2S(talk.JID(), room+"/"+getBotNickFromRoomConfig(room)); err != nil {
|
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
||||||
gTomb.Kill(err)
|
// Server Optimization.
|
||||||
continue
|
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
||||||
}
|
gTomb.Go(
|
||||||
}(room)
|
func() error {
|
||||||
*/
|
|
||||||
|
|
||||||
var roomMap interface{}
|
|
||||||
|
|
||||||
roomMap, exist = mucCapsList.Get(room)
|
|
||||||
|
|
||||||
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
|
||||||
// Server Optimization.
|
|
||||||
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
|
||||||
go func(room string) {
|
|
||||||
// Небольшая рандомная задержка перед пингом комнаты.
|
// Небольшая рандомная задержка перед пингом комнаты.
|
||||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
time.Sleep(sleepTime)
|
time.Sleep(sleepTime)
|
||||||
@@ -379,65 +352,54 @@ func probeMUCLiveness() { //nolint:gocognit
|
|||||||
log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room)
|
log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room)
|
||||||
|
|
||||||
if err := talk.PingS2S(talk.JID(), room); err != nil {
|
if err := talk.PingS2S(talk.JID(), room); err != nil {
|
||||||
err := fmt.Errorf("unable to ping MUC %s: %w", room, err)
|
return fmt.Errorf("unable to ping MUC %s: %w", room, err)
|
||||||
gTomb.Kill(err)
|
|
||||||
}
|
}
|
||||||
}(room)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
||||||
func RotateStatus(room string) {
|
func RotateStatus(room string) error {
|
||||||
for {
|
for {
|
||||||
select {
|
// TODO: Переделать на ticker-ы
|
||||||
case <-gTomb.Dying():
|
totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||||
gTomb.Done()
|
totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
for {
|
||||||
// TODO: Переделать на ticker-ы
|
status := randomPhrase(config.Jabber.RuntimeStatus.Text)
|
||||||
totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second
|
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
||||||
totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
|
||||||
|
|
||||||
for {
|
var p xmpp.Presence
|
||||||
status := randomPhrase(config.Jabber.RuntimeStatus.Text)
|
|
||||||
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
|
||||||
|
|
||||||
var p xmpp.Presence
|
if room != "" {
|
||||||
|
p = xmpp.Presence{ //nolint:exhaustruct
|
||||||
if room != "" {
|
To: room,
|
||||||
p = xmpp.Presence{ //nolint:exhaustruct
|
Status: status,
|
||||||
To: room,
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p = xmpp.Presence{ //nolint:exhaustruct
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if _, err := talk.SendPresence(p); err != nil {
|
p = xmpp.Presence{ //nolint:exhaustruct
|
||||||
log.Infof("Unable to send presence to MUC %s: %s", room, err)
|
Status: status,
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим.
|
|
||||||
if config.Jabber.RuntimeStatus.RotationTime == 0 {
|
|
||||||
gTomb.Done()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(totalSleepTime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := talk.SendPresence(p); err != nil {
|
||||||
|
return fmt.Errorf("Unable to send presence to MUC %s: %s", room, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим.
|
||||||
|
if config.Jabber.RuntimeStatus.RotationTime == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(totalSleepTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
277
main.go
277
main.go
@@ -2,10 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -14,10 +11,10 @@ import (
|
|||||||
"github.com/eleksir/go-xmpp"
|
"github.com/eleksir/go-xmpp"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/tomb.v1"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func main() {
|
||||||
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
||||||
DisableQuote: true,
|
DisableQuote: true,
|
||||||
DisableLevelTruncation: false,
|
DisableLevelTruncation: false,
|
||||||
@@ -35,73 +32,78 @@ func init() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
for {
|
||||||
configLocations = append(configLocations, configJSONPath)
|
sigChan = make(chan os.Signal, 1)
|
||||||
|
talk = new(xmpp.Client)
|
||||||
|
options = new(xmpp.Options)
|
||||||
|
|
||||||
if err := readConfig(); err != nil {
|
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||||
log.Error(err)
|
configLocations = append(configLocations, configJSONPath)
|
||||||
|
|
||||||
os.Exit(1)
|
if err := readConfig(); err != nil {
|
||||||
}
|
log.Error(err)
|
||||||
|
|
||||||
// no panic
|
os.Exit(1)
|
||||||
switch config.Loglevel {
|
|
||||||
case "fatal":
|
|
||||||
log.SetLevel(log.FatalLevel)
|
|
||||||
case "error":
|
|
||||||
log.SetLevel(log.ErrorLevel)
|
|
||||||
case "warn":
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
case "trace":
|
|
||||||
log.SetLevel(log.TraceLevel)
|
|
||||||
default:
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Откроем лог и скормим его логгеру
|
|
||||||
if config.Log != "" {
|
|
||||||
logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to open log file %s: %s", config.Log, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.SetOutput(logfile)
|
// no panic
|
||||||
}
|
switch config.Loglevel {
|
||||||
|
case "fatal":
|
||||||
|
log.SetLevel(log.FatalLevel)
|
||||||
|
case "error":
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
case "warn":
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
case "info":
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
case "debug":
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
case "trace":
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
default:
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
myLogLevel := log.GetLevel()
|
// Откроем лог и скормим его логгеру
|
||||||
log.Warnf("Loglevel set to %v", myLogLevel)
|
if config.Log != "" {
|
||||||
|
logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
verboseClient := false
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to open log file %s: %s", config.Log, err)
|
||||||
|
}
|
||||||
|
|
||||||
if myLogLevel == log.TraceLevel {
|
log.SetOutput(logfile)
|
||||||
verboseClient = true
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace.
|
myLogLevel := log.GetLevel()
|
||||||
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
log.Warnf("Loglevel set to %v", myLogLevel)
|
||||||
|
|
||||||
// Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком.
|
// Через tomb попробуем сделать выход горутинок управляемым.
|
||||||
go sigHandler()
|
gTomb := tomb.Tomb{}
|
||||||
signal.Notify(sigChan, os.Interrupt)
|
|
||||||
|
|
||||||
// Иницализируем redis-клиента.
|
// Иницализируем redis-клиента.
|
||||||
redisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct
|
redisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct
|
||||||
Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port),
|
Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port),
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Debugf("Lazy connect() to redis at %s:%d", config.Redis.Server, config.Redis.Port)
|
log.Debugf("Lazy connect() to redis at %s:%d", config.Redis.Server, config.Redis.Port)
|
||||||
subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel)
|
subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel)
|
||||||
|
|
||||||
go redisLoop(subscriber.Channel())
|
verboseClient := false
|
||||||
|
|
||||||
|
if myLogLevel == log.TraceLevel {
|
||||||
|
verboseClient = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace.
|
||||||
|
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
||||||
|
|
||||||
|
// Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком.
|
||||||
|
gTomb.Go(func() error { return sigHandler() })
|
||||||
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
|
|
||||||
|
gTomb.Go(func() error { return redisLoop(subscriber.Channel()) })
|
||||||
|
|
||||||
for {
|
|
||||||
options = &xmpp.Options{ //nolint:exhaustruct
|
options = &xmpp.Options{ //nolint:exhaustruct
|
||||||
Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port),
|
Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port),
|
||||||
User: config.Jabber.User,
|
User: config.Jabber.User,
|
||||||
@@ -121,109 +123,84 @@ func main() {
|
|||||||
DialTimeout: time.Duration(config.Jabber.ConnectionTimeout) * time.Second,
|
DialTimeout: time.Duration(config.Jabber.ConnectionTimeout) * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Через tomb попробуем сделать выход горутинок управляемым.
|
|
||||||
gTomb = tomb.Tomb{}
|
|
||||||
|
|
||||||
// Устанавливаем соединение и гребём события, посылаемые сервером.
|
// Устанавливаем соединение и гребём события, посылаемые сервером.
|
||||||
myLoop()
|
gTomb.Go(func() error { return myLoop() })
|
||||||
|
|
||||||
log.Error(gTomb.Wait())
|
// Ловим первый же kill и не дождаемся остальных, хотя формально надо бы.
|
||||||
|
_ = <-gTomb.Dying()
|
||||||
// Если у нас wire error, то вызов .Close() повлечёт за собой ошибку, но мы вынуждены звать .Close(), чтоб
|
|
||||||
// закрыть tls контекст и почистить всё что связанно с прерванным соединением.
|
|
||||||
log.Infoln("Closing connection to jabber server")
|
|
||||||
|
|
||||||
_ = talk.Close()
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func myLoop() {
|
func myLoop() error {
|
||||||
for {
|
for {
|
||||||
select {
|
// Зададим начальное значение глобальным переменным.
|
||||||
case <-gTomb.Dying():
|
serverPingTimestampRx = 0
|
||||||
gTomb.Done()
|
serverPingTimestampTx = 0
|
||||||
|
roomsConnected = make([]string, 1)
|
||||||
|
lastActivity = 0
|
||||||
|
lastServerActivity = 0
|
||||||
|
lastMucActivity = NewCollection()
|
||||||
|
serverCapsQueried = false
|
||||||
|
serverCapsList = NewCollection()
|
||||||
|
mucCapsList = NewCollection()
|
||||||
|
serverPingTimestampTx = 0
|
||||||
|
serverPingTimestampRx = 0
|
||||||
|
roomPresences = NewCollection()
|
||||||
|
|
||||||
return
|
// Установим коннект.
|
||||||
|
if err := establishConnection(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
default:
|
return err
|
||||||
// Зададим начальное значение глобальным переменным.
|
|
||||||
serverPingTimestampRx = 0
|
|
||||||
serverPingTimestampTx = 0
|
|
||||||
roomsConnected = make([]string, 1)
|
|
||||||
lastActivity = 0
|
|
||||||
lastServerActivity = 0
|
|
||||||
lastMucActivity = NewCollection()
|
|
||||||
serverCapsQueried = false
|
|
||||||
serverCapsList = NewCollection()
|
|
||||||
mucCapsList = NewCollection()
|
|
||||||
serverPingTimestampTx = 0
|
|
||||||
serverPingTimestampRx = 0
|
|
||||||
roomPresences = NewCollection()
|
|
||||||
|
|
||||||
// Установим коннект.
|
|
||||||
establishConnection()
|
|
||||||
|
|
||||||
serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен.
|
|
||||||
|
|
||||||
// Тыкаем сервер палочкой, проверяем, что коннект жив и переустанавливаем его, если он не жив.
|
|
||||||
go probeServerLiveness()
|
|
||||||
|
|
||||||
// Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали.
|
|
||||||
go probeMUCLiveness()
|
|
||||||
|
|
||||||
// Гребём сообщения...
|
|
||||||
for {
|
|
||||||
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например).
|
|
||||||
// грести не надо.
|
|
||||||
if shutdown {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isConnected {
|
|
||||||
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вынимаем ивент из "провода".
|
|
||||||
chat, err := talk.Recv()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Unable to get events from server: %s", err)
|
|
||||||
|
|
||||||
// Попробуем скрасить печальные будни generic-ошибок...
|
|
||||||
switch {
|
|
||||||
// Стрим не читается, он закрылся с той стороны во время чтения.
|
|
||||||
case errors.Is(err, io.EOF):
|
|
||||||
err = fmt.Errorf("tcp stream closed while reading, err=%w", err)
|
|
||||||
|
|
||||||
// Пытаемся читать закрытый сокет.
|
|
||||||
case errors.Is(err, net.ErrClosed):
|
|
||||||
err = fmt.Errorf("unable to read closed socket, err=%w", err)
|
|
||||||
|
|
||||||
// Не смогли записать в сокет.
|
|
||||||
case errors.Is(err, net.ErrWriteToConnected):
|
|
||||||
err = fmt.Errorf("unable to write to socket, err=%w", err)
|
|
||||||
|
|
||||||
// Не сетевая проблема.
|
|
||||||
default:
|
|
||||||
// Это уже что-то странное.
|
|
||||||
// Вероятно, ошибка парсинга xml. Собственно, баг сервера, тут мы ничего поделать не можем.
|
|
||||||
err = fmt.Errorf("error during parsing received message, err=%w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
// Не забываем выходить на ошибке :(
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseEvent(chat)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен.
|
||||||
|
|
||||||
|
// Тыкаем сервер палочкой, проверяем, что коннект жив и вываливаемся из mainLoop, если он не жив.
|
||||||
|
gTomb.Go(
|
||||||
|
func() error {
|
||||||
|
err := probeServerLiveness()
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали.
|
||||||
|
// Если пинги до комнаты пропали, то это фактически значит, что либо сервер потерял связь с MUC-компонентом,
|
||||||
|
// либо у нас какой-то wire error.
|
||||||
|
// gTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет.
|
||||||
|
go probeMUCLiveness()
|
||||||
|
|
||||||
|
// Гребём сообщения...
|
||||||
|
for {
|
||||||
|
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например).
|
||||||
|
// грести не надо.
|
||||||
|
if shutdown {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isConnected {
|
||||||
|
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вынимаем ивент из "провода".
|
||||||
|
chat, err := talk.Recv()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get recieve data from socket: %s", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEvent(chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
redis.go
6
redis.go
@@ -9,17 +9,19 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func redisLoop(redisMsgChan <-chan *redis.Message) {
|
func redisLoop(redisMsgChan <-chan *redis.Message) error {
|
||||||
// Обработчик событий от редиски
|
// Обработчик событий от редиски
|
||||||
for msg := range redisMsgChan {
|
for msg := range redisMsgChan {
|
||||||
if shutdown {
|
if shutdown {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := redisMsgParser(msg.Payload); err != nil {
|
if err := redisMsgParser(msg.Payload); err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки.
|
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки.
|
||||||
|
|||||||
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
56
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
56
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package context defines the Context type, which carries deadlines,
|
||||||
|
// cancelation signals, and other request-scoped values across API boundaries
|
||||||
|
// and between processes.
|
||||||
|
// As of Go 1.7 this package is available in the standard library under the
|
||||||
|
// name context. https://golang.org/pkg/context.
|
||||||
|
//
|
||||||
|
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||||
|
// servers should accept a Context. The chain of function calls between must
|
||||||
|
// propagate the Context, optionally replacing it with a modified copy created
|
||||||
|
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||||
|
//
|
||||||
|
// Programs that use Contexts should follow these rules to keep interfaces
|
||||||
|
// consistent across packages and enable static analysis tools to check context
|
||||||
|
// propagation:
|
||||||
|
//
|
||||||
|
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||||
|
// explicitly to each function that needs it. The Context should be the first
|
||||||
|
// parameter, typically named ctx:
|
||||||
|
//
|
||||||
|
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||||
|
// // ... use ctx ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||||
|
// if you are unsure about which Context to use.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
//
|
||||||
|
// The same Context may be passed to functions running in different goroutines;
|
||||||
|
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/context for example code for a server that uses
|
||||||
|
// Contexts.
|
||||||
|
package context // import "golang.org/x/net/context"
|
||||||
|
|
||||||
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
|
// requests.
|
||||||
|
func Background() Context {
|
||||||
|
return background
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||||
|
// it's unclear which Context to use or it is not yet available (because the
|
||||||
|
// surrounding function has not yet been extended to accept a Context
|
||||||
|
// parameter). TODO is recognized by static analysis tools that determine
|
||||||
|
// whether Contexts are propagated correctly in a program.
|
||||||
|
func TODO() Context {
|
||||||
|
return todo
|
||||||
|
}
|
||||||
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
Normal file
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.7
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context" // standard library's context, as of Go 1.7
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
todo = context.TODO()
|
||||||
|
background = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||||
|
var Canceled = context.Canceled
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
var DeadlineExceeded = context.DeadlineExceeded
|
||||||
|
|
||||||
|
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||||
|
// context's Done channel is closed when the returned cancel function is called
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
ctx, f := context.WithCancel(parent)
|
||||||
|
return ctx, f
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||||
|
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||||
|
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||||
|
// context's Done channel is closed when the deadline expires, when the returned
|
||||||
|
// cancel function is called, or when the parent context's Done channel is
|
||||||
|
// closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||||
|
ctx, f := context.WithDeadline(parent, deadline)
|
||||||
|
return ctx, f
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return WithDeadline(parent, time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is
|
||||||
|
// val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||||
|
return context.WithValue(parent, key, val)
|
||||||
|
}
|
||||||
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
Normal file
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "context" // standard library's context, as of Go 1.7
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context = context.Context
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc = context.CancelFunc
|
||||||
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
Normal file
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.7
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||||
|
// struct{}, since vars of this type must have distinct addresses.
|
||||||
|
type emptyCtx int
|
||||||
|
|
||||||
|
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *emptyCtx) String() string {
|
||||||
|
switch e {
|
||||||
|
case background:
|
||||||
|
return "context.Background"
|
||||||
|
case todo:
|
||||||
|
return "context.TODO"
|
||||||
|
}
|
||||||
|
return "unknown empty Context"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = new(emptyCtx)
|
||||||
|
todo = new(emptyCtx)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||||
|
var Canceled = errors.New("context canceled")
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||||
|
|
||||||
|
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||||
|
// context's Done channel is closed when the returned cancel function is called
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
c := newCancelCtx(parent)
|
||||||
|
propagateCancel(parent, c)
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCancelCtx returns an initialized cancelCtx.
|
||||||
|
func newCancelCtx(parent Context) *cancelCtx {
|
||||||
|
return &cancelCtx{
|
||||||
|
Context: parent,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagateCancel arranges for child to be canceled when parent is.
|
||||||
|
func propagateCancel(parent Context, child canceler) {
|
||||||
|
if parent.Done() == nil {
|
||||||
|
return // parent is never canceled
|
||||||
|
}
|
||||||
|
if p, ok := parentCancelCtx(parent); ok {
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.err != nil {
|
||||||
|
// parent has already been canceled
|
||||||
|
child.cancel(false, p.err)
|
||||||
|
} else {
|
||||||
|
if p.children == nil {
|
||||||
|
p.children = make(map[canceler]bool)
|
||||||
|
}
|
||||||
|
p.children[child] = true
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-parent.Done():
|
||||||
|
child.cancel(false, parent.Err())
|
||||||
|
case <-child.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parentCancelCtx follows a chain of parent references until it finds a
|
||||||
|
// *cancelCtx. This function understands how each of the concrete types in this
|
||||||
|
// package represents its parent.
|
||||||
|
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||||
|
for {
|
||||||
|
switch c := parent.(type) {
|
||||||
|
case *cancelCtx:
|
||||||
|
return c, true
|
||||||
|
case *timerCtx:
|
||||||
|
return c.cancelCtx, true
|
||||||
|
case *valueCtx:
|
||||||
|
parent = c.Context
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeChild removes a context from its parent.
|
||||||
|
func removeChild(parent Context, child canceler) {
|
||||||
|
p, ok := parentCancelCtx(parent)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.children != nil {
|
||||||
|
delete(p.children, child)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A canceler is a context type that can be canceled directly. The
|
||||||
|
// implementations are *cancelCtx and *timerCtx.
|
||||||
|
type canceler interface {
|
||||||
|
cancel(removeFromParent bool, err error)
|
||||||
|
Done() <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||||
|
// that implement canceler.
|
||||||
|
type cancelCtx struct {
|
||||||
|
Context
|
||||||
|
|
||||||
|
done chan struct{} // closed by the first cancel call.
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
children map[canceler]bool // set to nil by the first cancel call
|
||||||
|
err error // set to non-nil by the first cancel call
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Done() <-chan struct{} {
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Err() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel closes c.done, cancels each of c's children, and, if
|
||||||
|
// removeFromParent is true, removes c from its parent's children.
|
||||||
|
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
if err == nil {
|
||||||
|
panic("context: internal error: missing cancel error")
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.err != nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return // already canceled
|
||||||
|
}
|
||||||
|
c.err = err
|
||||||
|
close(c.done)
|
||||||
|
for child := range c.children {
|
||||||
|
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||||
|
child.cancel(false, err)
|
||||||
|
}
|
||||||
|
c.children = nil
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if removeFromParent {
|
||||||
|
removeChild(c.Context, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||||
|
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||||
|
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||||
|
// context's Done channel is closed when the deadline expires, when the returned
|
||||||
|
// cancel function is called, or when the parent context's Done channel is
|
||||||
|
// closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||||
|
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||||
|
// The current deadline is already sooner than the new one.
|
||||||
|
return WithCancel(parent)
|
||||||
|
}
|
||||||
|
c := &timerCtx{
|
||||||
|
cancelCtx: newCancelCtx(parent),
|
||||||
|
deadline: deadline,
|
||||||
|
}
|
||||||
|
propagateCancel(parent, c)
|
||||||
|
d := deadline.Sub(time.Now())
|
||||||
|
if d <= 0 {
|
||||||
|
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.err == nil {
|
||||||
|
c.timer = time.AfterFunc(d, func() {
|
||||||
|
c.cancel(true, DeadlineExceeded)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||||
|
// implement Done and Err. It implements cancel by stopping its timer then
|
||||||
|
// delegating to cancelCtx.cancel.
|
||||||
|
type timerCtx struct {
|
||||||
|
*cancelCtx
|
||||||
|
timer *time.Timer // Under cancelCtx.mu.
|
||||||
|
|
||||||
|
deadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return c.deadline, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
c.cancelCtx.cancel(false, err)
|
||||||
|
if removeFromParent {
|
||||||
|
// Remove this timerCtx from its parent cancelCtx's children.
|
||||||
|
removeChild(c.cancelCtx.Context, c)
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.timer != nil {
|
||||||
|
c.timer.Stop()
|
||||||
|
c.timer = nil
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return WithDeadline(parent, time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is
|
||||||
|
// val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||||
|
return &valueCtx{parent, key, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||||
|
// delegates all other calls to the embedded Context.
|
||||||
|
type valueCtx struct {
|
||||||
|
Context
|
||||||
|
key, val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||||
|
if c.key == key {
|
||||||
|
return c.val
|
||||||
|
}
|
||||||
|
return c.Context.Value(key)
|
||||||
|
}
|
||||||
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
Normal file
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context interface {
|
||||||
|
// Deadline returns the time when work done on behalf of this context
|
||||||
|
// should be canceled. Deadline returns ok==false when no deadline is
|
||||||
|
// set. Successive calls to Deadline return the same results.
|
||||||
|
Deadline() (deadline time.Time, ok bool)
|
||||||
|
|
||||||
|
// Done returns a channel that's closed when work done on behalf of this
|
||||||
|
// context should be canceled. Done may return nil if this context can
|
||||||
|
// never be canceled. Successive calls to Done return the same value.
|
||||||
|
//
|
||||||
|
// WithCancel arranges for Done to be closed when cancel is called;
|
||||||
|
// WithDeadline arranges for Done to be closed when the deadline
|
||||||
|
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||||
|
// elapses.
|
||||||
|
//
|
||||||
|
// Done is provided for use in select statements:
|
||||||
|
//
|
||||||
|
// // Stream generates values with DoSomething and sends them to out
|
||||||
|
// // until DoSomething returns an error or ctx.Done is closed.
|
||||||
|
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||||
|
// for {
|
||||||
|
// v, err := DoSomething(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return ctx.Err()
|
||||||
|
// case out <- v:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||||
|
// a Done channel for cancelation.
|
||||||
|
Done() <-chan struct{}
|
||||||
|
|
||||||
|
// Err returns a non-nil error value after Done is closed. Err returns
|
||||||
|
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||||
|
// context's deadline passed. No other values for Err are defined.
|
||||||
|
// After Done is closed, successive calls to Err return the same value.
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// Value returns the value associated with this context for key, or nil
|
||||||
|
// if no value is associated with key. Successive calls to Value with
|
||||||
|
// the same key returns the same result.
|
||||||
|
//
|
||||||
|
// Use context values only for request-scoped data that transits
|
||||||
|
// processes and API boundaries, not for passing optional parameters to
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// A key identifies a specific value in a Context. Functions that wish
|
||||||
|
// to store values in Context typically allocate a key in a global
|
||||||
|
// variable then use that key as the argument to context.WithValue and
|
||||||
|
// Context.Value. A key can be any type that supports equality;
|
||||||
|
// packages should define keys as an unexported type to avoid
|
||||||
|
// collisions.
|
||||||
|
//
|
||||||
|
// Packages that define a Context key should provide type-safe accessors
|
||||||
|
// for the values stores using that key:
|
||||||
|
//
|
||||||
|
// // Package user defines a User type that's stored in Contexts.
|
||||||
|
// package user
|
||||||
|
//
|
||||||
|
// import "golang.org/x/net/context"
|
||||||
|
//
|
||||||
|
// // User is the type of value stored in the Contexts.
|
||||||
|
// type User struct {...}
|
||||||
|
//
|
||||||
|
// // key is an unexported type for keys defined in this package.
|
||||||
|
// // This prevents collisions with keys defined in other packages.
|
||||||
|
// type key int
|
||||||
|
//
|
||||||
|
// // userKey is the key for user.User values in Contexts. It is
|
||||||
|
// // unexported; clients use user.NewContext and user.FromContext
|
||||||
|
// // instead of using this key directly.
|
||||||
|
// var userKey key = 0
|
||||||
|
//
|
||||||
|
// // NewContext returns a new Context that carries value u.
|
||||||
|
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||||
|
// return context.WithValue(ctx, userKey, u)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // FromContext returns the User value stored in ctx, if any.
|
||||||
|
// func FromContext(ctx context.Context) (*User, bool) {
|
||||||
|
// u, ok := ctx.Value(userKey).(*User)
|
||||||
|
// return u, ok
|
||||||
|
// }
|
||||||
|
Value(key interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc func()
|
||||||
176
vendor/gopkg.in/tomb.v1/tomb.go
generated
vendored
176
vendor/gopkg.in/tomb.v1/tomb.go
generated
vendored
@@ -1,176 +0,0 @@
|
|||||||
// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
//
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer in the documentation
|
|
||||||
// and/or other materials provided with the distribution.
|
|
||||||
// * Neither the name of the copyright holder nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
// The tomb package offers a conventional API for clean goroutine termination.
|
|
||||||
//
|
|
||||||
// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
|
|
||||||
// and the reason for its death.
|
|
||||||
//
|
|
||||||
// The zero value of a Tomb assumes that a goroutine is about to be
|
|
||||||
// created or already alive. Once Kill or Killf is called with an
|
|
||||||
// argument that informs the reason for death, the goroutine is in
|
|
||||||
// a dying state and is expected to terminate soon. Right before the
|
|
||||||
// goroutine function or method returns, Done must be called to inform
|
|
||||||
// that the goroutine is indeed dead and about to stop running.
|
|
||||||
//
|
|
||||||
// A Tomb exposes Dying and Dead channels. These channels are closed
|
|
||||||
// when the Tomb state changes in the respective way. They enable
|
|
||||||
// explicit blocking until the state changes, and also to selectively
|
|
||||||
// unblock select statements accordingly.
|
|
||||||
//
|
|
||||||
// When the tomb state changes to dying and there's still logic going
|
|
||||||
// on within the goroutine, nested functions and methods may choose to
|
|
||||||
// return ErrDying as their error value, as this error won't alter the
|
|
||||||
// tomb state if provided to the Kill method. This is a convenient way to
|
|
||||||
// follow standard Go practices in the context of a dying tomb.
|
|
||||||
//
|
|
||||||
// For background and a detailed example, see the following blog post:
|
|
||||||
//
|
|
||||||
// http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
|
|
||||||
//
|
|
||||||
// For a more complex code snippet demonstrating the use of multiple
|
|
||||||
// goroutines with a single Tomb, see:
|
|
||||||
//
|
|
||||||
// http://play.golang.org/p/Xh7qWsDPZP
|
|
||||||
//
|
|
||||||
package tomb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
|
|
||||||
// and the reason for its death.
|
|
||||||
//
|
|
||||||
// See the package documentation for details.
|
|
||||||
type Tomb struct {
|
|
||||||
m sync.Mutex
|
|
||||||
dying chan struct{}
|
|
||||||
dead chan struct{}
|
|
||||||
reason error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrStillAlive = errors.New("tomb: still alive")
|
|
||||||
ErrDying = errors.New("tomb: dying")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Tomb) init() {
|
|
||||||
t.m.Lock()
|
|
||||||
if t.dead == nil {
|
|
||||||
t.dead = make(chan struct{})
|
|
||||||
t.dying = make(chan struct{})
|
|
||||||
t.reason = ErrStillAlive
|
|
||||||
}
|
|
||||||
t.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dead returns the channel that can be used to wait
|
|
||||||
// until t.Done has been called.
|
|
||||||
func (t *Tomb) Dead() <-chan struct{} {
|
|
||||||
t.init()
|
|
||||||
return t.dead
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dying returns the channel that can be used to wait
|
|
||||||
// until t.Kill or t.Done has been called.
|
|
||||||
func (t *Tomb) Dying() <-chan struct{} {
|
|
||||||
t.init()
|
|
||||||
return t.dying
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait blocks until the goroutine is in a dead state and returns the
|
|
||||||
// reason for its death.
|
|
||||||
func (t *Tomb) Wait() error {
|
|
||||||
t.init()
|
|
||||||
<-t.dead
|
|
||||||
t.m.Lock()
|
|
||||||
reason := t.reason
|
|
||||||
t.m.Unlock()
|
|
||||||
return reason
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done flags the goroutine as dead, and should be called a single time
|
|
||||||
// right before the goroutine function or method returns.
|
|
||||||
// If the goroutine was not already in a dying state before Done is
|
|
||||||
// called, it will be flagged as dying and dead at once with no
|
|
||||||
// error.
|
|
||||||
func (t *Tomb) Done() {
|
|
||||||
t.Kill(nil)
|
|
||||||
close(t.dead)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill flags the goroutine as dying for the given reason.
|
|
||||||
// Kill may be called multiple times, but only the first
|
|
||||||
// non-nil error is recorded as the reason for termination.
|
|
||||||
//
|
|
||||||
// If reason is ErrDying, the previous reason isn't replaced
|
|
||||||
// even if it is nil. It's a runtime error to call Kill with
|
|
||||||
// ErrDying if t is not in a dying state.
|
|
||||||
func (t *Tomb) Kill(reason error) {
|
|
||||||
t.init()
|
|
||||||
t.m.Lock()
|
|
||||||
defer t.m.Unlock()
|
|
||||||
if reason == ErrDying {
|
|
||||||
if t.reason == ErrStillAlive {
|
|
||||||
panic("tomb: Kill with ErrDying while still alive")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if t.reason == nil || t.reason == ErrStillAlive {
|
|
||||||
t.reason = reason
|
|
||||||
}
|
|
||||||
// If the receive on t.dying succeeds, then
|
|
||||||
// it can only be because we have already closed it.
|
|
||||||
// If it blocks, then we know that it needs to be closed.
|
|
||||||
select {
|
|
||||||
case <-t.dying:
|
|
||||||
default:
|
|
||||||
close(t.dying)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Killf works like Kill, but builds the reason providing the received
|
|
||||||
// arguments to fmt.Errorf. The generated error is also returned.
|
|
||||||
func (t *Tomb) Killf(f string, a ...interface{}) error {
|
|
||||||
err := fmt.Errorf(f, a...)
|
|
||||||
t.Kill(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the reason for the goroutine death provided via Kill
|
|
||||||
// or Killf, or ErrStillAlive when the goroutine is still alive.
|
|
||||||
func (t *Tomb) Err() (reason error) {
|
|
||||||
t.init()
|
|
||||||
t.m.Lock()
|
|
||||||
reason = t.reason
|
|
||||||
t.m.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
0
vendor/gopkg.in/tomb.v1/LICENSE → vendor/gopkg.in/tomb.v2/LICENSE
generated
vendored
0
vendor/gopkg.in/tomb.v1/LICENSE → vendor/gopkg.in/tomb.v2/LICENSE
generated
vendored
2
vendor/gopkg.in/tomb.v1/README.md → vendor/gopkg.in/tomb.v2/README.md
generated
vendored
2
vendor/gopkg.in/tomb.v1/README.md → vendor/gopkg.in/tomb.v2/README.md
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Installation and usage
|
Installation and usage
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
See [gopkg.in/tomb.v1](https://gopkg.in/tomb.v1) for documentation and usage details.
|
See [gopkg.in/tomb.v2](https://gopkg.in/tomb.v2) for documentation and usage details.
|
||||||
74
vendor/gopkg.in/tomb.v2/context.go
generated
vendored
Normal file
74
vendor/gopkg.in/tomb.v2/context.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package tomb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithContext returns a new tomb that is killed when the provided parent
|
||||||
|
// context is canceled, and a copy of parent with a replaced Done channel
|
||||||
|
// that is closed when either the tomb is dying or the parent is canceled.
|
||||||
|
// The returned context may also be obtained via the tomb's Context method.
|
||||||
|
func WithContext(parent context.Context) (*Tomb, context.Context) {
|
||||||
|
var t Tomb
|
||||||
|
t.init()
|
||||||
|
if parent.Done() != nil {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-t.Dying():
|
||||||
|
case <-parent.Done():
|
||||||
|
t.Kill(parent.Err())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
t.parent = parent
|
||||||
|
child, cancel := context.WithCancel(parent)
|
||||||
|
t.addChild(parent, child, cancel)
|
||||||
|
return &t, child
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context returns a context that is a copy of the provided parent context with
|
||||||
|
// a replaced Done channel that is closed when either the tomb is dying or the
|
||||||
|
// parent is cancelled.
|
||||||
|
//
|
||||||
|
// If parent is nil, it defaults to the parent provided via WithContext, or an
|
||||||
|
// empty background parent if the tomb wasn't created via WithContext.
|
||||||
|
func (t *Tomb) Context(parent context.Context) context.Context {
|
||||||
|
t.init()
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
|
||||||
|
if parent == nil {
|
||||||
|
if t.parent == nil {
|
||||||
|
t.parent = context.Background()
|
||||||
|
}
|
||||||
|
parent = t.parent.(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if child, ok := t.child[parent]; ok {
|
||||||
|
return child.context.(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
child, cancel := context.WithCancel(parent)
|
||||||
|
t.addChild(parent, child, cancel)
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tomb) addChild(parent context.Context, child context.Context, cancel func()) {
|
||||||
|
if t.reason != ErrStillAlive {
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.child == nil {
|
||||||
|
t.child = make(map[interface{}]childContext)
|
||||||
|
}
|
||||||
|
t.child[parent] = childContext{child, cancel, child.Done()}
|
||||||
|
for parent, child := range t.child {
|
||||||
|
select {
|
||||||
|
case <-child.done:
|
||||||
|
delete(t.child, parent)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
vendor/gopkg.in/tomb.v2/context16.go
generated
vendored
Normal file
74
vendor/gopkg.in/tomb.v2/context16.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package tomb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithContext returns a new tomb that is killed when the provided parent
|
||||||
|
// context is canceled, and a copy of parent with a replaced Done channel
|
||||||
|
// that is closed when either the tomb is dying or the parent is canceled.
|
||||||
|
// The returned context may also be obtained via the tomb's Context method.
|
||||||
|
func WithContext(parent context.Context) (*Tomb, context.Context) {
|
||||||
|
var t Tomb
|
||||||
|
t.init()
|
||||||
|
if parent.Done() != nil {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-t.Dying():
|
||||||
|
case <-parent.Done():
|
||||||
|
t.Kill(parent.Err())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
t.parent = parent
|
||||||
|
child, cancel := context.WithCancel(parent)
|
||||||
|
t.addChild(parent, child, cancel)
|
||||||
|
return &t, child
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context returns a context that is a copy of the provided parent context with
|
||||||
|
// a replaced Done channel that is closed when either the tomb is dying or the
|
||||||
|
// parent is cancelled.
|
||||||
|
//
|
||||||
|
// If parent is nil, it defaults to the parent provided via WithContext, or an
|
||||||
|
// empty background parent if the tomb wasn't created via WithContext.
|
||||||
|
func (t *Tomb) Context(parent context.Context) context.Context {
|
||||||
|
t.init()
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
|
||||||
|
if parent == nil {
|
||||||
|
if t.parent == nil {
|
||||||
|
t.parent = context.Background()
|
||||||
|
}
|
||||||
|
parent = t.parent.(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if child, ok := t.child[parent]; ok {
|
||||||
|
return child.context.(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
child, cancel := context.WithCancel(parent)
|
||||||
|
t.addChild(parent, child, cancel)
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tomb) addChild(parent context.Context, child context.Context, cancel func()) {
|
||||||
|
if t.reason != ErrStillAlive {
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.child == nil {
|
||||||
|
t.child = make(map[interface{}]childContext)
|
||||||
|
}
|
||||||
|
t.child[parent] = childContext{child, cancel, child.Done()}
|
||||||
|
for parent, child := range t.child {
|
||||||
|
select {
|
||||||
|
case <-child.done:
|
||||||
|
delete(t.child, parent)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
237
vendor/gopkg.in/tomb.v2/tomb.go
generated
vendored
Normal file
237
vendor/gopkg.in/tomb.v2/tomb.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
// * Neither the name of the copyright holder nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// The tomb package handles clean goroutine tracking and termination.
|
||||||
|
//
|
||||||
|
// The zero value of a Tomb is ready to handle the creation of a tracked
|
||||||
|
// goroutine via its Go method, and then any tracked goroutine may call
|
||||||
|
// the Go method again to create additional tracked goroutines at
|
||||||
|
// any point.
|
||||||
|
//
|
||||||
|
// If any of the tracked goroutines returns a non-nil error, or the
|
||||||
|
// Kill or Killf method is called by any goroutine in the system (tracked
|
||||||
|
// or not), the tomb Err is set, Alive is set to false, and the Dying
|
||||||
|
// channel is closed to flag that all tracked goroutines are supposed
|
||||||
|
// to willingly terminate as soon as possible.
|
||||||
|
//
|
||||||
|
// Once all tracked goroutines terminate, the Dead channel is closed,
|
||||||
|
// and Wait unblocks and returns the first non-nil error presented
|
||||||
|
// to the tomb via a result or an explicit Kill or Killf method call,
|
||||||
|
// or nil if there were no errors.
|
||||||
|
//
|
||||||
|
// It is okay to create further goroutines via the Go method while
|
||||||
|
// the tomb is in a dying state. The final dead state is only reached
|
||||||
|
// once all tracked goroutines terminate, at which point calling
|
||||||
|
// the Go method again will cause a runtime panic.
|
||||||
|
//
|
||||||
|
// Tracked functions and methods that are still running while the tomb
|
||||||
|
// is in dying state may choose to return ErrDying as their error value.
|
||||||
|
// This preserves the well established non-nil error convention, but is
|
||||||
|
// understood by the tomb as a clean termination. The Err and Wait
|
||||||
|
// methods will still return nil if all observed errors were either
|
||||||
|
// nil or ErrDying.
|
||||||
|
//
|
||||||
|
// For background and a detailed example, see the following blog post:
|
||||||
|
//
|
||||||
|
// http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
|
||||||
|
//
|
||||||
|
package tomb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Tomb tracks the lifecycle of one or more goroutines as alive,
|
||||||
|
// dying or dead, and the reason for their death.
|
||||||
|
//
|
||||||
|
// See the package documentation for details.
|
||||||
|
type Tomb struct {
|
||||||
|
m sync.Mutex
|
||||||
|
alive int
|
||||||
|
dying chan struct{}
|
||||||
|
dead chan struct{}
|
||||||
|
reason error
|
||||||
|
|
||||||
|
// context.Context is available in Go 1.7+.
|
||||||
|
parent interface{}
|
||||||
|
child map[interface{}]childContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type childContext struct {
|
||||||
|
context interface{}
|
||||||
|
cancel func()
|
||||||
|
done <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrStillAlive = errors.New("tomb: still alive")
|
||||||
|
ErrDying = errors.New("tomb: dying")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Tomb) init() {
|
||||||
|
t.m.Lock()
|
||||||
|
if t.dead == nil {
|
||||||
|
t.dead = make(chan struct{})
|
||||||
|
t.dying = make(chan struct{})
|
||||||
|
t.reason = ErrStillAlive
|
||||||
|
}
|
||||||
|
t.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dead returns the channel that can be used to wait until
|
||||||
|
// all goroutines have finished running.
|
||||||
|
func (t *Tomb) Dead() <-chan struct{} {
|
||||||
|
t.init()
|
||||||
|
return t.dead
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dying returns the channel that can be used to wait until
|
||||||
|
// t.Kill is called.
|
||||||
|
func (t *Tomb) Dying() <-chan struct{} {
|
||||||
|
t.init()
|
||||||
|
return t.dying
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks until all goroutines have finished running, and
|
||||||
|
// then returns the reason for their death.
|
||||||
|
func (t *Tomb) Wait() error {
|
||||||
|
t.init()
|
||||||
|
<-t.dead
|
||||||
|
t.m.Lock()
|
||||||
|
reason := t.reason
|
||||||
|
t.m.Unlock()
|
||||||
|
return reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go runs f in a new goroutine and tracks its termination.
|
||||||
|
//
|
||||||
|
// If f returns a non-nil error, t.Kill is called with that
|
||||||
|
// error as the death reason parameter.
|
||||||
|
//
|
||||||
|
// It is f's responsibility to monitor the tomb and return
|
||||||
|
// appropriately once it is in a dying state.
|
||||||
|
//
|
||||||
|
// It is safe for the f function to call the Go method again
|
||||||
|
// to create additional tracked goroutines. Once all tracked
|
||||||
|
// goroutines return, the Dead channel is closed and the
|
||||||
|
// Wait method unblocks and returns the death reason.
|
||||||
|
//
|
||||||
|
// Calling the Go method after all tracked goroutines return
|
||||||
|
// causes a runtime panic. For that reason, calling the Go
|
||||||
|
// method a second time out of a tracked goroutine is unsafe.
|
||||||
|
func (t *Tomb) Go(f func() error) {
|
||||||
|
t.init()
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
select {
|
||||||
|
case <-t.dead:
|
||||||
|
panic("tomb.Go called after all goroutines terminated")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
t.alive++
|
||||||
|
go t.run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tomb) run(f func() error) {
|
||||||
|
err := f()
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
t.alive--
|
||||||
|
if t.alive == 0 || err != nil {
|
||||||
|
t.kill(err)
|
||||||
|
if t.alive == 0 {
|
||||||
|
close(t.dead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill puts the tomb in a dying state for the given reason,
|
||||||
|
// closes the Dying channel, and sets Alive to false.
|
||||||
|
//
|
||||||
|
// Althoguh Kill may be called multiple times, only the first
|
||||||
|
// non-nil error is recorded as the death reason.
|
||||||
|
//
|
||||||
|
// If reason is ErrDying, the previous reason isn't replaced
|
||||||
|
// even if nil. It's a runtime error to call Kill with ErrDying
|
||||||
|
// if t is not in a dying state.
|
||||||
|
func (t *Tomb) Kill(reason error) {
|
||||||
|
t.init()
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
t.kill(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tomb) kill(reason error) {
|
||||||
|
if reason == ErrStillAlive {
|
||||||
|
panic("tomb: Kill with ErrStillAlive")
|
||||||
|
}
|
||||||
|
if reason == ErrDying {
|
||||||
|
if t.reason == ErrStillAlive {
|
||||||
|
panic("tomb: Kill with ErrDying while still alive")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.reason == ErrStillAlive {
|
||||||
|
t.reason = reason
|
||||||
|
close(t.dying)
|
||||||
|
for _, child := range t.child {
|
||||||
|
child.cancel()
|
||||||
|
}
|
||||||
|
t.child = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.reason == nil {
|
||||||
|
t.reason = reason
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Killf calls the Kill method with an error built providing the received
|
||||||
|
// parameters to fmt.Errorf. The generated error is also returned.
|
||||||
|
func (t *Tomb) Killf(f string, a ...interface{}) error {
|
||||||
|
err := fmt.Errorf(f, a...)
|
||||||
|
t.Kill(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the death reason, or ErrStillAlive if the tomb
|
||||||
|
// is not in a dying or dead state.
|
||||||
|
func (t *Tomb) Err() (reason error) {
|
||||||
|
t.init()
|
||||||
|
t.m.Lock()
|
||||||
|
reason = t.reason
|
||||||
|
t.m.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alive returns true if the tomb is not in a dying or dead state.
|
||||||
|
func (t *Tomb) Alive() bool {
|
||||||
|
return t.Err() == ErrStillAlive
|
||||||
|
}
|
||||||
7
vendor/modules.txt
vendored
7
vendor/modules.txt
vendored
@@ -174,6 +174,9 @@ golang.org/x/crypto/pbkdf2
|
|||||||
golang.org/x/exp/constraints
|
golang.org/x/exp/constraints
|
||||||
golang.org/x/exp/rand
|
golang.org/x/exp/rand
|
||||||
golang.org/x/exp/slices
|
golang.org/x/exp/slices
|
||||||
|
# golang.org/x/net v0.26.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
golang.org/x/net/context
|
||||||
# golang.org/x/sys v0.22.0
|
# golang.org/x/sys v0.22.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/sys/execabs
|
golang.org/x/sys/execabs
|
||||||
@@ -223,6 +226,6 @@ google.golang.org/protobuf/reflect/protoregistry
|
|||||||
google.golang.org/protobuf/runtime/protoiface
|
google.golang.org/protobuf/runtime/protoiface
|
||||||
google.golang.org/protobuf/runtime/protoimpl
|
google.golang.org/protobuf/runtime/protoimpl
|
||||||
google.golang.org/protobuf/types/known/timestamppb
|
google.golang.org/protobuf/types/known/timestamppb
|
||||||
# gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
# gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/tomb.v1
|
gopkg.in/tomb.v2
|
||||||
|
|||||||
Reference in New Issue
Block a user