77
88package org .elasticsearch .xpack .esql .action .stream ;
99
10+ import org .elasticsearch .action .ActionListener ;
1011import org .elasticsearch .common .collect .Iterators ;
1112import org .elasticsearch .compute .data .Page ;
1213import org .elasticsearch .core .Nullable ;
1314import org .elasticsearch .core .Releasable ;
15+ import org .elasticsearch .logging .LogManager ;
16+ import org .elasticsearch .logging .Logger ;
1417import org .elasticsearch .rest .RestChannel ;
1518import org .elasticsearch .rest .RestRequest ;
1619import org .elasticsearch .rest .StreamingXContentResponse ;
3538 * TODO: Took header wouldn't be available on streaming
3639 */
3740public abstract class EsqlQueryResponseStream implements Releasable {
41+ private static final Logger LOGGER = LogManager .getLogger (EsqlQueryResponseStream .class );
3842
3943 public static EsqlQueryResponseStream forMediaType (RestChannel restChannel , RestRequest request ) {
4044 MediaType mediaType = EsqlMediaTypeParser .getResponseMediaType (request , XContentType .JSON );
@@ -53,7 +57,7 @@ public static EsqlQueryResponseStream forMediaType(RestChannel restChannel, Rest
5357 private final RestChannel restChannel ;
5458 protected final ToXContent .Params params ;
5559 /**
56- * Initialized on the first call to {@link #startResponse() } and used to write the response chunks.
60+ * Initialized on the first call to {@link #startResponse} and used to write the response chunks.
5761 */
5862 @ Nullable
5963 private StreamingXContentResponse streamingXContentResponse ;
@@ -79,7 +83,7 @@ protected EsqlQueryResponseStream(RestChannel restChannel, ToXContent.Params par
7983 */
8084 public final void startResponse (List <ColumnInfoImpl > columns ) throws IOException {
8185 assert streamingXContentResponse == null : "startResponse() called more than once" ;
82- assert finished == false : "sendPages() called after finishResponse() " ;
86+ assert finished == false : "sendPages() called on a finished stream " ;
8387
8488 streamingXContentResponse = new StreamingXContentResponse (restChannel , restChannel .request (), () -> {});
8589
@@ -93,7 +97,7 @@ public final void startResponse(List<ColumnInfoImpl> columns) throws IOException
9397
9498 public final void sendPages (Iterable <Page > pages ) {
9599 assert streamingXContentResponse != null : "sendPages() called before startResponse()" ;
96- assert finished == false : "sendPages() called after finishResponse() " ;
100+ assert finished == false : "sendPages() called on a finished stream " ;
97101
98102 if (initialStreamChunkSent ) {
99103 doSendPages (pages );
@@ -111,6 +115,36 @@ public final void finishResponse(EsqlQueryResponse response) {
111115 finished = true ;
112116 }
113117
118+ public final void handleException (Exception e ) {
119+ assert finished == false : "handleException() called on a finished stream" ;
120+
121+ // TODO: To be overridden by subclasses. This should append the error to the stream, if possible
122+ LOGGER .error ("Error while streaming response" , e );
123+
124+ finished = true ;
125+ }
126+
127+ // TODO: For error handling, check RestActionListener error listener
128+ // TODO: Also ensure that we check if the channel is closed at some points (Also see RestActionListener)
129+
130+ public final ActionListener <EsqlQueryResponse > completionListener () {
131+ return new ActionListener <>() {
132+ @ Override
133+ public void onResponse (EsqlQueryResponse esqlResponse ) {
134+ assert finished == false : "completionListener() called on a finished stream" ;
135+
136+ finishResponse (esqlResponse );
137+ }
138+
139+ @ Override
140+ public void onFailure (Exception e ) {
141+ assert finished == false : "onFailure() called on a finished stream" ;
142+
143+ handleException (e );
144+ }
145+ };
146+ }
147+
114148 /**
115149 * Returns true if the response can be streamed, false otherwise.
116150 * <p>
@@ -126,6 +160,8 @@ public final void finishResponse(EsqlQueryResponse response) {
126160
127161 protected abstract void doFinishResponse (EsqlQueryResponse response );
128162
163+ protected abstract void doHandleException (Exception e );
164+
129165 protected void doSendEverything (EsqlQueryResponse response ) {
130166 // TODO: Is this safe? Should this be abstract to ensure proper implementation? Add tests for both cases
131167 doStartResponse (response .columns ());
0 commit comments