Skip to content

Commit 71c9823

Browse files
committed
- Make the cWebem instance to wait for all connections to stop
gracefully ; - Ignore exceptions while canceling timers ; - Ignore exceptions while closing socket (on connection stop). - Ignore exceptions while stopping acceptor.
1 parent 25a3fb2 commit 71c9823

File tree

7 files changed

+211
-47
lines changed

7 files changed

+211
-47
lines changed

webserver/cWebem.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,13 @@ void cWebem::Stop() {
9797
}
9898
// Stop Web server
9999
if (myServer != NULL) {
100-
myServer->stop();
100+
myServer->stop(); // asynchronous stop
101+
while(true) {
102+
sleep_milliseconds(100);
103+
if (myServer->stopped()) {
104+
break;
105+
}
106+
}
101107
}
102108
}
103109

webserver/connection.cpp

Lines changed: 119 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ namespace server {
2222

2323
// this is the constructor for plain connections
2424
connection::connection(boost::asio::io_service& io_service,
25-
connection_manager& manager, request_handler& handler, int timeout)
26-
: connection_manager_(manager),
27-
request_handler_(handler),
28-
timeout_(timeout),
29-
timer_(io_service, boost::posix_time::seconds( timeout )),
30-
default_abandoned_timeout_(20*60), // 20mn before stopping abandoned connection
31-
abandoned_timer_(io_service, boost::posix_time::seconds(default_abandoned_timeout_))
25+
connection_manager& manager,
26+
request_handler& handler,
27+
int read_timeout) :
28+
connection_manager_(manager),
29+
request_handler_(handler),
30+
read_timeout_(read_timeout),
31+
read_timer_(io_service, boost::posix_time::seconds(read_timeout)),
32+
default_abandoned_timeout_(20*60), // 20mn before stopping abandoned connection
33+
abandoned_timer_(io_service, boost::posix_time::seconds(default_abandoned_timeout_)),
34+
status("initializing"),
35+
stop_required(false)
3236
{
3337
secure_ = false;
3438
keepalive_ = false;
@@ -41,13 +45,18 @@ connection::connection(boost::asio::io_service& io_service,
4145
#ifdef WWW_ENABLE_SSL
4246
// this is the constructor for secure connections
4347
connection::connection(boost::asio::io_service& io_service,
44-
connection_manager& manager, request_handler& handler, int timeout, boost::asio::ssl::context& context)
45-
: connection_manager_(manager),
46-
request_handler_(handler),
47-
timeout_(timeout),
48-
timer_(io_service, boost::posix_time::seconds( timeout )),
49-
default_abandoned_timeout_(20*60), // 20mn before stopping abandoned connection
50-
abandoned_timer_(io_service, boost::posix_time::seconds(default_abandoned_timeout_))
48+
connection_manager& manager,
49+
request_handler& handler,
50+
int read_timeout,
51+
boost::asio::ssl::context& context) :
52+
connection_manager_(manager),
53+
request_handler_(handler),
54+
read_timeout_(read_timeout),
55+
read_timer_(io_service, boost::posix_time::seconds(read_timeout)),
56+
default_abandoned_timeout_(20*60), // 20mn before stopping abandoned connection
57+
abandoned_timer_(io_service, boost::posix_time::seconds(default_abandoned_timeout_)),
58+
status("initializing"),
59+
stop_required(false)
5160
{
5261
secure_ = true;
5362
keepalive_ = false;
@@ -85,11 +94,13 @@ void connection::start()
8594
connection_manager_.stop(shared_from_this());
8695
return;
8796
}
97+
host_endpoint_ = endpoint.address().to_string();
8898

8999
set_abandoned_timeout();
90-
host_endpoint_ = endpoint.address().to_string();
100+
91101
if (secure_) {
92102
#ifdef WWW_ENABLE_SSL
103+
status = "waiting-handshake";
93104
// with ssl, we first need to complete the handshake before reading
94105
sslsocket_->async_handshake(boost::asio::ssl::stream_base::server,
95106
boost::bind(&connection::handle_handshake, shared_from_this(),
@@ -106,25 +117,23 @@ void connection::stop()
106117
{
107118
// Cancel timers
108119
cancel_abandoned_timeout();
109-
timer_.cancel();
120+
cancel_read_timeout();
110121

111-
// Initiate graceful connection closure.
112-
boost::system::error_code ignored_ec;
113-
socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); // @note For portable behaviour with respect to graceful closure of a
114-
// connected socket, call shutdown() before closing the socket.
115-
socket().close();
116-
}
117-
118-
void connection::handle_timeout(const boost::system::error_code& error)
119-
{
120-
if (error != boost::asio::error::operation_aborted) {
121-
connection_manager_.stop(shared_from_this());
122-
}
122+
try {
123+
// Initiate graceful connection closure.
124+
boost::system::error_code ignored_ec;
125+
socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); // @note For portable behaviour with respect to graceful closure of a
126+
// connected socket, call shutdown() before closing the socket.
127+
socket().close(ignored_ec);
128+
} catch(...) {
129+
_log.Log(LOG_ERROR, "%s -> exception thrown while stopping connection", host_endpoint_.c_str());
130+
}
123131
}
124132

125133
#ifdef WWW_ENABLE_SSL
126134
void connection::handle_handshake(const boost::system::error_code& error)
127135
{
136+
status = "handshaking";
128137
if (secure_) { // assert
129138
if (!error)
130139
{
@@ -141,12 +150,16 @@ void connection::handle_handshake(const boost::system::error_code& error)
141150

142151
void connection::read_more()
143152
{
153+
status = "waiting-read";
154+
if (is_stopping()) {
155+
return;
156+
}
157+
144158
// read chunks of max 4 KB
145159
boost::asio::streambuf::mutable_buffers_type buf = _buf.prepare(4096);
146160

147161
// set timeout timer
148-
timer_.expires_from_now(boost::posix_time::seconds(timeout_));
149-
timer_.async_wait(boost::bind(&connection::handle_timeout, shared_from_this(), boost::asio::placeholders::error));
162+
reset_read_timeout();
150163

151164
if (secure_) {
152165
#ifdef WWW_ENABLE_SSL
@@ -168,8 +181,14 @@ void connection::read_more()
168181

169182
void connection::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred)
170183
{
184+
status = "reading";
185+
if (is_stopping()) {
186+
return;
187+
}
188+
171189
// data read, no need for timeouts (RK, note: race condition)
172-
timer_.cancel();
190+
cancel_read_timeout();
191+
173192
if (!error && bytes_transferred > 0)
174193
{
175194
// ensure written bytes in the buffer
@@ -201,6 +220,12 @@ void connection::handle_read(const boost::system::error_code& error, std::size_t
201220
request_.host = request_.host.substr(7);
202221
}
203222
request_handler_.handle_request(request_, reply_);
223+
224+
status = "waiting-write";
225+
if (is_stopping()) {
226+
return;
227+
}
228+
204229
if (secure_) {
205230
#ifdef WWW_ENABLE_SSL
206231
boost::asio::async_write(*sslsocket_, reply_.to_buffers(request_.method),
@@ -218,6 +243,12 @@ void connection::handle_read(const boost::system::error_code& error, std::size_t
218243
{
219244
keepalive_ = false;
220245
reply_ = reply::stock_reply(reply::bad_request);
246+
247+
status = "writing";
248+
if (is_stopping()) {
249+
return;
250+
}
251+
221252
if (secure_) {
222253
#ifdef WWW_ENABLE_SSL
223254
boost::asio::async_write(*sslsocket_, reply_.to_buffers(request_.method),
@@ -244,7 +275,7 @@ void connection::handle_read(const boost::system::error_code& error, std::size_t
244275

245276
void connection::handle_write(const boost::system::error_code& error)
246277
{
247-
if (!error && keepalive_) {
278+
if (!error && keepalive_ && !stop_required) {
248279
// if a keep-alive connection is requested, we read the next request
249280
read_more();
250281
} else {
@@ -261,6 +292,39 @@ connection::~connection()
261292
#endif
262293
}
263294

295+
// schedule read timeout timer
296+
void connection::set_read_timeout() {
297+
read_timer_.expires_from_now(boost::posix_time::seconds(read_timeout_));
298+
read_timer_.async_wait(boost::bind(&connection::handle_read_timeout, shared_from_this(), boost::asio::placeholders::error));
299+
}
300+
301+
/// simply cancel read timeout timer
302+
void connection::cancel_read_timeout() {
303+
try {
304+
boost::system::error_code ignored_ec;
305+
read_timer_.cancel(ignored_ec);
306+
if (ignored_ec) {
307+
_log.Log(LOG_ERROR, "%s -> exception thrown while canceling read timeout : %s", host_endpoint_.c_str(), ignored_ec.message().c_str());
308+
}
309+
} catch (...) {
310+
_log.Log(LOG_ERROR, "%s -> exception thrown while canceling read timeout", host_endpoint_.c_str());
311+
}
312+
}
313+
314+
/// reschedule read timeout timer
315+
void connection::reset_read_timeout() {
316+
cancel_read_timeout();
317+
set_read_timeout();
318+
}
319+
320+
/// stop connection on read timeout
321+
void connection::handle_read_timeout(const boost::system::error_code& error) {
322+
if (error != boost::asio::error::operation_aborted) {
323+
_log.Log(LOG_STATUS, "%s -> handle read timeout", host_endpoint_.c_str());
324+
connection_manager_.stop(shared_from_this());
325+
}
326+
}
327+
264328
/// schedule abandoned timeout timer
265329
void connection::set_abandoned_timeout() {
266330
abandoned_timer_.expires_from_now(boost::posix_time::seconds(default_abandoned_timeout_));
@@ -269,7 +333,15 @@ void connection::set_abandoned_timeout() {
269333

270334
/// simply cancel abandoned timeout timer
271335
void connection::cancel_abandoned_timeout() {
272-
abandoned_timer_.cancel();
336+
try {
337+
boost::system::error_code ignored_ec;
338+
abandoned_timer_.cancel(ignored_ec);
339+
if (ignored_ec) {
340+
_log.Log(LOG_ERROR, "%s -> exception thrown while canceling abandoned timeout : %s", host_endpoint_.c_str(), ignored_ec.message().c_str());
341+
}
342+
} catch (...) {
343+
_log.Log(LOG_ERROR, "%s -> exception thrown while canceling abandoned timeout", host_endpoint_.c_str());
344+
}
273345
}
274346

275347
/// reschedule abandoned timeout timer
@@ -286,5 +358,19 @@ void connection::handle_abandoned_timeout(const boost::system::error_code& error
286358
}
287359
}
288360

361+
/// Wait for all asynchronous operations to abort.
362+
void connection::stop_gracefully() {
363+
stop_required = true;
364+
}
365+
366+
/// stop the connection if it is required
367+
bool connection::is_stopping() {
368+
if (stop_required) {
369+
connection_manager_.stop(shared_from_this());
370+
return true;
371+
}
372+
return false;
373+
}
374+
289375
} // namespace server
290376
} // namespace http

webserver/connection.hpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ class connection
5858
/// Stop all asynchronous operations associated with the connection.
5959
void stop();
6060

61+
/// Wait for all asynchronous operations to abort.
62+
void stop_gracefully();
63+
6164
/// Timer handlers
62-
void handle_timeout(const boost::system::error_code& error);
65+
void handle_read_timeout(const boost::system::error_code& error);
6366
void handle_abandoned_timeout(const boost::system::error_code& error);
6467

6568
private:
@@ -70,6 +73,12 @@ class connection
7073
/// Handle completion of a write operation.
7174
void handle_write(const boost::system::error_code& e);
7275

76+
/// Initialize read timeout timer
77+
void set_read_timeout();
78+
/// Stop read timeout timer
79+
void cancel_read_timeout();
80+
/// Reset read timeout timer
81+
void reset_read_timeout();
7382

7483
/// Schedule abandoned timeout timer
7584
void set_abandoned_timeout();
@@ -78,6 +87,9 @@ class connection
7887
/// Reschedule abandoned timeout timer
7988
void reset_abandoned_timeout();
8089

90+
/// Check if the connection is about to stop
91+
bool is_stopping();
92+
8193
/// Socket for the (PLAIN) connection.
8294
boost::asio::ip::tcp::socket *socket_;
8395
//Host EndPoint
@@ -87,10 +99,10 @@ class connection
8799
bool keepalive_;
88100

89101
/// Read timeout in seconds
90-
int timeout_;
102+
int read_timeout_;
91103

92104
/// Read timeout timer
93-
boost::asio::deadline_timer timer_;
105+
boost::asio::deadline_timer read_timer_;
94106

95107
/// Abandoned connection timeout (in seconds)
96108
long default_abandoned_timeout_;
@@ -112,6 +124,12 @@ class connection
112124
/// The buffer that we receive data in
113125
boost::asio::streambuf _buf;
114126

127+
/// The status of the connection (can be initializing, handshaking, waiting, reading, writing)
128+
std::string status;
129+
130+
/// Ask the connection to stop as soon as possible
131+
bool stop_required;
132+
115133
// secure connection members below
116134
// secure connection yes/no
117135
bool secure_;

webserver/connection_manager.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <algorithm>
1313
#include <boost/bind.hpp>
1414
#include <iostream>
15+
#include "../main/Helper.h"
1516
#include "../main/Logger.h"
1617
#include "../main/localtime_r.h"
1718

@@ -52,11 +53,31 @@ void connection_manager::stop(connection_ptr c)
5253
c->stop();
5354
}
5455

55-
void connection_manager::stop_all()
56+
void connection_manager::stop_all(bool graceful_stop)
5657
{
57-
std::for_each(connections_.begin(), connections_.end(),
58-
boost::bind(&connection::stop, _1));
59-
connections_.clear();
58+
_log.Log(LOG_STATUS,"connection_manager::stop_all(%s)", graceful_stop ? "true" : "false");
59+
if (graceful_stop) {
60+
std::for_each(connections_.begin(), connections_.end(),
61+
boost::bind(&connection::stop_gracefully, _1));
62+
int timeout = 10; // force stop after 10 seconds
63+
time_t start = mytime(NULL);
64+
while(true) {
65+
if (connections_.size() < 1) {
66+
break;
67+
}
68+
if ((mytime(NULL) - start) > timeout) {
69+
// timeout occurred : force stop
70+
_log.Log(LOG_STATUS,"Graceful stop timeout occurred : all connections will be closed now");
71+
break;
72+
}
73+
_log.Log(LOG_STATUS,"Graceful stopping : %d active connection(s)", (int) connections_.size());
74+
sleep_milliseconds(500);
75+
}
76+
}
77+
std::for_each(connections_.begin(), connections_.end(),
78+
boost::bind(&connection::stop, _1));
79+
connections_.clear();
80+
_log.Log(LOG_STATUS,"connection_manager::stop_all(%s) ends", graceful_stop ? "true" : "false");
6081
}
6182

6283

webserver/connection_manager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class connection_manager
3131
void stop(connection_ptr c);
3232

3333
/// Stop all connections.
34-
void stop_all();
34+
void stop_all(bool graceful_stop);
3535

3636
private:
3737
/// The managed connections.

0 commit comments

Comments
 (0)