From 53f583e8fc82854a2f77d4740c3b6a0273c0286e Mon Sep 17 00:00:00 2001 From: Gabor Retvari Date: Fri, 30 Aug 2024 18:06:38 +0200 Subject: [PATCH] Nested goroutines+context exercise --- 22-goroutines/06-context/.README.md | 40 +++++++++++++++++ 22-goroutines/06-context/.exercise_test.go | 50 ++++++++++++++++++++++ 22-goroutines/06-context/README.md | 1 + 22-goroutines/06-context/exercise.go | 6 +++ 22-goroutines/06-context/exercise.yaml | 1 + 22-goroutines/06-context/exercise_test.go | 1 + 6 files changed, 99 insertions(+) create mode 100644 22-goroutines/06-context/.README.md create mode 100644 22-goroutines/06-context/.exercise_test.go create mode 100644 22-goroutines/06-context/README.md create mode 100644 22-goroutines/06-context/exercise.go create mode 100644 22-goroutines/06-context/exercise.yaml create mode 100644 22-goroutines/06-context/exercise_test.go diff --git a/22-goroutines/06-context/.README.md b/22-goroutines/06-context/.README.md new file mode 100644 index 0000000..e21fbd2 --- /dev/null +++ b/22-goroutines/06-context/.README.md @@ -0,0 +1,40 @@ +# Nested Goroutines + +In this exercise, you'll practice using contexts to manage nested goroutines in Go. Your task is to +implement two functions: one that starts a long-running operation in a separate goroutine, and +another that performs the actual long-running operation. You'll use contexts to manage the lifetime +of these goroutines and implement a cancellation mechanism that allows the caller to stop both +goroutines. + +The two function signatures: + +```go +func StartTask(ctx context.Context) (result string, err error) +func SubTask(ctx context.Context) (result string, err error) +``` + +Requirements: + +1. `StartTask` should: + - Create a new context with a 1-second timeout, derived from the input context. + - Start `SubTask` in a new goroutine using the derived context. + - If the main context is canceled, cancel the subtask and return with an empty string and the + error provided by the main context (use [`ctx.Err()`](https://pkg.go.dev/context#Context)). + - If `SubTask` finishes with a non-empty error, return that error with an empty string. + - Otherwise return whatever string `SubTask` returns prepended with the string `"Main task + status:"` and a `nil` error. + +2. `SubTask` should: + - Simulate a long-running task by attempting to run for 200 milliseconds. + - If the provided context is canceled before the task completes, return immediately with the + error provided by the context (use [`ctx.Err()`](https://pkg.go.dev/context#Context)). + - Otherwise, return the string `"Subtask completed successfully"` with an empty error. + +3. Use channels to communicate the subtask result and errors between the goroutines. Make sure to + close the channels in the goroutine that actually writes the channels otherwise you will see + ugly race conditions and random panics. + +4. Ensure proper resource cleanup by using `defer` statements where appropriate. + +Insert the code into the file `exercise.go` at the placeholder `// INSERT YOUR CODE HERE`. + diff --git a/22-goroutines/06-context/.exercise_test.go b/22-goroutines/06-context/.exercise_test.go new file mode 100644 index 0000000..6032d87 --- /dev/null +++ b/22-goroutines/06-context/.exercise_test.go @@ -0,0 +1,50 @@ +package subtask + +import ( + "context" + "testing" + "time" +) + +func TestStartLongRunningTask(t *testing.T) { + t.Run("Successful completion", func(t *testing.T) { + ctx := context.Background() + result, err := StartTask(ctx) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if result != "Main task status: Subtask completed successfully" && + result != "Main task status:Subtask completed successfully" { + t.Errorf("Expected 'Main task status:Subtask completed successfully', got %s", result) + } + }) + + t.Run("Cancelled by main context", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(100 * time.Millisecond) + cancel() + }() + + result, err := StartTask(ctx) + if err == nil { + t.Error("Expected an error, got nil") + } + if result != "" { + t.Errorf("Expected empty result, got %s", result) + } + }) + + t.Run("Immediate cancellation", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + result, err := StartTask(ctx) + if err == nil { + t.Error("Expected an error, got nil") + } + if result != "" { + t.Errorf("Expected empty result, got %s", result) + } + }) +} diff --git a/22-goroutines/06-context/README.md b/22-goroutines/06-context/README.md new file mode 100644 index 0000000..90c149f --- /dev/null +++ b/22-goroutines/06-context/README.md @@ -0,0 +1 @@ +# PLEASE RUN make generate diff --git a/22-goroutines/06-context/exercise.go b/22-goroutines/06-context/exercise.go new file mode 100644 index 0000000..2425899 --- /dev/null +++ b/22-goroutines/06-context/exercise.go @@ -0,0 +1,6 @@ +package subtask + +// DO NOT REMOVE THIS COMMENT +//go:generate go run ../../exercises-cli.go -student-id=$STUDENT_ID generate + +// INSERT YOUR CODE HERE diff --git a/22-goroutines/06-context/exercise.yaml b/22-goroutines/06-context/exercise.yaml new file mode 100644 index 0000000..d8255d7 --- /dev/null +++ b/22-goroutines/06-context/exercise.yaml @@ -0,0 +1 @@ +name: subtask diff --git a/22-goroutines/06-context/exercise_test.go b/22-goroutines/06-context/exercise_test.go new file mode 100644 index 0000000..4cf09ae --- /dev/null +++ b/22-goroutines/06-context/exercise_test.go @@ -0,0 +1 @@ +// PLEASE RUN make generate