4
4
"context"
5
5
"errors"
6
6
"fmt"
7
+ "strconv"
7
8
"sync"
8
9
"time"
9
10
@@ -12,6 +13,9 @@ import (
12
13
"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
13
14
"github.com/smartcontractkit/chainlink-common/pkg/services"
14
15
16
+ cappb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb"
17
+ sdkpb "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk/v2/pb"
18
+ "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host"
15
19
wasmpb "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/v2/pb"
16
20
"github.com/smartcontractkit/chainlink/v2/core/services/workflows/internal"
17
21
"github.com/smartcontractkit/chainlink/v2/core/services/workflows/types"
@@ -31,13 +35,18 @@ type Engine struct {
31
35
32
36
allTriggerEventsQueueCh chan enqueuedTriggerEvent
33
37
executionsSemaphore chan struct {}
38
+ capCallsSemaphore chan struct {}
34
39
}
35
40
36
41
type enqueuedTriggerEvent struct {
37
- event capabilities.TriggerResponse
38
- timestamp time.Time
42
+ triggerCapID string
43
+ triggerIndex int
44
+ timestamp time.Time
45
+ event capabilities.TriggerResponse
39
46
}
40
47
48
+ var _ host.CapabilityExecutor = (* Engine )(nil )
49
+
41
50
func NewEngine (ctx context.Context , cfg * EngineConfig ) (* Engine , error ) {
42
51
err := cfg .Validate ()
43
52
if err != nil {
@@ -48,6 +57,7 @@ func NewEngine(ctx context.Context, cfg *EngineConfig) (*Engine, error) {
48
57
triggers : make (map [string ]capabilities.TriggerCapability ),
49
58
allTriggerEventsQueueCh : make (chan enqueuedTriggerEvent , cfg .LocalLimits .TriggerEventQueueSize ),
50
59
executionsSemaphore : make (chan struct {}, cfg .LocalLimits .MaxConcurrentWorkflowExecutions ),
60
+ capCallsSemaphore : make (chan struct {}, cfg .LocalLimits .MaxConcurrentCapabilityCallsPerWorkflow ),
51
61
}
52
62
engine .Service , engine .srvcEng = services.Config {
53
63
Name : "WorkflowEngineV2" ,
@@ -102,7 +112,15 @@ func (e *Engine) init(ctx context.Context) {
102
112
return
103
113
}
104
114
105
- err := e .runTriggerSubscriptionPhase (ctx )
115
+ err := e .cfg .Module .SetCapabilityExecutor (e )
116
+ if err != nil {
117
+ e .cfg .Lggr .Errorw ("Workflow Engine initialization failed" , "err" , err )
118
+ // TODO(CAPPL-736): observability
119
+ e .cfg .Hooks .OnInitialized (err )
120
+ return
121
+ }
122
+
123
+ err = e .runTriggerSubscriptionPhase (ctx )
106
124
if err != nil {
107
125
e .cfg .Lggr .Errorw ("Workflow Engine initialization failed" , "err" , err )
108
126
// TODO(CAPPL-736): observability
@@ -185,7 +203,7 @@ func (e *Engine) runTriggerSubscriptionPhase(ctx context.Context) error {
185
203
}
186
204
187
205
// start listening for trigger events only if all registrations succeeded
188
- for _ , triggerEventCh := range eventChans {
206
+ for idx , triggerEventCh := range eventChans {
189
207
e .srvcEng .Go (func (srvcCtx context.Context ) {
190
208
for {
191
209
select {
@@ -197,8 +215,10 @@ func (e *Engine) runTriggerSubscriptionPhase(ctx context.Context) error {
197
215
}
198
216
select {
199
217
case e .allTriggerEventsQueueCh <- enqueuedTriggerEvent {
200
- event : event ,
201
- timestamp : e .cfg .Clock .Now (),
218
+ triggerCapID : subs .Subscriptions [idx ].Id ,
219
+ triggerIndex : idx ,
220
+ timestamp : e .cfg .Clock .Now (),
221
+ event : event ,
202
222
}:
203
223
default : // queue full, drop the event
204
224
// TODO(CAPPL-736): observability
@@ -216,15 +236,15 @@ func (e *Engine) handleAllTriggerEvents(ctx context.Context) {
216
236
select {
217
237
case <- ctx .Done ():
218
238
return
219
- case queueElem , isOpen := <- e .allTriggerEventsQueueCh :
239
+ case queueHead , isOpen := <- e .allTriggerEventsQueueCh :
220
240
if ! isOpen {
221
241
return
222
242
}
223
243
// TODO(CAPPL-737): check if expired
224
244
select {
225
245
case e .executionsSemaphore <- struct {}{}: // block if too many concurrent workflow executions
226
246
e .srvcEng .Go (func (srvcCtx context.Context ) {
227
- e .startNewWorkflowExecution (srvcCtx , queueElem . event )
247
+ e .startExecution (srvcCtx , queueHead )
228
248
<- e .executionsSemaphore
229
249
})
230
250
case <- ctx .Done ():
@@ -234,8 +254,64 @@ func (e *Engine) handleAllTriggerEvents(ctx context.Context) {
234
254
}
235
255
}
236
256
237
- func (e * Engine ) startNewWorkflowExecution (_ context.Context , _ capabilities.TriggerResponse ) {
238
- // TODO(CAPPL-735): implement execution phase
257
+ // new workflow execution, blocking until completed
258
+ func (e * Engine ) startExecution (ctx context.Context , wrappedTriggerEvent enqueuedTriggerEvent ) {
259
+ triggerEvent := wrappedTriggerEvent .event .Event
260
+ executionID , err := types .GenerateExecutionID (e .cfg .WorkflowID , triggerEvent .ID )
261
+ if err != nil {
262
+ // TODO(CAPPL-736): observability
263
+ return
264
+ }
265
+
266
+ subCtx , cancel := context .WithTimeout (ctx , time .Millisecond * time .Duration (e .cfg .LocalLimits .WorkflowExecutionTimeoutMs ))
267
+ defer cancel ()
268
+ result , err := e .cfg .Module .Execute (subCtx , & wasmpb.ExecuteRequest {
269
+ Id : executionID ,
270
+ Request : & wasmpb.ExecuteRequest_Trigger {
271
+ Trigger : & sdkpb.Trigger {
272
+ Id : strconv .FormatInt (int64 (wrappedTriggerEvent .triggerIndex ), 10 ), // TODO: change to an integer oncce proto is refactored
273
+ Payload : triggerEvent .Payload ,
274
+ },
275
+ },
276
+ MaxResponseSize : uint64 (e .cfg .LocalLimits .ModuleExecuteMaxResponseSizeBytes ),
277
+ // no Config needed
278
+ })
279
+ if err != nil {
280
+ e .cfg .Lggr .Errorw ("Workflow execution failed" , "err" , err )
281
+ // TODO(CAPPL-736): observability
282
+ return
283
+ }
284
+ // TODO(CAPPL-736): handle execution result
285
+ e .cfg .Lggr .Debugw ("Workflow execution finished" , "executionID" , executionID , "result" , result )
286
+ e .cfg .Hooks .OnExecutionFinished (executionID )
287
+ }
288
+
289
+ // blocking
290
+ func (e * Engine ) CallCapability (ctx context.Context , request * cappb.CapabilityRequest ) (* cappb.CapabilityResponse , error ) {
291
+ select {
292
+ case e .capCallsSemaphore <- struct {}{}: // block if too many concurrent capability calls
293
+ case <- ctx .Done ():
294
+ return nil , ctx .Err ()
295
+ }
296
+ defer func () { <- e .capCallsSemaphore }()
297
+
298
+ // TODO (CAPPL-735): use request.Metadata.WorkflowExecutionId to associate the call with a specific execution
299
+ capability , err := e .cfg .CapRegistry .GetExecutable (ctx , request .CapabilityId )
300
+ if err != nil {
301
+ return nil , fmt .Errorf ("trigger capability not found: %w" , err )
302
+ }
303
+
304
+ capReq , err := cappb .CapabilityRequestFromProto (request )
305
+ if err != nil {
306
+ return nil , fmt .Errorf ("failed to convert capability request: %w" , err )
307
+ }
308
+
309
+ // TODO(CAPPL-737): run with a timeout
310
+ capResp , err := capability .Execute (ctx , capReq )
311
+ if err != nil {
312
+ return nil , fmt .Errorf ("failed to execute capability: %w" , err )
313
+ }
314
+ return cappb .CapabilityResponseToProto (capResp ), nil
239
315
}
240
316
241
317
func (e * Engine ) close () error {
0 commit comments