github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ext/dload/db.go (about) 1 // Package dload implements functionality to download resources into AIS cluster from external source. 2 /* 3 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package dload 6 7 import ( 8 "errors" 9 "path" 10 "sync" 11 12 "github.com/NVIDIA/aistore/cmn/cos" 13 "github.com/NVIDIA/aistore/cmn/kvdb" 14 "github.com/NVIDIA/aistore/cmn/nlog" 15 ) 16 17 const ( 18 downloaderErrors = "errors" 19 downloaderTasks = "tasks" 20 downloaderCollection = "downloads" 21 22 // Number of errors stored in memory. When the number of errors exceeds 23 // this number, then all errors will be flushed to disk 24 errCacheSize = 100 25 26 // Number of tasks stored in memory. When the number of tasks exceeds 27 // this number, then all errors will be flushed to disk 28 taskInfoCacheSize = 1000 29 ) 30 31 var errJobNotFound = errors.New("job not found") 32 33 type downloaderDB struct { 34 mtx sync.RWMutex 35 driver kvdb.Driver 36 37 errCache map[string][]TaskErrInfo // memory cache for errors, see: errCacheSize 38 taskInfoCache map[string][]TaskDlInfo // memory cache for tasks, see: taskInfoCacheSize 39 } 40 41 func newDownloadDB(driver kvdb.Driver) *downloaderDB { 42 return &downloaderDB{ 43 driver: driver, 44 errCache: make(map[string][]TaskErrInfo, 10), 45 taskInfoCache: make(map[string][]TaskDlInfo, 10), 46 } 47 } 48 49 func (db *downloaderDB) errors(id string) (errors []TaskErrInfo, err error) { 50 key := path.Join(downloaderErrors, id) 51 if err := db.driver.Get(downloaderCollection, key, &errors); err != nil { 52 if !cos.IsErrNotFound(err) { 53 nlog.Errorln(err) 54 return nil, err 55 } 56 // nothing in DB - return an empty list 57 return db.errCache[id], nil 58 } 59 60 errors = append(errors, db.errCache[id]...) 61 return 62 } 63 64 func (db *downloaderDB) getErrors(id string) (errors []TaskErrInfo, err error) { 65 db.mtx.RLock() 66 defer db.mtx.RUnlock() 67 return db.errors(id) 68 } 69 70 func (db *downloaderDB) persistError(id, objName, errMsg string) { 71 db.mtx.Lock() 72 defer db.mtx.Unlock() 73 74 errInfo := TaskErrInfo{Name: objName, Err: errMsg} 75 if len(db.errCache[id]) < errCacheSize { // if possible store error in cache 76 db.errCache[id] = append(db.errCache[id], errInfo) 77 return 78 } 79 80 errMsgs, err := db.errors(id) // it will also append errors from cache 81 if err != nil { 82 nlog.Errorln(err) 83 return 84 } 85 errMsgs = append(errMsgs, errInfo) 86 87 key := path.Join(downloaderErrors, id) 88 if err := db.driver.Set(downloaderCollection, key, errMsgs); err != nil { 89 nlog.Errorln(err) 90 return 91 } 92 93 db.errCache[id] = db.errCache[id][:0] // clear cache 94 } 95 96 func (db *downloaderDB) tasks(id string) (tasks []TaskDlInfo, err error) { 97 key := path.Join(downloaderTasks, id) 98 if err := db.driver.Get(downloaderCollection, key, &tasks); err != nil { 99 if !cos.IsErrNotFound(err) { 100 nlog.Errorln(err) 101 return nil, err 102 } 103 // nothing in DB - return an empty list 104 return db.taskInfoCache[id], nil 105 } 106 tasks = append(tasks, db.taskInfoCache[id]...) 107 return 108 } 109 110 func (db *downloaderDB) persistTaskInfo(singleTask *singleTask) error { 111 id := singleTask.jobID() 112 113 db.mtx.Lock() 114 defer db.mtx.Unlock() 115 116 if len(db.taskInfoCache[id]) < taskInfoCacheSize { // if possible store task in cache 117 db.taskInfoCache[id] = append(db.taskInfoCache[id], singleTask.ToTaskDlInfo()) 118 return nil 119 } 120 121 persistedTasks, err := db.tasks(id) // it will also append tasks from cache 122 if err != nil { 123 return err 124 } 125 persistedTasks = append(persistedTasks, singleTask.ToTaskDlInfo()) 126 127 key := path.Join(downloaderTasks, id) 128 if err := db.driver.Set(downloaderCollection, key, persistedTasks); err != nil { 129 return err 130 } 131 // clear cache 132 clear(db.taskInfoCache[id]) 133 db.taskInfoCache[id] = db.taskInfoCache[id][:0] 134 return nil 135 } 136 137 func (db *downloaderDB) getTasks(id string) (tasks []TaskDlInfo, err error) { 138 db.mtx.RLock() 139 defer db.mtx.RUnlock() 140 return db.tasks(id) 141 } 142 143 // flushes caches into the disk 144 func (db *downloaderDB) flush(id string) error { 145 db.mtx.Lock() 146 defer db.mtx.Unlock() 147 148 if len(db.errCache[id]) > 0 { 149 errMsgs, err := db.errors(id) // it will also append errors from cache 150 if err != nil { 151 return err 152 } 153 154 key := path.Join(downloaderErrors, id) 155 if err := db.driver.Set(downloaderCollection, key, errMsgs); err != nil { 156 nlog.Errorln(err) 157 return err 158 } 159 160 db.errCache[id] = db.errCache[id][:0] // clear cache 161 } 162 163 if len(db.taskInfoCache[id]) > 0 { 164 persistedTasks, err := db.tasks(id) // it will also append tasks from cache 165 if err != nil { 166 return err 167 } 168 169 key := path.Join(downloaderTasks, id) 170 if err := db.driver.Set(downloaderCollection, key, persistedTasks); err != nil { 171 nlog.Errorln(err) 172 return err 173 } 174 175 db.taskInfoCache[id] = db.taskInfoCache[id][:0] // clear cache 176 } 177 return nil 178 } 179 180 func (db *downloaderDB) delete(id string) { 181 db.mtx.Lock() 182 key := path.Join(downloaderErrors, id) 183 db.driver.Delete(downloaderCollection, key) 184 key = path.Join(downloaderTasks, id) 185 db.driver.Delete(downloaderCollection, key) 186 db.mtx.Unlock() 187 }