33import static io .a2a .server .ServerCallContext .TRANSPORT_KEY ;
44
55import java .util .List ;
6- import java .util .concurrent .CountDownLatch ;
7- import java .util .concurrent .TimeUnit ;
86import java .util .concurrent .atomic .AtomicReference ;
9- import java .util .function .BiConsumer ;
107
118import jakarta .enterprise .context .ApplicationScoped ;
129import jakarta .enterprise .inject .Produces ;
@@ -176,49 +173,56 @@ private void handleAgentToAgentTest(RequestContext context, AgentEmitter agentEm
176173
177174 /**
178175 * Handles delegation by forwarding to another agent via client.
176+ * <p>
177+ * Uses blocking client call (streaming=false) which should return the final task state
178+ * synchronously without requiring async callbacks and latches. This simplified approach
179+ * avoids race conditions between event consumption and callback invocation.
179180 */
180181 private void handleDelegation (String userInput , TransportProtocol transportProtocol ,
181182 AgentEmitter agentEmitter ) {
182183 // Strip "delegate:" prefix
183184 String delegatedContent = userInput .substring ("delegate:" .length ()).trim ();
184185
185- // Create client for same transport
186+ // Create client for same transport (streaming=false for blocking behavior)
186187 try (Client client = AgentToAgentClientFactory .createClient (agentCard , transportProtocol )) {
187188 agentEmitter .startWork ();
188189
189- // Set up consumer to capture task result
190- CountDownLatch latch = new CountDownLatch (1 );
191- AtomicReference <Task > resultRef = new AtomicReference <>();
192- AtomicReference <Throwable > errorRef = new AtomicReference <>();
193-
194- BiConsumer <ClientEvent , AgentCard > consumer =
195- AgentToAgentClientFactory .createTaskCaptureConsumer (resultRef , latch );
190+ // Store the result task from blocking call
191+ AtomicReference <Task > taskRef = new AtomicReference <>();
196192
197193 // Delegate to another agent (new task on same server)
198194 // Add a marker so the receiving agent knows to complete the task
199195 Message delegatedMessage = A2A .toUserMessage ("#a2a-delegated#" + delegatedContent );
200- client .sendMessage (delegatedMessage , List .of (consumer ), error -> {
201- errorRef .set (error );
202- latch .countDown ();
203- });
204196
205- // Wait for response
206- if (!latch .await (30 , TimeUnit .SECONDS )) {
207- agentEmitter .fail (new InternalError ("Timeout waiting for delegated response" ));
208- return ;
209- }
197+ // Blocking call should return final task synchronously
198+ client .sendMessage (delegatedMessage , List .of ((event , card ) -> {
199+ if (event instanceof TaskEvent te ) {
200+ taskRef .set (te .getTask ());
201+ } else if (event instanceof TaskUpdateEvent tue ) {
202+ taskRef .set (tue .getTask ());
203+ }
204+ }), null );
210205
211- Task delegatedResult = resultRef .get ();
206+ // Blocking call should have completed before returning
207+ Task delegatedResult = taskRef .get ();
212208
213- // Check for error only if we didn't get a successful result
214- // (errors can occur after completion due to stream cleanup)
215- if (delegatedResult == null && errorRef .get () != null ) {
216- agentEmitter .fail (new InternalError ("Delegation failed: " + errorRef .get ().getMessage ()));
209+ if (delegatedResult == null ) {
210+ agentEmitter .fail (new InternalError ("No result received from blocking delegation call" ));
217211 return ;
218212 }
219213
220- if (delegatedResult == null ) {
221- agentEmitter .fail (new InternalError ("No result received from delegation" ));
214+ // DIAGNOSTIC: Check if task is actually final
215+ // If blocking call returns non-final task, it indicates a server-side race condition
216+ if (!delegatedResult .status ().state ().isFinal ()) {
217+ String diagnostic = String .format (
218+ "RACE CONDITION DETECTED: Blocking call returned non-final task! " +
219+ "State: %s, TaskId: %s, Artifacts: %d. " +
220+ "This indicates DefaultRequestHandler wait logic failed to synchronize with MainEventBusProcessor." ,
221+ delegatedResult .status ().state (),
222+ delegatedResult .id (),
223+ delegatedResult .artifacts () != null ? delegatedResult .artifacts ().size () : 0 );
224+ System .err .println (diagnostic ); // Also print to stderr for CI visibility
225+ agentEmitter .fail (new InternalError (diagnostic ));
222226 return ;
223227 }
224228
@@ -234,9 +238,6 @@ private void handleDelegation(String userInput, TransportProtocol transportProto
234238 agentEmitter .complete ();
235239 } catch (A2AClientException e ) {
236240 agentEmitter .fail (new InternalError ("Failed to create client: " + e .getMessage ()));
237- } catch (InterruptedException e ) {
238- Thread .currentThread ().interrupt ();
239- agentEmitter .fail (new InternalError ("Interrupted while waiting for response" ));
240241 }
241242 }
242243
0 commit comments