github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/proxy/scheduler/scheduler.go (about) 1 package scheduler 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/docker/distribution/context" 10 "github.com/docker/distribution/registry/storage/driver" 11 ) 12 13 // onTTLExpiryFunc is called when a repository's TTL expires 14 type expiryFunc func(string) error 15 16 const ( 17 entryTypeBlob = iota 18 entryTypeManifest 19 indexSaveFrequency = 5 * time.Second 20 ) 21 22 // schedulerEntry represents an entry in the scheduler 23 // fields are exported for serialization 24 type schedulerEntry struct { 25 Key string `json:"Key"` 26 Expiry time.Time `json:"ExpiryData"` 27 EntryType int `json:"EntryType"` 28 29 timer *time.Timer 30 } 31 32 // New returns a new instance of the scheduler 33 func New(ctx context.Context, driver driver.StorageDriver, path string) *TTLExpirationScheduler { 34 return &TTLExpirationScheduler{ 35 entries: make(map[string]*schedulerEntry), 36 driver: driver, 37 pathToStateFile: path, 38 ctx: ctx, 39 stopped: true, 40 doneChan: make(chan struct{}), 41 saveTimer: time.NewTicker(indexSaveFrequency), 42 } 43 } 44 45 // TTLExpirationScheduler is a scheduler used to perform actions 46 // when TTLs expire 47 type TTLExpirationScheduler struct { 48 sync.Mutex 49 50 entries map[string]*schedulerEntry 51 52 driver driver.StorageDriver 53 ctx context.Context 54 pathToStateFile string 55 56 stopped bool 57 58 onBlobExpire expiryFunc 59 onManifestExpire expiryFunc 60 61 indexDirty bool 62 saveTimer *time.Ticker 63 doneChan chan struct{} 64 } 65 66 // OnBlobExpire is called when a scheduled blob's TTL expires 67 func (ttles *TTLExpirationScheduler) OnBlobExpire(f expiryFunc) { 68 ttles.Lock() 69 defer ttles.Unlock() 70 71 ttles.onBlobExpire = f 72 } 73 74 // OnManifestExpire is called when a scheduled manifest's TTL expires 75 func (ttles *TTLExpirationScheduler) OnManifestExpire(f expiryFunc) { 76 ttles.Lock() 77 defer ttles.Unlock() 78 79 ttles.onManifestExpire = f 80 } 81 82 // AddBlob schedules a blob cleanup after ttl expires 83 func (ttles *TTLExpirationScheduler) AddBlob(dgst string, ttl time.Duration) error { 84 ttles.Lock() 85 defer ttles.Unlock() 86 87 if ttles.stopped { 88 return fmt.Errorf("scheduler not started") 89 } 90 ttles.add(dgst, ttl, entryTypeBlob) 91 return nil 92 } 93 94 // AddManifest schedules a manifest cleanup after ttl expires 95 func (ttles *TTLExpirationScheduler) AddManifest(repoName string, ttl time.Duration) error { 96 ttles.Lock() 97 defer ttles.Unlock() 98 99 if ttles.stopped { 100 return fmt.Errorf("scheduler not started") 101 } 102 103 ttles.add(repoName, ttl, entryTypeManifest) 104 return nil 105 } 106 107 // Start starts the scheduler 108 func (ttles *TTLExpirationScheduler) Start() error { 109 ttles.Lock() 110 defer ttles.Unlock() 111 112 err := ttles.readState() 113 if err != nil { 114 return err 115 } 116 117 if !ttles.stopped { 118 return fmt.Errorf("Scheduler already started") 119 } 120 121 context.GetLogger(ttles.ctx).Infof("Starting cached object TTL expiration scheduler...") 122 ttles.stopped = false 123 124 // Start timer for each deserialized entry 125 for _, entry := range ttles.entries { 126 entry.timer = ttles.startTimer(entry, entry.Expiry.Sub(time.Now())) 127 } 128 129 // Start a ticker to periodically save the entries index 130 131 go func() { 132 for { 133 select { 134 case <-ttles.saveTimer.C: 135 if !ttles.indexDirty { 136 continue 137 } 138 139 ttles.Lock() 140 err := ttles.writeState() 141 if err != nil { 142 context.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) 143 } else { 144 ttles.indexDirty = false 145 } 146 ttles.Unlock() 147 148 case <-ttles.doneChan: 149 return 150 } 151 } 152 }() 153 154 return nil 155 } 156 157 func (ttles *TTLExpirationScheduler) add(key string, ttl time.Duration, eType int) { 158 entry := &schedulerEntry{ 159 Key: key, 160 Expiry: time.Now().Add(ttl), 161 EntryType: eType, 162 } 163 context.GetLogger(ttles.ctx).Infof("Adding new scheduler entry for %s with ttl=%s", entry.Key, entry.Expiry.Sub(time.Now())) 164 if oldEntry, present := ttles.entries[key]; present && oldEntry.timer != nil { 165 oldEntry.timer.Stop() 166 } 167 ttles.entries[key] = entry 168 entry.timer = ttles.startTimer(entry, ttl) 169 ttles.indexDirty = true 170 } 171 172 func (ttles *TTLExpirationScheduler) startTimer(entry *schedulerEntry, ttl time.Duration) *time.Timer { 173 return time.AfterFunc(ttl, func() { 174 ttles.Lock() 175 defer ttles.Unlock() 176 177 var f expiryFunc 178 179 switch entry.EntryType { 180 case entryTypeBlob: 181 f = ttles.onBlobExpire 182 case entryTypeManifest: 183 f = ttles.onManifestExpire 184 default: 185 f = func(repoName string) error { 186 return fmt.Errorf("Unexpected scheduler entry type") 187 } 188 } 189 190 if err := f(entry.Key); err != nil { 191 context.GetLogger(ttles.ctx).Errorf("Scheduler error returned from OnExpire(%s): %s", entry.Key, err) 192 } 193 194 delete(ttles.entries, entry.Key) 195 ttles.indexDirty = true 196 }) 197 } 198 199 // Stop stops the scheduler. 200 func (ttles *TTLExpirationScheduler) Stop() { 201 ttles.Lock() 202 defer ttles.Unlock() 203 204 if err := ttles.writeState(); err != nil { 205 context.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) 206 } 207 208 for _, entry := range ttles.entries { 209 entry.timer.Stop() 210 } 211 212 close(ttles.doneChan) 213 ttles.saveTimer.Stop() 214 ttles.stopped = true 215 } 216 217 func (ttles *TTLExpirationScheduler) writeState() error { 218 jsonBytes, err := json.Marshal(ttles.entries) 219 if err != nil { 220 return err 221 } 222 223 err = ttles.driver.PutContent(ttles.ctx, ttles.pathToStateFile, jsonBytes) 224 if err != nil { 225 return err 226 } 227 228 return nil 229 } 230 231 func (ttles *TTLExpirationScheduler) readState() error { 232 if _, err := ttles.driver.Stat(ttles.ctx, ttles.pathToStateFile); err != nil { 233 switch err := err.(type) { 234 case driver.PathNotFoundError: 235 return nil 236 default: 237 return err 238 } 239 } 240 241 bytes, err := ttles.driver.GetContent(ttles.ctx, ttles.pathToStateFile) 242 if err != nil { 243 return err 244 } 245 246 err = json.Unmarshal(bytes, &ttles.entries) 247 if err != nil { 248 return err 249 } 250 251 return nil 252 }