weiqingy commented on code in PR #924:
URL: https://github.com/apache/flink-agents/pull/924#discussion_r3697188385
##########
runtime/src/main/java/org/apache/flink/agents/runtime/operator/ActionExecutionOperator.java:
##########
@@ -369,36 +392,54 @@ private void processActionTaskForKey(Object key) throws
Exception {
key, sequenceNumber, actionTask.action,
actionTask.event);
}
- // Set up durable execution context for fine-grained recovery
- durableExecManager.setupDurableExecutionContext(
- actionTask, actionState, sequenceNumber);
-
- ActionTask.ActionTaskResult actionTaskResult =
- actionTask.invoke(
- getRuntimeContext().getUserCodeClassLoader(),
- this.pythonBridge.getPythonActionExecutor());
-
- // We remove the contexts from the map after the task is
processed. They will be added
- // back later if the action task has a generated action task,
meaning it is not
- // finished.
- contextManager.removeMemoryContext(actionTask);
- durableExecManager.removeDurableContext(actionTask);
- contextManager.removeContinuationContext(actionTask);
- contextManager.removePythonAwaitableRef(actionTask);
- durableExecManager.maybePersistTaskResult(
- key,
- sequenceNumber,
- actionTask.action,
- actionTask.event,
- actionTask.getRunnerContext(),
- actionTaskResult);
- isFinished = actionTaskResult.isFinished();
- outputEvents = actionTaskResult.getOutputEvents();
- generatedActionTaskOpt = actionTaskResult.getGeneratedActionTask();
+ notifyActionStarted(actionTask);
+ try {
+ // Set up durable execution context for fine-grained recovery
+ durableExecManager.setupDurableExecutionContext(
+ actionTask, actionState, sequenceNumber);
+
+ ActionTask.ActionTaskResult actionTaskResult =
+ actionTask.invoke(
+ getRuntimeContext().getUserCodeClassLoader(),
+ this.pythonBridge.getPythonActionExecutor());
+
+ // Drop task-local contexts after each step; continuations
transfer them back.
+ contextManager.removeMemoryContext(actionTask);
+ durableExecManager.removeDurableContext(actionTask);
+ contextManager.removeContinuationContext(actionTask);
+ contextManager.removePythonAwaitableRef(actionTask);
+ durableExecManager.maybePersistTaskResult(
+ key,
+ sequenceNumber,
+ actionTask.action,
+ actionTask.event,
+ actionTask.getRunnerContext(),
+ actionTaskResult);
+ isFinished = actionTaskResult.isFinished();
+ outputEvents = actionTaskResult.getOutputEvents();
+ generatedActionTaskOpt =
actionTaskResult.getGeneratedActionTask();
+ notifyFinished = isFinished;
+ } catch (Exception e) {
Review Comment:
Could the Action lifecycle guarantee cover the remaining failure paths here?
This `catch (Exception)` misses a raw `Error`, including one now unwrapped
by `JavaFunction.java:114`. Before this PR that body was just `return
getMethod().invoke(null, args);`, so every user throwable arrived wrapped in
`InvocationTargetException`. A tool throwing `AssertionError` used to be caught
at `ToolCallAction.java:174` and reported via `ExecutionReporters.failed` at
`:179`. Now it skips this catch too and fails the task at `:306`'s `catch
(Throwable t)`, leaving `_execution_started_event` with no terminal Event.
Separately, `processEvent(...)` at `:434` can throw after
`maybePersistTaskResult` at `:411` but before `notifyActionFinished` at `:437`.
This catch has already closed at `:429` and the `finally` at `:439` only calls
`completeActionExecution`, so that produces the same incomplete lifecycle, with
the result persisted.
Would catching `Throwable` long enough to report and clean up before
rethrowing, or emitting finished right after successful invocation and
persistence, keep every started Action terminal without changing which failures
stop the task?
##########
python/flink_agents/runtime/flink_runner_context.py:
##########
@@ -56,6 +59,24 @@
logger = logging.getLogger(__name__)
+def _error_type(error: BaseException) -> str:
+ return f"{error.__class__.__module__}.{error.__class__.__qualname__}"
+
+
+def _root_cause(error: BaseException) -> BaseException:
+ current = error
+ visited: set[int] = set()
+ while id(current) not in visited:
+ visited.add(id(current))
+ cause = current.__cause__
+ if cause is None and not current.__suppress_context__:
+ cause = current.__context__
Review Comment:
`_root_cause` follows `__cause__` then `__context__` when
`__suppress_context__` is false (`:71-73`), while Java walks only `getCause()`
(`ExecutionLifecycleEvents.java:100-107`). `__context__` is set implicitly by
any `raise` inside an `except` block, where `getCause()` is set only when a
cause is passed explicitly.
So a `MyError` raised inside `except JSONDecodeError` records `errorType:
json.decoder.JSONDecodeError` where Java records `MyError`. Same wrapped
failure, two `errorType` values in one log file, and a cross-language query on
that field splits. `AGENTS.md` asks that "Public API changes must keep Java,
Python, and YAML APIs semantically aligned".
Is following `__context__` deliberate? Restricting to `__cause__` would
match Java, though it may be buying something I can't see. Either way
`test_failed_execution_reports_deepest_cause` wires only `__cause__`
(`test_flink_runner_context_trace.py:39`), so that branch is unexercised on
both sides.
##########
runtime/src/main/java/org/apache/flink/agents/runtime/eventlog/EventLogWriter.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.apache.flink.agents.runtime.eventlog;
+
+import org.apache.flink.agents.api.Event;
+import org.apache.flink.agents.api.EventContext;
+import org.apache.flink.agents.api.logger.EventLogger;
+import org.apache.flink.agents.api.logger.EventLoggerConfig;
+import org.apache.flink.agents.api.logger.EventLoggerFactory;
+import org.apache.flink.agents.api.logger.EventLoggerOpenParams;
+import org.apache.flink.agents.api.logger.LoggerType;
+import org.apache.flink.agents.api.trace.ExecutionTraceContext;
+import org.apache.flink.agents.plan.AgentPlan;
+import org.apache.flink.agents.runtime.metrics.BuiltInMetrics;
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.streaming.api.operators.StreamingRuntimeContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import static
org.apache.flink.agents.api.configuration.AgentConfigOptions.BASE_LOG_DIR;
+import static
org.apache.flink.agents.api.configuration.AgentConfigOptions.EVENT_LOGGER_TYPE;
+import static
org.apache.flink.agents.api.configuration.AgentConfigOptions.EVENT_LOG_TRACE_ENABLED;
+
+/** Operator-scoped writer that owns the physical Event Log logger lifecycle.
*/
+@Internal
+public final class EventLogWriter implements AutoCloseable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(EventLogWriter.class);
+
+ @Nullable private final EventLogger eventLogger;
+ private final boolean traceEnabled;
+
+ public static EventLogWriter create(AgentPlan agentPlan) {
+ return new EventLogWriter(
+ createEventLogger(agentPlan),
agentPlan.getConfig().get(EVENT_LOG_TRACE_ENABLED));
+ }
+
+ @VisibleForTesting
+ public static EventLogWriter forEventLogger(@Nullable EventLogger
eventLogger) {
+ return forEventLogger(eventLogger, true);
+ }
+
+ @VisibleForTesting
+ public static EventLogWriter forEventLogger(
+ @Nullable EventLogger eventLogger, boolean traceEnabled) {
+ return new EventLogWriter(eventLogger, traceEnabled);
+ }
+
+ private EventLogWriter(@Nullable EventLogger eventLogger, boolean
traceEnabled) {
+ this.eventLogger = eventLogger;
+ this.traceEnabled = traceEnabled;
+ }
+
+ public void open(StreamingRuntimeContext runtimeContext, BuiltInMetrics
builtInMetrics)
+ throws Exception {
+ if (eventLogger == null) {
+ return;
+ }
+ eventLogger.open(new EventLoggerOpenParams(runtimeContext));
+ if (eventLogger instanceof FileEventLogger) {
+ ((FileEventLogger) eventLogger)
+
.setTruncatedEventsCounter(builtInMetrics.getEventLogTruncatedEventsCounter());
+ } else if (eventLogger instanceof Slf4jEventLogger) {
+ ((Slf4jEventLogger) eventLogger)
+
.setTruncatedEventsCounter(builtInMetrics.getEventLogTruncatedEventsCounter());
+ }
+ }
+
+ /** Appends and flushes a business Event best-effort. */
+ public void appendBusinessEventAndFlush(
+ EventContext eventContext, Event event, @Nullable
ExecutionTraceContext traceContext) {
+ appendAndFlush(eventContext, event, traceEnabled ? traceContext :
null);
+ }
+
+ /** Appends and flushes an execution lifecycle Event when Trace recording
is enabled. */
+ public void appendExecutionEventAndFlush(Event event,
ExecutionTraceContext traceContext) {
+ if (!traceEnabled) {
+ return;
+ }
+ appendAndFlush(new EventContext(event), event, traceContext);
+ }
+
+ private void appendAndFlush(
+ EventContext eventContext, Event event, @Nullable
ExecutionTraceContext traceContext) {
+ if (eventLogger == null) {
+ return;
+ }
+ try {
+ eventLogger.append(eventContext, event, traceContext);
+ eventLogger.flush();
+ } catch (Exception logError) {
Review Comment:
Best-effort writes look intentional, but this also changes what an `append`
or `flush` failure does. At the merge base (`6f020c50`) the work sat in
`EventRouter.notifyEventProcessed` (`EventRouter.java:233-242`), which had no
try/catch and declared `throws Exception`, so a failure propagated and failed
the task. Was making it non-fatal the intent, or a side effect of the move?
Either way, what would you want an operator to see when a write is dropped?
`BuiltInMetrics` declares and registers `eventLogTruncatedEvents` (`:40`,
`:53`) but has no equivalent for failed writes, so an operator whose disk
filled gets a log that just stops while the job stays green. A first-failure
WARN plus an `eventLogWriteFailures` counter next to the truncation one is the
shape I had in mind, though you may be weighing log noise against it.
Smaller thing in the same block: `flush()` at `:108` is skipped when
`append` at `:107` throws, so a partial line can sit in the `PrintWriter`
buffer.
##########
docs/content/docs/operations/monitoring.md:
##########
@@ -240,7 +251,7 @@ Example record at `STANDARD` with a long string and a large
array truncated:
### Per-event-type log levels
-You can override the level for individual event types using the
`event-log.type.<EVENT_TYPE>.level` config key, where `<EVENT_TYPE>` is the
event's routing type string (the same string that appears as `eventType` in the
JSON log). Built-in events use short snake-cased names such as:
+You can override the level for individual event types using the
`event-log.type.<EVENT_TYPE>.level` config key, where `<EVENT_TYPE>` is the
event's routing type string (the same string that appears as `eventType` in the
JSON log). Although the field name uses camelCase, built-in Event type values
remain snake-cased:
Review Comment:
Would it help to list the four `_execution_*` routing types in the
per-event-type table, with a note on how they compose with
`event-log.trace.enabled`?
Since this line says the per-type key is the event's routing type string,
Trace can be enabled while `event-log.type._execution_started_event.level: OFF`
still removes the started Events. That interaction isn't visible from the table
today, and the four lifecycle types aren't listed in it at all.
##########
docs/content/docs/operations/monitoring.md:
##########
@@ -291,4 +302,6 @@ Other per-type levels from `config.yaml` are preserved —
the `-D` flag only ov
### Compatibility Notes
Review Comment:
nit: could the Compatibility Notes name the actual rewrites, `event`
removed, `event.id` → `eventId`, `event.attributes` → `eventAttributes`, and
mention that Python Event IDs changed from content-derived to per-occurrence
UUIDs?
`eventType` was already top-level, so the current wording names the one
field that didn't move, and a reader can't derive the rest. Grepping `docs/`
for content-hash or `uuid4` returns nothing, so the Python change lives only in
`event.py`, and anything relying on content-hash id equality or dedup behaves
differently after upgrade.
##########
runtime/src/main/java/org/apache/flink/agents/runtime/eventlog/FileEventLogger.java:
##########
@@ -199,24 +207,18 @@ public void append(EventContext context, Event event)
throws Exception {
}
ObjectNode rootNode = (ObjectNode) tree;
- // Truncate the event subtree at STANDARD level.
+ // Truncate event attributes at STANDARD level.
if (level == EventLogLevel.STANDARD && truncator != null) {
- JsonNode eventNode = rootNode.get("event");
- if (eventNode instanceof ObjectNode) {
- boolean truncated = truncator.truncate((ObjectNode) eventNode);
+ JsonNode attributesNode = rootNode.get("eventAttributes");
Review Comment:
Protected names are settled, thanks. The test side is the part I'm still
unsure about.
The guarantee now lives in the two call sites (`FileEventLogger.java:212`,
`Slf4jEventLogger.java:179`) rather than in `JsonTruncator`, and I couldn't
find a test that pins it. Flipping `rootNode.get("eventAttributes")` back to
`rootNode` would fail none of the six candidate tests: the logger tests assert
only `eventAttributes.customData`, the Python e2e one only that
`"truncatedString"` appears somewhere in the line, and the `JsonTruncatorTest`
units never see a record. `eventId` is a 36-char UUID that would be wrapped at
`max-string-length=10` and nothing checks it.
That leaves the promise at `monitoring.md:219` ("Truncation only applies to
large nested content under `eventAttributes`") resting on review rather than
CI. Is one assertion in `FileEventLoggerTest.testStandardLevelTruncation`
enough to close it, checking `eventId` is still textual at
`max-string-length=10`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]