Skip to content

Commit

Permalink
Add Exercise - Session Cleaner
Browse files Browse the repository at this point in the history
  • Loading branch information
loong committed Apr 1, 2017
1 parent 7b6f224 commit bb08564
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 1 deletion.
Binary file added 5-session-cleaner/6-session-cleaner
Binary file not shown.
26 changes: 26 additions & 0 deletions 5-session-cleaner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Clean Inactive Sessions to Prevent Memory Overflow

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.

## Test your solution

To complete this exercise, you must pass two test. The normal `go
test` test cases as well as the race condition test.

Use the following commands to test your solution:
```
go test
go test --race
```
80 changes: 80 additions & 0 deletions 5-session-cleaner/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"testing"
"time"
)

func TestSessionManagersCreationAndUpdate(t *testing.T) {
// Create manager and new session
m := NewSessionManager()
sID, err := m.CreateSession()
if err != nil {
t.Error("Error CreateSession:", err)
}

data, err := m.GetSessionData(sID)
if err != nil {
t.Error("Error GetSessionData:", err)
}

// Modify and update data
data["website"] = "longhoang.de"
err = m.UpdateSessionData(sID, data)
if err != nil {
t.Error("Error UpdateSessionData:", err)
}

// Retrieve data from manager again
data, err = m.GetSessionData(sID)
if err != nil {
t.Error("Error GetSessionData:", err)
}

if data["website"] != "longhoang.de" {
t.Error("Expected website to be longhoang.de")
}
}

func TestSessionManagersCleaner(t *testing.T) {
m := NewSessionManager()
sID, err := m.CreateSession()
if err != nil {
t.Error("Error CreateSession:", err)
}

// Note that the cleaner is only running every 5s
time.Sleep(7 * time.Second)
_, err = m.GetSessionData(sID)
if err != ErrSessionNotFound {
t.Error("Session still in memory after 7 seconds")
}
}

func TestSessionManagersCleanerAfterUpdate(t *testing.T) {
m := NewSessionManager()
sID, err := m.CreateSession()
if err != nil {
t.Error("Error CreateSession:", err)
}

time.Sleep(3 * time.Second)

err = m.UpdateSessionData(sID, make(map[string]interface{}))
if err != nil {
t.Error("Error UpdateSessionData:", err)
}

time.Sleep(3 * time.Second)

_, err = m.GetSessionData(sID)
if err == ErrSessionNotFound {
t.Error("Session not found although has been updated 3 seconds earlier.")
}

time.Sleep(4 * time.Second)
_, err = m.GetSessionData(sID)
if err != ErrSessionNotFound {
t.Error("Session still in memory 7 seconds after update")
}
}
24 changes: 24 additions & 0 deletions 5-session-cleaner/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//////////////////////////////////////////////////////////////////////
//
// DO NOT EDIT THIS PART
// Your task is to edit `main.go`
//

package main

import (
"crypto/rand"
"encoding/base64"
"io"
)

// MakeSessionID is used to generate a random dummy sessionID
func MakeSessionID() (string, error) {
buf := make([]byte, 26)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(buf), nil
}
116 changes: 116 additions & 0 deletions 5-session-cleaner/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//////////////////////////////////////////////////////////////////////
//
// 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"
)

// SessionManager keeps track of all sessions from creation, updating
// to destroying.
type SessionManager struct {
sessions map[string]Session
}

// Session stores the session's data
type Session struct {
Data map[string]interface{}
}

// NewSessionManager creates a new sessionManager
func NewSessionManager() *SessionManager {
m := &SessionManager{
sessions: make(map[string]Session),
}

return m
}

// CreateSession creates a new session and returns the sessionID
func (m *SessionManager) CreateSession() (string, error) {
sessionID, err := MakeSessionID()
if err != nil {
return "", err
}

m.sessions[sessionID] = Session{
Data: make(map[string]interface{}),
}

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) {
session, ok := m.sessions[sessionID]
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 {
_, ok := m.sessions[sessionID]
if !ok {
return ErrSessionNotFound
}

// Hint: you should renew expiry of the session here
m.sessions[sessionID] = Session{
Data: data,
}

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)
}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ The Go community has plenty resources to read about go's concurrency model and h
3. If you get stuck, join us on [Slack](https://gophersinvite.herokuapp.com/)! I'm sure there will be people who are happy to give you some code review (if not, find me via @beertocode ;) )

## Overview
| # | Name of the Challenge + URL |
| # | Name of the Challenge + URL |
| - |:-------------|
| 0 | [Limit your Crawler](https://github.com/mindworker/go-concurrency-exercises/tree/master/0-limit-crawler) |
| 1 | [Producer-Consumer](https://github.com/mindworker/go-concurrency-exercises/tree/master/1-producer-consumer) |
| 2 | [Race Condition in Caching Cache](https://github.com/mindworker/go-concurrency-exercises/tree/master/2-race-in-cache#race-condition-in-caching-szenario) |
| 3 | [Limit Service Time for Free-tier Users](https://github.com/mindworker/go-concurrency-exercises/tree/master/3-limit-service-time) |
| 4 | [Graceful SIGINT Killing](https://github.com/mindworker/go-concurrency-exercises/tree/master/4-graceful-sigint) |
| 5 | [Clean Inactive Sessions to Prevent Memory Overflow](https://github.com/mindworker/go-concurrency-exercises/tree/master/5-session-cleaner) |

## License

Expand Down

0 comments on commit bb08564

Please sign in to comment.