Skip to content

Commit 870e93a

Browse files
authored
Merge pull request #15 from Azure/haitao/panic_handling
panic handling, seal a jobDefinition
2 parents f530737 + 42bf785 commit 870e93a

11 files changed

+178
-106
lines changed

error.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ type JobErrorCode string
1010
const (
1111
ErrPrecedentStepFailure JobErrorCode = "precedent step failed"
1212
ErrStepFailed JobErrorCode = "step failed"
13-
ErrStepNotInJob JobErrorCode = "trying to reference to a step not registered in job"
13+
ErrRefStepNotInJob JobErrorCode = "trying to reference to a step not registered in job"
14+
ErrAddStepInSealedJob JobErrorCode = "trying to add step to a sealed job definition"
1415
)
1516

1617
func (code JobErrorCode) Error() string {

go.mod

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
module github.com/Azure/go-asyncjob/v2
1+
module github.com/Azure/go-asyncjob
22

33
go 1.18
44

55
require (
6-
github.com/Azure/go-asyncjob/graph v0.2.0 // indirect
7-
github.com/Azure/go-asynctask v1.3.1 // indirect
6+
github.com/Azure/go-asyncjob/graph v0.2.0
7+
github.com/Azure/go-asynctask v1.3.1
8+
github.com/stretchr/testify v1.8.1
9+
)
10+
11+
require (
812
github.com/davecgh/go-spew v1.1.1 // indirect
913
github.com/pmezard/go-difflib v1.0.0 // indirect
10-
github.com/stretchr/objx v0.5.0 // indirect
11-
github.com/stretchr/testify v1.8.1 // indirect
1214
gopkg.in/yaml.v3 v3.0.1 // indirect
1315
)

go.sum

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
github.com/Azure/go-asyncjob/graph v0.1.0 h1:qisFc4PtgaE2FDE41GRcbk2eASsR12OecJeD8qk6fkc=
2-
github.com/Azure/go-asyncjob/graph v0.1.0/go.mod h1:3Z7w9aUBIrDriypH8O+hK0aeqKWKYuKSNxwrDxFy34s=
31
github.com/Azure/go-asyncjob/graph v0.2.0 h1:0GFnQit3+ZUxpc67ogusooa38GSFRPH2e1+h+L/33hc=
42
github.com/Azure/go-asyncjob/graph v0.2.0/go.mod h1:3Z7w9aUBIrDriypH8O+hK0aeqKWKYuKSNxwrDxFy34s=
53
github.com/Azure/go-asynctask v1.3.1 h1:zE/7Zwbdg7/+V2kRKb3IV4RTqmn8DUKriVzXcNq7ubg=
@@ -11,12 +9,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
119
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1210
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1311
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
14-
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
1512
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
1613
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1714
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
1815
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
1916
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2018
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2119
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2220
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

job.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
type JobDefinitionMeta interface {
1414
GetName() string
1515
GetStep(stepName string) (StepDefinitionMeta, bool) // TODO: switch bool to error
16+
Seal()
17+
Sealed() bool
1618

1719
// not exposing for now.
1820
addStep(step StepDefinitionMeta, precedingSteps ...StepDefinitionMeta)
@@ -21,7 +23,9 @@ type JobDefinitionMeta interface {
2123

2224
// JobDefinition defines a job with child steps, and step is organized in a Directed Acyclic Graph (DAG).
2325
type JobDefinition[T any] struct {
24-
Name string
26+
name string
27+
28+
sealed bool
2529
steps map[string]StepDefinitionMeta
2630
stepsDag *graph.Graph[StepDefinitionMeta]
2731
rootStep *StepDefinition[T]
@@ -31,7 +35,7 @@ type JobDefinition[T any] struct {
3135
// it is suggest to build jobDefinition statically on process start, and reuse it for each job instance.
3236
func NewJobDefinition[T any](name string) *JobDefinition[T] {
3337
j := &JobDefinition[T]{
34-
Name: name,
38+
name: name,
3539
steps: make(map[string]StepDefinitionMeta),
3640
stepsDag: graph.NewGraph[StepDefinitionMeta](connectStepDefinition),
3741
}
@@ -49,6 +53,9 @@ func NewJobDefinition[T any](name string) *JobDefinition[T] {
4953
// this will create and return new instance of the job
5054
// caller will then be able to wait for the job instance
5155
func (jd *JobDefinition[T]) Start(ctx context.Context, input *T, jobOptions ...JobOptionPreparer) *JobInstance[T] {
56+
if !jd.Sealed() {
57+
jd.Seal()
58+
}
5259

5360
ji := newJobInstance(jd, input, jobOptions...)
5461
ji.start(ctx)
@@ -61,7 +68,18 @@ func (jd *JobDefinition[T]) getRootStep() StepDefinitionMeta {
6168
}
6269

6370
func (jd *JobDefinition[T]) GetName() string {
64-
return jd.Name
71+
return jd.name
72+
}
73+
74+
func (jd *JobDefinition[T]) Seal() {
75+
if jd.sealed {
76+
return
77+
}
78+
jd.sealed = true
79+
}
80+
81+
func (jd *JobDefinition[T]) Sealed() bool {
82+
return jd.sealed
6583
}
6684

6785
// GetStep returns the stepDefinition by name
@@ -155,7 +173,7 @@ func (ji *JobInstance[T]) start(ctx context.Context) {
155173
// construct job instance graph, with TopologySort ordering
156174
orderedSteps := ji.Definition.stepsDag.TopologicalSort()
157175
for _, stepDef := range orderedSteps {
158-
if stepDef.GetName() == ji.Definition.Name {
176+
if stepDef.GetName() == ji.Definition.GetName() {
159177
continue
160178
}
161179
ji.steps[stepDef.GetName()] = stepDef.createStepInstance(ctx, ji)

job_result.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type JobDefinitionWithResult[Tin, Tout any] struct {
1212
func JobWithResult[Tin, Tout any](jd *JobDefinition[Tin], resultStep *StepDefinition[Tout]) (*JobDefinitionWithResult[Tin, Tout], error) {
1313
sdGet, ok := jd.GetStep(resultStep.GetName())
1414
if !ok || sdGet != resultStep {
15-
return nil, ErrStepNotInJob
15+
return nil, ErrRefStepNotInJob
1616
}
1717

1818
return &JobDefinitionWithResult[Tin, Tout]{

job_result_test.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,20 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/Azure/go-asyncjob/v2"
87
"github.com/stretchr/testify/assert"
98
)
109

1110
func TestSimpleJobWithResult(t *testing.T) {
1211
t.Parallel()
1312

14-
jd, err := BuildJobWithResult(context.Background(), map[string]asyncjob.RetryPolicy{})
15-
assert.NoError(t, err)
16-
renderGraph(t, jd)
17-
18-
jobInstance := jd.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLibAdvanced{
13+
jobInstance := SqlSummaryAsyncJobDefinition.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLib{
1914
Params: &SqlSummaryJobParameters{
2015
ServerName: "server2",
2116
Table1: "table3",
2217
Query1: "query3",
2318
Table2: "table4",
2419
Query2: "query4",
2520
},
26-
SqlSummaryJobLib: SqlSummaryJobLib{},
2721
})
2822
jobErr := jobInstance.Wait(context.Background())
2923
assert.NoError(t, jobErr)

job_test.go

+18-26
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,34 @@ import (
77
"testing"
88
"time"
99

10-
"github.com/Azure/go-asyncjob/v2"
10+
"github.com/Azure/go-asyncjob"
1111
"github.com/stretchr/testify/assert"
1212
)
1313

1414
func TestSimpleJob(t *testing.T) {
1515
t.Parallel()
1616

17-
jd, err := BuildJob(context.Background(), map[string]asyncjob.RetryPolicy{})
18-
assert.NoError(t, err)
19-
renderGraph(t, jd)
20-
21-
jobInstance := jd.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLibAdvanced{
17+
jobInstance := SqlSummaryAsyncJobDefinition.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLib{
2218
Params: &SqlSummaryJobParameters{
2319
ServerName: "server1",
2420
Table1: "table1",
2521
Query1: "query1",
2622
Table2: "table2",
2723
Query2: "query2",
2824
},
29-
SqlSummaryJobLib: SqlSummaryJobLib{},
3025
})
3126
jobErr := jobInstance.Wait(context.Background())
3227
assert.NoError(t, jobErr)
3328
renderGraph(t, jobInstance)
3429

35-
jobInstance2 := jd.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLibAdvanced{
30+
jobInstance2 := SqlSummaryAsyncJobDefinition.Start(context.WithValue(context.Background(), testLoggingContextKey, t), &SqlSummaryJobLib{
3631
Params: &SqlSummaryJobParameters{
3732
ServerName: "server2",
3833
Table1: "table3",
3934
Query1: "query3",
4035
Table2: "table4",
4136
Query2: "query4",
4237
},
43-
SqlSummaryJobLib: SqlSummaryJobLib{},
4438
})
4539
jobErr = jobInstance2.Wait(context.Background())
4640
assert.NoError(t, jobErr)
@@ -50,23 +44,21 @@ func TestSimpleJob(t *testing.T) {
5044
func TestJobError(t *testing.T) {
5145
t.Parallel()
5246

53-
jd, err := BuildJob(context.Background(), map[string]asyncjob.RetryPolicy{})
54-
assert.NoError(t, err)
55-
5647
ctx := context.WithValue(context.Background(), testLoggingContextKey, t)
57-
ctx = context.WithValue(ctx, "error-injection.server1.table1", fmt.Errorf("table1 not exists"))
58-
jobInstance := jd.Start(ctx, &SqlSummaryJobLibAdvanced{
48+
jobInstance := SqlSummaryAsyncJobDefinition.Start(ctx, &SqlSummaryJobLib{
5949
Params: &SqlSummaryJobParameters{
6050
ServerName: "server1",
6151
Table1: "table1",
6252
Query1: "query1",
6353
Table2: "table2",
6454
Query2: "query2",
55+
ErrorInjection: map[string]func() error{
56+
"GetTableClient.server1.table1": func() error { return fmt.Errorf("table1 not exists") },
57+
},
6558
},
66-
SqlSummaryJobLib: SqlSummaryJobLib{},
6759
})
6860

69-
err = jobInstance.Wait(context.Background())
61+
err := jobInstance.Wait(context.Background())
7062
assert.Error(t, err)
7163

7264
jobErr := &asyncjob.JobError{}
@@ -77,30 +69,28 @@ func TestJobError(t *testing.T) {
7769

7870
func TestJobPanic(t *testing.T) {
7971
t.Parallel()
80-
jd, err := BuildJob(context.Background(), map[string]asyncjob.RetryPolicy{})
81-
assert.NoError(t, err)
8272

8373
ctx := context.WithValue(context.Background(), testLoggingContextKey, t)
84-
ctx = context.WithValue(ctx, "panic-injection.server1.table2", true)
85-
jobInstance := jd.Start(ctx, &SqlSummaryJobLibAdvanced{
74+
jobInstance := SqlSummaryAsyncJobDefinition.Start(ctx, &SqlSummaryJobLib{
8675
Params: &SqlSummaryJobParameters{
8776
ServerName: "server1",
8877
Table1: "table1",
8978
Query1: "query1",
9079
Table2: "table2",
9180
Query2: "query2",
81+
PanicInjection: map[string]bool{
82+
"GetTableClient.server1.table2": true,
83+
},
9284
},
93-
SqlSummaryJobLib: SqlSummaryJobLib{},
9485
})
9586

96-
err = jobInstance.Wait(context.Background())
87+
err := jobInstance.Wait(context.Background())
9788
assert.Error(t, err)
9889

99-
/* panic is out of reach of jobError, but planning to catch panic in the future
10090
jobErr := &asyncjob.JobError{}
10191
assert.True(t, errors.As(err, &jobErr))
10292
assert.Equal(t, jobErr.Code, asyncjob.ErrStepFailed)
103-
assert.Equal(t, jobErr.StepName, "getTableClient1")*/
93+
assert.Equal(t, jobErr.StepInstance.GetName(), "GetTableClient2")
10494
}
10595

10696
func TestJobStepRetry(t *testing.T) {
@@ -110,15 +100,17 @@ func TestJobStepRetry(t *testing.T) {
110100

111101
ctx := context.WithValue(context.Background(), testLoggingContextKey, t)
112102
ctx = context.WithValue(ctx, "error-injection.server1.table1.query1", fmt.Errorf("query exeeded memory limit"))
113-
jobInstance := jd.Start(ctx, &SqlSummaryJobLibAdvanced{
103+
jobInstance := jd.Start(ctx, &SqlSummaryJobLib{
114104
Params: &SqlSummaryJobParameters{
115105
ServerName: "server1",
116106
Table1: "table1",
117107
Query1: "query1",
118108
Table2: "table2",
119109
Query2: "query2",
110+
ErrorInjection: map[string]func() error{
111+
"ExecuteQuery.server1.table1.query1": func() error { return fmt.Errorf("query exeeded memory limit") },
112+
},
120113
},
121-
SqlSummaryJobLib: SqlSummaryJobLib{},
122114
})
123115

124116
err = jobInstance.Wait(context.Background())

retryer.go

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package asyncjob
22

33
import (
4-
"fmt"
5-
"runtime/debug"
64
"time"
75
)
86

@@ -17,24 +15,13 @@ func newRetryer[T any](policy RetryPolicy, report *RetryReport, toRetry func() (
1715
return &retryer[T]{retryPolicy: policy, retryReport: report, function: toRetry}
1816
}
1917

20-
func (r *retryer[T]) funcWithPanicHandled() (result *T, err error) {
21-
defer func() {
22-
if r := recover(); r != nil {
23-
err = fmt.Errorf("Panic cought: %v, StackTrace: %s", r, debug.Stack())
24-
}
25-
}()
26-
result, err = r.function()
27-
28-
return result, err
29-
}
30-
3118
func (r retryer[T]) Run() (*T, error) {
32-
t, err := r.funcWithPanicHandled()
19+
t, err := r.function()
3320
for err != nil {
3421
if shouldRetry, duration := r.retryPolicy.ShouldRetry(err); shouldRetry {
3522
r.retryReport.Count++
3623
time.Sleep(duration)
37-
t, err = r.funcWithPanicHandled()
24+
t, err = r.function()
3825
} else {
3926
break
4027
}

step.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func (si *StepInstance[T]) GetState() StepState {
154154
func (si *StepInstance[T]) EnrichContext(ctx context.Context) (result context.Context) {
155155
result = ctx
156156
if si.Definition.executionOptions.ContextPolicy != nil {
157-
// handle panic from user code
157+
// TODO: bubble up the error somehow
158158
defer func() {
159159
if r := recover(); r != nil {
160160
fmt.Println("Recovered in EnrichContext", r)

0 commit comments

Comments
 (0)