keepalive.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // Copyright (c) 2021 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "context"
  9. "math/rand/v2"
  10. "time"
  11. "go.mau.fi/whatsmeow/types"
  12. "go.mau.fi/whatsmeow/types/events"
  13. )
  14. var (
  15. // KeepAliveResponseDeadline specifies the duration to wait for a response to websocket keepalive pings.
  16. KeepAliveResponseDeadline = 10 * time.Second
  17. // KeepAliveIntervalMin specifies the minimum interval for websocket keepalive pings.
  18. KeepAliveIntervalMin = 20 * time.Second
  19. // KeepAliveIntervalMax specifies the maximum interval for websocket keepalive pings.
  20. KeepAliveIntervalMax = 30 * time.Second
  21. // KeepAliveMaxFailTime specifies the maximum time to wait before forcing a reconnect if keepalives fail repeatedly.
  22. KeepAliveMaxFailTime = 3 * time.Minute
  23. )
  24. func (cli *Client) keepAliveLoop(ctx, connCtx context.Context) {
  25. lastSuccess := time.Now()
  26. var errorCount int
  27. for {
  28. interval := rand.Int64N(KeepAliveIntervalMax.Milliseconds()-KeepAliveIntervalMin.Milliseconds()) + KeepAliveIntervalMin.Milliseconds()
  29. select {
  30. case <-time.After(time.Duration(interval) * time.Millisecond):
  31. isSuccess, shouldContinue := cli.sendKeepAlive(connCtx)
  32. if !shouldContinue {
  33. return
  34. } else if !isSuccess {
  35. errorCount++
  36. go cli.dispatchEvent(&events.KeepAliveTimeout{
  37. ErrorCount: errorCount,
  38. LastSuccess: lastSuccess,
  39. })
  40. if cli.EnableAutoReconnect && time.Since(lastSuccess) > KeepAliveMaxFailTime {
  41. cli.Log.Debugf("Forcing reconnect due to keepalive failure")
  42. cli.Disconnect()
  43. cli.resetExpectedDisconnect()
  44. go cli.autoReconnect(ctx)
  45. }
  46. } else {
  47. if errorCount > 0 {
  48. errorCount = 0
  49. go cli.dispatchEvent(&events.KeepAliveRestored{})
  50. }
  51. lastSuccess = time.Now()
  52. }
  53. case <-connCtx.Done():
  54. return
  55. }
  56. }
  57. }
  58. func (cli *Client) sendKeepAlive(ctx context.Context) (isSuccess, shouldContinue bool) {
  59. respCh, err := cli.sendIQAsync(ctx, infoQuery{
  60. Namespace: "w:p",
  61. Type: "get",
  62. To: types.ServerJID,
  63. })
  64. if ctx.Err() != nil {
  65. return false, false
  66. } else if err != nil {
  67. cli.Log.Warnf("Failed to send keepalive: %v", err)
  68. return false, true
  69. }
  70. select {
  71. case <-respCh:
  72. // All good
  73. return true, true
  74. case <-time.After(KeepAliveResponseDeadline):
  75. cli.Log.Warnf("Keepalive timed out")
  76. return false, true
  77. case <-ctx.Done():
  78. return false, false
  79. }
  80. }