github.com/uber/kraken@v0.1.4/lib/store/base/file_map.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package base
    15  
    16  import (
    17  	"container/list"
    18  	"os"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/uber/kraken/lib/store/metadata"
    23  	"github.com/uber/kraken/utils/log"
    24  
    25  	"github.com/andres-erbsen/clock"
    26  )
    27  
    28  // FileMap is a thread-safe name -> FileEntry map.
    29  type FileMap interface {
    30  	Contains(name string) bool
    31  	TryStore(name string, entry FileEntry, f func(string, FileEntry) bool) bool
    32  	LoadForWrite(name string, f func(string, FileEntry)) bool
    33  	LoadForRead(name string, f func(string, FileEntry)) bool
    34  	LoadForPeek(name string, f func(string, FileEntry)) bool
    35  	Delete(name string, f func(string, FileEntry) bool) bool
    36  }
    37  
    38  var _ FileMap = (*lruFileMap)(nil)
    39  
    40  type fileEntryWithAccessTime struct {
    41  	sync.RWMutex
    42  
    43  	fe FileEntry
    44  
    45  	// The last time that LoadForWrite/LoadForRead is called on the entry.
    46  	lastAccessTime time.Time
    47  }
    48  
    49  // lruFileMap implements FileMap interface, with an optional max capacity, and
    50  // will evict least recently accessed entry when the capacity is reached, which
    51  // will only be updated by LoadForRead and LoadForWrite.
    52  type lruFileMap struct {
    53  	sync.Mutex
    54  
    55  	// Capacity limit of the LRU map. Set capacity to 0 to disable eviction.
    56  	size int
    57  
    58  	clk clock.Clock
    59  
    60  	// Min timespan between two updates of LAT for the same file.
    61  	timeResolution time.Duration
    62  	queue          *list.List
    63  	elements       map[string]*list.Element
    64  }
    65  
    66  // NewLRUFileMap creates a new LRU map given capacity.
    67  func NewLRUFileMap(size int, clk clock.Clock) FileMap {
    68  	m := &lruFileMap{
    69  		size:           size,
    70  		clk:            clk,
    71  		timeResolution: time.Minute * 5,
    72  		queue:          list.New(),
    73  		elements:       make(map[string]*list.Element),
    74  	}
    75  
    76  	return m
    77  }
    78  
    79  // NewLATFileMap creates a new file map that tracks last access time, but no
    80  // auto-eviction.
    81  func NewLATFileMap(clk clock.Clock) FileMap {
    82  	m := &lruFileMap{
    83  		size:           0, // Disable eviction.
    84  		clk:            clk,
    85  		timeResolution: time.Minute * 5,
    86  		queue:          list.New(),
    87  		elements:       make(map[string]*list.Element),
    88  	}
    89  
    90  	return m
    91  }
    92  
    93  func (fm *lruFileMap) get(name string) (*fileEntryWithAccessTime, bool) {
    94  	if element, ok := fm.elements[name]; ok {
    95  		fm.queue.MoveToFront(element)
    96  		return element.Value.(*fileEntryWithAccessTime), ok
    97  	}
    98  	return nil, false
    99  }
   100  
   101  func (fm *lruFileMap) syncGet(name string) (*fileEntryWithAccessTime, bool) {
   102  	fm.Lock()
   103  	defer fm.Unlock()
   104  
   105  	return fm.get(name)
   106  }
   107  
   108  func (fm *lruFileMap) syncGetAndTouch(name string) (*fileEntryWithAccessTime, bool) {
   109  	fm.Lock()
   110  	defer fm.Unlock()
   111  
   112  	e, ok := fm.get(name)
   113  	if !ok {
   114  		return nil, false
   115  	}
   116  
   117  	// Update last access time.
   118  	t := fm.clk.Now()
   119  	if t.Sub(e.lastAccessTime) >= fm.timeResolution {
   120  		// Only update if new timestamp is <timeResolution> newer than previous
   121  		// value.
   122  		e.lastAccessTime = t
   123  		e.fe.SetMetadata(metadata.NewLastAccessTime(t))
   124  	}
   125  
   126  	return e, true
   127  }
   128  
   129  func (fm *lruFileMap) add(name string, e *fileEntryWithAccessTime) bool {
   130  	if _, ok := fm.elements[name]; !ok {
   131  		element := fm.queue.PushFront(e)
   132  		fm.elements[name] = element
   133  		return true
   134  	}
   135  	return false
   136  }
   137  
   138  func (fm *lruFileMap) getOldest() (*fileEntryWithAccessTime, bool) {
   139  	if e := fm.queue.Back(); e != nil {
   140  		return e.Value.(*fileEntryWithAccessTime), true
   141  	}
   142  	return nil, false
   143  }
   144  
   145  func (fm *lruFileMap) remove(name string) (*fileEntryWithAccessTime, bool) {
   146  	if e, ok := fm.elements[name]; ok {
   147  		delete(fm.elements, name)
   148  		fm.queue.Remove(e)
   149  		return e.Value.(*fileEntryWithAccessTime), ok
   150  	}
   151  	return nil, false
   152  }
   153  
   154  func (fm *lruFileMap) syncRemove(name string) (*fileEntryWithAccessTime, bool) {
   155  	fm.Lock()
   156  	defer fm.Unlock()
   157  
   158  	return fm.remove(name)
   159  }
   160  
   161  func (fm *lruFileMap) syncRemoveOldestIfNeeded() (e *fileEntryWithAccessTime, ok bool) {
   162  	// Verify if size limit was defined and exceeded.
   163  	fm.Lock()
   164  	if fm.size <= 0 || fm.queue.Len() <= fm.size {
   165  		defer fm.Unlock()
   166  		return nil, false
   167  	}
   168  	e, ok = fm.getOldest()
   169  	if !ok {
   170  		defer fm.Unlock()
   171  		return nil, false
   172  	}
   173  	fm.Unlock()
   174  
   175  	e.Lock()
   176  	defer e.Unlock()
   177  
   178  	// Now that we have the entry lock, make sure k was not deleted or
   179  	// overwritten.
   180  	name := e.fe.GetName()
   181  	if ne, ok := fm.syncGet(name); !ok {
   182  		return nil, false
   183  	} else if ne != e {
   184  		return nil, false
   185  	}
   186  
   187  	if err := e.fe.Delete(); err != nil {
   188  		log.With("name", e.fe.GetName()).Errorf("Error deleting evicted entry: %s", err)
   189  	}
   190  
   191  	// Remove from map while the entry lock is still being held.
   192  	fm.syncRemove(name)
   193  
   194  	return e, true
   195  }
   196  
   197  // Contains returns true if the given key is stored in the map.
   198  func (fm *lruFileMap) Contains(name string) bool {
   199  	fm.Lock()
   200  	defer fm.Unlock()
   201  
   202  	_, ok := fm.elements[name]
   203  	return ok
   204  }
   205  
   206  // TryStore tries to stores the given key / value pair into the map.
   207  // If object is successfully stored, execute f under the protection of Lock.
   208  // Returns false if the name is already present.
   209  func (fm *lruFileMap) TryStore(name string, entry FileEntry, f func(string, FileEntry) bool) bool {
   210  	// Lock on entry first, in case the lock is taken by other goroutine before f().
   211  	e := &fileEntryWithAccessTime{
   212  		fe: entry,
   213  	}
   214  
   215  	// After store, make sure size limit wasn't exceeded.
   216  	// Also make sure this happens after e.RUnlock(), in case the new entry is to be deleted.
   217  	defer fm.syncRemoveOldestIfNeeded()
   218  
   219  	e.Lock()
   220  	defer e.Unlock()
   221  
   222  	fm.Lock()
   223  	// Verify if it's already in the map.
   224  	if _, ok := fm.get(name); ok {
   225  		defer fm.Unlock()
   226  
   227  		// Update last access time.
   228  		t := fm.clk.Now()
   229  		if t.Sub(e.lastAccessTime) >= fm.timeResolution {
   230  			// Only update if new timestamp is <timeResolution> newer than
   231  			// previous value.
   232  			e.lastAccessTime = t
   233  			e.fe.SetMetadata(metadata.NewLastAccessTime(t))
   234  		}
   235  
   236  		return false
   237  	}
   238  
   239  	// Add new entry to map.
   240  	fm.add(name, e)
   241  
   242  	lat := metadata.NewLastAccessTime(fm.clk.Now())
   243  	if err := e.fe.GetMetadata(lat); err != nil {
   244  		// Set LAT if it doesn't exist on disk or cannot be read.
   245  		if !os.IsNotExist(err) {
   246  			log.With("name", e.fe.GetName()).Errorf("Error reading LAT: %s", err)
   247  		}
   248  		if _, err := e.fe.SetMetadata(lat); err != nil {
   249  			log.With("name", e.fe.GetName()).Errorf("Error setting LAT: %s", err)
   250  		}
   251  	}
   252  	e.lastAccessTime = lat.Time
   253  
   254  	fm.Unlock()
   255  
   256  	if !f(name, e.fe) {
   257  		// Remove from map while the entry lock is still being held.
   258  		fm.syncRemove(name)
   259  		return false
   260  	}
   261  
   262  	return true
   263  }
   264  
   265  // LoadForWrite looks up the value of key k and executes f under the protection
   266  // of RLock.
   267  // While f executes, it is guaranteed that k will not be deleted from the map.
   268  // Returns false if k was not found.
   269  // It updates last access time and file size.
   270  func (fm *lruFileMap) LoadForWrite(name string, f func(string, FileEntry)) bool {
   271  	e, ok := fm.syncGet(name)
   272  	if !ok {
   273  		return false
   274  	}
   275  
   276  	e.Lock()
   277  	defer e.Unlock()
   278  
   279  	// Now that we have the entry lock, make sure k was not deleted or
   280  	// overwritten.
   281  	if ne, ok := fm.syncGetAndTouch(name); !ok {
   282  		return false
   283  	} else if ne != e {
   284  		return false
   285  	}
   286  
   287  	f(name, e.fe)
   288  
   289  	return true
   290  }
   291  
   292  // LoadForRead looks up the value of key k and executes f under the protection
   293  // of RLock.
   294  // While f executes, it is guaranteed that k will not be deleted from the map.
   295  // Returns false if k was not found.
   296  // It updates last access time.
   297  func (fm *lruFileMap) LoadForRead(name string, f func(string, FileEntry)) bool {
   298  	e, ok := fm.syncGet(name)
   299  	if !ok {
   300  		return false
   301  	}
   302  
   303  	e.RLock()
   304  	defer e.RUnlock()
   305  
   306  	// Now that we have the entry lock, make sure k was not deleted or
   307  	// overwritten.
   308  	if ne, ok := fm.syncGetAndTouch(name); !ok {
   309  		return false
   310  	} else if ne != e {
   311  		return false
   312  	}
   313  
   314  	f(name, e.fe)
   315  
   316  	return true
   317  }
   318  
   319  // LoadForPeek looks up the value of key k and executes f under the protection
   320  // of RLock.
   321  // While f executes, it is guaranteed that k will not be deleted from the map.
   322  // Returns false if k was not found.
   323  func (fm *lruFileMap) LoadForPeek(name string, f func(string, FileEntry)) bool {
   324  	e, ok := fm.syncGet(name)
   325  	if !ok {
   326  		return false
   327  	}
   328  
   329  	e.RLock()
   330  	defer e.RUnlock()
   331  
   332  	// Now that we have the entry lock, make sure k was not deleted or
   333  	// overwritten.
   334  	if ne, ok := fm.syncGet(name); !ok {
   335  		return false
   336  	} else if ne != e {
   337  		return false
   338  	}
   339  
   340  	f(name, e.fe)
   341  
   342  	return true
   343  }
   344  
   345  // Delete deletes the given key from the Map.
   346  // It also executes f under the protection of Lock.
   347  // If f returns false, abort before key deletion.
   348  func (fm *lruFileMap) Delete(name string, f func(string, FileEntry) bool) bool {
   349  	e, ok := fm.syncGet(name)
   350  	if !ok {
   351  		return false
   352  	}
   353  
   354  	e.Lock()
   355  	defer e.Unlock()
   356  
   357  	// Now that we have the entry lock, make sure k was not deleted or
   358  	// overwritten.
   359  	if ne, ok := fm.syncGet(name); !ok {
   360  		return false
   361  	} else if ne != e {
   362  		return false
   363  	}
   364  
   365  	if !f(name, e.fe) {
   366  		return false
   367  	}
   368  
   369  	// Remove from map while the entry lock is still being held.
   370  	fm.syncRemove(name)
   371  
   372  	return true
   373  }