@@ -108,13 +108,16 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
108
108
109
109
private static final String TYPE = "type" ;
110
110
private static final String REASON = "reason" ;
111
+ private static final String TIMED_OUT = "timed_out" ;
111
112
private static final String CAUSED_BY = "caused_by" ;
112
113
private static final ParseField SUPPRESSED = new ParseField ("suppressed" );
113
114
public static final String STACK_TRACE = "stack_trace" ;
114
115
private static final String HEADER = "header" ;
115
116
private static final String ERROR = "error" ;
116
117
private static final String ROOT_CAUSE = "root_cause" ;
117
118
119
+ static final String TIMED_OUT_HEADER = "X-Timed-Out" ;
120
+
118
121
private static final Map <Integer , CheckedFunction <StreamInput , ? extends ElasticsearchException , IOException >> ID_TO_SUPPLIER ;
119
122
private static final Map <Class <? extends ElasticsearchException >, ElasticsearchExceptionHandle > CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE ;
120
123
private final Map <String , List <String >> metadata = new HashMap <>();
@@ -123,8 +126,10 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
123
126
/**
124
127
* Construct a <code>ElasticsearchException</code> with the specified cause exception.
125
128
*/
129
+ @ SuppressWarnings ("this-escape" )
126
130
public ElasticsearchException (Throwable cause ) {
127
131
super (cause );
132
+ maybePutTimeoutHeader ();
128
133
}
129
134
130
135
/**
@@ -136,8 +141,10 @@ public ElasticsearchException(Throwable cause) {
136
141
* @param msg the detail message
137
142
* @param args the arguments for the message
138
143
*/
144
+ @ SuppressWarnings ("this-escape" )
139
145
public ElasticsearchException (String msg , Object ... args ) {
140
146
super (LoggerMessageFormat .format (msg , args ));
147
+ maybePutTimeoutHeader ();
141
148
}
142
149
143
150
/**
@@ -151,8 +158,10 @@ public ElasticsearchException(String msg, Object... args) {
151
158
* @param cause the nested exception
152
159
* @param args the arguments for the message
153
160
*/
161
+ @ SuppressWarnings ("this-escape" )
154
162
public ElasticsearchException (String msg , Throwable cause , Object ... args ) {
155
163
super (LoggerMessageFormat .format (msg , args ), cause );
164
+ maybePutTimeoutHeader ();
156
165
}
157
166
158
167
@ SuppressWarnings ("this-escape" )
@@ -163,6 +172,13 @@ public ElasticsearchException(StreamInput in) throws IOException {
163
172
metadata .putAll (in .readMapOfLists (StreamInput ::readString ));
164
173
}
165
174
175
+ private void maybePutTimeoutHeader () {
176
+ if (isTimeout ()) {
177
+ // see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.1.9 for booleans in structured headers
178
+ headers .put (TIMED_OUT_HEADER , List .of ("?1" ));
179
+ }
180
+ }
181
+
166
182
/**
167
183
* Adds a new piece of metadata with the given key.
168
184
* If the provided key is already present, the corresponding metadata will be replaced
@@ -253,6 +269,13 @@ public RestStatus status() {
253
269
}
254
270
}
255
271
272
+ /**
273
+ * Returns whether this exception represents a timeout.
274
+ */
275
+ public boolean isTimeout () {
276
+ return false ;
277
+ }
278
+
256
279
/**
257
280
* Unwraps the actual cause from the exception for cases when the exception is a
258
281
* {@link ElasticsearchWrapperException}.
@@ -386,6 +409,15 @@ protected static void innerToXContent(
386
409
builder .field (TYPE , type );
387
410
builder .field (REASON , message );
388
411
412
+ boolean timedOut = false ;
413
+ if (throwable instanceof ElasticsearchException exception ) {
414
+ // TODO: we could walk the exception chain to see if _any_ causes are timeouts?
415
+ timedOut = exception .isTimeout ();
416
+ }
417
+ if (timedOut ) {
418
+ builder .field (TIMED_OUT , timedOut );
419
+ }
420
+
389
421
for (Map .Entry <String , List <String >> entry : metadata .entrySet ()) {
390
422
headerToXContent (builder , entry .getKey ().substring ("es." .length ()), entry .getValue ());
391
423
}
0 commit comments