Skip to content

Async function execution internals#1355

Open
TristonianJones wants to merge 2 commits into
cel-expr:masterfrom
TristonianJones:async-internals
Open

Async function execution internals#1355
TristonianJones wants to merge 2 commits into
cel-expr:masterfrom
TristonianJones:async-internals

Conversation

@TristonianJones

Copy link
Copy Markdown
Collaborator

Internal support for async function evaluation.

  • Declaration support with guard functions just like sync functions
  • The framework manages async function launches according to configurable concurrency limits
  • Async functions are provided the context.Context object and expected to block
    until a result is available and return it. The framework manages go routines

The implementation supports minimal call deduplication logic which users
will be able to extend with functionality introduced into later PRs.

@TristonianJones TristonianJones requested a review from l46kok June 22, 2026 22:35
Comment thread interpreter/async.go
ch := acs.impl(ctx, acs.argVals...)
// Wait for the async computation to finish or for the context to be cancelled.
select {
case r := <-ch:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably check for the channel being nil, or the channel being closed without a value (or the channel itself sends a nil).

Comment thread interpreter/async.go

// hashCall computes the composite bucket key for an async call.
//
// Only string and bool argument values contribute to the hash. Numeric and more complex types

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of excluding numbers, wdyt about normalizing tofloat64?

The concern I have is for an expression like [1, 2, ..., 1000].map(x, asyncFn(x)), everything gets hashed into the same bucket, and it's a O(N^2) pass given that findInBucket is a linear scan.

Ideally, we should handle things like numbers beyond 53 bit mantissa, lists/maps too, but those are rare enough that we could likely defer.

Comment thread interpreter/async_test.go
if err != nil {
t.Fatalf("NewExecutionFrame() failed: %v", err)
}
defer frame.Close()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is unlikely, but perhaps consider adding a simple guard for double close in ExecutionFrame's Close() function (something like if.Activation == nil { return } should make it idempotent)

Comment thread interpreter/async.go
function string
overload string
args []InterpretableV2
impl functions.AsyncOp

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: what happens if the user provided implementation panics? Do we ordinarily recover gracefully?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants