forked from loong/go-concurrency-exercises
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
248 additions
and
1 deletion.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters