You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fakelag.go 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "maps"
  6. "time"
  7. )
  8. // fakelag is a system for artificially delaying commands when a user issues
  9. // them too rapidly
  10. type FakelagState uint
  11. const (
  12. // initially, the client is "bursting" and can send n commands without
  13. // encountering fakelag
  14. FakelagBursting FakelagState = iota
  15. // after that, they're "throttled" and we sleep in between commands until
  16. // they're spaced sufficiently far apart
  17. FakelagThrottled
  18. )
  19. // this is intentionally not threadsafe, because it should only be touched
  20. // from the loop that accepts the client's input and runs commands
  21. type Fakelag struct {
  22. config FakelagConfig
  23. suspended bool
  24. nowFunc func() time.Time
  25. sleepFunc func(time.Duration)
  26. state FakelagState
  27. burstCount uint // number of messages sent in the current burst
  28. lastTouch time.Time
  29. }
  30. func (fl *Fakelag) Initialize(config FakelagConfig) {
  31. fl.config = config
  32. // XXX don't share mutable member CommandBudgets:
  33. if config.CommandBudgets != nil {
  34. fl.config.CommandBudgets = maps.Clone(config.CommandBudgets)
  35. }
  36. fl.nowFunc = time.Now
  37. fl.sleepFunc = time.Sleep
  38. fl.state = FakelagBursting
  39. }
  40. // Idempotently turn off fakelag if it's enabled
  41. func (fl *Fakelag) Suspend() {
  42. if fl.config.Enabled {
  43. fl.suspended = true
  44. fl.config.Enabled = false
  45. }
  46. }
  47. // Idempotently turn fakelag back on if it was previously Suspend'ed
  48. func (fl *Fakelag) Unsuspend() {
  49. if fl.suspended {
  50. fl.config.Enabled = true
  51. fl.suspended = false
  52. }
  53. }
  54. // register a new command, sleep if necessary to delay it
  55. func (fl *Fakelag) Touch(command string) {
  56. if !fl.config.Enabled {
  57. return
  58. }
  59. if budget, ok := fl.config.CommandBudgets[command]; ok && budget > 0 {
  60. fl.config.CommandBudgets[command] = budget - 1
  61. return
  62. }
  63. now := fl.nowFunc()
  64. // XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine
  65. elapsed := now.Sub(fl.lastTouch)
  66. fl.lastTouch = now
  67. if fl.state == FakelagBursting {
  68. // determine if the previous burst is over
  69. if elapsed > fl.config.Cooldown {
  70. fl.burstCount = 0
  71. }
  72. fl.burstCount++
  73. if fl.burstCount > fl.config.BurstLimit {
  74. // reset burst window for next time
  75. fl.burstCount = 0
  76. // transition to throttling
  77. fl.state = FakelagThrottled
  78. // continue to throttling logic
  79. } else {
  80. return
  81. }
  82. }
  83. if fl.state == FakelagThrottled {
  84. if elapsed > fl.config.Cooldown {
  85. // let them burst again
  86. fl.state = FakelagBursting
  87. fl.burstCount = 1
  88. return
  89. }
  90. var sleepDuration time.Duration
  91. if fl.config.MessagesPerWindow > 0 {
  92. // space them out by at least window/messagesperwindow
  93. sleepDuration = time.Duration((int64(fl.config.Window) / int64(fl.config.MessagesPerWindow)) - int64(elapsed))
  94. } else {
  95. // only burst messages are allowed: sleep until cooldown expires,
  96. // then count this as a burst message
  97. sleepDuration = time.Duration(int64(fl.config.Cooldown) - int64(elapsed))
  98. fl.state = FakelagBursting
  99. fl.burstCount = 1
  100. }
  101. if sleepDuration > 0 {
  102. fl.sleepFunc(sleepDuration)
  103. // the touch time should take into account the time we slept
  104. fl.lastTouch = fl.nowFunc()
  105. }
  106. }
  107. }