Skip to content

Commit d8826b4

Browse files
committed
[BUG] End the Elasticsearch exporter's wait on a read or write error
ReadError and WriteError logged a line and recorded nothing, so a synchronous Export() blocked in waitForResponse() with no completion to wake it and no deadline of its own. Both are terminal for the session, so record a failure on each. recordCompletion() returns whether this call is the one that decided the outcome. A read or write error can arrive after a response has already succeeded, and the log line then describes a failure the caller was never told about, so only the writer that won reports. The accepted body the fixtures use now carries a per item status. Elasticsearch always reports one for a bulk operation, so the fixture was not a response the server could actually send. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
1 parent 768d04d commit d8826b4

4 files changed

Lines changed: 368 additions & 24 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Increment the:
3434
deprecated C headers (`stdint.h`, `stddef.h`, `stdlib.h`, `string.h`,
3535
`stdio.h`, `ctype.h`, `limits.h`, `assert.h`) with their C++ equivalents
3636
([#4349](https://github.com/open-telemetry/opentelemetry-cpp/pull/4349))
37+
* [BUG] End the Elasticsearch exporter's wait on a read or write error
38+
[#4331](https://github.com/open-telemetry/opentelemetry-cpp/pull/4331)
3739

3840
* [CONFIGURATION] Add SDK component builder interfaces to the registry
3941
[#4358](https://github.com/open-telemetry/opentelemetry-cpp/issues/4358)

‎exporters/elasticsearch/CMakeLists.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,8 @@ if(BUILD_TESTING)
5555
TARGET es_log_record_exporter_test
5656
TEST_PREFIX exporter.
5757
TEST_LIST es_log_record_exporter_test)
58+
59+
# These cases exist to catch a wait that never returns. Without a per test
60+
# bound a regression stalls the job instead of failing it.
61+
set_tests_properties(${es_log_record_exporter_test} PROPERTIES TIMEOUT 30)
5862
endif() # BUILD_TESTING

‎exporters/elasticsearch/src/es_log_record_exporter.cc‎

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ class ResponseHandler : public http_client::EventHandler
110110

111111
/**
112112
* A method the user calls to block their thread until the request has either produced a
113-
* response or failed. The longest duration is the timeout of the request, set by
114-
* SetTimeoutMs(), which arrives here as a TimedOut session event.
113+
* response or failed. It has no deadline of its own and relies on the HTTP client reporting
114+
* one of the terminal session states.
115115
*/
116116
bool waitForResponse()
117117
{
@@ -135,12 +135,18 @@ class ResponseHandler : public http_client::EventHandler
135135
// Callback method when an http event occurs
136136
void OnEvent(http_client::SessionState state, nostd::string_view /* reason */) noexcept override
137137
{
138-
// If any failure event occurs, release the condition variable to unblock main thread
138+
// If any failure event occurs, release the condition variable to unblock main thread.
139+
//
140+
// A failure is reported only by the event that decided the outcome. Recording is first writer
141+
// wins, so any of these can arrive after a response has already succeeded, and an error line
142+
// there would describe a failure the caller was never told about.
139143
switch (state)
140144
{
141145
case http_client::SessionState::CreateFailed:
142-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to create session");
143-
recordCompletion(CompletionState::Failure);
146+
if (recordCompletion(CompletionState::Failure))
147+
{
148+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to create session");
149+
}
144150
break;
145151
case http_client::SessionState::Created:
146152
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Session created");
@@ -155,8 +161,10 @@ class ResponseHandler : public http_client::EventHandler
155161
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Connecting to peer");
156162
break;
157163
case http_client::SessionState::ConnectFailed:
158-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to connect to peer");
159-
recordCompletion(CompletionState::Failure);
164+
if (recordCompletion(CompletionState::Failure))
165+
{
166+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to connect to peer");
167+
}
160168
break;
161169
case http_client::SessionState::Connected:
162170
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Connected to peer");
@@ -165,33 +173,49 @@ class ResponseHandler : public http_client::EventHandler
165173
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Sending request");
166174
break;
167175
case http_client::SessionState::SendFailed:
168-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to send request");
169-
recordCompletion(CompletionState::Failure);
176+
if (recordCompletion(CompletionState::Failure))
177+
{
178+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed to send request");
179+
}
170180
break;
171181
case http_client::SessionState::Response:
172182
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Received response");
173183
break;
174184
case http_client::SessionState::SSLHandshakeFailed:
175-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed SSL Handshake");
176-
recordCompletion(CompletionState::Failure);
185+
if (recordCompletion(CompletionState::Failure))
186+
{
187+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Failed SSL Handshake");
188+
}
177189
break;
178190
case http_client::SessionState::TimedOut:
179-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Request timed out");
180-
recordCompletion(CompletionState::Failure);
191+
if (recordCompletion(CompletionState::Failure))
192+
{
193+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Request timed out");
194+
}
181195
break;
182196
case http_client::SessionState::NetworkError:
183-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Network error");
184-
recordCompletion(CompletionState::Failure);
197+
if (recordCompletion(CompletionState::Failure))
198+
{
199+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Network error");
200+
}
185201
break;
186202
case http_client::SessionState::ReadError:
187-
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Read error");
203+
if (recordCompletion(CompletionState::Failure))
204+
{
205+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Read error");
206+
}
188207
break;
189208
case http_client::SessionState::WriteError:
190-
OTEL_INTERNAL_LOG_DEBUG("[ES Log Exporter] Write error");
209+
if (recordCompletion(CompletionState::Failure))
210+
{
211+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Write error");
212+
}
191213
break;
192214
case http_client::SessionState::Cancelled:
193-
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] (manually) cancelled");
194-
recordCompletion(CompletionState::Failure);
215+
if (recordCompletion(CompletionState::Failure))
216+
{
217+
OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] (manually) cancelled");
218+
}
195219
break;
196220
}
197221
}
@@ -208,22 +232,27 @@ class ResponseHandler : public http_client::EventHandler
208232
* Record the outcome of the request, first writer wins, then release any waiter. Keeping the
209233
* first outcome means a session destroyed after a successful response does not overwrite it.
210234
*/
211-
void recordCompletion(CompletionState state)
235+
/// Returns whether this call is the one that decided the outcome.
236+
bool recordCompletion(CompletionState state)
212237
{
238+
bool recorded = false;
213239
{
214240
std::unique_lock<std::mutex> lk(mutex_);
215-
recordCompletionLocked(state);
241+
recorded = recordCompletionLocked(state);
216242
}
217243
cv_.notify_all();
244+
return recorded;
218245
}
219246

220247
/// As recordCompletion(), for callers that already hold mutex_ and notify themselves.
221-
void recordCompletionLocked(CompletionState state)
248+
bool recordCompletionLocked(CompletionState state)
222249
{
223-
if (completion_ == CompletionState::Pending)
250+
if (completion_ != CompletionState::Pending)
224251
{
225-
completion_ = state;
252+
return false;
226253
}
254+
completion_ = state;
255+
return true;
227256
}
228257

229258
// Define a condition variable and mutex

0 commit comments

Comments
 (0)