store.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package captcha
  2. import (
  3. "container/list"
  4. "sync"
  5. "time"
  6. )
  7. // expValue stores timestamp and id of captchas. It is used in a list inside
  8. // store for indexing generated captchas by timestamp to enable garbage
  9. // collection of expired captchas.
  10. type expValue struct {
  11. timestamp int64
  12. id string
  13. }
  14. // store is an internal store for captcha ids and their values.
  15. type store struct {
  16. mu sync.RWMutex
  17. ids map[string][]byte
  18. exp *list.List
  19. // Number of items stored after last collection.
  20. numStored int
  21. // Number of saved items that triggers collection.
  22. collectNum int
  23. // Expiration time of captchas.
  24. expiration int64
  25. }
  26. // newStore initializes and returns a new store.
  27. func newStore(collectNum int, expiration int64) *store {
  28. s := new(store)
  29. s.ids = make(map[string][]byte)
  30. s.exp = list.New()
  31. s.collectNum = collectNum
  32. s.expiration = expiration
  33. return s
  34. }
  35. // saveCaptcha saves the captcha id and the corresponding digits.
  36. func (s *store) saveCaptcha(id string, digits []byte) {
  37. s.mu.Lock()
  38. s.ids[id] = digits
  39. s.exp.PushBack(expValue{time.Seconds(), id})
  40. s.numStored++
  41. s.mu.Unlock()
  42. if s.numStored > s.collectNum {
  43. go s.collect()
  44. }
  45. }
  46. // getDigits returns the digits for the given id.
  47. func (s *store) getDigits(id string) (digits []byte) {
  48. s.mu.RLock()
  49. defer s.mu.RUnlock()
  50. digits, _ = s.ids[id]
  51. return
  52. }
  53. // getDigitsClear returns the digits for the given id, and removes them from
  54. // the store.
  55. func (s *store) getDigitsClear(id string) (digits []byte) {
  56. s.mu.Lock()
  57. defer s.mu.Unlock()
  58. digits, ok := s.ids[id]
  59. if !ok {
  60. return
  61. }
  62. s.ids[id] = nil, false
  63. // XXX(dchest) Index (s.exp) will be cleaned when collecting expired
  64. // captchas. Can't clean it here, because we don't store reference to
  65. // expValue in the map. Maybe store it?
  66. return
  67. }
  68. // collect deletes expired captchas from the store.
  69. func (s *store) collect() {
  70. now := time.Seconds()
  71. s.mu.Lock()
  72. defer s.mu.Unlock()
  73. s.numStored = 0
  74. for e := s.exp.Front(); e != nil; {
  75. ev, ok := e.Value.(expValue)
  76. if !ok {
  77. return
  78. }
  79. if ev.timestamp+s.expiration < now {
  80. s.ids[ev.id] = nil, false
  81. next := e.Next()
  82. s.exp.Remove(e)
  83. e = next
  84. } else {
  85. return
  86. }
  87. }
  88. }