github.com/braveheart12/just@v0.8.7/ledger/recentstorage/recentstorage_concrete.go (about)

     1  /*
     2   *    Copyright 2019 Insolar
     3   *
     4   *    Licensed under the Apache License, Version 2.0 (the "License");
     5   *    you may not use this file except in compliance with the License.
     6   *    You may obtain a copy of the License at
     7   *
     8   *        http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   *    Unless required by applicable law or agreed to in writing, software
    11   *    distributed under the License is distributed on an "AS IS" BASIS,
    12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *    See the License for the specific language governing permissions and
    14   *    limitations under the License.
    15   */
    16  
    17  package recentstorage
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  
    23  	"github.com/insolar/insolar/core"
    24  	"github.com/insolar/insolar/instrumentation/inslogger"
    25  	"github.com/insolar/insolar/instrumentation/insmetrics"
    26  	"go.opencensus.io/stats"
    27  )
    28  
    29  // RecentStorageProvider provides a recent storage for jet
    30  type RecentStorageProvider struct { //nolint: golint
    31  	indexStorages   map[core.RecordID]*RecentIndexStorageConcrete
    32  	pendingStorages map[core.RecordID]*PendingStorageConcrete
    33  
    34  	indexLock   sync.Mutex
    35  	pendingLock sync.Mutex
    36  
    37  	DefaultTTL int
    38  }
    39  
    40  // NewRecentStorageProvider creates new provider
    41  func NewRecentStorageProvider(defaultTTL int) *RecentStorageProvider {
    42  	return &RecentStorageProvider{
    43  		DefaultTTL:      defaultTTL,
    44  		indexStorages:   map[core.RecordID]*RecentIndexStorageConcrete{},
    45  		pendingStorages: map[core.RecordID]*PendingStorageConcrete{},
    46  	}
    47  }
    48  
    49  // GetIndexStorage returns a recent indexes for a specific jet
    50  func (p *RecentStorageProvider) GetIndexStorage(ctx context.Context, jetID core.RecordID) RecentIndexStorage {
    51  	p.indexLock.Lock()
    52  	defer p.indexLock.Unlock()
    53  
    54  	storage, ok := p.indexStorages[jetID]
    55  	if !ok {
    56  		storage = NewRecentIndexStorage(jetID, p.DefaultTTL)
    57  		p.indexStorages[jetID] = storage
    58  	}
    59  	return storage
    60  }
    61  
    62  // GetPendingStorage returns pendings for a specific jet
    63  func (p *RecentStorageProvider) GetPendingStorage(ctx context.Context, jetID core.RecordID) PendingStorage {
    64  	p.pendingLock.Lock()
    65  	defer p.pendingLock.Unlock()
    66  
    67  	storage, ok := p.pendingStorages[jetID]
    68  	if !ok {
    69  		storage = NewPendingStorage(jetID)
    70  		p.pendingStorages[jetID] = storage
    71  	}
    72  	return storage
    73  }
    74  
    75  // Count returns count of pendings in all storages
    76  func (p *RecentStorageProvider) Count() int {
    77  	p.pendingLock.Lock()
    78  	defer p.pendingLock.Unlock()
    79  
    80  	count := 0
    81  	for _, storage := range p.pendingStorages {
    82  		count += len(storage.requests)
    83  	}
    84  
    85  	return count
    86  }
    87  
    88  // CloneIndexStorage clones indexes from one jet to another one
    89  func (p *RecentStorageProvider) CloneIndexStorage(ctx context.Context, fromJetID, toJetID core.RecordID) {
    90  	p.indexLock.Lock()
    91  	defer p.indexLock.Unlock()
    92  
    93  	fromStorage, ok := p.indexStorages[fromJetID]
    94  	if !ok {
    95  		return
    96  	}
    97  	toStorage := &RecentIndexStorageConcrete{
    98  		jetID:      toJetID,
    99  		indexes:    map[core.RecordID]recentObjectMeta{},
   100  		DefaultTTL: p.DefaultTTL,
   101  	}
   102  	for k, v := range fromStorage.indexes {
   103  		clone := v
   104  		toStorage.indexes[k] = clone
   105  	}
   106  	p.indexStorages[toJetID] = toStorage
   107  }
   108  
   109  // ClonePendingStorage clones pending requests from one jet to another one
   110  func (p *RecentStorageProvider) ClonePendingStorage(ctx context.Context, fromJetID, toJetID core.RecordID) {
   111  	p.pendingLock.Lock()
   112  	fromStorage, ok := p.pendingStorages[fromJetID]
   113  	p.pendingLock.Unlock()
   114  
   115  	if !ok {
   116  		return
   117  	}
   118  
   119  	fromStorage.lock.RLock()
   120  	toStorage := &PendingStorageConcrete{
   121  		jetID:    toJetID,
   122  		requests: map[core.RecordID]*lockedPendingObjectContext{},
   123  	}
   124  	for objID, pendingContext := range fromStorage.requests {
   125  		if len(pendingContext.Context.Requests) == 0 {
   126  			continue
   127  		}
   128  
   129  		pendingContext.lock.Lock()
   130  
   131  		clone := PendingObjectContext{
   132  			Active:   pendingContext.Context.Active,
   133  			Requests: []core.RecordID{},
   134  		}
   135  
   136  		clone.Requests = append(clone.Requests, pendingContext.Context.Requests...)
   137  		toStorage.requests[objID] = &lockedPendingObjectContext{Context: &clone}
   138  
   139  		pendingContext.lock.Unlock()
   140  	}
   141  	fromStorage.lock.RUnlock()
   142  
   143  	p.pendingLock.Lock()
   144  	p.pendingStorages[toJetID] = toStorage
   145  	p.pendingLock.Unlock()
   146  }
   147  
   148  // DecreaseIndexesTTL decrease ttl of all indexes in all storages
   149  // If storage contains indexes with zero ttl, they are removed from storage and returned to a caller
   150  // If there are no indexes with ttl more then 0, storage is removed
   151  func (p *RecentStorageProvider) DecreaseIndexesTTL(ctx context.Context) map[core.RecordID][]core.RecordID {
   152  	p.indexLock.Lock()
   153  	defer p.indexLock.Unlock()
   154  
   155  	resMapLock := sync.Mutex{}
   156  	resMap := map[core.RecordID][]core.RecordID{}
   157  	wg := sync.WaitGroup{}
   158  	wg.Add(len(p.indexStorages))
   159  
   160  	for jetID, storage := range p.indexStorages {
   161  		go func(jetID core.RecordID, s *RecentIndexStorageConcrete) {
   162  			res := s.DecreaseIndexTTL(ctx)
   163  
   164  			if len(res) > 0 {
   165  				resMapLock.Lock()
   166  				resMap[jetID] = res
   167  				resMapLock.Unlock()
   168  			}
   169  
   170  			wg.Done()
   171  		}(jetID, storage)
   172  	}
   173  
   174  	wg.Wait()
   175  
   176  	for jetID, storage := range p.indexStorages {
   177  		if len(storage.indexes) == 0 {
   178  			delete(p.indexStorages, jetID)
   179  		}
   180  	}
   181  
   182  	return resMap
   183  }
   184  
   185  // RemovePendingStorage removes pending requests for a specific jet from provider
   186  // If there is a reference to RecentIndexStorage somewhere, it won't be affected
   187  func (p *RecentStorageProvider) RemovePendingStorage(ctx context.Context, id core.RecordID) {
   188  	p.pendingLock.Lock()
   189  	defer p.pendingLock.Unlock()
   190  
   191  	if storage, ok := p.pendingStorages[id]; ok {
   192  
   193  		ctx = insmetrics.InsertTag(ctx, tagJet, storage.jetID.DebugString())
   194  		stats.Record(ctx,
   195  			statRecentStoragePendingsRemoved.M(int64(len(storage.requests))),
   196  		)
   197  
   198  		delete(p.pendingStorages, id)
   199  	}
   200  }
   201  
   202  // RecentIndexStorageConcrete is an implementation of RecentIndexStorage interface
   203  // This is a in-memory cache for indexes` ids
   204  type RecentIndexStorageConcrete struct {
   205  	jetID      core.RecordID
   206  	indexes    map[core.RecordID]recentObjectMeta
   207  	lock       sync.Mutex
   208  	DefaultTTL int
   209  }
   210  
   211  type recentObjectMeta struct {
   212  	ttl int
   213  }
   214  
   215  // NewRecentIndexStorage creates new *RecentIndexStorage
   216  func NewRecentIndexStorage(jetID core.RecordID, defaultTTL int) *RecentIndexStorageConcrete {
   217  	return &RecentIndexStorageConcrete{jetID: jetID, DefaultTTL: defaultTTL, indexes: map[core.RecordID]recentObjectMeta{}}
   218  }
   219  
   220  // AddObject adds index's id to an in-memory cache and sets DefaultTTL for it
   221  func (r *RecentIndexStorageConcrete) AddObject(ctx context.Context, id core.RecordID) {
   222  	r.AddObjectWithTLL(ctx, id, r.DefaultTTL)
   223  }
   224  
   225  // AddObjectWithTLL adds index's id to an in-memory cache with provided ttl
   226  func (r *RecentIndexStorageConcrete) AddObjectWithTLL(ctx context.Context, id core.RecordID, ttl int) {
   227  	r.lock.Lock()
   228  	defer r.lock.Unlock()
   229  
   230  	if ttl < 0 {
   231  		inslogger.FromContext(ctx).Error("below zero ttl happened")
   232  		panic("below zero ttl happened")
   233  	}
   234  
   235  	r.indexes[id] = recentObjectMeta{ttl: ttl}
   236  
   237  	ctx = insmetrics.InsertTag(ctx, tagJet, r.jetID.DebugString())
   238  	stats.Record(ctx, statRecentStorageObjectsAdded.M(1))
   239  }
   240  
   241  // GetObjects returns deep copy of indexes' ids
   242  func (r *RecentIndexStorageConcrete) GetObjects() map[core.RecordID]int {
   243  	r.lock.Lock()
   244  	defer r.lock.Unlock()
   245  
   246  	targetMap := make(map[core.RecordID]int, len(r.indexes))
   247  	for key, value := range r.indexes {
   248  		targetMap[key] = value.ttl
   249  	}
   250  
   251  	return targetMap
   252  }
   253  
   254  // FilterNotExistWithLock filters candidates from params
   255  // found indexes, which aren't removed from cache
   256  // and the passes it to lockedFn
   257  func (r *RecentIndexStorageConcrete) FilterNotExistWithLock(
   258  	ctx context.Context,
   259  	candidates []core.RecordID,
   260  	lockedFn func(filtered []core.RecordID),
   261  ) {
   262  	r.lock.Lock()
   263  	markedCandidates := r.markForDelete(candidates)
   264  	lockedFn(markedCandidates)
   265  	r.lock.Unlock()
   266  }
   267  
   268  func (r *RecentIndexStorageConcrete) markForDelete(candidates []core.RecordID) []core.RecordID {
   269  	result := make([]core.RecordID, 0, len(candidates))
   270  
   271  	for _, c := range candidates {
   272  		_, exists := r.indexes[c]
   273  		if !exists {
   274  			result = append(result, c)
   275  		}
   276  	}
   277  	return result
   278  }
   279  
   280  // DecreaseIndexTTL decreases ttls and remove indexes with 0 ttl
   281  // Removed indexes will be returned as a functon's result
   282  func (r *RecentIndexStorageConcrete) DecreaseIndexTTL(ctx context.Context) []core.RecordID {
   283  	r.lock.Lock()
   284  	defer r.lock.Unlock()
   285  
   286  	var clearedObjects []core.RecordID
   287  	for key, value := range r.indexes {
   288  		value.ttl--
   289  		if value.ttl <= 0 {
   290  			clearedObjects = append(clearedObjects, key)
   291  			delete(r.indexes, key)
   292  			continue
   293  		}
   294  		r.indexes[key] = value
   295  	}
   296  	return clearedObjects
   297  }
   298  
   299  // PendingStorageConcrete contains indexes of unclosed requests (pendings) for a specific object id
   300  type PendingStorageConcrete struct {
   301  	lock sync.RWMutex
   302  
   303  	jetID core.RecordID
   304  
   305  	requests map[core.RecordID]*lockedPendingObjectContext
   306  }
   307  
   308  // PendingObjectContext contains a list of requests for an object
   309  // Also it contains a boolean-flag for determination object's status
   310  // If It's false, current LME, when it gets a hot-data, needs to send
   311  // notifications about forgotten requests
   312  type PendingObjectContext struct {
   313  	Active   bool
   314  	Requests []core.RecordID
   315  }
   316  
   317  type lockedPendingObjectContext struct {
   318  	Context *PendingObjectContext
   319  	lock    sync.RWMutex
   320  }
   321  
   322  // NewPendingStorage creates *PendingStorage
   323  func NewPendingStorage(jetID core.RecordID) *PendingStorageConcrete {
   324  	return &PendingStorageConcrete{
   325  		jetID:    jetID,
   326  		requests: map[core.RecordID]*lockedPendingObjectContext{},
   327  	}
   328  }
   329  
   330  // AddPendingRequest adds an id of pending request to memory
   331  // The id stores in a collection ids of a specific object
   332  func (r *PendingStorageConcrete) AddPendingRequest(ctx context.Context, obj, req core.RecordID) {
   333  	r.lock.Lock()
   334  	defer r.lock.Unlock()
   335  
   336  	var objectContext *lockedPendingObjectContext
   337  	var ok bool
   338  	if objectContext, ok = r.requests[obj]; !ok {
   339  		objectContext = &lockedPendingObjectContext{
   340  			lock: sync.RWMutex{},
   341  			Context: &PendingObjectContext{
   342  				Active:   true,
   343  				Requests: []core.RecordID{},
   344  			},
   345  		}
   346  		r.requests[obj] = objectContext
   347  	}
   348  
   349  	objectContext.lock.Lock()
   350  	defer objectContext.lock.Unlock()
   351  
   352  	objectContext.Context.Requests = append(objectContext.Context.Requests, req)
   353  
   354  	ctx = insmetrics.InsertTag(ctx, tagJet, r.jetID.DebugString())
   355  	stats.Record(ctx, statRecentStoragePendingsAdded.M(1))
   356  }
   357  
   358  // SetContextToObject add a context to a provided object
   359  func (r *PendingStorageConcrete) SetContextToObject(ctx context.Context, obj core.RecordID, objContext PendingObjectContext) {
   360  	r.lock.Lock()
   361  	defer r.lock.Unlock()
   362  
   363  	r.requests[obj] = &lockedPendingObjectContext{
   364  		Context: &objContext,
   365  	}
   366  	ctx = insmetrics.InsertTag(ctx, tagJet, r.jetID.DebugString())
   367  	stats.Record(ctx, statRecentStoragePendingsAdded.M(int64(len(objContext.Requests))))
   368  }
   369  
   370  // GetRequests returns a deep-copy of requests collections
   371  func (r *PendingStorageConcrete) GetRequests() map[core.RecordID]PendingObjectContext {
   372  	r.lock.RLock()
   373  	defer r.lock.RUnlock()
   374  
   375  	requestsClone := map[core.RecordID]PendingObjectContext{}
   376  	for objID, objContext := range r.requests {
   377  		objContext.lock.RLock()
   378  
   379  		objectClone := PendingObjectContext{
   380  			Active:   objContext.Context.Active,
   381  			Requests: []core.RecordID{},
   382  		}
   383  		objectClone.Requests = append(objectClone.Requests, objContext.Context.Requests...)
   384  		requestsClone[objID] = objectClone
   385  
   386  		objContext.lock.RUnlock()
   387  	}
   388  
   389  	return requestsClone
   390  }
   391  
   392  // GetRequestsForObject returns a deep-copy of requests collection for a specific object
   393  func (r *PendingStorageConcrete) GetRequestsForObject(obj core.RecordID) []core.RecordID {
   394  	r.lock.RLock()
   395  	defer r.lock.RUnlock()
   396  
   397  	forObject, ok := r.requests[obj]
   398  	if !ok {
   399  		return nil
   400  	}
   401  
   402  	forObject.lock.RLock()
   403  	defer forObject.lock.RUnlock()
   404  
   405  	results := make([]core.RecordID, 0, len(forObject.Context.Requests))
   406  	results = append(results, forObject.Context.Requests...)
   407  
   408  	return results
   409  }
   410  
   411  // RemovePendingRequest removes a request on object from cache
   412  func (r *PendingStorageConcrete) RemovePendingRequest(ctx context.Context, obj, req core.RecordID) {
   413  	r.lock.RLock()
   414  	defer r.lock.RUnlock()
   415  
   416  	objContext, ok := r.requests[obj]
   417  	if !ok {
   418  		return
   419  	}
   420  
   421  	objContext.lock.Lock()
   422  	defer objContext.lock.Unlock()
   423  
   424  	if len(objContext.Context.Requests) == 0 {
   425  		return
   426  	}
   427  
   428  	firstRequest := objContext.Context.Requests[0]
   429  	if firstRequest.Pulse() == req.Pulse() {
   430  		objContext.Context.Active = true
   431  	}
   432  
   433  	index := -1
   434  	for internalIndex, objReq := range objContext.Context.Requests {
   435  		if objReq == req {
   436  			index = internalIndex
   437  			break
   438  		}
   439  	}
   440  
   441  	if index == -1 {
   442  		return
   443  	}
   444  
   445  	if len(objContext.Context.Requests) == 1 {
   446  		objContext.Context.Requests = []core.RecordID{}
   447  		return
   448  	}
   449  
   450  	objContext.Context.Requests = append(objContext.Context.Requests[:index], objContext.Context.Requests[index+1:]...)
   451  
   452  	ctx = insmetrics.InsertTag(ctx, tagJet, r.jetID.DebugString())
   453  	stats.Record(ctx, statRecentStoragePendingsRemoved.M(1))
   454  }