-
-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
group.Wait()
doesn't wait for all started jobs to finish unlike pool.StopAndWait()
#68
Comments
Hi @mikegleasonjr! Yes, the behaviour you are describing is correct and it's by design. Unlike the |
Hey @mikegleasonjr I wanted to let you know v2 of this library now comes with a new kind of pool that could be useful in this particular use case. You can see more details in this section of the readme, but let me show you how the workaround you described would look like if using it: // This struct represents the result of a job
type JobResult struct {
value string
err error
}
// Create a pool
pool := pond.NewResultPool[JobResult](10)
// Create a task group
group := pool.NewGroup()
// Submit all jobs as part of the group
for i := 0; i < jobCount; i++ {
group.Submit(func() JobResult {
// Execute the job
value, err := runJob()
// Wrap the result in a struct that holds the value and any error that occurred
return JobResult{
value: value,
err: err,
}
})
}
// Wait for all jobs to be executed (concurrently) and retrieve the results in the order they were submitted
jobResults, err := group.Wait() Notice I am using the Please let me know your thoughts on this 🙂. Thanks! |
In my use case my task is a tree of tasks. I would like each task and its sub-tasks to share the same context, so any error in the tree propagates through the tree via a cancelled context. I want to submit each child task onto the pond and have the top of the tree, the main task, wait for all the children to finish and respect the context. I assume I have to use groups with contexts for that approach? |
Hey @pkovacs, I'd like to know more about that use case, could you share a pseudo-code snippet of what your tree of tasks looks like? In // Create a pool with a result type of string
pool := pond.NewResultPool[string](10)
// Create a task group
group := pool.NewGroup()
// Creating a custom context for this particular group
ctx, cancel := context.WithCancel(context.Background())
// Create tasks for fetching URLs
for _, url := range urls {
url := url
group.SubmitErr(func() (string, error) {
return fetchURL(ctx, url) // <- context is passed directly in the task's function body
})
}
// Wait for all HTTP requests to complete.
responses, err := group.Wait() If |
Sure, essentially this simplified example. I've omitted any context cancellations. so just imagine that any of the tasks might produce an error serious enough to warrant cancellation of anything in that context which has not started yet. I have several layers of tasks in my real application. The code below seems to work just fine.
|
What do you think about this example? package main
import (
"context"
"fmt"
"github.com/alitto/pond/v2"
)
var pool pond.Pool
type Task struct {
n int
}
type SubTask struct {
n int
}
func main() {
pool = pond.NewPool(100)
ctx := context.Background()
group := pool.NewGroup()
for i := 0; i < 5; i++ {
task := &Task{
n: i,
}
group.SubmitErr(doTask(ctx, task))
}
group.Wait()
pool.StopAndWait()
}
func doTask(ctx context.Context, t *Task) func() error {
return func() error {
select {
case <-ctx.Done():
// something cancelled us
return ctx.Err()
default:
// run normally
fmt.Printf("Running task #%d\n", t.n)
group := pool.NewGroup()
for i := 0; i < 5; i++ {
subtask := &SubTask{
n: i,
}
group.SubmitErr(doSubTask(ctx, t.n, subtask))
}
// return the first error encountered
err := group.Wait()
if err != nil {
fmt.Printf("Task #%d failed: %v\n", t.n, err)
} else {
fmt.Printf("Task #%d completed successfully\n", t.n)
}
return err
}
}
}
func doSubTask(ctx context.Context, task int, t *SubTask) func() error {
return func() error {
select {
case <-ctx.Done():
// something cancelled us
return ctx.Err()
default:
// run normally
fmt.Printf(" - Running subtask #%d-%d\n", task, t.n)
if task%2 == 1 && t.n == 2 {
return fmt.Errorf("subtask #%d encountered an error", t.n)
}
return nil
}
}
} I modified the snippet you posted to use |
Yeah, that's helpful -- propagating the error up via the return. I just have to decide how serious each particular error is and make decisions on whether to cancel or continue. Some errors are more appropriate to log but continue. Anyway it's a nice package you've written and it looks like my timing for v2 is fortuitous. |
I'm glad You find it useful! 🙂 |
Given this test:
It has the following output:
If we uncomment
pool.StopAndWait()
ortime.Sleep(100 * time.Millisecond)
, the test passes:What should happen:
What happens:
group.Wait()
is not waiting forjob 3
.Side effects:
If
job 3
creates and returns resources that must be freed up (like anio.Closer
), it causes a memory leak because the pool manager never knew it ended and returned something. It is not up to the worker to check if the pool stopped before returning its value to the pool and do cleanup.What should happen:
group.Wait()
should behave likepool.StopAndWait()
(minus the stop)Temporary fix:
The text was updated successfully, but these errors were encountered: