You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
3.3 KiB
Go

//////////////////////////////////////////////////////////////////////
//
// Given is a SessionManager that stores session information in
// memory. The SessionManager itself is working, however, since we
// keep on adding new sessions to the manager our program will
// eventually run out of memory.
//
// Your task is to implement a session cleaner routine that runs
// concurrently in the background and cleans every session that
// hasn't been updated for more than 5 seconds (of course usually
// session times are much longer).
//
// Note that we expect the session to be removed anytime between 5 and
// 7 seconds after the last update. Also, note that you have to be
// very careful in order to prevent race conditions.
//
package main
import (
"errors"
"log"
"sync"
"time"
)
// SessionManager keeps track of all sessions from creation, updating
// to destroying.
type SessionManager struct {
sessions map[string]Session
rwmu sync.RWMutex
}
// Session stores the session's data
type Session struct {
Data map[string]interface{}
timer *time.Timer
mu *sync.Mutex
}
// NewSessionManager creates a new sessionManager
func NewSessionManager() *SessionManager {
m := &SessionManager{
sessions: make(map[string]Session),
}
return m
}
func (m *SessionManager) CleanSession(sessionID string) {
m.rwmu.Lock()
delete(m.sessions, sessionID)
m.rwmu.Unlock()
}
// CreateSession creates a new session and returns the sessionID
func (m *SessionManager) CreateSession() (string, error) {
sessionID, err := MakeSessionID()
if err != nil {
return "", err
}
m.rwmu.Lock()
m.sessions[sessionID] = Session{
Data: make(map[string]interface{}),
timer: time.AfterFunc(5*time.Second, func() {
m.CleanSession(sessionID)
}),
mu: &sync.Mutex{},
}
m.rwmu.Unlock()
return sessionID, nil
}
// ErrSessionNotFound returned when sessionID not listed in
// SessionManager
var ErrSessionNotFound = errors.New("SessionID does not exists")
// GetSessionData returns data related to session if sessionID is
// found, errors otherwise
func (m *SessionManager) GetSessionData(sessionID string) (map[string]interface{}, error) {
m.rwmu.RLock()
session, ok := m.sessions[sessionID]
m.rwmu.RUnlock()
if !ok {
return nil, ErrSessionNotFound
}
return session.Data, nil
}
// UpdateSessionData overwrites the old session data with the new one
func (m *SessionManager) UpdateSessionData(sessionID string, data map[string]interface{}) error {
m.rwmu.RLock()
s, ok := m.sessions[sessionID]
m.rwmu.RUnlock()
if !ok {
return ErrSessionNotFound
}
// Lock the session data to handle concurrent access to the same session
s.mu.Lock()
s.Data = data
if !s.timer.Stop() {
<-s.timer.C
}
s.timer.Reset(5 * time.Second)
s.mu.Unlock()
return nil
}
func main() {
// Create new sessionManager and new session
m := NewSessionManager()
sID, err := m.CreateSession()
if err != nil {
log.Fatal(err)
}
log.Println("Created new session with ID", sID)
// Update session data
data := make(map[string]interface{})
data["website"] = "longhoang.de"
err = m.UpdateSessionData(sID, data)
if err != nil {
log.Fatal(err)
}
log.Println("Update session data, set website to longhoang.de")
// Retrieve data from manager again
updatedData, err := m.GetSessionData(sID)
if err != nil {
log.Fatal(err)
}
log.Println("Get session data:", updatedData)
}