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 {
|
||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||
v.Type, v.From, v.Show, v.Status)
|
||||
log.Errorf(spew.Sdump(v))
|
||||
log.Error(spew.Sdump(v))
|
||||
}
|
||||
} else {
|
||||
log.Infof(
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/eleksir/go-xmpp"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"gopkg.in/tomb.v1"
|
||||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
// Config - это у нас глобальная штука :) .
|
||||
@@ -33,7 +33,7 @@ var connecting = false
|
||||
var isConnected = false
|
||||
|
||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||
var sigChan = make(chan os.Signal, 1)
|
||||
var sigChan chan os.Signal
|
||||
|
||||
// Основной инстанс xmpp-клиента.
|
||||
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/sirupsen/logrus v1.9.3
|
||||
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 (
|
||||
@@ -37,6 +37,7 @@ require (
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.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/text v0.16.0 // 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/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.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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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 Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
||||
func sigHandler() {
|
||||
func sigHandler() error {
|
||||
log.Debug("Installing signal handler")
|
||||
|
||||
for s := range sigChan {
|
||||
@@ -86,14 +86,16 @@ func sigHandler() {
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// establishConnection устанавливает соединение с jabber-сервером.
|
||||
func establishConnection() {
|
||||
func establishConnection() error {
|
||||
var err error
|
||||
|
||||
if connecting && !isConnected {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Проставляем глобальные переменные.
|
||||
@@ -104,26 +106,22 @@ func establishConnection() {
|
||||
talk, err = options.NewClient()
|
||||
|
||||
if err != nil {
|
||||
gTomb.Kill(err)
|
||||
|
||||
return
|
||||
return fmt.Errorf("Unable to connect to %s: %w", options.Host, err)
|
||||
}
|
||||
|
||||
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
||||
if _, err := talk.SendKeepAlive(); err != nil {
|
||||
log.Errorf("Try to send initial KeepAlive, got error: %s", err)
|
||||
|
||||
gTomb.Kill(err)
|
||||
return fmt.Errorf("Try to send initial KeepAlive, got error: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Connected")
|
||||
|
||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
||||
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()
|
||||
connecting = false
|
||||
@@ -134,21 +132,18 @@ func establishConnection() {
|
||||
_, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server)
|
||||
|
||||
if err != nil {
|
||||
log.Infof("Unable to send disco#info to jabber server: %s", err)
|
||||
gTomb.Kill(err)
|
||||
|
||||
return
|
||||
return fmt.Errorf("Unable to send disco#info to jabber server: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// joinMu джойнится к конференциям/каналам/комнатам в джаббере.
|
||||
func joinMuc(room string) {
|
||||
func joinMuc(room string) error {
|
||||
log.Debugf("Sending disco#info from %s to %s", talk.JID(), room)
|
||||
|
||||
if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil {
|
||||
log.Infof("Unable to send disco#info to MUC %s: %s", room, err)
|
||||
|
||||
gTomb.Kill(err)
|
||||
return fmt.Errorf("Unable to send disco#info to MUC %s: %s", room, err)
|
||||
}
|
||||
|
||||
// Ждём, пока 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 :)")
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Пытаемся зайти в комнату
|
||||
if _, err := talk.JoinMUCNoHistory(room, getBotNickFromRoomConfig(room)); err != nil {
|
||||
log.Errorf("Unable to join to MUC: %s", room)
|
||||
|
||||
gTomb.Kill(err)
|
||||
return fmt.Errorf("Unable to join to MUC: %s, %w", room, err)
|
||||
}
|
||||
|
||||
log.Infof("Joining to MUC: %s", room)
|
||||
@@ -206,172 +199,152 @@ func joinMuc(room string) {
|
||||
20*int(config.Jabber.ConnectionTimeout)+1,
|
||||
)
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Вот теперь точно можно слать статус.
|
||||
log.Infof("Joined to MUC: %s", room)
|
||||
|
||||
go RotateStatus(room)
|
||||
gTomb.Go(func() error { return RotateStatus(room) })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
||||
// они выкидывают клиента через некоторое время неактивности.
|
||||
func probeServerLiveness() { //nolint:gocognit
|
||||
func probeServerLiveness() error { //nolint:gocognit
|
||||
for {
|
||||
select {
|
||||
case <-gTomb.Dying():
|
||||
gTomb.Done()
|
||||
return
|
||||
for {
|
||||
if shutdown {
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
for {
|
||||
if shutdown {
|
||||
return
|
||||
}
|
||||
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
||||
time.Sleep(sleepTime)
|
||||
if !isConnected {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isConnected {
|
||||
continue
|
||||
}
|
||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||
// continue
|
||||
// }
|
||||
if serverCapsQueried { // Сервер ответил на disco#info
|
||||
var (
|
||||
value interface{}
|
||||
exist bool
|
||||
)
|
||||
|
||||
if serverCapsQueried { // Сервер ответил на disco#info
|
||||
var (
|
||||
value interface{}
|
||||
exist bool
|
||||
)
|
||||
value, exist = serverCapsList.Get("urn:xmpp:ping")
|
||||
|
||||
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 {
|
||||
// Сервер анонсировал, что умеет в c2s пинги
|
||||
case exist && value.(bool):
|
||||
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||
// максимальную корректировку разброса.
|
||||
txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout
|
||||
txTimeout += config.Jabber.PingSplayDelay
|
||||
rxTimeout := txTimeout
|
||||
rxTimeAgo := time.Now().Unix() - serverPingTimestampRx
|
||||
|
||||
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 { // Первая пуля от нас ушла...
|
||||
switch {
|
||||
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
||||
case rxTimeAgo > (rxTimeout * 2):
|
||||
err := fmt.Errorf( //nolint:goerr113
|
||||
"stall connection detected. No c2s pong for %d seconds",
|
||||
rxTimeAgo,
|
||||
)
|
||||
return err
|
||||
|
||||
gTomb.Kill(err)
|
||||
|
||||
continue
|
||||
|
||||
// По-умолчанию, мы отправляем 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)
|
||||
// По-умолчанию, мы отправляем 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
|
||||
return err
|
||||
}
|
||||
|
||||
serverPingTimestampTx = time.Now().Unix()
|
||||
}
|
||||
} else { // Первая пуля пока не вылетела, отправляем
|
||||
log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
||||
|
||||
// Сервер не анонсировал, что умеет в c2s пинги
|
||||
default:
|
||||
log.Debug("Sending keepalive whitespace ping")
|
||||
|
||||
if _, err := talk.SendKeepAlive(); err != nil {
|
||||
gTomb.Kill(err)
|
||||
|
||||
continue
|
||||
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverPingTimestampTx = time.Now().Unix()
|
||||
}
|
||||
} else { // Сервер не ответил на disco#info
|
||||
|
||||
// Сервер не анонсировал, что умеет в c2s пинги
|
||||
default:
|
||||
log.Debug("Sending keepalive whitespace ping")
|
||||
|
||||
if _, err := talk.SendKeepAlive(); err != nil {
|
||||
gTomb.Kill(err)
|
||||
|
||||
continue
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else { // Сервер не ответил на disco#info
|
||||
log.Debug("Sending keepalive whitespace ping")
|
||||
|
||||
if _, err := talk.SendKeepAlive(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// probeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е.
|
||||
func probeMUCLiveness() { //nolint:gocognit
|
||||
func probeMUCLiveness() error { //nolint:gocognit
|
||||
for {
|
||||
select {
|
||||
case <-gTomb.Dying():
|
||||
gTomb.Done()
|
||||
return
|
||||
for {
|
||||
for _, room := range roomsConnected {
|
||||
var (
|
||||
exist bool
|
||||
lastActivityTS interface{}
|
||||
)
|
||||
|
||||
default:
|
||||
for {
|
||||
for _, room := range roomsConnected {
|
||||
var (
|
||||
exist bool
|
||||
lastActivityTS interface{}
|
||||
)
|
||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||
if lastActivityTS, exist = lastMucActivity.Get(room); !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||
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
|
||||
}
|
||||
}(room)
|
||||
*/
|
||||
|
||||
// Если время последней активности в чятике не превысило
|
||||
// config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем.
|
||||
if (time.Now().Unix() - lastActivityTS.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||
continue
|
||||
}
|
||||
var roomMap interface{}
|
||||
|
||||
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
||||
go func(room string) {
|
||||
// Небольшая рандомная задержка перед пингом комнаты
|
||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||
time.Sleep(sleepTime)
|
||||
roomMap, exist = mucCapsList.Get(room)
|
||||
|
||||
if err := talk.PingS2S(talk.JID(), room+"/"+getBotNickFromRoomConfig(room)); err != nil {
|
||||
gTomb.Kill(err)
|
||||
continue
|
||||
}
|
||||
}(room)
|
||||
*/
|
||||
|
||||
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) {
|
||||
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
||||
// Server Optimization.
|
||||
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
||||
gTomb.Go(
|
||||
func() error {
|
||||
// Небольшая рандомная задержка перед пингом комнаты.
|
||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
||||
time.Sleep(sleepTime)
|
||||
@@ -379,65 +352,54 @@ func probeMUCLiveness() { //nolint:gocognit
|
||||
log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room)
|
||||
|
||||
if err := talk.PingS2S(talk.JID(), room); err != nil {
|
||||
err := fmt.Errorf("unable to ping MUC %s: %w", room, err)
|
||||
gTomb.Kill(err)
|
||||
return fmt.Errorf("unable to ping MUC %s: %w", room, err)
|
||||
}
|
||||
}(room)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
||||
func RotateStatus(room string) {
|
||||
func RotateStatus(room string) error {
|
||||
for {
|
||||
select {
|
||||
case <-gTomb.Dying():
|
||||
gTomb.Done()
|
||||
return
|
||||
// TODO: Переделать на ticker-ы
|
||||
totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||
totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||
|
||||
default:
|
||||
// TODO: Переделать на ticker-ы
|
||||
totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||
totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||
for {
|
||||
status := randomPhrase(config.Jabber.RuntimeStatus.Text)
|
||||
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
||||
|
||||
for {
|
||||
status := randomPhrase(config.Jabber.RuntimeStatus.Text)
|
||||
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
||||
var p xmpp.Presence
|
||||
|
||||
var p xmpp.Presence
|
||||
|
||||
if room != "" {
|
||||
p = xmpp.Presence{ //nolint:exhaustruct
|
||||
To: room,
|
||||
Status: status,
|
||||
}
|
||||
} else {
|
||||
p = xmpp.Presence{ //nolint:exhaustruct
|
||||
Status: status,
|
||||
}
|
||||
if room != "" {
|
||||
p = xmpp.Presence{ //nolint:exhaustruct
|
||||
To: room,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if _, err := talk.SendPresence(p); err != nil {
|
||||
log.Infof("Unable to send presence to MUC %s: %s", room, err)
|
||||
gTomb.Kill(err)
|
||||
|
||||
return
|
||||
} else {
|
||||
p = xmpp.Presence{ //nolint:exhaustruct
|
||||
Status: status,
|
||||
}
|
||||
|
||||
// Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим.
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -14,10 +11,10 @@ import (
|
||||
"github.com/eleksir/go-xmpp"
|
||||
"github.com/go-redis/redis/v8"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/tomb.v1"
|
||||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
func main() {
|
||||
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
||||
DisableQuote: true,
|
||||
DisableLevelTruncation: false,
|
||||
@@ -35,73 +32,78 @@ func init() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
configLocations = append(configLocations, configJSONPath)
|
||||
for {
|
||||
sigChan = make(chan os.Signal, 1)
|
||||
talk = new(xmpp.Client)
|
||||
options = new(xmpp.Options)
|
||||
|
||||
if err := readConfig(); err != nil {
|
||||
log.Error(err)
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
configLocations = append(configLocations, configJSONPath)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := readConfig(); err != nil {
|
||||
log.Error(err)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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 {
|
||||
verboseClient = true
|
||||
}
|
||||
log.SetOutput(logfile)
|
||||
}
|
||||
|
||||
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace.
|
||||
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
||||
myLogLevel := log.GetLevel()
|
||||
log.Warnf("Loglevel set to %v", myLogLevel)
|
||||
|
||||
// Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком.
|
||||
go sigHandler()
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
// Через tomb попробуем сделать выход горутинок управляемым.
|
||||
gTomb := tomb.Tomb{}
|
||||
|
||||
// Иницализируем redis-клиента.
|
||||
redisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct
|
||||
Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port),
|
||||
})
|
||||
// Иницализируем redis-клиента.
|
||||
redisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct
|
||||
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)
|
||||
subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel)
|
||||
log.Debugf("Lazy connect() to redis at %s:%d", config.Redis.Server, config.Redis.Port)
|
||||
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
|
||||
Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port),
|
||||
User: config.Jabber.User,
|
||||
@@ -121,109 +123,84 @@ func main() {
|
||||
DialTimeout: time.Duration(config.Jabber.ConnectionTimeout) * time.Second,
|
||||
}
|
||||
|
||||
// Через tomb попробуем сделать выход горутинок управляемым.
|
||||
gTomb = tomb.Tomb{}
|
||||
|
||||
// Устанавливаем соединение и гребём события, посылаемые сервером.
|
||||
myLoop()
|
||||
gTomb.Go(func() error { return myLoop() })
|
||||
|
||||
log.Error(gTomb.Wait())
|
||||
|
||||
// Если у нас wire error, то вызов .Close() повлечёт за собой ошибку, но мы вынуждены звать .Close(), чтоб
|
||||
// закрыть tls контекст и почистить всё что связанно с прерванным соединением.
|
||||
log.Infoln("Closing connection to jabber server")
|
||||
|
||||
_ = talk.Close()
|
||||
// Ловим первый же kill и не дождаемся остальных, хотя формально надо бы.
|
||||
_ = <-gTomb.Dying()
|
||||
|
||||
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func myLoop() {
|
||||
func myLoop() error {
|
||||
for {
|
||||
select {
|
||||
case <-gTomb.Dying():
|
||||
gTomb.Done()
|
||||
// Зададим начальное значение глобальным переменным.
|
||||
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()
|
||||
|
||||
return
|
||||
// Установим коннект.
|
||||
if err := establishConnection(); err != nil {
|
||||
log.Error(err)
|
||||
|
||||
default:
|
||||
// Зададим начальное значение глобальным переменным.
|
||||
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)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func redisLoop(redisMsgChan <-chan *redis.Message) {
|
||||
func redisLoop(redisMsgChan <-chan *redis.Message) error {
|
||||
// Обработчик событий от редиски
|
||||
for msg := range redisMsgChan {
|
||||
if shutdown {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := redisMsgParser(msg.Payload); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
----------------------
|
||||
|
||||
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/rand
|
||||
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
|
||||
## explicit; go 1.18
|
||||
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/protoimpl
|
||||
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
|
||||
gopkg.in/tomb.v1
|
||||
gopkg.in/tomb.v2
|
||||
|
||||
Reference in New Issue
Block a user