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  }