From 6d82cda8599e448c3bc4d16d9f1c392122fdabda Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Tue, 13 Aug 2024 20:38:07 +0530 Subject: [PATCH 1/4] added solution for problem 1 --- 0-limit-crawler/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/0-limit-crawler/main.go b/0-limit-crawler/main.go index 1464420..4db6eff 100644 --- a/0-limit-crawler/main.go +++ b/0-limit-crawler/main.go @@ -12,17 +12,18 @@ package main import ( "fmt" "sync" + "time" ) // Crawl uses `fetcher` from the `mockfetcher.go` file to imitate a // real crawler. It crawls until the maximum depth has reached. -func Crawl(url string, depth int, wg *sync.WaitGroup) { +func Crawl(url string, depth int, wg *sync.WaitGroup, ticker <-chan time.Time) { defer wg.Done() - if depth <= 0 { return } + <-ticker body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) @@ -35,15 +36,14 @@ func Crawl(url string, depth int, wg *sync.WaitGroup) { for _, u := range urls { // Do not remove the `go` keyword, as Crawl() must be // called concurrently - go Crawl(u, depth-1, wg) + go Crawl(u, depth-1, wg, ticker) } - return + } func main() { var wg sync.WaitGroup - wg.Add(1) - Crawl("http://golang.org/", 4, &wg) + Crawl("http://golang.org/", 4, &wg, time.Tick(2*time.Second)) wg.Wait() } From c78fc35112c05e1de4f4fc63f79fde3f45b444aa Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Wed, 14 Aug 2024 21:25:19 +0530 Subject: [PATCH 2/4] added changes for go routines and test file --- 1-producer-consumer/check_test.go | 31 ++++++++++++++++++++++++ 1-producer-consumer/main.go | 40 +++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 1-producer-consumer/check_test.go diff --git a/1-producer-consumer/check_test.go b/1-producer-consumer/check_test.go new file mode 100644 index 0000000..3f46523 --- /dev/null +++ b/1-producer-consumer/check_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "strings" + "testing" +) + +func TestMain(t *testing.T) { + result := processStream(GetMockStream()) + lines := strings.Split(strings.TrimSpace(result), "\n") + expected := []string{ + "davecheney \ttweets about golang", + "beertocode \tdoes not tweet about golang", + "ironzeb \ttweets about golang", + "beertocode \ttweets about golang", + "vampirewalk666 \ttweets about golang", + } + if len(lines) != len(expected)+1 { // +1 for the "Process took" line + t.Fatalf("Expected %d lines, got %d", len(expected)+1, len(lines)) + } + + for i, line := range expected { + if !strings.EqualFold(lines[i], line) { + t.Errorf("Line %d: expected %q, got %q", i+1, line, lines[i]) + } + } + + if !strings.HasPrefix(lines[len(lines)-1], "Process took") { + t.Errorf("Last line should start with 'Process took', got %q", lines[len(lines)-1]) + } +} diff --git a/1-producer-consumer/main.go b/1-producer-consumer/main.go index e508e93..e577edb 100644 --- a/1-producer-consumer/main.go +++ b/1-producer-consumer/main.go @@ -9,40 +9,56 @@ package main import ( + "bytes" "fmt" + "sync" "time" ) -func producer(stream Stream) (tweets []*Tweet) { +func producer(stream Stream, ch chan *Tweet) { for { tweet, err := stream.Next() if err == ErrEOF { - return tweets + close(ch) + return } - tweets = append(tweets, tweet) + ch <- tweet } } -func consumer(tweets []*Tweet) { - for _, t := range tweets { +func consumer(ch chan *Tweet, output *bytes.Buffer, wg *sync.WaitGroup) { + defer wg.Done() + for t := range ch { if t.IsTalkingAboutGo() { - fmt.Println(t.Username, "\ttweets about golang") + fmt.Fprintln(output, t.Username, "\ttweets about golang") } else { - fmt.Println(t.Username, "\tdoes not tweet about golang") + fmt.Fprintln(output, t.Username, "\tdoes not tweet about golang") } } } -func main() { +func processStream(stream Stream) string { start := time.Now() - stream := GetMockStream() + var output bytes.Buffer + var wg sync.WaitGroup + + ch := make(chan *Tweet) // Producer - tweets := producer(stream) + go producer(stream, ch) // Consumer - consumer(tweets) + wg.Add(1) + go consumer(ch, &output, &wg) + wg.Wait() - fmt.Printf("Process took %s\n", time.Since(start)) + fmt.Fprintf(&output, "Process took %s\n", time.Since(start)) + return output.String() +} + +func main() { + stream := GetMockStream() + result := processStream(stream) + fmt.Println(result) } From befe128d1e65d6315fbd8f29d11c07d80a830ed4 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Wed, 14 Aug 2024 21:28:50 +0530 Subject: [PATCH 3/4] added global ticker --- 0-limit-crawler/main.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/0-limit-crawler/main.go b/0-limit-crawler/main.go index 4db6eff..1bf2f4c 100644 --- a/0-limit-crawler/main.go +++ b/0-limit-crawler/main.go @@ -15,9 +15,11 @@ import ( "time" ) +var ticker <-chan time.Time + // Crawl uses `fetcher` from the `mockfetcher.go` file to imitate a // real crawler. It crawls until the maximum depth has reached. -func Crawl(url string, depth int, wg *sync.WaitGroup, ticker <-chan time.Time) { +func Crawl(url string, depth int, wg *sync.WaitGroup) { defer wg.Done() if depth <= 0 { return @@ -36,14 +38,15 @@ func Crawl(url string, depth int, wg *sync.WaitGroup, ticker <-chan time.Time) { for _, u := range urls { // Do not remove the `go` keyword, as Crawl() must be // called concurrently - go Crawl(u, depth-1, wg, ticker) + go Crawl(u, depth-1, wg) } } func main() { var wg sync.WaitGroup + ticker = time.Tick(1 * time.Second) wg.Add(1) - Crawl("http://golang.org/", 4, &wg, time.Tick(2*time.Second)) + Crawl("http://golang.org/", 4, &wg) wg.Wait() } From 9a655222d770620bf38bf437b7de84ed4e8d3fb8 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Fri, 16 Aug 2024 20:53:22 +0530 Subject: [PATCH 4/4] added solutions for problem 2 and 4 --- 2-race-in-cache/main.go | 5 +++++ 4-graceful-sigint/main.go | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/2-race-in-cache/main.go b/2-race-in-cache/main.go index 7618dd1..4b4299c 100644 --- a/2-race-in-cache/main.go +++ b/2-race-in-cache/main.go @@ -10,6 +10,7 @@ package main import ( "container/list" + "sync" "testing" ) @@ -32,6 +33,7 @@ type KeyStoreCache struct { cache map[string]*list.Element pages list.List load func(string) string + mtx sync.Mutex } // New creates a new KeyStoreCache @@ -44,6 +46,9 @@ func New(load KeyStoreCacheLoader) *KeyStoreCache { // Get gets the key from cache, loads it from the source if needed func (k *KeyStoreCache) Get(key string) string { + k.mtx.Lock() + defer k.mtx.Unlock() + // If key exists in cache, move the key to front of the doubly linked list if e, ok := k.cache[key]; ok { k.pages.MoveToFront(e) return e.Value.(page).Value diff --git a/4-graceful-sigint/main.go b/4-graceful-sigint/main.go index 38c7d3d..13cbb94 100644 --- a/4-graceful-sigint/main.go +++ b/4-graceful-sigint/main.go @@ -13,10 +13,35 @@ package main +import ( + "log" + "os" + "os/signal" + "time" +) + func main() { // Create a process proc := MockProcess{} // Run the process (blocking) - proc.Run() + go proc.Run() + + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt) + + sig := <-c + log.Printf("captured sigint %v, stopping application and exiting...", sig) + go proc.Stop() + + // Wait for either graceful stop or second SIGINT + select { + case <-time.After(10 * time.Second): + log.Println("Graceful shutdown completed.") + case sig := <-c: + log.Printf("Captured %v again, forcing shutdown...", sig) + } + + os.Exit(0) + }