0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/api/cookiestore.go
peusebiu 7642e5af98
fix(scheduler): fix data race (#2085)
* fix(scheduler): data race when pushing new tasks

the problem here is that scheduler can be closed in two ways:
- canceling the context given as argument to scheduler.RunScheduler()
- running scheduler.Shutdown()

because of this shutdown can trigger a data race between calling scheduler.inShutdown()
and actually pushing tasks into the pool workers

solved that by keeping a quit channel and listening on both quit channel and ctx.Done()
and closing the worker chan and scheduler afterwards.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

* refactor(scheduler): refactor into a single shutdown

before this we could stop scheduler either by closing the context
provided to RunScheduler(ctx) or by running Shutdown().

simplify things by getting rid of the external context in RunScheduler().
keep an internal context in the scheduler itself and pass it down to all tasks.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

---------

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
2023-12-11 10:00:34 -08:00

171 lines
3.4 KiB
Go

package api
import (
"context"
"encoding/gob"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/scheduler"
"zotregistry.io/zot/pkg/storage"
storageConstants "zotregistry.io/zot/pkg/storage/constants"
)
const cookiesMaxAge = 7200 // 2h
type CookieStore struct {
needsCleanup bool // if store should be periodically cleaned
rootDir string
sessions.Store
}
func (c *CookieStore) RunSessionCleaner(sch *scheduler.Scheduler) {
if c.needsCleanup {
sch.SubmitGenerator(&SessionCleanup{rootDir: c.rootDir}, cookiesMaxAge, scheduler.LowPriority)
}
}
func NewCookieStore(storeController storage.StoreController) (*CookieStore, error) {
// To store custom types in our cookies
// we must first register them using gob.Register
gob.Register(map[string]interface{}{})
hashKey, err := getHashKey()
if err != nil {
return &CookieStore{}, err
}
var store sessions.Store
var sessionsDir string
var needsCleanup bool
if storeController.DefaultStore.Name() == storageConstants.LocalStorageDriverName {
sessionsDir = path.Join(storeController.DefaultStore.RootDir(), "_sessions")
if err := os.MkdirAll(sessionsDir, storageConstants.DefaultDirPerms); err != nil {
return &CookieStore{}, err
}
localStore := sessions.NewFilesystemStore(sessionsDir, hashKey)
localStore.MaxAge(cookiesMaxAge)
store = localStore
needsCleanup = true
} else {
memStore := sessions.NewCookieStore(hashKey)
memStore.MaxAge(cookiesMaxAge)
store = memStore
}
return &CookieStore{
Store: store,
rootDir: sessionsDir,
needsCleanup: needsCleanup,
}, nil
}
func getHashKey() ([]byte, error) {
hashKey := securecookie.GenerateRandomKey(64)
if hashKey == nil {
return nil, zerr.ErrHashKeyNotCreated
}
return hashKey, nil
}
func getExpiredSessions(dir string) ([]string, error) {
sessions := make([]string, 0)
err := filepath.WalkDir(dir, func(_ string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
fileInfo, err := dirEntry.Info()
if err != nil { // may have been deleted in the meantime
return nil //nolint: nilerr
}
if !strings.HasPrefix(fileInfo.Name(), "session_") {
return nil
}
if fileInfo.ModTime().Add(cookiesMaxAge * time.Second).Before(time.Now()) {
sessions = append(sessions, path.Join(dir, fileInfo.Name()))
}
return nil
})
return sessions, err
}
type SessionCleanup struct {
rootDir string
done bool
}
func (gen *SessionCleanup) Next() (scheduler.Task, error) {
sessions, err := getExpiredSessions(gen.rootDir)
if err != nil {
return nil, err
}
if len(sessions) == 0 {
gen.done = true
return nil, nil
}
return &CleanTask{sessions: sessions}, nil
}
func (gen *SessionCleanup) IsDone() bool {
return gen.done
}
func (gen *SessionCleanup) IsReady() bool {
return true
}
func (gen *SessionCleanup) Reset() {
gen.done = false
}
type CleanTask struct {
sessions []string
}
func (cleanTask *CleanTask) DoWork(ctx context.Context) error {
for _, session := range cleanTask.sessions {
if err := os.Remove(session); err != nil {
if !os.IsNotExist(err) {
return err
}
}
}
return nil
}
func (cleanTask *CleanTask) String() string {
return fmt.Sprintf("{Name: %s, sessions: %s}",
cleanTask.Name(), cleanTask.sessions)
}
func (cleanTask *CleanTask) Name() string {
return "SessionCleanupTask"
}