Skip to content
This repository has been archived by the owner on May 11, 2022. It is now read-only.

Commit

Permalink
fix #27: implement in-memory cache for licenses
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilsk committed Oct 24, 2018
1 parent 4786dc4 commit be96156
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 25 deletions.
2 changes: 0 additions & 2 deletions cmd/guard/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
var (
_ api.Service = guard.New(config.ServiceConfig{}, nil)
_ guard.Storage = storage.Must()
_ guard.AccountStorage = storage.Must()
_ guard.LicenseStorage = storage.Must()
_ grpc.ProtectedStorage = storage.Must()
_ grpc.Maintenance = guard.New(config.ServiceConfig{}, nil)
)
Expand Down
13 changes: 10 additions & 3 deletions pkg/service/guard/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ import (

// Storage TODO issue#docs
type Storage interface {
accountStorage
licenseStorage
}

type accountStorage interface {
// RegisterAccount TODO issue#docs
RegisterAccount(context.Context, *query.RegisterAccount) (*repository.Account, error)
}

type licenseStorage interface {
// LicenseByID TODO issue#docs
LicenseByID(context.Context, domain.ID) (repository.License, error)
// LicenseByEmployee TODO issue#docs
LicenseByEmployee(context.Context, domain.ID) (repository.License, error)

// RegisterAccount TODO issue#docs
RegisterAccount(context.Context, *query.RegisterAccount) (*repository.Account, error)
}
125 changes: 125 additions & 0 deletions pkg/service/guard/internal/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package internal

import (
"context"
"time"

domain "github.com/kamilsk/guard/pkg/service/types"
repository "github.com/kamilsk/guard/pkg/storage/types"

"github.com/kamilsk/guard/pkg/storage/types"
"github.com/pkg/errors"
)

// TODO issue#draft {

type licenseCache interface {
LicenseByID(context.Context, domain.ID) (repository.License, error)
LicenseByEmployee(context.Context, domain.ID) (repository.License, error)
}

// NewLicenseCache returns a blob of memory
// to store licenses.
func NewLicenseCache(in licenseCache) (out licenseCache) {
c := &cache{
origin: in,
idx: make(map[domain.ID]response, maxLicenses),

byID: make(chan request, 1),
byEmployee: make(chan request, 1),
memorize: make(chan response, maxLicenses/100),
}
go c.listen()
return c
}

type cache struct {
origin licenseCache
idx map[domain.ID]response

memorize chan response
byID chan request
byEmployee chan request
}

// LicenseByID TODO issue#docs
func (c *cache) LicenseByID(ctx context.Context, id domain.ID) (types.License, error) {
req := request{ctx, id, make(chan response, 1)}
c.byID <- req
resp := <-req.result
return resp.license, resp.err
}

// LicenseByEmployee TODO issue#docs
func (c *cache) LicenseByEmployee(ctx context.Context, id domain.ID) (types.License, error) {
req := request{ctx, id, make(chan response, 1)}
c.byEmployee <- req
resp := <-req.result
return resp.license, resp.err
}

func (c *cache) listen() {
defer func() {
if r := recover(); r != nil {
// TODO issue#critical
// TODO issue#6
go c.listen()
}
}()
for {
select {
case res := <-c.memorize:
c.idx[res.license.ID] = response{res.license, res.err, time.Now().Add(licenseTTL)}
case req := <-c.byID:
res, found := c.idx[req.id]
if found && res.ttl.After(time.Now()) {
req.result <- res
continue
}
go func() {
defer func() {
if r := recover(); r != nil {
// TODO issue#critical
// TODO issue#6
req.result <- response{err: errors.New("unexpected panic handled")}
}
}()
res.license, res.err = c.origin.LicenseByID(req.ctx, req.id)
req.result <- res
c.memorize <- res
}()
case req := <-c.byEmployee:
res, found := c.idx[req.id]
if found && res.ttl.After(time.Now()) {
req.result <- res
continue
}
go func() {
defer func() {
if r := recover(); r != nil {
// TODO issue#critical
// TODO issue#6
req.result <- response{err: errors.New("unexpected panic handled")}
}
}()
res.license, res.err = c.origin.LicenseByEmployee(req.ctx, req.id)
req.result <- res
c.memorize <- res
}()
}
}
}

type request struct {
ctx context.Context
id domain.ID
result chan response
}

type response struct {
license repository.License
err error
ttl time.Time
}

// issue#draft }
10 changes: 5 additions & 5 deletions pkg/service/guard/internal/counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,17 @@ func (s *slot) acquire(workplace domain.ID, capacity int) bool {

if i = len(s.pool); i < capacity {
s.idx[workplace] = i
s.pool = append(s.pool, record{id: workplace, lastActive: time.Now()})
s.pool = append(s.pool, record{workplace: workplace, lastActive: time.Now()})
return true
}

// try to displace
now := time.Now()
for i = range s.pool {
if now.Sub(s.pool[i].lastActive) > workplaceTTL {
delete(s.idx, s.pool[i].id)
delete(s.idx, s.pool[i].workplace)
s.idx[workplace] = i
s.pool[i] = record{id: workplace, lastActive: now}
s.pool[i] = record{workplace: workplace, lastActive: now}
return true
}
}
Expand All @@ -195,12 +195,12 @@ func (s *slot) shrink(size int) {
sort.Sort(sort.Reverse(recordsByActivity(s.pool)))
s.pool = s.pool[:size]
for i := range s.pool {
s.idx[s.pool[i].id] = i
s.idx[s.pool[i].workplace] = i
}
}

type record struct {
id domain.ID
workplace domain.ID
lastActive time.Time
}

Expand Down
24 changes: 12 additions & 12 deletions pkg/service/guard/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

type licenseService struct {
disabled bool
storage Storage
storage licenseStorage
}

// Check TODO issue#docs
Expand All @@ -30,10 +30,10 @@ func (service *licenseService) Check(ctx context.Context, req request.CheckLicen
var license domain.License

switch {
case !(req.ID.IsValid() && req.Employee.IsValid()) || !req.Workplace.IsValid():
case !(req.License.IsValid() || req.Employee.IsValid()) || !req.Workplace.IsValid():
return resp.With(errors.New(http.StatusText(http.StatusBadRequest)))
case req.ID.IsValid():
entity, err := service.storage.LicenseByID(ctx, req.ID)
case req.License.IsValid():
entity, err := service.storage.LicenseByID(ctx, req.License)
if err != nil {
return resp.With(err)
}
Expand All @@ -47,23 +47,23 @@ func (service *licenseService) Check(ctx context.Context, req request.CheckLicen
}

// TODO issue#composite
if err := service.checkLifetimeLimits(&license, req.Workplace); err != nil {
if err := service.checkLifetimeLimits(license, req.Workplace); err != nil {
return resp.With(err)
}
if err := service.checkRateLimits(&license, req.Workplace); err != nil {
if err := service.checkRateLimits(license, req.Workplace); err != nil {
return resp.With(err)
}
if err := service.checkRequestLimits(&license, req.Workplace); err != nil {
if err := service.checkRequestLimits(license, req.Workplace); err != nil {
return resp.With(err)
}
if err := service.checkWorkplaceLimits(&license, req.Workplace); err != nil {
if err := service.checkWorkplaceLimits(license, req.Workplace); err != nil {
return resp.With(err)
}

return
}

func (service *licenseService) checkLifetimeLimits(license *domain.License, _ domain.ID) error {
func (service *licenseService) checkLifetimeLimits(license domain.License, _ domain.ID) error {
now := time.Now()
if license.Since != nil {
if license.Since.After(now) {
Expand All @@ -78,7 +78,7 @@ func (service *licenseService) checkLifetimeLimits(license *domain.License, _ do
return nil
}

func (service *licenseService) checkRateLimits(license *domain.License, _ domain.ID) error {
func (service *licenseService) checkRateLimits(license domain.License, _ domain.ID) error {
if license.Rate.IsValid() {
// TODO issue#future
// errors.New(http.StatusText(http.StatusTooManyRequests))
Expand All @@ -87,7 +87,7 @@ func (service *licenseService) checkRateLimits(license *domain.License, _ domain
return nil
}

func (service *licenseService) checkRequestLimits(license *domain.License, _ domain.ID) error {
func (service *licenseService) checkRequestLimits(license domain.License, _ domain.ID) error {
counter := internal.LicenseRequests
if license.Requests > 0 && license.Requests < counter.Increment(license.ID) {
go counter.Rollback(license.ID)
Expand All @@ -96,7 +96,7 @@ func (service *licenseService) checkRequestLimits(license *domain.License, _ dom
return nil
}

func (service *licenseService) checkWorkplaceLimits(license *domain.License, workplace domain.ID) error {
func (service *licenseService) checkWorkplaceLimits(license domain.License, workplace domain.ID) error {
if !internal.LicenseWorkplaces.Acquire(license.ID, workplace, int(license.Workplaces)) {
return errors.New(http.StatusText(http.StatusPaymentRequired))
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/service/guard/maintenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

type maintenanceService struct {
storage Storage
storage accountStorage
}

// Install TODO issue#docs
Expand Down
6 changes: 5 additions & 1 deletion pkg/service/guard/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import (
"context"

"github.com/kamilsk/guard/pkg/config"
"github.com/kamilsk/guard/pkg/service/guard/internal"
"github.com/kamilsk/guard/pkg/service/types/request"
"github.com/kamilsk/guard/pkg/service/types/response"
)

// New TODO issue#docs
func New(cnf config.ServiceConfig, storage Storage) *Guard {
return &Guard{&licenseService{cnf.Disabled, storage}, &maintenanceService{storage}}
return &Guard{
&licenseService{cnf.Disabled, internal.NewLicenseCache(storage)},
&maintenanceService{storage},
}
}

// Guard TODO issue#docs
Expand Down
2 changes: 1 addition & 1 deletion pkg/service/types/request/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import domain "github.com/kamilsk/guard/pkg/service/types"

// CheckLicense TODO issue#docs
type CheckLicense struct {
ID domain.ID
License domain.ID
Employee domain.ID
Workplace domain.ID
}

0 comments on commit be96156

Please sign in to comment.