-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add parallel steps #51
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package group | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/inngest/inngestgo/step" | ||
) | ||
|
||
type Result struct { | ||
Error error | ||
Value any | ||
} | ||
|
||
func Parallel( | ||
ctx context.Context, | ||
fns ...func(ctx context.Context, | ||
) (any, error)) []Result { | ||
ctx = context.WithValue(ctx, step.ParallelKey, true) | ||
|
||
results := []Result{} | ||
isPlanned := false | ||
ch := make(chan struct{}, 1) | ||
for _, fn := range fns { | ||
fn := fn | ||
go func(fn func(ctx context.Context) (any, error)) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
if _, ok := r.(step.ControlHijack); ok { | ||
isPlanned = true | ||
} else { | ||
// TODO: What to do here? | ||
fmt.Println("TODO") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we just repanic for now? I suppose this might be where we capture the non-Inngest panic and could return it as an error to Inngest in the future? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we should recover the panic and repanic it outside the goroutine? I think that'll let our normal non-control-flow panic recovery logic work There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooh yeah if we already have something that captures panics then that'd be awesome. |
||
} | ||
} | ||
ch <- struct{}{} | ||
}() | ||
|
||
value, err := fn(ctx) | ||
results = append(results, Result{Error: err, Value: value}) | ||
}(fn) | ||
<-ch | ||
} | ||
|
||
if isPlanned { | ||
panic(step.ControlHijack{}) | ||
} | ||
|
||
return results | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package tests | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"syscall" | ||
"testing" | ||
"time" | ||
|
||
"github.com/inngest/inngestgo" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
teardown, err := setup() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to setup: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
code := m.Run() | ||
|
||
err = teardown() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to teardown: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
os.Exit(code) | ||
} | ||
|
||
func setup() (func() error, error) { | ||
os.Setenv("INNGEST_DEV", "1") | ||
|
||
inngestgo.DefaultClient = inngestgo.NewClient( | ||
inngestgo.ClientOpts{ | ||
EventKey: inngestgo.StrPtr("dev"), | ||
}, | ||
) | ||
|
||
// return func() error { return nil }, nil | ||
|
||
stopDevServer, err := startDevServer() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return stopDevServer, nil | ||
} | ||
|
||
func startDevServer() (func() error, error) { | ||
fmt.Println("Starting Dev Server") | ||
cmd := exec.Command( | ||
"bash", | ||
"-c", | ||
"npx --yes inngest-cli@latest dev --no-discovery --no-poll", | ||
) | ||
|
||
// Run in a new process group so we can kill the process and its children | ||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} | ||
|
||
err := cmd.Start() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to start command: %w", err) | ||
} | ||
|
||
// Wait for Dev Server to start | ||
fmt.Println("Waiting for Dev Server to start") | ||
httpClient := http.Client{Timeout: time.Second} | ||
start := time.Now() | ||
for { | ||
resp, err := httpClient.Get("http://0.0.0.0:8288") | ||
if err == nil && resp.StatusCode == 200 { | ||
break | ||
} | ||
if time.Since(start) > 20*time.Second { | ||
return nil, fmt.Errorf("timeout waiting for Dev Server to start: %w", err) | ||
} | ||
<-time.After(500 * time.Millisecond) | ||
} | ||
|
||
// Callback to stop the Dev Server | ||
stop := func() error { | ||
fmt.Println("Stopping Dev Server") | ||
pgid, err := syscall.Getpgid(cmd.Process.Pid) | ||
if err != nil { | ||
return fmt.Errorf("failed to get process group ID: %w", err) | ||
} | ||
if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil { | ||
return fmt.Errorf("failed to kill process group: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
return stop, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used as a waitgroup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's effectively a waitgroup of 1. We want to run each step sequentially but also in a goroutine so that we can recover the control hijack