From 11107f5155216ac33796b34f65b4ec920d730bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Wed, 4 Oct 2023 10:45:09 +0200 Subject: [PATCH 1/7] make Gateway.Open wait until ready event is received --- gateway/gateway_impl.go | 56 ++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index dc0ccbb4..644dfbce 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -80,8 +80,8 @@ func (g *gatewayImpl) open(ctx context.Context) error { g.config.Logger.Debug("opening gateway connection") g.connMu.Lock() - defer g.connMu.Unlock() if g.conn != nil { + g.connMu.Unlock() return discord.ErrGatewayAlreadyConnected } g.status = StatusConnecting @@ -107,6 +107,7 @@ func (g *gatewayImpl) open(ctx context.Context) error { } g.config.Logger.Error("error connecting to the gateway", slog.Any("err", err), slog.String("url", gatewayURL), slog.String("body", body)) + g.connMu.Unlock() return err } @@ -115,13 +116,27 @@ func (g *gatewayImpl) open(ctx context.Context) error { }) g.conn = conn + g.connMu.Unlock() // reset rate limiter when connecting g.config.RateLimiter.Reset() g.status = StatusWaitingForHello - go g.listen(conn) + readyChan := make(chan error) + go g.listen(conn, readyChan) + + select { + case <-ctx.Done(): + closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + g.Close(closeCtx) + return ctx.Err() + case err = <-readyChan: + if err != nil { + return fmt.Errorf("failed to open gateway connection: %w", err) + } + } return nil } @@ -216,6 +231,13 @@ func (g *gatewayImpl) reconnectTry(ctx context.Context, try int) error { } if err := g.open(ctx); err != nil { + var closeError *websocket.CloseError + if errors.As(err, &closeError) { + closeCode := CloseEventCodeByCode(closeError.Code) + if !closeCode.Reconnect { + return err + } + } if errors.Is(err, discord.ErrGatewayAlreadyConnected) { return err } @@ -271,7 +293,7 @@ func (g *gatewayImpl) sendHeartbeat() { g.lastHeartbeatSent = time.Now().UTC() } -func (g *gatewayImpl) identify() { +func (g *gatewayImpl) identify() error { g.status = StatusIdentifying g.config.Logger.Debug("sending Identify command") @@ -292,12 +314,13 @@ func (g *gatewayImpl) identify() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := g.Send(ctx, OpcodeIdentify, identify); err != nil { - g.config.Logger.Error("error sending Identify command", slog.Any("err", err)) + return err } g.status = StatusWaitingForReady + return nil } -func (g *gatewayImpl) resume() { +func (g *gatewayImpl) resume() error { g.status = StatusResuming resume := MessageDataResume{ Token: g.token, @@ -309,16 +332,22 @@ func (g *gatewayImpl) resume() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := g.Send(ctx, OpcodeResume, resume); err != nil { - g.config.Logger.Error("error sending resume command", slog.Any("err", err)) + return err } + return nil } -func (g *gatewayImpl) listen(conn *websocket.Conn) { +func (g *gatewayImpl) listen(conn *websocket.Conn, readyChan chan<- error) { defer g.config.Logger.Debug("exiting listen goroutine") loop: for { mt, r, err := conn.NextReader() if err != nil { + if g.status != StatusReady { + readyChan <- err + close(readyChan) + break loop + } g.connMu.Lock() sameConnection := g.conn == conn g.connMu.Unlock() @@ -383,9 +412,14 @@ loop: go g.heartbeat() if g.config.LastSequenceReceived == nil || g.config.SessionID == nil { - g.identify() + err = g.identify() } else { - g.resume() + err = g.resume() + } + if err != nil { + readyChan <- err + close(readyChan) + return } case OpcodeDispatch: @@ -419,6 +453,10 @@ loop: continue } g.eventHandlerFunc(message.T, message.S, g.config.ShardID, eventData) + if _, ok = eventData.(EventReady); ok { + readyChan <- nil + close(readyChan) + } case OpcodeHeartbeat: g.sendHeartbeat() From 130fd344dce5948ec6cd2d6f00189c4f86f47275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Wed, 4 Oct 2023 12:03:38 +0200 Subject: [PATCH 2/7] make sure gateway is closed on ready error --- gateway/gateway_impl.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index 644dfbce..f8fa62b3 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -134,6 +134,9 @@ func (g *gatewayImpl) open(ctx context.Context) error { return ctx.Err() case err = <-readyChan: if err != nil { + closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + g.Close(closeCtx) return fmt.Errorf("failed to open gateway connection: %w", err) } } From cc1355f8d71f7933fba152707be3aebc31e73a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Tue, 10 Oct 2023 01:43:34 +0200 Subject: [PATCH 3/7] handle resume with rady chan --- gateway/gateway_impl.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index f8fa62b3..809a6be3 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -425,6 +425,12 @@ loop: return } + case OpcodeResume: + g.config.Logger.Debug(g.formatLogs("resume successful")) + g.status = StatusReady + readyChan <- nil + close(readyChan) + case OpcodeDispatch: // set last sequence received g.config.LastSequenceReceived = &message.S From 76f1e9bf4f5df88f111f1ccd4f50463ca273e607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Tue, 10 Oct 2023 20:40:42 +0200 Subject: [PATCH 4/7] actually handle the resumed event --- gateway/gateway_events.go | 6 ++++++ gateway/gateway_impl.go | 12 ++++++------ gateway/gateway_messages.go | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index b033e018..e4657e09 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -43,6 +43,12 @@ type EventReady struct { func (EventReady) messageData() {} func (EventReady) eventData() {} +// EventResumed is the event sent by discord when you successfully resume +type EventResumed struct{} + +func (EventResumed) messageData() {} +func (EventResumed) eventData() {} + type EventApplicationCommandPermissionsUpdate struct { discord.ApplicationCommandPermissions } diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index 809a6be3..8591457a 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -425,12 +425,6 @@ loop: return } - case OpcodeResume: - g.config.Logger.Debug(g.formatLogs("resume successful")) - g.status = StatusReady - readyChan <- nil - close(readyChan) - case OpcodeDispatch: // set last sequence received g.config.LastSequenceReceived = &message.S @@ -463,6 +457,12 @@ loop: } g.eventHandlerFunc(message.T, message.S, g.config.ShardID, eventData) if _, ok = eventData.(EventReady); ok { + g.config.Logger.Debug(g.formatLogs("ready successful")) + readyChan <- nil + close(readyChan) + } else if _, ok = eventData.(EventResumed); ok { + g.config.Logger.Debug(g.formatLogs("resume successful")) + g.status = StatusReady readyChan <- nil close(readyChan) } diff --git a/gateway/gateway_messages.go b/gateway/gateway_messages.go index 8b538071..c44936ec 100644 --- a/gateway/gateway_messages.go +++ b/gateway/gateway_messages.go @@ -119,7 +119,7 @@ func UnmarshalEventData(data []byte, eventType EventType) (EventData, error) { eventData = d case EventTypeResumed: - // no data + eventData = EventResumed{} case EventTypeApplicationCommandPermissionsUpdate: var d EventApplicationCommandPermissionsUpdate From 775dce0b2bf0190292d0ecc8d1927b5445098aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Fri, 10 Nov 2023 22:39:36 +0100 Subject: [PATCH 5/7] migrate to slog & go 1.21 (#294) --- .github/workflows/lint.yml | 38 ++++++++++++++++++++++++++++++++++++++ gateway/gateway_impl.go | 4 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c20a0c05 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,38 @@ +name: Go + +on: + push: + pull_request_target: + +jobs: + gobuild: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + - uses: actions/checkout@v3 + - name: go build + run: go build -v ./... + + gotest: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + - uses: actions/checkout@v3 + - name: go build + run: go test -v ./... + + golangci: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index 8591457a..e7fec0bb 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -457,11 +457,11 @@ loop: } g.eventHandlerFunc(message.T, message.S, g.config.ShardID, eventData) if _, ok = eventData.(EventReady); ok { - g.config.Logger.Debug(g.formatLogs("ready successful")) + g.config.Logger.Debug("ready successful") readyChan <- nil close(readyChan) } else if _, ok = eventData.(EventResumed); ok { - g.config.Logger.Debug(g.formatLogs("resume successful")) + g.config.Logger.Debug("resume successful") g.status = StatusReady readyChan <- nil close(readyChan) From e7eed7aa2c24ff751765a82ee6cb62dccc7954ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= <git@topi.wtf> Date: Wed, 4 Oct 2023 10:45:09 +0200 Subject: [PATCH 6/7] make Gateway.Open wait until ready event is received --- gateway/gateway_impl.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index e7fec0bb..73205cf0 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -466,6 +466,10 @@ loop: readyChan <- nil close(readyChan) } + if _, ok = eventData.(EventReady); ok { + readyChan <- nil + close(readyChan) + } case OpcodeHeartbeat: g.sendHeartbeat() From 82b4fa8068d989fc70bb433dad75b6ef927d02bc Mon Sep 17 00:00:00 2001 From: apricotbucket28 <agustin.nicolas.marcos@outlook.com> Date: Sun, 16 Feb 2025 13:31:48 -0300 Subject: [PATCH 7/7] handle ready and resume before dispatching --- .github/workflows/lint.yml | 38 -------------------------------------- gateway/gateway_impl.go | 24 ++++++++---------------- 2 files changed, 8 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index c20a0c05..00000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Go - -on: - push: - pull_request_target: - -jobs: - gobuild: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v3 - with: - go-version: 1.21 - - uses: actions/checkout@v3 - - name: go build - run: go build -v ./... - - gotest: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v3 - with: - go-version: 1.21 - - uses: actions/checkout@v3 - - name: go build - run: go test -v ./... - - golangci: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v3 - with: - go-version: 1.21 - - uses: actions/checkout@v3 - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: latest diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index 73205cf0..0eef0caa 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -435,12 +435,18 @@ loop: continue } - // get session id here if readyEvent, ok := eventData.(EventReady); ok { g.config.SessionID = &readyEvent.SessionID g.config.ResumeURL = &readyEvent.ResumeGatewayURL - g.status = StatusReady g.config.Logger.Debug("ready message received") + g.status = StatusReady + readyChan <- nil + close(readyChan) + } else if _, ok = eventData.(EventResumed); ok { + g.config.Logger.Debug("resume message received") + g.status = StatusReady + readyChan <- nil + close(readyChan) } // push message to the command manager @@ -456,20 +462,6 @@ loop: continue } g.eventHandlerFunc(message.T, message.S, g.config.ShardID, eventData) - if _, ok = eventData.(EventReady); ok { - g.config.Logger.Debug("ready successful") - readyChan <- nil - close(readyChan) - } else if _, ok = eventData.(EventResumed); ok { - g.config.Logger.Debug("resume successful") - g.status = StatusReady - readyChan <- nil - close(readyChan) - } - if _, ok = eventData.(EventReady); ok { - readyChan <- nil - close(readyChan) - } case OpcodeHeartbeat: g.sendHeartbeat()