66 "io"
77 "maps"
88 "slices"
9+ "time"
910
1011 "github.com/samber/lo"
1112 "github.com/smartcontractkit/mcms"
@@ -34,12 +35,13 @@ type analyzerEngine struct {
3435 solanaRegistry experimentalanalyzer.SolanaDecoderRegistry
3536 executionContext types.ExecutionContext // Store for formatters
3637 logger logger.Logger
38+ analyzerTimeout time.Duration
3739}
3840
3941var _ types.AnalyzerEngine = & analyzerEngine {}
4042
4143// NewAnalyzerEngine creates a new analyzer engine
42- // Options can be provided to customize the engine behavior, such as injecting registries and logger
44+ // Options can be provided to customize the engine behavior, such as injecting registries, logger, and timeouts
4345func NewAnalyzerEngine (opts ... EngineOption ) types.AnalyzerEngine {
4446 // Apply options to get configuration
4547 cfg := ApplyEngineOptions (opts ... )
@@ -50,6 +52,7 @@ func NewAnalyzerEngine(opts ...EngineOption) types.AnalyzerEngine {
5052 evmRegistry : cfg .GetEVMRegistry (),
5153 solanaRegistry : cfg .GetSolanaRegistry (),
5254 logger : cfg .GetLogger (),
55+ analyzerTimeout : cfg .GetAnalyzerTimeout (),
5356 }
5457 return engine
5558}
@@ -262,9 +265,17 @@ func (ae *analyzerEngine) analyzeProposal(
262265 continue
263266 }
264267
265- annotations , err := proposalAnalyzer .Analyze (ctx , req , decodedProposal )
268+ // Execute analyzer with timeout
269+ analyzerCtx , cancel := context .WithTimeout (ctx , ae .analyzerTimeout )
270+ annotations , err := proposalAnalyzer .Analyze (analyzerCtx , req , decodedProposal )
271+ cancel () // Always cancel to free resources
272+
266273 if err != nil {
267- ae .logger .Errorw ("Proposal analyzer failed" , "analyzerID" , proposalAnalyzer .ID (), "error" , err )
274+ if analyzerCtx .Err () == context .DeadlineExceeded {
275+ ae .logger .Errorw ("Proposal analyzer timed out" , "analyzerID" , proposalAnalyzer .ID (), "timeout" , ae .analyzerTimeout )
276+ } else {
277+ ae .logger .Errorw ("Proposal analyzer failed" , "analyzerID" , proposalAnalyzer .ID (), "error" , err )
278+ }
268279 continue
269280 }
270281 // Track which analyzer created the annotations
@@ -332,9 +343,17 @@ func (ae *analyzerEngine) analyzeBatchOperation(
332343 continue
333344 }
334345
335- annotations , err := batchOpAnalyzer .Analyze (ctx , req , decodedBatchOperation )
346+ // Execute analyzer with timeout
347+ analyzerCtx , cancel := context .WithTimeout (ctx , ae .analyzerTimeout )
348+ annotations , err := batchOpAnalyzer .Analyze (analyzerCtx , req , decodedBatchOperation )
349+ cancel () // Always cancel to free resources
350+
336351 if err != nil {
337- ae .logger .Errorw ("Batch operation analyzer failed" , "analyzerID" , batchOpAnalyzer .ID (), "chainSelector" , decodedBatchOperation .ChainSelector (), "error" , err )
352+ if analyzerCtx .Err () == context .DeadlineExceeded {
353+ ae .logger .Errorw ("Batch operation analyzer timed out" , "analyzerID" , batchOpAnalyzer .ID (), "chainSelector" , decodedBatchOperation .ChainSelector (), "timeout" , ae .analyzerTimeout )
354+ } else {
355+ ae .logger .Errorw ("Batch operation analyzer failed" , "analyzerID" , batchOpAnalyzer .ID (), "chainSelector" , decodedBatchOperation .ChainSelector (), "error" , err )
356+ }
338357 continue
339358 }
340359 trackedAnnotations := trackAnnotations (annotations , batchOpAnalyzer .ID ())
@@ -413,9 +432,17 @@ func (ae *analyzerEngine) analyzeCall(
413432 continue
414433 }
415434
416- annotations , err := callAnalyzer .Analyze (ctx , req , decodedCall )
435+ // Execute analyzer with timeout
436+ analyzerCtx , cancel := context .WithTimeout (ctx , ae .analyzerTimeout )
437+ annotations , err := callAnalyzer .Analyze (analyzerCtx , req , decodedCall )
438+ cancel () // Always cancel to free resources
439+
417440 if err != nil {
418- ae .logger .Errorw ("Call analyzer failed" , "analyzerID" , callAnalyzer .ID (), "callName" , decodedCall .Name (), "error" , err )
441+ if analyzerCtx .Err () == context .DeadlineExceeded {
442+ ae .logger .Errorw ("Call analyzer timed out" , "analyzerID" , callAnalyzer .ID (), "callName" , decodedCall .Name (), "timeout" , ae .analyzerTimeout )
443+ } else {
444+ ae .logger .Errorw ("Call analyzer failed" , "analyzerID" , callAnalyzer .ID (), "callName" , decodedCall .Name (), "error" , err )
445+ }
419446 continue
420447 }
421448 trackedAnnotations := trackAnnotations (annotations , callAnalyzer .ID ())
@@ -467,9 +494,17 @@ func (ae *analyzerEngine) analyzeParameter(
467494 continue
468495 }
469496
470- annotations , err := paramAnalyzer .Analyze (ctx , req , decodedParameter )
497+ // Execute analyzer with timeout
498+ analyzerCtx , cancel := context .WithTimeout (ctx , ae .analyzerTimeout )
499+ annotations , err := paramAnalyzer .Analyze (analyzerCtx , req , decodedParameter )
500+ cancel () // Always cancel to free resources
501+
471502 if err != nil {
472- ae .logger .Errorw ("Parameter analyzer failed" , "analyzerID" , paramAnalyzer .ID (), "paramName" , decodedParameter .Name (), "paramType" , decodedParameter .Type (), "error" , err )
503+ if analyzerCtx .Err () == context .DeadlineExceeded {
504+ ae .logger .Errorw ("Parameter analyzer timed out" , "analyzerID" , paramAnalyzer .ID (), "paramName" , decodedParameter .Name (), "paramType" , decodedParameter .Type (), "timeout" , ae .analyzerTimeout )
505+ } else {
506+ ae .logger .Errorw ("Parameter analyzer failed" , "analyzerID" , paramAnalyzer .ID (), "paramName" , decodedParameter .Name (), "paramType" , decodedParameter .Type (), "error" , err )
507+ }
473508 continue
474509 }
475510 trackedAnnotations := trackAnnotations (annotations , paramAnalyzer .ID ())
0 commit comments