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 2.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. // Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "time"
  6. )
  7. // fakelag is a system for artificially delaying commands when a user issues
  8. // them too rapidly
  9. type FakelagState uint
  10. const (
  11. // initially, the client is "bursting" and can send n commands without
  12. // encountering fakelag
  13. FakelagBursting FakelagState = iota
  14. // after that, they're "throttled" and we sleep in between commands until
  15. // they're spaced sufficiently far apart
  16. FakelagThrottled
  17. )
  18. // this is intentionally not threadsafe, because it should only be touched
  19. // from the loop that accepts the client's input and runs commands
  20. type Fakelag struct {
  21. window time.Duration
  22. burstLimit uint
  23. throttleMessagesPerWindow uint
  24. cooldown time.Duration
  25. nowFunc func() time.Time
  26. sleepFunc func(time.Duration)
  27. state FakelagState
  28. burstCount uint // number of messages sent in the current burst
  29. lastTouch time.Time
  30. }
  31. func NewFakelag(window time.Duration, burstLimit uint, throttleMessagesPerWindow uint, cooldown time.Duration) *Fakelag {
  32. return &Fakelag{
  33. window: window,
  34. burstLimit: burstLimit,
  35. throttleMessagesPerWindow: throttleMessagesPerWindow,
  36. cooldown: cooldown,
  37. nowFunc: time.Now,
  38. sleepFunc: time.Sleep,
  39. state: FakelagBursting,
  40. }
  41. }
  42. // register a new command, sleep if necessary to delay it
  43. func (fl *Fakelag) Touch() {
  44. if fl == nil {
  45. return
  46. }
  47. now := fl.nowFunc()
  48. // XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine
  49. elapsed := now.Sub(fl.lastTouch)
  50. fl.lastTouch = now
  51. if fl.state == FakelagBursting {
  52. // determine if the previous burst is over
  53. if elapsed > fl.cooldown {
  54. fl.burstCount = 0
  55. }
  56. fl.burstCount++
  57. if fl.burstCount > fl.burstLimit {
  58. // reset burst window for next time
  59. fl.burstCount = 0
  60. // transition to throttling
  61. fl.state = FakelagThrottled
  62. // continue to throttling logic
  63. } else {
  64. return
  65. }
  66. }
  67. if fl.state == FakelagThrottled {
  68. if elapsed > fl.cooldown {
  69. // let them burst again
  70. fl.state = FakelagBursting
  71. return
  72. }
  73. // space them out by at least window/messagesperwindow
  74. sleepDuration := time.Duration((int64(fl.window) / int64(fl.throttleMessagesPerWindow)) - int64(elapsed))
  75. if sleepDuration < 0 {
  76. sleepDuration = 0
  77. }
  78. fl.sleepFunc(sleepDuration)
  79. }
  80. }