Contextualized Task Execution in Go
Contexts in Go are a powerful tool for managing request-scoped values, cancellation signals, and deadlines across API boundaries and goroutines. This challenge asks you to build a simple task execution system that leverages Go's context package to gracefully handle task cancellation and timeouts. Understanding context usage is crucial for building robust and scalable Go applications.
Problem Description
You are tasked with creating a TaskExecutor that manages the execution of tasks within a given context. The TaskExecutor should accept a slice of functions (tasks), each of which takes a context.Context and returns a boolean indicating success or failure. The executor should run these tasks concurrently, respecting the context's cancellation signal and deadline.
The executor must:
- Accept a slice of tasks: Each task is a function of type
func(context.Context) bool. - Execute tasks concurrently: Launch each task in its own goroutine.
- Respect the context: Pass the provided context to each task. If the context is cancelled or the deadline is reached, the executor should stop accepting new tasks and attempt to gracefully shut down the running tasks.
- Return a slice of booleans: The returned slice should indicate the success (true) or failure (false) of each corresponding task.
- Handle errors gracefully: If a task panics, catch the panic and record the failure in the result slice.
Examples
Example 1:
Input: []func(context.Context) bool{
func(ctx context.Context) bool {
select {
case <-time.After(2 * time.Second):
fmt.Println("Task 1 completed")
return true
case <-ctx.Done():
fmt.Println("Task 1 cancelled")
return false
}
},
func(ctx context.Context) bool {
select {
case <-time.After(1 * time.Second):
fmt.Println("Task 2 completed")
return true
case <-ctx.Done():
fmt.Println("Task 2 cancelled")
return false
}
},
} , context.Background()
Output: [true false]
Explanation: Task 1 takes 2 seconds, Task 2 takes 1 second. The context is not cancelled, so both tasks complete successfully.
Example 2:
Input: []func(context.Context) bool{
func(ctx context.Context) bool {
select {
case <-time.After(3 * time.Second):
fmt.Println("Task 1 completed")
return true
case <-ctx.Done():
fmt.Println("Task 1 cancelled")
return false
}
},
func(ctx context.Context) bool {
panic("Task 2 panicked!")
return true
},
} , context.WithTimeout(context.Background(), 1 * time.Second)
Output: [false false]
Explanation: The context times out after 1 second. Task 1 is cancelled. Task 2 panics, which is caught and recorded as a failure.
Example 3: (Edge Case - Empty Task List)
Input: []func(context.Context) bool{}, context.Background()
Output: []bool{}
Explanation: If the task list is empty, return an empty slice.
Constraints
- The number of tasks can range from 0 to 100.
- Each task function should complete within 5 seconds even without context cancellation.
- The
TaskExecutorshould return within 10 seconds, even if some tasks are long-running and the context is cancelled. - The input task functions cannot return errors. They should panic on any unexpected error condition.
Notes
- Consider using a
sync.WaitGroupto manage the goroutines. - The
context.Done()channel is your primary tool for detecting cancellation. - Use
recover()to handle panics within the tasks. - Focus on clean, readable code that demonstrates a good understanding of Go's concurrency primitives and the
contextpackage. - The
timepackage is available for simulating task durations. You'll need to import it. - The
fmtpackage is available for printing debug messages. You'll need to import it. - The
syncpackage is available for synchronization primitives. You'll need to import it. - The
contextpackage is essential for this challenge. You'll need to import it.