Skip to content

Commit 9e2df02

Browse files
author
Sotiris Nanopoulos
authored
[Win32 Signals] Add term and ctrl-c signal handlers (envoyproxy#13954)
Part 1 of envoyproxy#13188. Adds support for ctrl+c and ctrl+break for Envoy on Windows. The implementation for this following: * On platform_impl we register a CtrlHandler which runs on a separate thread. * On signal_impl we register a read event reader. Thread (1) and (2) communicate via a socket pair and the event is handled on Windows the same way as it is handled on POSIX Signed-off-by: Sotiris Nanopoulos <[email protected]>
1 parent 9093131 commit 9e2df02

19 files changed

+309
-23
lines changed

.bazelrc

+1
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ build:windows --action_env=TMPDIR
298298
build:windows --define signal_trace=disabled
299299
build:windows --define hot_restart=disabled
300300
build:windows --define tcmalloc=disabled
301+
build:windows --define wasm=disabled
301302
build:windows --define manual_stamp=manual_stamp
302303
build:windows --cxxopt="/std:c++17"
303304

ci/run_clang_tidy.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ echo "Generating compilation database..."
3131
# Do not run clang-tidy against win32 impl
3232
# TODO(scw00): We should run clang-tidy against win32 impl once we have clang-cl support for Windows
3333
function exclude_win32_impl() {
34-
grep -v source/common/filesystem/win32/ | grep -v source/common/common/win32 | grep -v source/exe/win32 | grep -v source/common/api/win32
34+
grep -v source/common/filesystem/win32/ | grep -v source/common/common/win32 | grep -v source/exe/win32 | grep -v source/common/api/win32 | grep -v source/common/event/win32
3535
}
3636

3737
# Do not run clang-tidy against macOS impl

ci/windows_ci_steps.sh

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ BAZEL_BUILD_OPTIONS=(
4949
-c opt
5050
--show_task_finish
5151
--verbose_failures
52-
--define "wasm=disabled"
5352
"--test_output=errors"
5453
"${BAZEL_BUILD_EXTRA_OPTIONS[@]}"
5554
"${BAZEL_EXTRA_TEST_OPTIONS[@]}")

include/envoy/common/platform.h

+7
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef uint32_t mode_t;
6565

6666
typedef SOCKET os_fd_t;
6767
typedef HANDLE filesystem_os_id_t; // NOLINT(modernize-use-using)
68+
typedef DWORD signal_t; // NOLINT(modernize-use-using)
6869

6970
typedef unsigned int sa_family_t;
7071

@@ -151,6 +152,9 @@ struct msghdr {
151152
#define HANDLE_ERROR_PERM ERROR_ACCESS_DENIED
152153
#define HANDLE_ERROR_INVALID ERROR_INVALID_HANDLE
153154

155+
#define ENVOY_WIN32_SIGNAL_COUNT 1
156+
#define ENVOY_SIGTERM 0
157+
154158
namespace Platform {
155159
constexpr absl::string_view null_device_path{"NUL"};
156160
}
@@ -215,6 +219,7 @@ constexpr absl::string_view null_device_path{"NUL"};
215219

216220
typedef int os_fd_t;
217221
typedef int filesystem_os_id_t; // NOLINT(modernize-use-using)
222+
typedef int signal_t; // NOLINT(modernize-use-using)
218223

219224
#define INVALID_HANDLE -1
220225
#define INVALID_SOCKET -1
@@ -245,6 +250,8 @@ typedef int filesystem_os_id_t; // NOLINT(modernize-use-using)
245250
#define HANDLE_ERROR_PERM EACCES
246251
#define HANDLE_ERROR_INVALID EBADF
247252

253+
#define ENVOY_SIGTERM SIGTERM
254+
248255
namespace Platform {
249256
constexpr absl::string_view null_device_path{"/dev/null"};
250257
}

include/envoy/event/dispatcher.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ class Dispatcher {
205205
* @param cb supplies the callback to invoke when the signal fires.
206206
* @return SignalEventPtr a signal event that is owned by the caller.
207207
*/
208-
virtual SignalEventPtr listenForSignal(int signal_num, SignalCb cb) PURE;
208+
virtual SignalEventPtr listenForSignal(signal_t signal_num, SignalCb cb) PURE;
209209

210210
/**
211211
* Posts a functor to the dispatcher. This is safe cross thread. The functor runs in the context

source/common/event/BUILD

+47-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
load(
22
"//bazel:envoy_build_system.bzl",
33
"envoy_cc_library",
4+
"envoy_cc_platform_dep",
5+
"envoy_cc_posix_library",
6+
"envoy_cc_win32_library",
47
"envoy_package",
58
)
69

@@ -13,15 +16,24 @@ envoy_cc_library(
1316
srcs = [
1417
"dispatcher_impl.cc",
1518
"file_event_impl.cc",
16-
"signal_impl.cc",
17-
],
18-
hdrs = [
19-
"signal_impl.h",
2019
],
20+
hdrs = select({
21+
"//bazel:windows_x86_64": [
22+
"win32/signal_impl.h",
23+
],
24+
"//conditions:default": [
25+
"posix/signal_impl.h",
26+
],
27+
}),
28+
strip_include_prefix = select({
29+
"//bazel:windows_x86_64": "win32",
30+
"//conditions:default": "posix",
31+
}),
2132
deps = [
2233
":dispatcher_includes",
2334
":libevent_scheduler_lib",
2435
":real_time_system_lib",
36+
":signal_lib",
2537
"//include/envoy/common:scope_tracker_interface",
2638
"//include/envoy/common:time_interface",
2739
"//include/envoy/event:signal_interface",
@@ -39,6 +51,37 @@ envoy_cc_library(
3951
}),
4052
)
4153

54+
envoy_cc_library(
55+
name = "signal_lib",
56+
deps = envoy_cc_platform_dep("signal_impl_lib"),
57+
)
58+
59+
envoy_cc_posix_library(
60+
name = "signal_impl_lib",
61+
srcs = ["posix/signal_impl.cc"],
62+
hdrs = ["posix/signal_impl.h"],
63+
strip_include_prefix = "posix",
64+
deps = [
65+
":dispatcher_includes",
66+
"//include/envoy/event:signal_interface",
67+
"//source/common/common:thread_lib",
68+
],
69+
)
70+
71+
envoy_cc_win32_library(
72+
name = "signal_impl_lib",
73+
srcs = ["win32/signal_impl.cc"],
74+
hdrs = ["win32/signal_impl.h"],
75+
strip_include_prefix = "win32",
76+
deps = [
77+
":dispatcher_includes",
78+
"//include/envoy/event:signal_interface",
79+
"//source/common/api:os_sys_calls_lib",
80+
"//source/common/common:thread_lib",
81+
"//source/common/network:default_socket_interface_lib",
82+
],
83+
)
84+
4285
envoy_cc_library(
4386
name = "event_impl_base_lib",
4487
srcs = ["event_impl_base.cc"],

source/common/event/dispatcher_impl.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ void DispatcherImpl::deferredDelete(DeferredDeletablePtr&& to_delete) {
218218

219219
void DispatcherImpl::exit() { base_scheduler_.loopExit(); }
220220

221-
SignalEventPtr DispatcherImpl::listenForSignal(int signal_num, SignalCb cb) {
221+
SignalEventPtr DispatcherImpl::listenForSignal(signal_t signal_num, SignalCb cb) {
222222
ASSERT(isThreadSafe());
223223
return SignalEventPtr{new SignalEventImpl(*this, signal_num, cb)};
224224
}

source/common/event/dispatcher_impl.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>,
7171
Event::SchedulableCallbackPtr createSchedulableCallback(std::function<void()> cb) override;
7272
void deferredDelete(DeferredDeletablePtr&& to_delete) override;
7373
void exit() override;
74-
SignalEventPtr listenForSignal(int signal_num, SignalCb cb) override;
74+
SignalEventPtr listenForSignal(signal_t signal_num, SignalCb cb) override;
7575
void post(std::function<void()> callback) override;
7676
void run(RunType type) override;
7777
Buffer::WatermarkFactory& getWatermarkFactory() override { return *buffer_factory_; }

source/common/event/signal_impl.cc source/common/event/posix/signal_impl.cc

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
#include "common/event/signal_impl.h"
2-
31
#include "common/event/dispatcher_impl.h"
2+
#include "common/event/signal_impl.h"
43

54
#include "event2/event.h"
65

76
namespace Envoy {
87
namespace Event {
98

10-
SignalEventImpl::SignalEventImpl(DispatcherImpl& dispatcher, int signal_num, SignalCb cb)
9+
SignalEventImpl::SignalEventImpl(DispatcherImpl& dispatcher, signal_t signal_num, SignalCb cb)
1110
: cb_(cb) {
1211
evsignal_assign(
1312
&raw_event_, &dispatcher.base(), signal_num,

source/common/event/signal_impl.h source/common/event/posix/signal_impl.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ namespace Event {
1313
*/
1414
class SignalEventImpl : public SignalEvent, ImplBase {
1515
public:
16-
SignalEventImpl(DispatcherImpl& dispatcher, int signal_num, SignalCb cb);
16+
SignalEventImpl(DispatcherImpl& dispatcher, signal_t signal_num, SignalCb cb);
1717

1818
private:
1919
SignalCb cb_;
2020
};
21-
2221
} // namespace Event
2322
} // namespace Envoy
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "common/api/os_sys_calls_impl.h"
2+
#include "common/event/dispatcher_impl.h"
3+
#include "common/event/signal_impl.h"
4+
5+
#include "event2/event.h"
6+
7+
namespace Envoy {
8+
namespace Event {
9+
10+
SignalEventImpl::SignalEventImpl(DispatcherImpl& dispatcher, signal_t signal_num, SignalCb cb)
11+
: cb_(cb) {
12+
13+
if (signal_num > eventBridgeHandlersSingleton::get().size()) {
14+
PANIC("Attempting to create SignalEventImpl with a signal id that exceeds the number of "
15+
"supported signals.");
16+
}
17+
18+
if (eventBridgeHandlersSingleton::get()[signal_num]) {
19+
return;
20+
}
21+
os_fd_t socks[2];
22+
Api::SysCallIntResult result =
23+
Api::OsSysCallsSingleton::get().socketpair(AF_INET, SOCK_STREAM, IPPROTO_TCP, socks);
24+
ASSERT(result.rc_ == 0);
25+
26+
read_handle_ = std::make_unique<Network::IoSocketHandleImpl>(socks[0], false, AF_INET);
27+
result = read_handle_->setBlocking(false);
28+
ASSERT(result.rc_ == 0);
29+
auto write_handle = std::make_shared<Network::IoSocketHandleImpl>(socks[1], false, AF_INET);
30+
result = write_handle->setBlocking(false);
31+
ASSERT(result.rc_ == 0);
32+
33+
read_handle_->initializeFileEvent(
34+
dispatcher,
35+
[this](uint32_t events) -> void {
36+
ASSERT(events == Event::FileReadyType::Read);
37+
cb_();
38+
},
39+
Event::FileTriggerType::Level, Event::FileReadyType::Read);
40+
eventBridgeHandlersSingleton::get()[signal_num] = write_handle;
41+
}
42+
43+
} // namespace Event
44+
} // namespace Envoy
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#pragma once
2+
3+
#include "envoy/event/signal.h"
4+
#include "envoy/network/io_handle.h"
5+
6+
#include "common/event/dispatcher_impl.h"
7+
#include "common/event/event_impl_base.h"
8+
#include "common/network/io_socket_handle_impl.h"
9+
#include "common/singleton/threadsafe_singleton.h"
10+
11+
#include "absl/container/flat_hash_map.h"
12+
13+
namespace Envoy {
14+
namespace Event {
15+
16+
/**
17+
* libevent implementation of Event::SignalEvent.
18+
*/
19+
class SignalEventImpl : public SignalEvent {
20+
public:
21+
SignalEventImpl(DispatcherImpl& dispatcher, signal_t signal_num, SignalCb cb);
22+
23+
private:
24+
SignalCb cb_;
25+
Network::IoHandlePtr read_handle_;
26+
};
27+
28+
// Windows ConsoleControlHandler does not allow for a context. As a result the thread
29+
// spawned to handle the console events communicates with the main program with this socketpair.
30+
// Here we have a map from signal types to IoHandle. When we write to this handle we trigger an
31+
// event that notifies Envoy to act on the signal.
32+
using eventBridgeHandlersSingleton =
33+
ThreadSafeSingleton<std::array<std::shared_ptr<Network::IoHandle>, ENVOY_WIN32_SIGNAL_COUNT>>;
34+
} // namespace Event
35+
} // namespace Envoy

source/exe/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@ envoy_cc_win32_library(
168168
srcs = ["win32/platform_impl.cc"],
169169
deps = [
170170
":platform_header_lib",
171+
"//source/common/buffer:buffer_lib",
171172
"//source/common/common:assert_lib",
172173
"//source/common/common:thread_lib",
174+
"//source/common/event:signal_lib",
173175
"//source/common/filesystem:filesystem_lib",
174176
],
175177
)

source/exe/win32/platform_impl.cc

+42
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,59 @@
1+
#include <chrono>
2+
#include <thread>
3+
4+
#include "common/buffer/buffer_impl.h"
15
#include "common/common/assert.h"
26
#include "common/common/thread_impl.h"
7+
#include "common/event/signal_impl.h"
38
#include "common/filesystem/filesystem_impl.h"
49

510
#include "exe/platform_impl.h"
611

712
namespace Envoy {
813

14+
static std::atomic<bool> shutdown_pending = false;
15+
16+
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) {
17+
if (shutdown_pending) {
18+
return 0;
19+
}
20+
shutdown_pending = true;
21+
22+
auto handler = Event::eventBridgeHandlersSingleton::get()[ENVOY_SIGTERM];
23+
if (!handler) {
24+
return 0;
25+
}
26+
27+
// This code is executed as part of a thread running under a thread owned and
28+
// managed by Windows console host. For that reason we want to avoid allocating
29+
// substantial amount of memory or taking locks.
30+
// This is why we write to a socket to wake up the signal handler.
31+
char data[] = {'a'};
32+
Buffer::RawSlice buffer{data, 1};
33+
auto result = handler->writev(&buffer, 1);
34+
RELEASE_ASSERT(result.rc_ == 1,
35+
fmt::format("failed to write 1 byte: {}", result.err_->getErrorDetails()));
36+
37+
if (fdwCtrlType == CTRL_LOGOFF_EVENT || fdwCtrlType == CTRL_SHUTDOWN_EVENT) {
38+
// These events terminate the process immediately so we want to give a couple of seconds
39+
// to the dispatcher to shutdown the server.
40+
constexpr size_t delay = 3;
41+
absl::SleepFor(absl::Seconds(delay));
42+
}
43+
return 1;
44+
}
45+
946
PlatformImpl::PlatformImpl()
1047
: thread_factory_(std::make_unique<Thread::ThreadFactoryImplWin32>()),
1148
file_system_(std::make_unique<Filesystem::InstanceImplWin32>()) {
1249
WSADATA wsa_data;
1350
const WORD version_requested = MAKEWORD(2, 2);
1451
RELEASE_ASSERT(WSAStartup(version_requested, &wsa_data) == 0, "WSAStartup failed with error");
52+
53+
if (!SetConsoleCtrlHandler(CtrlHandler, 1)) {
54+
// The Control Handler is executing in a different thread.
55+
ENVOY_LOG_MISC(warn, "Could not set Windows Control Handlers. Continuing without them.");
56+
}
1557
}
1658

1759
PlatformImpl::~PlatformImpl() { ::WSACleanup(); }

source/server/server.cc

+7-6
Original file line numberDiff line numberDiff line change
@@ -648,15 +648,16 @@ RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatch
648648
}
649649
}) {
650650
// Setup signals.
651+
// Since signals are not supported on Windows we have an internal definition for `SIGTERM`
652+
// On POSIX it resolves as expected to SIGTERM
653+
// On Windows we use it internally for all the console events that indicate that we should
654+
// terminate the process.
651655
if (options.signalHandlingEnabled()) {
652-
// TODO(Pivotal): Figure out solution to graceful shutdown on Windows. None of these signals exist
653-
// on Windows.
654-
#ifndef WIN32
655-
sigterm_ = dispatcher.listenForSignal(SIGTERM, [&instance]() {
656-
ENVOY_LOG(warn, "caught SIGTERM");
656+
sigterm_ = dispatcher.listenForSignal(ENVOY_SIGTERM, [&instance]() {
657+
ENVOY_LOG(warn, "caught ENVOY_SIGTERM");
657658
instance.shutdown();
658659
});
659-
660+
#ifndef WIN32
660661
sigint_ = dispatcher.listenForSignal(SIGINT, [&instance]() {
661662
ENVOY_LOG(warn, "caught SIGINT");
662663
instance.shutdown();

test/exe/BUILD

+22
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,25 @@ envoy_cc_test(
8484
"//test/test_common:utility_lib",
8585
],
8686
)
87+
88+
# Due to the limitiations of how Windows signals work
89+
# this test cannot be executed through bazel and it must
90+
# be executed as a standalone executable.
91+
# e.g.: `./bazel-bin/test/exe/win32_outofproc_main_test.exe`
92+
envoy_cc_test(
93+
name = "win32_outofproc_main_test",
94+
srcs = ["win32_outofproc_main_test.cc"],
95+
data = [
96+
"//source/exe:envoy-static",
97+
"//test/config/integration:google_com_proxy_port_0",
98+
],
99+
# TODO(envoyproxy/windows-dev): Disable the manual tag.
100+
tags = ["manual"],
101+
deps = [
102+
"//source/common/api:api_lib",
103+
"//source/exe:main_common_lib",
104+
"//test/mocks/runtime:runtime_mocks",
105+
"//test/test_common:contention_lib",
106+
"//test/test_common:environment_lib",
107+
],
108+
)

0 commit comments

Comments
 (0)