From a68ba74b5ab2130d8d0dcda7f7d65645a25b20c9 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 13 Oct 2024 09:16:59 -0400 Subject: [PATCH] Add debugger nodes and select chapter --- debugger/README.md | 6 +- .../concurrency/CheckWebsites.go | 2 + learn_go_with_tests/my_select/racer.go | 65 +++++++++++++++---- learn_go_with_tests/my_select/racer_test.go | 39 +++++++---- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/debugger/README.md b/debugger/README.md index 1dd9bb4..90deb3d 100644 --- a/debugger/README.md +++ b/debugger/README.md @@ -2,6 +2,8 @@ Go doesn't ship with a debugger but you can use the delve package instead. +https://www.youtube.com/watch?v=UA0SirX6Siw + ``` go get github.com/go-delve/delve/cmd/dlv ``` @@ -294,4 +296,6 @@ And hey this time we used print to check a value. Technically you can edit code with `edit`. -## Debugging goroutines \ No newline at end of file +## Debugging goroutines + + diff --git a/learn_go_with_tests/concurrency/CheckWebsites.go b/learn_go_with_tests/concurrency/CheckWebsites.go index 1fbbfda..511d484 100644 --- a/learn_go_with_tests/concurrency/CheckWebsites.go +++ b/learn_go_with_tests/concurrency/CheckWebsites.go @@ -25,6 +25,8 @@ func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool { }(url) // or just pass by value to give the func it's own url } + // Improvement idea. We could use a wait group here. Instead we are blocking + // each go routine because we are using an unbuffered channel. for i := 0; i < len(urls); i++ { r := <-resultChannel // receive statement results[r.string] = r.bool diff --git a/learn_go_with_tests/my_select/racer.go b/learn_go_with_tests/my_select/racer.go index 43b89d8..305a610 100644 --- a/learn_go_with_tests/my_select/racer.go +++ b/learn_go_with_tests/my_select/racer.go @@ -1,23 +1,66 @@ package my_select import ( + "fmt" "net/http" "time" ) -func Racer(a string, b string) (winner string) { - aDuration := measureResponseTime(a) - bDuration := measureResponseTime(b) +var defaultTimeout time.Duration = 10 * time.Second - if aDuration < bDuration { - return a - } +func Racer(a, b string) (winner string, err error) { + return ConfigurableRacer(a, b, defaultTimeout) +} + +func ConfigurableRacer(a string, b string, timeout time.Duration) (winner string, err error) { + // aDuration := measureResponseTime(a) + // bDuration := measureResponseTime(b) + + // if aDuration < bDuration { + // return a + // } + + // return b + + select { + // Here we are simply waiting on the channel closing out + // The first one that does so will return it's url - return b + // These are blocking operations because they are unbuffered channels. + // This works though because `select` allows us to wait on multiple channels + case <-ping(a): + return a, nil + case <-ping(b): + return b, nil + // Super handy function during select, returns a channel, waits the timeout, then + //sends the current time that it was triggered at. + // Helps get us out of a blocking case. + case <-time.After(timeout): + return "", fmt.Errorf("timed out waiting for %s and %s", a, b) + } } -func measureResponseTime(url string) time.Duration { - start := time.Now() - http.Get(url) - return time.Since(start) +// In our case, we don't care what type is sent to +//the channel, we just want to signal we are done +// and closing the channel works perfectly! + +// a chan struct{} is the smallest data type available +// from a memory perspective + +func ping(url string) chan struct{} { + // Notice how we have to use make when creating a channel; rather than say var ch chan struct{}. When you use var the variable will be initialised with the "zero" value of the type. So for string it is "", int it is 0, etc. + + // For channels the zero value is nil and if you try and send to it with <- it will block forever because you cannot send to nil channels + ch := make(chan struct{}) + go func() { + http.Get(url) + close(ch) + }() + return ch } + +// func measureResponseTime(url string) time.Duration { +// start := time.Now() +// http.Get(url) +// return time.Since(start) +// } diff --git a/learn_go_with_tests/my_select/racer_test.go b/learn_go_with_tests/my_select/racer_test.go index d5d27a3..7262ab6 100644 --- a/learn_go_with_tests/my_select/racer_test.go +++ b/learn_go_with_tests/my_select/racer_test.go @@ -9,21 +9,38 @@ import ( func TestRacer(t *testing.T) { - slowServer := makeDelayedServer(20 * time.Microsecond) - defer slowServer.Close() + t.Run("compares speeds of servers, returning url of the fastest one", func(t *testing.T) { + slowServer := makeDelayedServer(20 * time.Microsecond) + defer slowServer.Close() - fastServer := makeDelayedServer(0 * time.Microsecond) - defer fastServer.Close() + fastServer := makeDelayedServer(0 * time.Microsecond) + defer fastServer.Close() - slowUrl := slowServer.URL - fastUrl := fastServer.URL + slowUrl := slowServer.URL + fastUrl := fastServer.URL - want := fastUrl - got := Racer(slowUrl, fastUrl) + want := fastUrl + got, _ := Racer(slowUrl, fastUrl) - if got != want { - t.Errorf("got %q, want %q", got, want) - } + if got != want { + t.Errorf("got %q, want %q", got, want) + } + }) + + t.Run("returns an error if server doesn't respond within 10s", func(t *testing.T) { + serverA := makeDelayedServer(50 * time.Millisecond) + serverB := makeDelayedServer(60 * time.Millisecond) + + defer serverA.Close() + defer serverB.Close() + + _, err := ConfigurableRacer(serverA.URL, serverB.URL, 10*time.Millisecond) + + if err == nil { + t.Error("expected an error but didn't get one") + } + + }) } func makeDelayedServer(delay time.Duration) *httptest.Server {