Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions publish/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ RUNTIME_TARGETS = [
"//runtime/src/main/java/dev/cel/runtime:async_call",
"//runtime/src/main/java/dev/cel/runtime:async_drain_strategy",
"//runtime/src/main/java/dev/cel/runtime:async_observer",
"//runtime/src/main/java/dev/cel/runtime:async_options",
"//runtime/src/main/java/dev/cel/runtime:base",
"//runtime/src/main/java/dev/cel/runtime:interpreter",
"//runtime/src/main/java/dev/cel/runtime:late_function_binding",
Expand Down
11 changes: 11 additions & 0 deletions runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ java_library(
":async_call",
":async_drain_strategy",
":async_observer",
":async_options",
":descriptor_message_provider",
":evaluation_exception",
":function_overload",
Expand Down Expand Up @@ -417,3 +418,13 @@ cel_android_library(
name = "async_observer_android",
exports = ["//runtime/src/main/java/dev/cel/runtime:async_observer_android"],
)

java_library(
name = "async_options",
exports = ["//runtime/src/main/java/dev/cel/runtime:async_options"],
)

cel_android_library(
name = "async_options_android",
exports = ["//runtime/src/main/java/dev/cel/runtime:async_options_android"],
)
36 changes: 36 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ java_library(
tags = [
],
deps = [
":async_options",
":descriptor_type_resolver",
":dispatcher",
":evaluation_exception",
Expand Down Expand Up @@ -871,6 +872,7 @@ java_library(
tags = [
],
deps = [
":async_options",
":descriptor_message_provider",
":descriptor_type_resolver",
":dispatcher",
Expand Down Expand Up @@ -926,6 +928,7 @@ java_library(
],
deps = [
":activation",
":async_options",
":evaluation_exception",
":evaluation_listener",
":function_binding",
Expand All @@ -950,6 +953,7 @@ java_library(
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:org_jspecify_jspecify",
],
)

Expand Down Expand Up @@ -1363,6 +1367,36 @@ cel_android_library(
],
)

java_library(
name = "async_options",
srcs = ["CelAsyncEvaluationOptions.java"],
tags = [
],
deps = [
":async_drain_strategy",
":async_observer",
"//:auto_value",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:org_jspecify_jspecify",
],
)

cel_android_library(
name = "async_options_android",
srcs = ["CelAsyncEvaluationOptions.java"],
tags = [
],
deps = [
":async_drain_strategy_android",
":async_observer_android",
"//:auto_value",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:org_jspecify_jspecify",
],
)

java_library(
name = "program",
srcs = ["Program.java"],
Expand All @@ -1374,6 +1408,7 @@ java_library(
":partial_vars",
":variable_resolver",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)

Expand All @@ -1388,6 +1423,7 @@ cel_android_library(
":partial_vars_android",
":variable_resolver",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven_android//:com_google_guava_guava",
],
)

Expand Down
141 changes: 141 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/CelAsyncEvaluationOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cel.runtime;

import com.google.auto.value.AutoValue;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong;

/** Options for configuring asynchronous CEL evaluation. */
@AutoValue
@ThreadSafe
public abstract class CelAsyncEvaluationOptions {

private static final int DEFAULT_MAX_CONCURRENCY = 100;
private static final int DEFAULT_MAX_ITERATIONS = 1_000;

/**
* Maximum number of concurrent async function calls in-flight simultaneously. A value <= 0
* indicates unbounded concurrency.
*/
public abstract int maxConcurrency();

/** Strategy governing when to trigger re-evaluation after async call completions. */
public abstract CelAsyncDrainStrategy drainStrategy();

/** Safety cap on the maximum number of AST re-evaluation passes before aborting. */
public abstract int maxIterations();

/**
* Returns the custom configured {@link ScheduledExecutorService}, if present.
*
* <p>If absent, {@link #resolveScheduledExecutorService()} falls back to an internal, shared
* single-threaded daemon scheduler.
*/
public abstract Optional<ScheduledExecutorService> scheduledExecutorService();

/** Returns the configured lifecycle observer, if present. */
public abstract Optional<CelAsyncObserver> observer();

/**
* Resolves the {@link ScheduledExecutorService} used for debounce timers, falling back to a
* shared, lazily initialized single-threaded daemon scheduler (named {@code
* cel-async-debounce-*}) if not custom-configured.
*
* <p>The scheduler is used exclusively as an alarm clock to trigger continuation wakeups; it does
* not execute CEL evaluation tasks.
*/
public ScheduledExecutorService resolveScheduledExecutorService() {
return scheduledExecutorService().orElse(DefaultDebounceSchedulerHolder.INSTANCE);
}

public abstract Builder toBuilder();

/**
* Returns a new {@link Builder} initialized with standard default options:
*
* <ul>
* <li>Maximum concurrency: 100 in-flight calls
* <li>Maximum iterations: 1,000 evaluation passes
* <li>Drain strategy: {@link CelAsyncDrainStrategy#drainReady()} (100-microsecond debounce
* window)
* <li>Scheduled executor service: A shared, lazily initialized single-threaded daemon scheduler
* used exclusively for debounce timer wakeups.
* </ul>
*/
public static Builder newBuilder() {
return new AutoValue_CelAsyncEvaluationOptions.Builder()
.setMaxConcurrency(DEFAULT_MAX_CONCURRENCY)
.setDrainStrategy(CelAsyncDrainStrategy.drainReady())
.setMaxIterations(DEFAULT_MAX_ITERATIONS);
}

/**
* Returns a new {@link Builder} initialized with standard default options.
*
* <p>Equivalent to calling {@link #newBuilder()}.
*/
public static Builder builder() {
return newBuilder();
}

/**
* Returns a {@link CelAsyncEvaluationOptions} instance with the {@link #newBuilder() default
* configuration}.
*/
public static CelAsyncEvaluationOptions defaultOptions() {
return newBuilder().build();
}

private static final class DefaultDebounceSchedulerHolder {
private static final AtomicLong counter = new AtomicLong();
private static final ScheduledExecutorService INSTANCE =
Executors.newSingleThreadScheduledExecutor(
r -> {
Thread t = new Thread(r);
t.setName("cel-async-debounce-" + counter.getAndIncrement());
t.setDaemon(true);
return t;
});
}

/** Builder for {@link CelAsyncEvaluationOptions}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setMaxConcurrency(int maxConcurrency);

public abstract Builder setDrainStrategy(CelAsyncDrainStrategy drainStrategy);

public abstract Builder setMaxIterations(int maxIterations);

/**
* Sets a custom {@link ScheduledExecutorService} for debounce timers.
*
* <p>If not set, defaults to an internal, shared single-threaded daemon scheduler.
*/
public abstract Builder setScheduledExecutorService(
ScheduledExecutorService scheduledExecutorService);

public abstract Builder setObserver(CelAsyncObserver observer);

public abstract CelAsyncEvaluationOptions build();
}

// Package-private constructor prevents extension outside package while allowing AutoValue.
CelAsyncEvaluationOptions() {}
}
7 changes: 7 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/CelRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package dev.cel.runtime;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.Immutable;
import javax.annotation.concurrent.ThreadSafe;
Expand Down Expand Up @@ -42,6 +43,12 @@ interface Program extends dev.cel.runtime.Program {
/** Evaluate the expression using {@code message} fields as the source of input variables. */
Object eval(Message message) throws CelEvaluationException;

/**
* Evaluate the expression asynchronously using {@code message} fields as the source of input
* variables.
*/
ListenableFuture<Object> evalAsync(Message message);

/**
* Trace evaluates a compiled program without any variables and invokes the listener as
* evaluation progresses through the AST.
Expand Down
17 changes: 17 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package dev.cel.runtime;

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
Expand Down Expand Up @@ -214,6 +215,22 @@ public interface CelRuntimeBuilder {
@CanIgnoreReturnValue
CelRuntimeBuilder setContainer(CelContainer container);

/**
* Sets options to use for asynchronous evaluation.
*
* <p>If not configured, defaults to {@link CelAsyncEvaluationOptions#defaultOptions()}.
*/
@CanIgnoreReturnValue
CelRuntimeBuilder setAsyncEvaluationOptions(CelAsyncEvaluationOptions asyncEvaluationOptions);

/**
* Sets the executor to use for asynchronous evaluation.
*
* <p>This executor is required when evaluating expressions asynchronously via {@link
* Program#evalAsync}.
*/
@CanIgnoreReturnValue
CelRuntimeBuilder setAsyncExecutor(ListeningExecutorService asyncExecutor);

/** Build a new instance of the {@code CelRuntime}. */
@CheckReturnValue
Expand Down
Loading
Loading