1
#include "source/server/hot_restart_impl.h"
2

            
3
#include <sys/prctl.h>
4
#include <sys/types.h>
5
#include <sys/un.h>
6

            
7
#include <csignal>
8
#include <cstdint>
9
#include <memory>
10
#include <string>
11

            
12
#include "envoy/event/dispatcher.h"
13
#include "envoy/event/file_event.h"
14
#include "envoy/server/instance.h"
15

            
16
#include "source/common/api/os_sys_calls_impl.h"
17
#include "source/common/api/os_sys_calls_impl_hot_restart.h"
18
#include "source/common/common/fmt.h"
19
#include "source/common/common/lock_guard.h"
20

            
21
#include "absl/strings/string_view.h"
22

            
23
namespace Envoy {
24
namespace Server {
25

            
26
19
SharedMemory* attachSharedMemory(uint32_t base_id, uint32_t restart_epoch) {
27
19
  Api::OsSysCalls& os_sys_calls = Api::OsSysCallsSingleton::get();
28
19
  Api::HotRestartOsSysCalls& hot_restart_os_sys_calls = Api::HotRestartOsSysCallsSingleton::get();
29

            
30
19
  int flags = O_RDWR;
31
19
  const std::string shmem_name = fmt::format("/envoy_shared_memory_{}", base_id);
32
19
  if (restart_epoch == 0) {
33
18
    flags |= O_CREAT | O_EXCL;
34

            
35
    // If we are meant to be first, attempt to unlink a previous shared memory instance. If this
36
    // is a clean restart this should then allow the shm_open() call below to succeed.
37
18
    hot_restart_os_sys_calls.shmUnlink(shmem_name.c_str());
38
18
  }
39

            
40
19
  const Api::SysCallIntResult result =
41
19
      hot_restart_os_sys_calls.shmOpen(shmem_name.c_str(), flags, S_IRUSR | S_IWUSR);
42
19
  if (result.return_value_ == -1) {
43
    PANIC(fmt::format("cannot open shared memory region {} check user permissions. Error: {}",
44
                      shmem_name, errorDetails(result.errno_)));
45
  }
46

            
47
19
  if (restart_epoch == 0) {
48
18
    const Api::SysCallIntResult truncateRes =
49
18
        os_sys_calls.ftruncate(result.return_value_, sizeof(SharedMemory));
50
18
    RELEASE_ASSERT(truncateRes.return_value_ != -1, "");
51
18
  }
52

            
53
19
  const Api::SysCallPtrResult mmapRes = os_sys_calls.mmap(
54
19
      nullptr, sizeof(SharedMemory), PROT_READ | PROT_WRITE, MAP_SHARED, result.return_value_, 0);
55
19
  SharedMemory* shmem = reinterpret_cast<SharedMemory*>(mmapRes.return_value_);
56
19
  RELEASE_ASSERT(shmem != MAP_FAILED, "");
57
19
  RELEASE_ASSERT((reinterpret_cast<uintptr_t>(shmem) % alignof(decltype(shmem))) == 0, "");
58

            
59
19
  if (restart_epoch == 0) {
60
18
    shmem->size_ = sizeof(SharedMemory);
61
18
    shmem->version_ = HOT_RESTART_VERSION;
62
18
    initializeMutex(shmem->log_lock_);
63
18
    initializeMutex(shmem->access_log_lock_);
64
18
  } else {
65
1
    RELEASE_ASSERT(shmem->size_ == sizeof(SharedMemory),
66
1
                   "Hot restart SharedMemory size mismatch! You must have hot restarted into a "
67
1
                   "not-hot-restart-compatible new version of Envoy.");
68
1
    RELEASE_ASSERT(shmem->version_ == HOT_RESTART_VERSION,
69
1
                   "Hot restart version mismatch! You must have hot restarted into a "
70
1
                   "not-hot-restart-compatible new version of Envoy.");
71
1
  }
72

            
73
  // Here we catch the case where a new Envoy starts up when the current Envoy has not yet fully
74
  // initialized. The startup logic is quite complicated, and it's not worth trying to handle this
75
  // in a finer way. This will cause the startup to fail with an error code early, without
76
  // affecting any currently running processes. The process runner should try again later with some
77
  // back off and with the same hot restart epoch number.
78
19
  uint64_t old_flags = shmem->flags_.fetch_or(SHMEM_FLAGS_INITIALIZING);
79
19
  if (old_flags & SHMEM_FLAGS_INITIALIZING) {
80
    throw EnvoyException("previous envoy process is still initializing");
81
  }
82
19
  return shmem;
83
19
}
84

            
85
36
void initializeMutex(pthread_mutex_t& mutex) {
86
36
  pthread_mutexattr_t attribute;
87
36
  pthread_mutexattr_init(&attribute);
88
36
  pthread_mutexattr_setpshared(&attribute, PTHREAD_PROCESS_SHARED);
89
36
  pthread_mutexattr_setrobust(&attribute, PTHREAD_MUTEX_ROBUST);
90
36
  pthread_mutex_init(&mutex, &attribute);
91
36
}
92

            
93
// The base id is automatically scaled by 10 to prevent overlap of domain socket names when
94
// multiple Envoys with different base-ids run on a single host. Note that older versions of Envoy
95
// performed the multiplication in OptionsImpl which produced incorrect server info output.
96
// TODO(zuercher): ideally, the base_id would be separated from the restart_epoch in
97
// the socket names to entirely prevent collisions between consecutive base ids.
98
HotRestartImpl::HotRestartImpl(uint32_t base_id, uint32_t restart_epoch,
99
                               const std::string& socket_path, mode_t socket_mode,
100
                               bool skip_hot_restart_on_no_parent, bool skip_parent_stats)
101
127
    : base_id_(base_id), scaled_base_id_(base_id * 10),
102
127
      as_child_(HotRestartingChild(scaled_base_id_, restart_epoch, socket_path, socket_mode,
103
127
                                   skip_hot_restart_on_no_parent, skip_parent_stats)),
104
127
      as_parent_(HotRestartingParent(scaled_base_id_, restart_epoch, socket_path, socket_mode)),
105
127
      shmem_(attachSharedMemory(scaled_base_id_, restart_epoch)), log_lock_(shmem_->log_lock_),
106
127
      access_log_lock_(shmem_->access_log_lock_) {
107
  // If our parent ever goes away just terminate us so that we don't have to rely on ops/launching
108
  // logic killing the entire process tree. We should never exist without our parent.
109
127
  int rc = prctl(PR_SET_PDEATHSIG, SIGTERM);
110
127
  RELEASE_ASSERT(rc != -1, "");
111
127
}
112

            
113
9
void HotRestartImpl::drainParentListeners() {
114
9
  as_child_.drainParentListeners();
115
  // At this point we are initialized and a new Envoy can startup if needed.
116
9
  shmem_->flags_ &= ~SHMEM_FLAGS_INITIALIZING;
117
9
}
118

            
119
int HotRestartImpl::duplicateParentListenSocket(const std::string& address, uint32_t worker_index,
120
32
                                                absl::string_view network_namespace) {
121
32
  return as_child_.duplicateParentListenSocket(address, worker_index, network_namespace);
122
32
}
123

            
124
void HotRestartImpl::registerUdpForwardingListener(
125
    Network::Address::InstanceConstSharedPtr address,
126
    std::shared_ptr<Network::UdpListenerConfig> listener_config) {
127
  as_child_.registerUdpForwardingListener(address, listener_config);
128
}
129

            
130
1
OptRef<Network::ParentDrainedCallbackRegistrar> HotRestartImpl::parentDrainedCallbackRegistrar() {
131
1
  return as_child_;
132
1
}
133

            
134
10
void HotRestartImpl::initialize(Event::Dispatcher& dispatcher, Server::Instance& server) {
135
10
  as_parent_.initialize(dispatcher, server);
136
10
  as_child_.initialize(dispatcher);
137
10
}
138

            
139
9
absl::optional<HotRestart::AdminShutdownResponse> HotRestartImpl::sendParentAdminShutdownRequest() {
140
9
  return as_child_.sendParentAdminShutdownRequest();
141
9
}
142

            
143
1
void HotRestartImpl::sendParentTerminateRequest() { as_child_.sendParentTerminateRequest(); }
144

            
145
HotRestart::ServerStatsFromParent
146
33
HotRestartImpl::mergeParentStatsIfAny(Stats::StoreRoot& stats_store) {
147
33
  std::unique_ptr<envoy::HotRestartMessage> wrapper_msg = as_child_.getParentStats();
148
33
  ServerStatsFromParent response;
149
  // getParentStats() will happily and cleanly return nullptr if we have no parent.
150
33
  if (wrapper_msg) {
151
    as_child_.mergeParentStats(stats_store, wrapper_msg->reply().stats());
152
    response.parent_memory_allocated_ = wrapper_msg->reply().stats().memory_allocated();
153
    response.parent_connections_ = wrapper_msg->reply().stats().num_connections();
154
  }
155
33
  return response;
156
33
}
157

            
158
10
void HotRestartImpl::shutdown() {
159
10
  as_parent_.shutdown();
160
10
  as_child_.shutdown();
161
10
}
162

            
163
10
uint32_t HotRestartImpl::baseId() { return base_id_; }
164
12
std::string HotRestartImpl::version() { return hotRestartVersion(); }
165

            
166
4
bool HotRestartImpl::isInitializing() const {
167
4
  return (shmem_->flags_.load() & SHMEM_FLAGS_INITIALIZING) != 0;
168
4
}
169

            
170
12
std::string HotRestartImpl::hotRestartVersion() {
171
12
  return fmt::format("{}.{}", HOT_RESTART_VERSION, sizeof(SharedMemory));
172
12
}
173

            
174
} // namespace Server
175
} // namespace Envoy