Skip to content

Commit 06e40b2

Browse files
authored
filesystem: Fix crash when watch callback returns error or throws (#42554)
## Description Today, when a filesystem watch callback returns a non-OK status or throws an exception, the error gets propagated to `FileEventImpl` which uses `THROW_IF_NOT_OK`. Since there's no exception handler in the `libevent` loop, this causes `std::terminate` to be called, which crashes Envoy. **Stack Trace:** ``` Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.119][234999][warning][misc] [source/common/protobuf/message_validator_impl.cc:23] Deprecated field: type envoy.config.core.v3.HeaderValueOption Using deprecated option 'envoy.config.core.v3.HeaderValueOption.append' from file base.proto. This configuration will be removed from Envoy soon. Please see https://www.envoyproxy.io/docs/envoy/latest/version_history/version_history for details. If continued use of this field is absolutely necessary, see https://www.envoyproxy.io/docs/envoy/latest/configuration/operations/runtime#using-runtime-overrides-for-deprecated-features for how to apply a temporary and highly discouraged override. Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.120][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '0_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.123][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '1_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.126][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '2_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.127][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '3_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.128][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '4_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.130][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '5_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.132][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener '6_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.134][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener 'mtls_untrusted_regional_transparent_tunnel_listener' Dec 11 00:11:26 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:26.135][234999][info][upstream] [source/common/listener_manager/lds_api.cc:109] lds: add/update listener 'mtls_app_trusted_regional_transparent_tunnel_listener' Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.097][234999][critical][main] [source/exe/terminate_handler.cc:36] std::terminate called! Uncaught unknown exception, see trace. Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.097][234999][critical][backtrace] [./source/server/backtrace.h:113] Backtrace (use tools/stack_decode.py to get line numbers): Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.097][234999][critical][backtrace] [./source/server/backtrace.h:114] Envoy version: 5eaabe0bbaad4612cb85473cd151039d8f1a2760/1.34.2-dev/Clean/RELEASE/BoringSSL Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.097][234999][critical][backtrace] [./source/server/backtrace.h:116] Address mapping: 558d8afcc000-558d8ee2f000 /usr/local/bin/envoy Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.100][234999][critical][backtrace] [./source/server/backtrace.h:123] #0: [0x558d8da5784f] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.102][234999][critical][backtrace] [./source/server/backtrace.h:123] #1: [0x558d8edd8673] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.104][234999][critical][backtrace] [./source/server/backtrace.h:123] #2: [0x558d8e3b120b] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.106][234999][critical][backtrace] [./source/server/backtrace.h:121] #3: Envoy::Filesystem::WatcherImpl::onInotifyEvent() [0x558d8e3990c3] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.108][234999][critical][backtrace] [./source/server/backtrace.h:123] #4: [0x558d8e3998d2] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.109][234999][critical][backtrace] [./source/server/backtrace.h:123] #5: [0x558d8e393de6] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.111][234999][critical][backtrace] [./source/server/backtrace.h:121] #6: Envoy::Event::FileEventImpl::mergeInjectedEventsAndRunCb() [0x558d8e394eb5] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.113][234999][critical][backtrace] [./source/server/backtrace.h:123] #7: [0x558d8e710823] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.115][234999][critical][backtrace] [./source/server/backtrace.h:121] #8: event_base_loop [0x558d8e70d4a1] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.117][234999][critical][backtrace] [./source/server/backtrace.h:121] #9: Envoy::Server::InstanceBase::run() [0x558d8daa2b99] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.119][234999][critical][backtrace] [./source/server/backtrace.h:121] #10: Envoy::MainCommonBase::run() [0x558d8da4327a] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.121][234999][critical][backtrace] [./source/server/backtrace.h:121] #11: Envoy::MainCommon::main() [0x558d8da44234] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.123][234999][critical][backtrace] [./source/server/backtrace.h:121] #12: main [0x558d8afcc11c] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.123][234999][critical][backtrace] [./source/server/backtrace.h:123] #13: [0x7f1d54073efb] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.123][234999][critical][backtrace] [./source/server/backtrace.h:121] #14: __libc_start_main [0x7f1d54073fbb] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:121] #15: _start [0x558d8afcc02e] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:129] Caught Aborted, suspect faulting address 0x395f7 Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:113] Backtrace (use tools/stack_decode.py to get line numbers): Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:114] Envoy version: 5eaabe0bbaad4612cb85473cd151039d8f1a2760/1.34.2-dev/Clean/RELEASE/BoringSSL Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:116] Address mapping: 558d8afcc000-558d8ee2f000 /usr/local/bin/envoy Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:123] #0: [0x7f1d54089c90] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:121] #1: gsignal [0x7f1d54089bde] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.124][234999][critical][backtrace] [./source/server/backtrace.h:121] #2: abort [0x7f1d54072832] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.126][234999][critical][backtrace] [./source/server/backtrace.h:123] #3: [0x558d8da5785c] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.128][234999][critical][backtrace] [./source/server/backtrace.h:123] #4: [0x558d8edd8673] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.129][234999][critical][backtrace] [./source/server/backtrace.h:123] #5: [0x558d8e3b120b] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.129][234999][critical][backtrace] [./source/server/backtrace.h:121] #6: Envoy::Filesystem::WatcherImpl::onInotifyEvent() [0x558d8e3990c3] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.131][234999][critical][backtrace] [./source/server/backtrace.h:123] #7: [0x558d8e3998d2] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.133][234999][critical][backtrace] [./source/server/backtrace.h:123] #8: [0x558d8e393de6] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.133][234999][critical][backtrace] [./source/server/backtrace.h:121] #9: Envoy::Event::FileEventImpl::mergeInjectedEventsAndRunCb() [0x558d8e394eb5] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:123] #10: [0x558d8e710823] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #11: event_base_loop [0x558d8e70d4a1] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #12: Envoy::Server::InstanceBase::run() [0x558d8daa2b99] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #13: Envoy::MainCommonBase::run() [0x558d8da4327a] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #14: Envoy::MainCommon::main() [0x558d8da44234] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #15: main [0x558d8afcc11c] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:123] #16: [0x7f1d54073efb] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #17: __libc_start_main [0x7f1d54073fbb] Dec 11 00:11:30 dbletE9433T node-envoy[234999]: [2025-12-11 00:11:30.135][234999][critical][backtrace] [./source/server/backtrace.h:121] #18: _start [0x558d8afcc02e] ``` In this change, we are making the `inotify` and `kqueue` watchers handle callback errors gracefully by catching any exceptions using `TRY_ASSERT_MAIN_THREAD`, logging errors instead of propagating them and always returning the `OkStatus` to the event loop. --- **Commit Message:** filesystem: Fix crash when watch callback returns error or throws **Additional Description:** Make `inotify` and `kqueue` watchers handle callback errors gracefully. **Risk Level:** Low **Testing:** CI **Docs Changes:** N/A **Release Notes:** N/A --------- Signed-off-by: Rohit Agrawal <rohit.agrawal@salesforce.com> Signed-off-by: Rohit Agrawal <rohit.agrawal@databricks.com>
1 parent ef2e3fb commit 06e40b2

File tree

7 files changed

+165
-9
lines changed

7 files changed

+165
-9
lines changed

source/common/filesystem/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,12 @@ envoy_cc_library(
120120
"//source/common/common:assert_lib",
121121
"//source/common/common:linked_object",
122122
"//source/common/common:minimal_logger_lib",
123+
"//source/common/common:thread_lib",
123124
"//source/common/common:utility_lib",
124125
"//source/common/network:default_socket_interface_lib",
125126
] + select({
126127
"//bazel:windows_x86_64": [
127128
"//source/common/api:os_sys_calls_lib",
128-
"//source/common/common:thread_lib",
129129
],
130130
"//conditions:default": [],
131131
}),

source/common/filesystem/inotify/watcher_impl.cc

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "source/common/common/assert.h"
1212
#include "source/common/common/fmt.h"
13+
#include "source/common/common/thread.h"
1314
#include "source/common/common/utility.h"
1415
#include "source/common/filesystem/watcher_impl.h"
1516

@@ -52,6 +53,28 @@ absl::Status WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnCh
5253
return absl::OkStatus();
5354
}
5455

56+
void WatcherImpl::callAndLogOnError(OnChangedCb& cb, uint32_t events, const std::string& file) {
57+
TRY_ASSERT_MAIN_THREAD {
58+
const absl::Status status = cb(events);
59+
if (!status.ok()) {
60+
// Use ENVOY_LOG_EVERY_POW_2 to avoid log spam if a callback keeps failing.
61+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' returned error: {}", file,
62+
status.message());
63+
}
64+
}
65+
END_TRY
66+
MULTI_CATCH(
67+
const std::exception& e,
68+
{
69+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw exception: {}", file,
70+
e.what());
71+
},
72+
{
73+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw unknown exception",
74+
file);
75+
});
76+
}
77+
5578
absl::Status WatcherImpl::onInotifyEvent() {
5679
while (true) {
5780
// The buffer needs to be suitably aligned to store the first inotify_event structure.
@@ -90,10 +113,10 @@ absl::Status WatcherImpl::onInotifyEvent() {
90113
if (watch.events_ & events) {
91114
if (watch.file_ == file) {
92115
ENVOY_LOG(debug, "matched callback: file: {}", file);
93-
RETURN_IF_NOT_OK(watch.cb_(events));
116+
callAndLogOnError(watch.cb_, events, file);
94117
} else if (watch.file_.empty()) {
95118
ENVOY_LOG(debug, "matched callback: directory: {}", file);
96-
RETURN_IF_NOT_OK(watch.cb_(events));
119+
callAndLogOnError(watch.cb_, events, file);
97120
}
98121
}
99122
}

source/common/filesystem/inotify/watcher_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class WatcherImpl : public Watcher, Logger::Loggable<Logger::Id::file> {
4040
};
4141

4242
absl::Status onInotifyEvent();
43+
void callAndLogOnError(OnChangedCb& cb, uint32_t events, const std::string& file);
4344

4445
Filesystem::Instance& file_system_;
4546
int inotify_fd_;

source/common/filesystem/kqueue/watcher_impl.cc

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "source/common/common/assert.h"
1010
#include "source/common/common/fmt.h"
11+
#include "source/common/common/thread.h"
1112
#include "source/common/common/utility.h"
1213
#include "source/common/filesystem/watcher_impl.h"
1314

@@ -116,20 +117,31 @@ absl::Status WatcherImpl::onKqueueEvent() {
116117

117118
absl::StatusOr<PathSplitResult> pathname_or_error =
118119
file_system_.splitPathFromFilename(file->file_);
119-
RETURN_IF_NOT_OK_REF(pathname_or_error.status());
120+
if (!pathname_or_error.ok()) {
121+
// Path split failure is permanent and we can't recover.
122+
// We remove the broken watch to avoid repeated failures.
123+
ENVOY_LOG(warn, "Failed to split path '{}', removing watch: {}", file->file_,
124+
pathname_or_error.status().message());
125+
removeWatch(file);
126+
continue;
127+
}
120128
PathSplitResult& pathname = pathname_or_error.value();
121129

122130
if (file->watching_dir_) {
123131
if (event.fflags & NOTE_DELETE) {
124-
// directory was deleted
132+
// Directory was deleted.
125133
removeWatch(file);
126134
return absl::OkStatus();
127135
}
128136

129137
if (event.fflags & NOTE_WRITE) {
130-
// directory was written -- check if the file we're actually watching appeared
138+
// Directory was written -- check if the file we're actually watching appeared.
131139
auto file_or_error = addWatch(file->file_, file->events_, file->callback_, true);
132-
RETURN_IF_NOT_OK_REF(file_or_error.status());
140+
if (!file_or_error.ok()) {
141+
ENVOY_LOG_EVERY_POW_2(warn, "Failed to re-add watch for '{}': {}", file->file_,
142+
file_or_error.status().message());
143+
continue;
144+
}
133145
FileWatchPtr new_file = file_or_error.value();
134146
if (new_file != nullptr) {
135147
removeWatch(file);
@@ -150,7 +162,11 @@ absl::Status WatcherImpl::onKqueueEvent() {
150162
removeWatch(file);
151163

152164
auto file_or_error = addWatch(file->file_, file->events_, file->callback_, true);
153-
RETURN_IF_NOT_OK_REF(file_or_error.status());
165+
if (!file_or_error.ok()) {
166+
ENVOY_LOG_EVERY_POW_2(warn, "Failed to re-add watch for '{}': {}", file->file_,
167+
file_or_error.status().message());
168+
continue;
169+
}
154170
FileWatchPtr new_file = file_or_error.value();
155171
if (new_file == nullptr) {
156172
return absl::OkStatus();
@@ -173,11 +189,34 @@ absl::Status WatcherImpl::onKqueueEvent() {
173189

174190
if (events & file->events_) {
175191
ENVOY_LOG(debug, "matched callback: file: {}", file->file_);
176-
RETURN_IF_NOT_OK(file->callback_(events));
192+
callAndLogOnError(file->callback_, events, file->file_);
177193
}
178194
}
179195
return absl::OkStatus();
180196
}
181197

198+
void WatcherImpl::callAndLogOnError(Watcher::OnChangedCb& cb, uint32_t events,
199+
const std::string& file) {
200+
TRY_ASSERT_MAIN_THREAD {
201+
const absl::Status status = cb(events);
202+
if (!status.ok()) {
203+
// Use ENVOY_LOG_EVERY_POW_2 to avoid log spam if a callback keeps failing.
204+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' returned error: {}", file,
205+
status.message());
206+
}
207+
}
208+
END_TRY
209+
MULTI_CATCH(
210+
const std::exception& e,
211+
{
212+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw exception: {}", file,
213+
e.what());
214+
},
215+
{
216+
ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw unknown exception",
217+
file);
218+
});
219+
}
220+
182221
} // namespace Filesystem
183222
} // namespace Envoy

source/common/filesystem/kqueue/watcher_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class WatcherImpl : public Watcher, Logger::Loggable<Logger::Id::file> {
4646
absl::StatusOr<FileWatchPtr> addWatch(absl::string_view path, uint32_t events,
4747
Watcher::OnChangedCb cb, bool pathMustExist);
4848
void removeWatch(FileWatchPtr& watch);
49+
void callAndLogOnError(OnChangedCb& cb, uint32_t events, const std::string& file);
4950

5051
Filesystem::Instance& file_system_;
5152
int queue_;

test/common/filesystem/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ envoy_cc_test(
3434
srcs = ["watcher_impl_test.cc"],
3535
rbe_pool = "6gig",
3636
deps = [
37+
"//envoy/common:exception_lib",
3738
"//source/common/common:assert_lib",
3839
"//source/common/event:dispatcher_includes",
3940
"//source/common/event:dispatcher_lib",
4041
"//source/common/filesystem:watcher_lib",
4142
"//test/test_common:environment_lib",
43+
"//test/test_common:logging_lib",
4244
],
4345
)

test/common/filesystem/watcher_impl_test.cc

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#include <cstdint>
22
#include <fstream>
33

4+
#include "envoy/common/exception.h"
5+
46
#include "source/common/common/assert.h"
57
#include "source/common/event/dispatcher_impl.h"
68
#include "source/common/filesystem/watcher_impl.h"
79

810
#include "test/test_common/environment.h"
11+
#include "test/test_common/logging.h"
912
#include "test/test_common/utility.h"
1013

1114
#include "gmock/gmock.h"
@@ -214,5 +217,92 @@ TEST_F(WatcherImplTest, SymlinkAtomicRename) {
214217
}
215218
#endif
216219

220+
// Test that callback returning error status is logged and doesn't crash.
221+
TEST_F(WatcherImplTest, CallbackReturnsErrorStatus) {
222+
Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher();
223+
224+
TestEnvironment::createPath(TestEnvironment::temporaryPath("envoy_test"));
225+
std::ofstream file(TestEnvironment::temporaryPath("envoy_test/watcher_target"));
226+
227+
WatchCallback callback;
228+
EXPECT_CALL(callback, called(Watcher::Events::Modified));
229+
ASSERT_TRUE(watcher
230+
->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_target"),
231+
Watcher::Events::Modified,
232+
[&](uint32_t events) {
233+
callback.called(events);
234+
dispatcher_->exit();
235+
// Return an error status - should be logged but not crash.
236+
return absl::InternalError("simulated callback error");
237+
})
238+
.ok());
239+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
240+
241+
EXPECT_LOG_CONTAINS("warn", "Filesystem watch callback for", file << "text" << std::flush;
242+
file.close(); dispatcher_->run(Event::Dispatcher::RunType::Block););
243+
}
244+
245+
// Test that callback throwing exception is caught and logged.
246+
TEST_F(WatcherImplTest, CallbackThrowsException) {
247+
Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher();
248+
249+
TestEnvironment::createPath(TestEnvironment::temporaryPath("envoy_test"));
250+
std::ofstream file(TestEnvironment::temporaryPath("envoy_test/watcher_target"));
251+
252+
WatchCallback callback;
253+
EXPECT_CALL(callback, called(Watcher::Events::Modified));
254+
ASSERT_TRUE(watcher
255+
->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_target"),
256+
Watcher::Events::Modified,
257+
[&](uint32_t events) -> absl::Status {
258+
callback.called(events);
259+
dispatcher_->exit();
260+
// Throw an exception - should be caught and logged.
261+
throw EnvoyException("simulated callback exception");
262+
})
263+
.ok());
264+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
265+
266+
EXPECT_LOG_CONTAINS("warn", "threw exception", file << "text" << std::flush; file.close();
267+
dispatcher_->run(Event::Dispatcher::RunType::Block););
268+
}
269+
270+
// Test that multiple callbacks can fail without affecting each other.
271+
TEST_F(WatcherImplTest, MultipleCallbacksWithErrors) {
272+
Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher();
273+
274+
TestEnvironment::createPath(TestEnvironment::temporaryPath("envoy_test"));
275+
std::ofstream file(TestEnvironment::temporaryPath("envoy_test/watcher_target"));
276+
277+
int callback_count = 0;
278+
ASSERT_TRUE(watcher
279+
->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_target"),
280+
Watcher::Events::Modified,
281+
[&](uint32_t) {
282+
callback_count++;
283+
if (callback_count >= 2) {
284+
dispatcher_->exit();
285+
}
286+
// First callback returns error, second returns OK.
287+
if (callback_count == 1) {
288+
return absl::InternalError("first callback error");
289+
}
290+
return absl::OkStatus();
291+
})
292+
.ok());
293+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
294+
295+
// Trigger first modification. The first callback returns error, but watcher continues.
296+
file << "text1" << std::flush;
297+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
298+
299+
// Trigger second modification. It should still work.
300+
file << "text2" << std::flush;
301+
file.close();
302+
dispatcher_->run(Event::Dispatcher::RunType::Block);
303+
304+
EXPECT_EQ(2, callback_count);
305+
}
306+
217307
} // namespace Filesystem
218308
} // namespace Envoy

0 commit comments

Comments
 (0)