Skip to content

Commit 9b0fe25

Browse files
Copilotbrunoborges
andcommitted
Implement EventErrorPolicy enum instead of breaking EventErrorHandler change
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
1 parent 1ee3b61 commit 9b0fe25

4 files changed

Lines changed: 380 additions & 54 deletions

File tree

src/main/java/com/github/copilot/sdk/CopilotSession.java

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public final class CopilotSession implements AutoCloseable {
9696
private final AtomicReference<UserInputHandler> userInputHandler = new AtomicReference<>();
9797
private final AtomicReference<SessionHooks> hooksHandler = new AtomicReference<>();
9898
private volatile EventErrorHandler eventErrorHandler;
99+
private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.CONTINUE;
99100

100101
/**
101102
* Creates a new session with the given ID and RPC client.
@@ -158,12 +159,18 @@ public String getWorkspacePath() {
158159
* <p>
159160
* When an event handler registered via {@link #on(Consumer)} or
160161
* {@link #on(Class, Consumer)} throws an exception during event dispatch, the
161-
* error handler is invoked instead of the default behavior (logging at
162-
* {@link Level#SEVERE}).
162+
* error handler is invoked with the event and exception. When no error handler
163+
* is set, exceptions are silently consumed.
163164
*
164165
* <p>
165-
* If the error handler itself throws an exception, that exception is silently
166-
* caught and logged to prevent cascading failures.
166+
* Whether dispatch continues or stops after an error is controlled by the
167+
* {@link EventErrorPolicy} set via {@link #setEventErrorPolicy}. The error
168+
* handler is always invoked regardless of the policy.
169+
*
170+
* <p>
171+
* If the error handler itself throws an exception, that exception is caught and
172+
* logged at {@link Level#SEVERE}, and dispatch is stopped regardless of the
173+
* configured policy.
167174
*
168175
* <p>
169176
* <b>Example:</b>
@@ -176,15 +183,53 @@ public String getWorkspacePath() {
176183
* }</pre>
177184
*
178185
* @param handler
179-
* the error handler, or {@code null} to restore default logging
186+
* the error handler, or {@code null} to restore default silent
180187
* behavior
181188
* @see EventErrorHandler
189+
* @see #setEventErrorPolicy(EventErrorPolicy)
182190
* @since 1.0.8
183191
*/
184192
public void setEventErrorHandler(EventErrorHandler handler) {
185193
this.eventErrorHandler = handler;
186194
}
187195

196+
/**
197+
* Sets the error propagation policy for event dispatch.
198+
* <p>
199+
* Controls whether remaining event listeners continue to execute when a
200+
* preceding listener throws an exception.
201+
*
202+
* <ul>
203+
* <li>{@link EventErrorPolicy#CONTINUE} (default) — dispatch to all remaining
204+
* listeners regardless of errors</li>
205+
* <li>{@link EventErrorPolicy#STOP} — stop dispatching after the first
206+
* error</li>
207+
* </ul>
208+
*
209+
* <p>
210+
* The configured {@link EventErrorHandler} (if any) is always invoked
211+
* regardless of the policy.
212+
*
213+
* <p>
214+
* <b>Example:</b>
215+
*
216+
* <pre>{@code
217+
* // Opt-in to short-circuit on first error
218+
* session.setEventErrorPolicy(EventErrorPolicy.STOP);
219+
* session.setEventErrorHandler(
220+
* (event, ex) -> logger.error("Handler failed, stopping dispatch: {}", ex.getMessage(), ex));
221+
* }</pre>
222+
*
223+
* @param policy
224+
* the error policy (default is {@link EventErrorPolicy#CONTINUE})
225+
* @see EventErrorPolicy
226+
* @see #setEventErrorHandler(EventErrorHandler)
227+
* @since 1.0.8
228+
*/
229+
public void setEventErrorPolicy(EventErrorPolicy policy) {
230+
this.eventErrorPolicy = policy;
231+
}
232+
188233
/**
189234
* Sends a simple text message to the Copilot session.
190235
* <p>
@@ -330,8 +375,10 @@ public CompletableFuture<AssistantMessageEvent> sendAndWait(MessageOptions optio
330375
* instead.
331376
*
332377
* <p>
333-
* <b>Exception isolation:</b> If a handler throws an exception, the error is
334-
* logged and remaining handlers still execute.
378+
* <b>Exception handling:</b> If a handler throws an exception, the error is
379+
* routed to the configured {@link EventErrorHandler} (if set). Whether
380+
* remaining handlers execute depends on the configured
381+
* {@link EventErrorPolicy}.
335382
*
336383
* <p>
337384
* <b>Example:</b>
@@ -347,6 +394,7 @@ public CompletableFuture<AssistantMessageEvent> sendAndWait(MessageOptions optio
347394
* @return a Closeable that, when closed, unsubscribes the handler
348395
* @see #on(Class, Consumer)
349396
* @see AbstractSessionEvent
397+
* @see #setEventErrorPolicy(EventErrorPolicy)
350398
*/
351399
public Closeable on(Consumer<AbstractSessionEvent> handler) {
352400
eventHandlers.add(handler);
@@ -361,8 +409,10 @@ public Closeable on(Consumer<AbstractSessionEvent> handler) {
361409
* matching the specified type.
362410
*
363411
* <p>
364-
* <b>Exception isolation:</b> If a handler throws an exception, the error is
365-
* logged and remaining handlers still execute.
412+
* <b>Exception handling:</b> If a handler throws an exception, the error is
413+
* routed to the configured {@link EventErrorHandler} (if set). Whether
414+
* remaining handlers execute depends on the configured
415+
* {@link EventErrorPolicy}.
366416
*
367417
* <p>
368418
* <b>Example Usage</b>
@@ -409,16 +459,23 @@ public <T extends AbstractSessionEvent> Closeable on(Class<T> eventType, Consume
409459
* Dispatches an event to all registered handlers.
410460
* <p>
411461
* This is called internally when events are received from the server. Each
412-
* handler is invoked in its own try/catch block so that an exception thrown by
413-
* one handler does not prevent subsequent handlers from executing.
462+
* handler is invoked in its own try/catch block. Whether dispatch continues
463+
* after a handler error depends on the configured {@link EventErrorPolicy}:
464+
* <ul>
465+
* <li>{@link EventErrorPolicy#CONTINUE} (default) — remaining handlers still
466+
* execute</li>
467+
* <li>{@link EventErrorPolicy#STOP} — dispatch stops after the first error</li>
468+
* </ul>
414469
* <p>
415-
* If a custom {@link EventErrorHandler} has been set via
416-
* {@link #setEventErrorHandler(EventErrorHandler)}, it is called with the event
417-
* and exception. Otherwise, exceptions are logged at {@link Level#SEVERE}.
470+
* The configured {@link EventErrorHandler} is always invoked (if set),
471+
* regardless of the policy. When no error handler is set, exceptions are
472+
* silently consumed. If the error handler itself throws, dispatch stops
473+
* regardless of policy and the error is logged at {@link Level#SEVERE}.
418474
*
419475
* @param event
420476
* the event to dispatch
421477
* @see #setEventErrorHandler(EventErrorHandler)
478+
* @see #setEventErrorPolicy(EventErrorPolicy)
422479
*/
423480
void dispatchEvent(AbstractSessionEvent event) {
424481
for (Consumer<AbstractSessionEvent> handler : eventHandlers) {
@@ -431,9 +488,11 @@ void dispatchEvent(AbstractSessionEvent event) {
431488
errorHandler.handleError(event, e);
432489
} catch (Exception errorHandlerException) {
433490
LOG.log(Level.SEVERE, "Error in event error handler", errorHandlerException);
491+
break; // error handler itself failed — stop regardless of policy
434492
}
435-
} else {
436-
LOG.log(Level.SEVERE, "Error in event handler", e);
493+
}
494+
if (eventErrorPolicy == EventErrorPolicy.STOP) {
495+
break;
437496
}
438497
}
439498
}

src/main/java/com/github/copilot/sdk/EventErrorHandler.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
* being dispatched and the exception that was thrown.
1717
*
1818
* <p>
19-
* The default behavior logs errors at {@link java.util.logging.Level#SEVERE}.
20-
* You can override this to integrate with your own logging, metrics, or
21-
* error-reporting systems:
19+
* When no error handler is set, exceptions are silently consumed. Applications
20+
* should set an error handler to log, track, or respond to handler failures:
2221
*
2322
* <pre>{@code
2423
* session.setEventErrorHandler((event, exception) -> {
@@ -28,10 +27,18 @@
2827
* }</pre>
2928
*
3029
* <p>
31-
* If the error handler itself throws an exception, that exception is silently
32-
* caught and logged to prevent cascading failures.
30+
* Whether dispatch continues or stops after an error is controlled by the
31+
* {@link EventErrorPolicy} set via
32+
* {@link CopilotSession#setEventErrorPolicy(EventErrorPolicy)}. The error
33+
* handler is always invoked regardless of the policy.
34+
*
35+
* <p>
36+
* If the error handler itself throws an exception, that exception is caught and
37+
* logged at {@link java.util.logging.Level#SEVERE}, and dispatch is stopped
38+
* regardless of the configured policy.
3339
*
3440
* @see CopilotSession#setEventErrorHandler(EventErrorHandler)
41+
* @see EventErrorPolicy
3542
* @since 1.0.8
3643
*/
3744
@FunctionalInterface
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk;
6+
7+
/**
8+
* Controls how event dispatch behaves when an event handler throws an
9+
* exception.
10+
* <p>
11+
* This policy is set via
12+
* {@link CopilotSession#setEventErrorPolicy(EventErrorPolicy)} and determines
13+
* whether remaining event listeners continue to execute after a preceding
14+
* listener throws an exception.
15+
*
16+
* <p>
17+
* The configured {@link EventErrorHandler} (if any) is always invoked
18+
* regardless of the policy — the policy only controls whether dispatch
19+
* continues after the error handler has been called.
20+
*
21+
* <p>
22+
* <b>Example:</b>
23+
*
24+
* <pre>{@code
25+
* // Default: continue dispatching despite errors
26+
* session.setEventErrorPolicy(EventErrorPolicy.CONTINUE);
27+
*
28+
* // Opt-in to short-circuit on first error
29+
* session.setEventErrorPolicy(EventErrorPolicy.STOP);
30+
* }</pre>
31+
*
32+
* @see CopilotSession#setEventErrorPolicy(EventErrorPolicy)
33+
* @see EventErrorHandler
34+
* @since 1.0.8
35+
*/
36+
public enum EventErrorPolicy {
37+
38+
/**
39+
* Stop dispatching on first listener error.
40+
* <p>
41+
* When a handler throws an exception, no further handlers are invoked. The
42+
* configured {@link EventErrorHandler} is still called before dispatch stops.
43+
*/
44+
STOP,
45+
46+
/**
47+
* Continue dispatching to remaining listeners despite errors (default).
48+
* <p>
49+
* When a handler throws an exception, remaining handlers still execute. The
50+
* configured {@link EventErrorHandler} is called for each error.
51+
*/
52+
CONTINUE
53+
}

0 commit comments

Comments
 (0)