Skip to content

Commit 975082f

Browse files
l46kokcopybara-github
authored andcommitted
Add async function bindings, observer, and drain strategies
PiperOrigin-RevId: 974934412
1 parent a2353b3 commit 975082f

14 files changed

Lines changed: 1187 additions & 3 deletions

.bazelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ build --java_language_version=11
1616
common --javacopt=-Xlint:-options
1717

1818
# Remove flag once https://github.com/google/cel-spec/issues/508 and rules_jvm_external is fixed.
19-
common --incompatible_autoload_externally=proto_library,cc_proto_library,java_proto_library,java_test
19+
common --incompatible_autoload_externally=proto_library,cc_proto_library,java_proto_library,java_test,java_import
2020

2121
# Limit repository cache size by not caching extracted repository contents
2222
build --repo_contents_cache=

publish/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ COMMON_TARGETS = [
2929
# keep sorted
3030
RUNTIME_TARGETS = [
3131
"//runtime/src/main/java/dev/cel/runtime",
32+
"//runtime/src/main/java/dev/cel/runtime:async_call",
33+
"//runtime/src/main/java/dev/cel/runtime:async_drain_strategy",
34+
"//runtime/src/main/java/dev/cel/runtime:async_observer",
3235
"//runtime/src/main/java/dev/cel/runtime:base",
3336
"//runtime/src/main/java/dev/cel/runtime:interpreter",
3437
"//runtime/src/main/java/dev/cel/runtime:late_function_binding",

runtime/BUILD.bazel

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ package(
99
java_library(
1010
name = "runtime",
1111
exports = [
12+
":async_call",
13+
":async_drain_strategy",
14+
":async_observer",
1215
":descriptor_message_provider",
1316
":evaluation_exception",
1417
":function_overload",
@@ -340,6 +343,11 @@ java_library(
340343
exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload"],
341344
)
342345

346+
cel_android_library(
347+
name = "function_overload_android",
348+
exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload_android"],
349+
)
350+
343351
java_library(
344352
name = "descriptor_message_provider",
345353
visibility = ["//:internal"],
@@ -379,3 +387,33 @@ cel_android_library(
379387
name = "partial_vars_android",
380388
exports = ["//runtime/src/main/java/dev/cel/runtime:partial_vars_android"],
381389
)
390+
391+
java_library(
392+
name = "async_call",
393+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_call"],
394+
)
395+
396+
cel_android_library(
397+
name = "async_call_android",
398+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_call_android"],
399+
)
400+
401+
java_library(
402+
name = "async_drain_strategy",
403+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_drain_strategy"],
404+
)
405+
406+
cel_android_library(
407+
name = "async_drain_strategy_android",
408+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_drain_strategy_android"],
409+
)
410+
411+
java_library(
412+
name = "async_observer",
413+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_observer"],
414+
)
415+
416+
cel_android_library(
417+
name = "async_observer_android",
418+
exports = ["//runtime/src/main/java/dev/cel/runtime:async_observer_android"],
419+
)

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,7 @@ cel_android_library(
784784
java_library(
785785
name = "function_overload",
786786
srcs = [
787+
"CelAsyncFunctionOverload.java",
787788
"CelFunctionOverload.java",
788789
"OptimizedFunctionOverload.java",
789790
],
@@ -800,9 +801,12 @@ java_library(
800801
cel_android_library(
801802
name = "function_overload_android",
802803
srcs = [
804+
"CelAsyncFunctionOverload.java",
803805
"CelFunctionOverload.java",
804806
"OptimizedFunctionOverload.java",
805807
],
808+
tags = [
809+
],
806810
deps = [
807811
":evaluation_exception",
808812
":unknown_attributes_android",
@@ -1277,6 +1281,88 @@ cel_android_library(
12771281
],
12781282
)
12791283

1284+
java_library(
1285+
name = "async_call",
1286+
srcs = ["CelAsyncCall.java"],
1287+
tags = [
1288+
],
1289+
deps = [
1290+
"@maven//:com_google_code_findbugs_annotations",
1291+
"@maven//:com_google_errorprone_error_prone_annotations",
1292+
],
1293+
)
1294+
1295+
cel_android_library(
1296+
name = "async_call_android",
1297+
srcs = ["CelAsyncCall.java"],
1298+
tags = [
1299+
],
1300+
deps = [
1301+
"@maven//:com_google_code_findbugs_annotations",
1302+
"@maven//:com_google_errorprone_error_prone_annotations",
1303+
],
1304+
)
1305+
1306+
java_library(
1307+
name = "async_drain_strategy",
1308+
srcs = [
1309+
"CelAsyncDrainAction.java",
1310+
"CelAsyncDrainStrategy.java",
1311+
],
1312+
tags = [
1313+
],
1314+
deps = [
1315+
":async_call",
1316+
"//:auto_value",
1317+
"@maven//:com_google_errorprone_error_prone_annotations",
1318+
"@maven//:com_google_guava_guava",
1319+
],
1320+
)
1321+
1322+
cel_android_library(
1323+
name = "async_drain_strategy_android",
1324+
srcs = [
1325+
"CelAsyncDrainAction.java",
1326+
"CelAsyncDrainStrategy.java",
1327+
],
1328+
tags = [
1329+
],
1330+
deps = [
1331+
":async_call_android",
1332+
"//:auto_value",
1333+
"@maven//:com_google_errorprone_error_prone_annotations",
1334+
"@maven_android//:com_google_guava_guava",
1335+
],
1336+
)
1337+
1338+
java_library(
1339+
name = "async_observer",
1340+
srcs = ["CelAsyncObserver.java"],
1341+
tags = [
1342+
],
1343+
deps = [
1344+
":async_call",
1345+
"@maven//:com_google_code_findbugs_annotations",
1346+
"@maven//:com_google_errorprone_error_prone_annotations",
1347+
"@maven//:com_google_guava_guava",
1348+
"@maven//:org_jspecify_jspecify",
1349+
],
1350+
)
1351+
1352+
cel_android_library(
1353+
name = "async_observer_android",
1354+
srcs = ["CelAsyncObserver.java"],
1355+
tags = [
1356+
],
1357+
deps = [
1358+
":async_call_android",
1359+
"@maven//:com_google_code_findbugs_annotations",
1360+
"@maven//:com_google_errorprone_error_prone_annotations",
1361+
"@maven//:org_jspecify_jspecify",
1362+
"@maven_android//:com_google_guava_guava",
1363+
],
1364+
)
1365+
12801366
java_library(
12811367
name = "program",
12821368
srcs = ["Program.java"],
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import javax.annotation.concurrent.ThreadSafe;
18+
19+
/** Describes a pending or completed asynchronous function call. */
20+
@ThreadSafe
21+
public interface CelAsyncCall {
22+
23+
/** Returns the unique incremental tracking ID assigned to this call. */
24+
long callId();
25+
26+
/** Returns the AST expression node ID where the call is located. */
27+
long exprId();
28+
29+
/** Returns the name of the function being invoked. */
30+
String functionName();
31+
32+
/** Returns the specific overload ID being invoked. */
33+
String overloadId();
34+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import static com.google.common.base.Preconditions.checkArgument;
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
import com.google.auto.value.AutoValue;
21+
import com.google.errorprone.annotations.Immutable;
22+
import java.time.Duration;
23+
24+
/** Dictates what asynchronous evaluation should do after inspecting completions. */
25+
@AutoValue
26+
@Immutable
27+
public abstract class CelAsyncDrainAction {
28+
29+
CelAsyncDrainAction() {}
30+
31+
/** Indicates that the AST should be re-evaluated immediately. */
32+
public abstract boolean shouldReevaluate();
33+
34+
/**
35+
* Indicates how long the evaluator should wait for additional completions before deciding to
36+
* re-evaluate. A duration of ZERO with reevaluate=false means wait indefinitely for the next
37+
* completion.
38+
*/
39+
public abstract Duration waitDuration();
40+
41+
public static CelAsyncDrainAction waitDuration(Duration duration) {
42+
checkNotNull(duration);
43+
checkArgument(!duration.isNegative(), "duration must not be negative");
44+
if (duration.isZero()) {
45+
return reevaluate();
46+
}
47+
return new AutoValue_CelAsyncDrainAction(false, duration);
48+
}
49+
50+
public static CelAsyncDrainAction reevaluate() {
51+
return new AutoValue_CelAsyncDrainAction(true, Duration.ZERO);
52+
}
53+
54+
public static CelAsyncDrainAction waitForMore() {
55+
return new AutoValue_CelAsyncDrainAction(false, Duration.ZERO);
56+
}
57+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import static com.google.common.base.Preconditions.checkArgument;
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
import com.google.errorprone.annotations.Immutable;
21+
import java.time.Duration;
22+
import java.util.List;
23+
24+
/**
25+
* Controls when asynchronous evaluation re-evaluates the AST after async completions.
26+
*
27+
* <p>The evaluator consults the strategy each time completions are received.
28+
*/
29+
@Immutable
30+
public interface CelAsyncDrainStrategy {
31+
32+
/**
33+
* Evaluates the current state of asynchronous evaluation and determines the next step.
34+
*
35+
* @param completedBatch The batch of async call completions accumulated so far in this drain
36+
* cycle.
37+
* @param activeCallsCount The number of async calls currently launched but unresolved.
38+
*/
39+
CelAsyncDrainAction nextAction(List<CelAsyncCall> completedBatch, int activeCallsCount);
40+
41+
/**
42+
* Re-evaluates after a debounce window after the first completion, batching completions that
43+
* complete at roughly the same time.
44+
*/
45+
static CelAsyncDrainStrategy drainReady(Duration debounce) {
46+
return new DrainReadyStrategy(debounce);
47+
}
48+
49+
/** Re-evaluates with the default debounce window of 100 microseconds. */
50+
static CelAsyncDrainStrategy drainReady() {
51+
return drainReady(Duration.ofNanos(100_000));
52+
}
53+
54+
/** Re-evaluates immediately as soon as any single call completes. */
55+
static CelAsyncDrainStrategy drainNone() {
56+
return (completed, active) -> {
57+
checkNotNull(completed, "completedBatch must not be null");
58+
checkArgument(active >= 0, "activeCallsCount must be non-negative: %s", active);
59+
return active == 0 || !completed.isEmpty()
60+
? CelAsyncDrainAction.reevaluate()
61+
: CelAsyncDrainAction.waitForMore();
62+
};
63+
}
64+
65+
/** Waits for all currently pending calls to finish before re-evaluating. */
66+
static CelAsyncDrainStrategy drainAll() {
67+
return (completed, active) -> {
68+
checkNotNull(completed, "completedBatch must not be null");
69+
checkArgument(active >= 0, "activeCallsCount must be non-negative: %s", active);
70+
return active == 0 ? CelAsyncDrainAction.reevaluate() : CelAsyncDrainAction.waitForMore();
71+
};
72+
}
73+
74+
/** Internal implementation of the drain ready strategy with configurable debounce duration. */
75+
@Immutable
76+
final class DrainReadyStrategy implements CelAsyncDrainStrategy {
77+
private final Duration debounce;
78+
79+
DrainReadyStrategy(Duration debounce) {
80+
this.debounce = checkNotNull(debounce);
81+
checkArgument(!debounce.isNegative(), "debounce duration must not be negative");
82+
}
83+
84+
@Override
85+
public CelAsyncDrainAction nextAction(List<CelAsyncCall> completedBatch, int activeCallsCount) {
86+
checkNotNull(completedBatch, "completedBatch must not be null");
87+
checkArgument(
88+
activeCallsCount >= 0, "activeCallsCount must be non-negative: %s", activeCallsCount);
89+
if (activeCallsCount == 0) {
90+
return CelAsyncDrainAction.reevaluate();
91+
}
92+
if (completedBatch.isEmpty()) {
93+
return CelAsyncDrainAction.waitForMore();
94+
}
95+
if (debounce.isZero()) {
96+
return CelAsyncDrainAction.reevaluate();
97+
}
98+
return CelAsyncDrainAction.waitDuration(debounce);
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)