Skip to content

Commit 1f6bb05

Browse files
authored
Merge pull request #373 from cschleiden/max-history-size
Support max history size
2 parents 4973be8 + b99bd5a commit 1f6bb05

File tree

9 files changed

+87
-10
lines changed

9 files changed

+87
-10
lines changed

backend/metrics/metrics.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import "time"
55
type Tags map[string]string
66

77
type Client interface {
8+
//Counter records a value at a point in time.
89
Counter(name string, tags Tags, value int64)
910

11+
// Distribution records a value at a point in time.
1012
Distribution(name string, tags Tags, value float64)
1113

14+
// Gauge records a value at a point in time.
1215
Gauge(name string, tags Tags, value int64)
1316

17+
// Timing records the duration of an event.
1418
Timing(name string, tags Tags, duration time.Duration)
1519

20+
// WithTags returns a new client with the given tags applied to all metrics.
1621
WithTags(tags Tags) Client
1722
}

backend/options.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type Options struct {
4343
// removed immediately, including their history. If set to false, the instance will be removed after the configured
4444
// retention period or never.
4545
RemoveContinuedAsNewInstances bool
46+
47+
// MaxHistorySize is the maximum size of a workflow history. If a workflow exceeds this size, it will be failed.
48+
MaxHistorySize int64
4649
}
4750

4851
var DefaultOptions Options = Options{
@@ -58,6 +61,8 @@ var DefaultOptions Options = Options{
5861
ContextPropagators: []workflow.ContextPropagator{&propagators.TracingContextPropagator{}},
5962

6063
RemoveContinuedAsNewInstances: false,
64+
65+
MaxHistorySize: 10_000,
6166
}
6267

6368
type BackendOption func(*Options)
@@ -104,6 +109,12 @@ func WithRemoveContinuedAsNewInstances() BackendOption {
104109
}
105110
}
106111

112+
func WithMaxHistorySize(size int64) BackendOption {
113+
return func(o *Options) {
114+
o.MaxHistorySize = size
115+
}
116+
}
117+
107118
func ApplyOptions(opts ...BackendOption) *Options {
108119
options := DefaultOptions
109120

backend/test/e2e.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,34 @@ func EndToEndBackendTest(t *testing.T, setup func(options ...backend.BackendOpti
613613
require.Equal(t, "hello-23", r)
614614
},
615615
},
616+
{
617+
name: "MaxHistorySize",
618+
options: []backend.BackendOption{backend.WithMaxHistorySize(2)},
619+
f: func(t *testing.T, ctx context.Context, c *client.Client, w *worker.Worker, b TestBackend) {
620+
b.Options()
621+
622+
a := func(ctx context.Context) (int, error) {
623+
return 0, nil
624+
}
625+
626+
wf := func(ctx workflow.Context) (int, error) {
627+
for i := 0; i < 10; i++ {
628+
_, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, a).Get(ctx)
629+
if err != nil {
630+
return 0, err
631+
}
632+
}
633+
634+
return 42, nil
635+
}
636+
register(t, ctx, w, []interface{}{wf}, nil)
637+
638+
instance := runWorkflow(t, ctx, c, wf)
639+
_, err := client.GetWorkflowResult[int](ctx, c, instance, time.Second*5)
640+
require.Error(t, err)
641+
require.EqualError(t, err, "workflow history size exceeded 2 events")
642+
},
643+
},
616644
}
617645

618646
tests = append(tests, e2eActivityTests...)

internal/worker/workflow.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ func (wtw *WorkflowTaskWorker) getExecutor(ctx context.Context, t *backend.Workf
180180
t.WorkflowInstance,
181181
t.Metadata,
182182
clock.New(),
183+
wtw.backend.Options().MaxHistorySize,
183184
)
184185
if err != nil {
185186
return nil, fmt.Errorf("creating workflow task executor: %w", err)

tester/options.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import (
99
)
1010

1111
type options struct {
12-
TestTimeout time.Duration
13-
Logger *slog.Logger
14-
Converter converter.Converter
15-
Propagators []workflow.ContextPropagator
16-
InitialTime time.Time
12+
TestTimeout time.Duration
13+
Logger *slog.Logger
14+
Converter converter.Converter
15+
Propagators []workflow.ContextPropagator
16+
InitialTime time.Time
17+
MaxHistorySize int64
1718
}
1819

1920
type WorkflowTesterOption func(*options)
@@ -47,3 +48,9 @@ func WithInitialTime(t time.Time) WorkflowTesterOption {
4748
o.InitialTime = t
4849
}
4950
}
51+
52+
func WithMaxHistorySize(size int64) WorkflowTesterOption {
53+
return func(o *options) {
54+
o.MaxHistorySize = size
55+
}
56+
}

tester/tester.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,10 @@ func NewWorkflowTester[TResult any](workflow workflow.Workflow, opts ...Workflow
194194
}
195195

196196
options := &options{
197-
TestTimeout: time.Second * 10,
198-
Logger: slog.Default(),
199-
Converter: converter.DefaultConverter,
197+
TestTimeout: time.Second * 10,
198+
Logger: slog.Default(),
199+
Converter: converter.DefaultConverter,
200+
MaxHistorySize: 10_000,
200201
}
201202

202203
for _, o := range opts {
@@ -357,7 +358,18 @@ func (wt *workflowTester[TResult]) Execute(ctx context.Context, args ...any) {
357358
tw.pendingEvents = tw.pendingEvents[:0]
358359

359360
// Execute task
360-
e, err := executor.NewExecutor(wt.logger, wt.tracer, wt.registry, wt.converter, wt.propagators, &testHistoryProvider{tw.history}, tw.instance, tw.metadata, wt.clock)
361+
e, err := executor.NewExecutor(
362+
wt.logger,
363+
wt.tracer,
364+
wt.registry,
365+
wt.converter,
366+
wt.propagators,
367+
&testHistoryProvider{tw.history},
368+
tw.instance,
369+
tw.metadata,
370+
wt.clock,
371+
wt.options.MaxHistorySize,
372+
)
361373
if err != nil {
362374
panic(fmt.Errorf("could not create workflow executor: %v", err))
363375
}

workflow/executor/cache/cache_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ func Test_Cache_StoreAndGet(t *testing.T) {
3131
e, err := executor.NewExecutor(
3232
slog.Default(), noop.NewTracerProvider().Tracer(backend.TracerName), r, converter.DefaultConverter,
3333
[]workflow.ContextPropagator{}, &testHistoryProvider{}, i, &metadata.WorkflowMetadata{}, clock.New(),
34+
10_000,
3435
)
3536
require.NoError(t, err)
3637

3738
i2 := core.NewWorkflowInstance("instanceID2", "executionID2")
3839
e2, err := executor.NewExecutor(
3940
slog.Default(), noop.NewTracerProvider().Tracer(backend.TracerName), r, converter.DefaultConverter,
4041
[]workflow.ContextPropagator{}, &testHistoryProvider{}, i, &metadata.WorkflowMetadata{}, clock.New(),
42+
10_000,
4143
)
4244
require.NoError(t, err)
4345

@@ -72,6 +74,7 @@ func Test_Cache_AutoEviction(t *testing.T) {
7274
slog.Default(), noop.NewTracerProvider().Tracer(backend.TracerName), r,
7375
converter.DefaultConverter, []workflow.ContextPropagator{}, &testHistoryProvider{}, i,
7476
&metadata.WorkflowMetadata{}, clock.New(),
77+
10_000,
7578
)
7679
require.NoError(t, err)
7780

@@ -102,6 +105,7 @@ func Test_Cache_Evict(t *testing.T) {
102105
slog.Default(), noop.NewTracerProvider().Tracer(backend.TracerName), r,
103106
converter.DefaultConverter, []workflow.ContextPropagator{}, &testHistoryProvider{}, i,
104107
&metadata.WorkflowMetadata{}, clock.New(),
108+
10_000,
105109
)
106110
require.NoError(t, err)
107111

workflow/executor/executor.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ type executor struct {
7474

7575
parentSpan trace.Span
7676
workflowSpan trace.Span
77+
78+
maxHistorySize int64
7779
}
7880

7981
func NewExecutor(
@@ -86,6 +88,7 @@ func NewExecutor(
8688
instance *core.WorkflowInstance,
8789
metadata *metadata.WorkflowMetadata,
8890
clock clock.Clock,
91+
maxHistorySize int64,
8992
) (WorkflowExecutor, error) {
9093
s := workflowstate.NewWorkflowState(instance, logger, tracer, clock)
9194

@@ -118,6 +121,7 @@ func NewExecutor(
118121
workflowCtxCancel: cancel,
119122
cv: cv,
120123
clock: clock,
124+
maxHistorySize: maxHistorySize,
121125
logger: logger,
122126
tracer: tracer,
123127
}, nil
@@ -170,6 +174,11 @@ func (e *executor) ExecuteTask(ctx context.Context, t *backend.WorkflowTask) (*E
170174
}
171175
}
172176

177+
// Enforce max history size limit
178+
if e.lastSequenceID+int64(len(executedEvents)) >= e.maxHistorySize {
179+
e.workflowCompleted(nil, fmt.Errorf("workflow history size exceeded %d events", e.maxHistorySize))
180+
}
181+
173182
// Process any commands added while executing new events
174183
state := core.WorkflowInstanceStateActive
175184
newCommandEvents := make([]*history.Event, 0)

workflow/executor/executor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func newExecutor(r *registry.Registry, i *core.WorkflowInstance, historyProvider
3939
logger := slog.Default()
4040
tracer := noop.NewTracerProvider().Tracer("test")
4141

42-
e, err := NewExecutor(logger, tracer, r, converter.DefaultConverter, []wf.ContextPropagator{}, historyProvider, i, &metadata.WorkflowMetadata{}, clock.New())
42+
e, err := NewExecutor(logger, tracer, r, converter.DefaultConverter, []wf.ContextPropagator{}, historyProvider, i, &metadata.WorkflowMetadata{}, clock.New(), 10_000)
4343

4444
return e.(*executor), err
4545
}

0 commit comments

Comments
 (0)