A simple TicTacToe app with Golang backend and WebSockets gluing it all together.
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.

144 lines
2.9 KiB

  1. package cache
  2. import (
  3. "runtime"
  4. "time"
  5. )
  6. // NewCache returns a simple in-memory cache
  7. func NewCache(defaultExp, cleanupInterval time.Duration) *Cache {
  8. items := make(map[string]Item)
  9. return newCacheWithJanitor(defaultExp, cleanupInterval, items)
  10. }
  11. func newCacheWithJanitor(defaultExp, cleanupInterval time.Duration, items map[string]Item) *Cache {
  12. cache := newCache(defaultExp, items)
  13. Cache := &Cache{cache}
  14. if cleanupInterval > 0 {
  15. runJanitor(cache, cleanupInterval)
  16. runtime.SetFinalizer(Cache, stopJanitor)
  17. }
  18. return Cache
  19. }
  20. func newCache(de time.Duration, m map[string]Item) *cache {
  21. if de == 0 {
  22. de = -1
  23. }
  24. c := &cache{
  25. defaultExpiration: de,
  26. items: m,
  27. }
  28. return c
  29. }
  30. // Add an item to the cache, replacing any existing item. If the duration is 0
  31. // (DefaultExpiration), the cache's default expiration time is used. If it is -1
  32. // (NoExpiration), the item never expires.
  33. func (c *cache) Set(k string, x Game, d time.Duration) {
  34. // "Inlining" of set
  35. var e int64
  36. if d == DefaultExpiration {
  37. d = c.defaultExpiration
  38. }
  39. if d > 0 {
  40. e = time.Now().Add(d).UnixNano()
  41. }
  42. c.mu.Lock()
  43. c.items[k] = Item{
  44. Game: x,
  45. Expiration: e,
  46. }
  47. // TODO: Calls to mu.Unlock are currently not deferred because defer
  48. // adds ~200 ns (as of go1.)
  49. c.mu.Unlock()
  50. }
  51. func (c *cache) set(k string, x Game, d time.Duration) {
  52. var e int64
  53. if d == DefaultExpiration {
  54. d = c.defaultExpiration
  55. }
  56. if d > 0 {
  57. e = time.Now().Add(d).UnixNano()
  58. }
  59. c.items[k] = Item{
  60. Game: x,
  61. Expiration: e,
  62. }
  63. }
  64. // Add an item to the cache, replacing any existing item, using the default
  65. // expiration.
  66. func (c *cache) SetDefault(k string, x Game) {
  67. c.Set(k, x, DefaultExpiration)
  68. }
  69. // Get an item from the cache. Returns the item or nil, and a bool indicating
  70. // whether the key was found.
  71. func (c *cache) Get(k string) (Game, bool) {
  72. c.mu.RLock()
  73. // "Inlining" of get and Expired
  74. item, found := c.items[k]
  75. if !found {
  76. c.mu.RUnlock()
  77. return item.Game, false
  78. }
  79. if item.Expiration > 0 {
  80. if time.Now().UnixNano() > item.Expiration {
  81. c.mu.RUnlock()
  82. return item.Game, false
  83. }
  84. }
  85. c.mu.RUnlock()
  86. return item.Game, true
  87. }
  88. type keyAndValue struct {
  89. key string
  90. value interface{}
  91. }
  92. // Delete all expired items from the cache.
  93. func (c *cache) DeleteExpired() {
  94. now := time.Now().UnixNano()
  95. c.mu.Lock()
  96. for k, v := range c.items {
  97. // "Inlining" of expired
  98. if v.Expiration > 0 && now > v.Expiration {
  99. delete(c.items, k)
  100. }
  101. }
  102. c.mu.Unlock()
  103. }
  104. type janitor struct {
  105. Interval time.Duration
  106. stop chan bool
  107. }
  108. func (j *janitor) Run(c *cache) {
  109. ticker := time.NewTicker(j.Interval)
  110. for {
  111. select {
  112. case <-ticker.C:
  113. c.DeleteExpired()
  114. case <-j.stop:
  115. ticker.Stop()
  116. return
  117. }
  118. }
  119. }
  120. func stopJanitor(c *Cache) {
  121. c.janitor.stop <- true
  122. }
  123. func runJanitor(c *cache, ci time.Duration) {
  124. j := &janitor{
  125. Interval: ci,
  126. stop: make(chan bool),
  127. }
  128. c.janitor = j
  129. go j.Run(c)
  130. }