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.

259 lines
6.6 KiB

  1. // Copyright (c) 2015-2021 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package cmd
  18. import (
  19. "context"
  20. "errors"
  21. "runtime/debug"
  22. "sort"
  23. "sync"
  24. "time"
  25. "github.com/minio/minio/internal/logger"
  26. "github.com/minio/pkg/v3/console"
  27. )
  28. // a bucketMetacache keeps track of all caches generated
  29. // for a bucket.
  30. type bucketMetacache struct {
  31. // Name of bucket
  32. bucket string
  33. // caches indexed by id.
  34. caches map[string]metacache
  35. // cache ids indexed by root paths
  36. cachesRoot map[string][]string `msg:"-"`
  37. // Internal state
  38. mu sync.RWMutex `msg:"-"`
  39. updated bool `msg:"-"`
  40. }
  41. type deleteAllStorager interface {
  42. deleteAll(ctx context.Context, bucket, prefix string)
  43. }
  44. // newBucketMetacache creates a new bucketMetacache.
  45. // Optionally remove all existing caches.
  46. func newBucketMetacache(bucket string, cleanup bool) *bucketMetacache {
  47. if cleanup {
  48. // Recursively delete all caches.
  49. objAPI := newObjectLayerFn()
  50. if objAPI != nil {
  51. ez, ok := objAPI.(deleteAllStorager)
  52. if ok {
  53. ctx := context.Background()
  54. ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(bucket, slashSeparator))
  55. }
  56. }
  57. }
  58. return &bucketMetacache{
  59. bucket: bucket,
  60. caches: make(map[string]metacache, 10),
  61. cachesRoot: make(map[string][]string, 10),
  62. }
  63. }
  64. func (b *bucketMetacache) debugf(format string, data ...interface{}) {
  65. if serverDebugLog {
  66. console.Debugf(format+"\n", data...)
  67. }
  68. }
  69. // findCache will attempt to find a matching cache for the provided options.
  70. // If a cache with the same ID exists already it will be returned.
  71. // If none can be found a new is created with the provided ID.
  72. func (b *bucketMetacache) findCache(o listPathOptions) metacache {
  73. if b == nil {
  74. logger.Info("bucketMetacache.findCache: nil cache for bucket %s", o.Bucket)
  75. return metacache{}
  76. }
  77. if o.Bucket != b.bucket {
  78. logger.Info("bucketMetacache.findCache: bucket %s does not match this bucket %s", o.Bucket, b.bucket)
  79. debug.PrintStack()
  80. return metacache{}
  81. }
  82. // Grab a write lock, since we create one if we cannot find one.
  83. b.mu.Lock()
  84. defer b.mu.Unlock()
  85. // Check if exists already.
  86. if c, ok := b.caches[o.ID]; ok {
  87. c.lastHandout = time.Now()
  88. b.caches[o.ID] = c
  89. b.debugf("returning existing %v", o.ID)
  90. return c
  91. }
  92. if !o.Create {
  93. return metacache{
  94. id: o.ID,
  95. bucket: o.Bucket,
  96. status: scanStateNone,
  97. }
  98. }
  99. // Create new and add.
  100. best := o.newMetacache()
  101. b.caches[o.ID] = best
  102. b.cachesRoot[best.root] = append(b.cachesRoot[best.root], best.id)
  103. b.updated = true
  104. b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket)
  105. return best
  106. }
  107. // cleanup removes redundant and outdated entries.
  108. func (b *bucketMetacache) cleanup() {
  109. // Entries to remove.
  110. remove := make(map[string]struct{})
  111. // Test on a copy
  112. // cleanup is the only one deleting caches.
  113. caches, _ := b.cloneCaches()
  114. for id, cache := range caches {
  115. if !cache.worthKeeping() {
  116. b.debugf("cache %s not worth keeping", id)
  117. remove[id] = struct{}{}
  118. continue
  119. }
  120. if cache.id != id {
  121. logger.Info("cache ID mismatch %s != %s", id, cache.id)
  122. remove[id] = struct{}{}
  123. continue
  124. }
  125. if cache.bucket != b.bucket {
  126. logger.Info("cache bucket mismatch %s != %s", b.bucket, cache.bucket)
  127. remove[id] = struct{}{}
  128. continue
  129. }
  130. }
  131. // If above limit, remove the caches with the oldest handout time.
  132. if len(caches)-len(remove) > metacacheMaxEntries {
  133. remainCaches := make([]metacache, 0, len(caches)-len(remove))
  134. for id, cache := range caches {
  135. if _, ok := remove[id]; ok {
  136. continue
  137. }
  138. remainCaches = append(remainCaches, cache)
  139. }
  140. if len(remainCaches) > metacacheMaxEntries {
  141. // Sort oldest last...
  142. sort.Slice(remainCaches, func(i, j int) bool {
  143. return remainCaches[i].lastHandout.Before(remainCaches[j].lastHandout)
  144. })
  145. // Keep first metacacheMaxEntries...
  146. for _, cache := range remainCaches[metacacheMaxEntries:] {
  147. if time.Since(cache.lastHandout) > metacacheMaxClientWait {
  148. remove[cache.id] = struct{}{}
  149. }
  150. }
  151. }
  152. }
  153. for id := range remove {
  154. b.deleteCache(id)
  155. }
  156. }
  157. // updateCacheEntry will update a cache.
  158. // Returns the updated status.
  159. func (b *bucketMetacache) updateCacheEntry(update metacache) (metacache, error) {
  160. b.mu.Lock()
  161. defer b.mu.Unlock()
  162. existing, ok := b.caches[update.id]
  163. if !ok {
  164. return update, errFileNotFound
  165. }
  166. existing.update(update)
  167. b.caches[update.id] = existing
  168. b.updated = true
  169. return existing, nil
  170. }
  171. // cloneCaches will return a clone of all current caches.
  172. func (b *bucketMetacache) cloneCaches() (map[string]metacache, map[string][]string) {
  173. b.mu.RLock()
  174. defer b.mu.RUnlock()
  175. dst := make(map[string]metacache, len(b.caches))
  176. for k, v := range b.caches {
  177. dst[k] = v
  178. }
  179. // Copy indexes
  180. dst2 := make(map[string][]string, len(b.cachesRoot))
  181. for k, v := range b.cachesRoot {
  182. tmp := make([]string, len(v))
  183. copy(tmp, v)
  184. dst2[k] = tmp
  185. }
  186. return dst, dst2
  187. }
  188. // deleteAll will delete all on disk data for ALL caches.
  189. // Deletes are performed concurrently.
  190. func (b *bucketMetacache) deleteAll() {
  191. ctx := context.Background()
  192. objAPI := newObjectLayerFn()
  193. if objAPI == nil {
  194. return
  195. }
  196. ez, ok := objAPI.(deleteAllStorager)
  197. if !ok {
  198. bugLogIf(ctx, errors.New("bucketMetacache: expected objAPI to be 'deleteAllStorager'"))
  199. return
  200. }
  201. b.mu.Lock()
  202. defer b.mu.Unlock()
  203. b.updated = true
  204. // Delete all.
  205. ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator))
  206. b.caches = make(map[string]metacache, 10)
  207. b.cachesRoot = make(map[string][]string, 10)
  208. }
  209. // deleteCache will delete a specific cache and all files related to it across the cluster.
  210. func (b *bucketMetacache) deleteCache(id string) {
  211. b.mu.Lock()
  212. c, ok := b.caches[id]
  213. if ok {
  214. // Delete from root map.
  215. list := b.cachesRoot[c.root]
  216. for i, lid := range list {
  217. if id == lid {
  218. list = append(list[:i], list[i+1:]...)
  219. break
  220. }
  221. }
  222. b.cachesRoot[c.root] = list
  223. delete(b.caches, id)
  224. b.updated = true
  225. }
  226. b.mu.Unlock()
  227. if ok {
  228. c.delete(context.Background())
  229. }
  230. }