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.

261 lines
5.8 KiB

  1. package cache
  2. import (
  3. "fmt"
  4. "runtime"
  5. "time"
  6. uuid "github.com/satori/go.uuid"
  7. )
  8. // Expired returns true if the item has expired
  9. func (item Item) Expired() bool {
  10. if item.Expiration == 0 {
  11. return false
  12. }
  13. return time.Now().UnixNano() > item.Expiration
  14. }
  15. // NewCache returns a simple in-memory cache
  16. func NewCache(defaultExp, cleanupInterval time.Duration) *Cache {
  17. items := make(map[uuid.UUID]Item)
  18. return newCacheWithJanitor(defaultExp, cleanupInterval, items)
  19. }
  20. func newCacheWithJanitor(defaultExp, cleanupInterval time.Duration, items map[uuid.UUID]Item) *Cache {
  21. cache := newCache(defaultExp, items)
  22. Cache := &Cache{cache}
  23. if cleanupInterval > 0 {
  24. runJanitor(cache, cleanupInterval)
  25. runtime.SetFinalizer(Cache, stopJanitor)
  26. }
  27. return Cache
  28. }
  29. func newCache(de time.Duration, m map[uuid.UUID]Item) *cache {
  30. if de == 0 {
  31. de = -1
  32. }
  33. c := &cache{
  34. defaultExpiration: de,
  35. items: m,
  36. }
  37. return c
  38. }
  39. // Add an item to the cache, replacing any existing item. If the duration is 0
  40. // (DefaultExpiration), the cache's default expiration time is used. If it is -1
  41. // (NoExpiration), the item never expires.
  42. func (c *cache) Set(k uuid.UUID, x interface{}, d time.Duration) {
  43. // "Inlining" of set
  44. var e int64
  45. if d == DefaultExpiration {
  46. d = c.defaultExpiration
  47. }
  48. if d > 0 {
  49. e = time.Now().Add(d).UnixNano()
  50. }
  51. c.mu.Lock()
  52. c.items[k] = Item{
  53. Object: x,
  54. Expiration: e,
  55. }
  56. // TODO: Calls to mu.Unlock are currently not deferred because defer
  57. // adds ~200 ns (as of go1.)
  58. c.mu.Unlock()
  59. }
  60. func (c *cache) set(k uuid.UUID, x interface{}, d time.Duration) {
  61. var e int64
  62. if d == DefaultExpiration {
  63. d = c.defaultExpiration
  64. }
  65. if d > 0 {
  66. e = time.Now().Add(d).UnixNano()
  67. }
  68. c.items[k] = Item{
  69. Object: x,
  70. Expiration: e,
  71. }
  72. }
  73. // Add an item to the cache, replacing any existing item, using the default
  74. // expiration.
  75. func (c *cache) SetDefault(k uuid.UUID, x interface{}) {
  76. c.Set(k, x, DefaultExpiration)
  77. }
  78. // Add an item to the cache only if an item doesn't already exist for the given
  79. // key, or if the existing item has expired. Returns an error otherwise.
  80. func (c *cache) Add(k uuid.UUID, x interface{}, d time.Duration) error {
  81. c.mu.Lock()
  82. _, found := c.get(k)
  83. if found {
  84. c.mu.Unlock()
  85. return fmt.Errorf("Item %s already exists", k)
  86. }
  87. c.set(k, x, d)
  88. c.mu.Unlock()
  89. return nil
  90. }
  91. // Set a new value for the cache key only if it already exists, and the existing
  92. // item hasn't expired. Returns an error otherwise.
  93. func (c *cache) Replace(k uuid.UUID, x interface{}, d time.Duration) error {
  94. c.mu.Lock()
  95. _, found := c.get(k)
  96. if !found {
  97. c.mu.Unlock()
  98. return fmt.Errorf("Item %s doesn't exist", k)
  99. }
  100. c.set(k, x, d)
  101. c.mu.Unlock()
  102. return nil
  103. }
  104. // Get an item from the cache. Returns the item or nil, and a bool indicating
  105. // whether the key was found.
  106. func (c *cache) Get(k uuid.UUID) (interface{}, bool) {
  107. c.mu.RLock()
  108. // "Inlining" of get and Expired
  109. item, found := c.items[k]
  110. if !found {
  111. c.mu.RUnlock()
  112. return nil, false
  113. }
  114. if item.Expiration > 0 {
  115. if time.Now().UnixNano() > item.Expiration {
  116. c.mu.RUnlock()
  117. return nil, false
  118. }
  119. }
  120. c.mu.RUnlock()
  121. return item.Object, true
  122. }
  123. // GetWithExpiration returns an item and its expiration time from the cache.
  124. // It returns the item or nil, the expiration time if one is set (if the item
  125. // never expires a zero value for time.Time is returned), and a bool indicating
  126. // whether the key was found.
  127. func (c *cache) GetWithExpiration(k uuid.UUID) (interface{}, time.Time, bool) {
  128. c.mu.RLock()
  129. // "Inlining" of get and Expired
  130. item, found := c.items[k]
  131. if !found {
  132. c.mu.RUnlock()
  133. return nil, time.Time{}, false
  134. }
  135. if item.Expiration > 0 {
  136. if time.Now().UnixNano() > item.Expiration {
  137. c.mu.RUnlock()
  138. return nil, time.Time{}, false
  139. }
  140. // Return the item and the expiration time
  141. c.mu.RUnlock()
  142. return item.Object, time.Unix(0, item.Expiration), true
  143. }
  144. // If expiration <= 0 (i.e. no expiration time set) then return the item
  145. // and a zeroed time.Time
  146. c.mu.RUnlock()
  147. return item.Object, time.Time{}, true
  148. }
  149. func (c *cache) get(k uuid.UUID) (interface{}, bool) {
  150. item, found := c.items[k]
  151. if !found {
  152. return nil, false
  153. }
  154. // "Inlining" of Expired
  155. if item.Expiration > 0 {
  156. if time.Now().UnixNano() > item.Expiration {
  157. return nil, false
  158. }
  159. }
  160. return item.Object, true
  161. }
  162. func (c *cache) delete(k uuid.UUID) (interface{}, bool) {
  163. if c.onEvicted != nil {
  164. if v, found := c.items[k]; found {
  165. delete(c.items, k)
  166. return v.Object, true
  167. }
  168. }
  169. delete(c.items, k)
  170. return nil, false
  171. }
  172. type keyAndValue struct {
  173. key uuid.UUID
  174. value interface{}
  175. }
  176. // Delete all expired items from the cache.
  177. func (c *cache) DeleteExpired() {
  178. var evictedItems []keyAndValue
  179. now := time.Now().UnixNano()
  180. c.mu.Lock()
  181. for k, v := range c.items {
  182. // "Inlining" of expired
  183. if v.Expiration > 0 && now > v.Expiration {
  184. ov, evicted := c.delete(k)
  185. if evicted {
  186. evictedItems = append(evictedItems, keyAndValue{k, ov})
  187. }
  188. }
  189. }
  190. c.mu.Unlock()
  191. for _, v := range evictedItems {
  192. c.onEvicted(v.key, v.value)
  193. }
  194. }
  195. // Sets an (optional) function that is called with the key and value when an
  196. // item is evicted from the cache. (Including when it is deleted manually, but
  197. // not when it is overwritten.) Set to nil to disable.
  198. func (c *cache) OnEvicted(f func(uuid.UUID, interface{})) {
  199. c.mu.Lock()
  200. c.onEvicted = f
  201. c.mu.Unlock()
  202. }
  203. // Delete all items from the cache.
  204. func (c *cache) Flush() {
  205. c.mu.Lock()
  206. c.items = map[uuid.UUID]Item{}
  207. c.mu.Unlock()
  208. }
  209. type janitor struct {
  210. Interval time.Duration
  211. stop chan bool
  212. }
  213. func (j *janitor) Run(c *cache) {
  214. ticker := time.NewTicker(j.Interval)
  215. for {
  216. select {
  217. case <-ticker.C:
  218. c.DeleteExpired()
  219. case <-j.stop:
  220. ticker.Stop()
  221. return
  222. }
  223. }
  224. }
  225. func stopJanitor(c *Cache) {
  226. c.janitor.stop <- true
  227. }
  228. func runJanitor(c *cache, ci time.Duration) {
  229. j := &janitor{
  230. Interval: ci,
  231. stop: make(chan bool),
  232. }
  233. c.janitor = j
  234. go j.Run(c)
  235. }