Skip to content

Commit 9a1d358

Browse files
authoredJun 13, 2018
Server: move shutdown parts to a specific shutdown state object (#7437)
* Server: move shutdown parts to a specific shutdown state object
1 parent 10634f0 commit 9a1d358

File tree

7 files changed

+296
-137
lines changed

7 files changed

+296
-137
lines changed
 

‎src/game.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,7 @@ void Game::run()
10651065

10661066
while (RenderingEngine::run()
10671067
&& !(*kill || g_gamecallback->shutdown_requested
1068-
|| (server && server->getShutdownRequested()))) {
1068+
|| (server && server->isShutdownRequested()))) {
10691069

10701070
const irr::core::dimension2d<u32> &current_screen_size =
10711071
RenderingEngine::get_video_driver()->getScreenSize();
@@ -1271,6 +1271,7 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
12711271
}
12721272

12731273
server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false);
1274+
server->init();
12741275
server->start();
12751276

12761277
return true;

‎src/main.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
870870
// Create server
871871
Server server(game_params.world_path, game_params.game_spec,
872872
false, bind_addr, true, &iface);
873+
server.init();
873874

874875
g_term_console.setup(&iface, &kill, admin_nick);
875876

@@ -904,6 +905,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
904905
// Create server
905906
Server server(game_params.world_path, game_params.game_spec, false,
906907
bind_addr, true);
908+
server.init();
907909
server.start();
908910

909911
// Run server

‎src/network/serverpackethandler.cpp

+2-5
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,8 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
402402
m_clients.event(peer_id, CSE_SetClientReady);
403403
m_script->on_joinplayer(playersao);
404404
// Send shutdown timer if shutdown has been scheduled
405-
if (m_shutdown_timer > 0.0f) {
406-
std::wstringstream ws;
407-
ws << L"*** Server shutting down in "
408-
<< duration_to_string(myround(m_shutdown_timer)).c_str() << ".";
409-
SendChatMessage(pkt->getPeerId(), ws.str());
405+
if (m_shutdown_state.isTimerRunning()) {
406+
SendChatMessage(pkt->getPeerId(), m_shutdown_state.getShutdownTimerMessage());
410407
}
411408
}
412409

‎src/server.cpp

+146-124
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,60 @@ v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const
139139
return v3f(0,0,0);
140140
}
141141

142+
void Server::ShutdownState::reset()
143+
{
144+
m_timer = 0.0f;
145+
message.clear();
146+
should_reconnect = false;
147+
is_requested = false;
148+
}
149+
150+
void Server::ShutdownState::trigger(float delay, const std::string &msg, bool reconnect)
151+
{
152+
m_timer = delay;
153+
message = msg;
154+
should_reconnect = reconnect;
155+
}
142156

157+
void Server::ShutdownState::tick(float dtime, Server *server)
158+
{
159+
if (m_timer <= 0.0f)
160+
return;
161+
162+
// Timed shutdown
163+
static const float shutdown_msg_times[] =
164+
{
165+
1, 2, 3, 4, 5, 10, 20, 40, 60, 120, 180, 300, 600, 1200, 1800, 3600
166+
};
167+
168+
// Automated messages
169+
if (m_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) {
170+
for (float t : shutdown_msg_times) {
171+
// If shutdown timer matches an automessage, shot it
172+
if (m_timer > t && m_timer - dtime < t) {
173+
std::wstring periodicMsg = getShutdownTimerMessage();
174+
175+
infostream << wide_to_utf8(periodicMsg).c_str() << std::endl;
176+
server->SendChatMessage(PEER_ID_INEXISTENT, periodicMsg);
177+
break;
178+
}
179+
}
180+
}
181+
182+
m_timer -= dtime;
183+
if (m_timer < 0.0f) {
184+
m_timer = 0.0f;
185+
is_requested = true;
186+
}
187+
}
188+
189+
std::wstring Server::ShutdownState::getShutdownTimerMessage() const
190+
{
191+
std::wstringstream ws;
192+
ws << L"*** Server shutting down in "
193+
<< duration_to_string(myround(m_timer)).c_str() << ".";
194+
return ws.str();
195+
}
143196

144197
/*
145198
Server
@@ -174,22 +227,96 @@ Server::Server(
174227
{
175228
m_lag = g_settings->getFloat("dedicated_server_step");
176229

177-
if (path_world.empty())
230+
if (m_path_world.empty())
178231
throw ServerError("Supplied empty world path");
179232

180-
if(!gamespec.isValid())
233+
if (!gamespec.isValid())
181234
throw ServerError("Supplied invalid gamespec");
235+
}
236+
237+
Server::~Server()
238+
{
239+
infostream << "Server destructing" << std::endl;
240+
241+
// Send shutdown message
242+
SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(CHATMESSAGE_TYPE_ANNOUNCE,
243+
L"*** Server shutting down"));
244+
245+
if (m_env) {
246+
MutexAutoLock envlock(m_env_mutex);
247+
248+
infostream << "Server: Saving players" << std::endl;
249+
m_env->saveLoadedPlayers();
182250

183-
infostream<<"Server created for gameid \""<<m_gamespec.id<<"\"";
184-
if(m_simple_singleplayer_mode)
185-
infostream<<" in simple singleplayer mode"<<std::endl;
251+
infostream << "Server: Kicking players" << std::endl;
252+
std::string kick_msg;
253+
bool reconnect = false;
254+
if (isShutdownRequested()) {
255+
reconnect = m_shutdown_state.should_reconnect;
256+
kick_msg = m_shutdown_state.message;
257+
}
258+
if (kick_msg.empty()) {
259+
kick_msg = g_settings->get("kick_msg_shutdown");
260+
}
261+
m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN,
262+
kick_msg, reconnect);
263+
}
264+
265+
// Do this before stopping the server in case mapgen callbacks need to access
266+
// server-controlled resources (like ModStorages). Also do them before
267+
// shutdown callbacks since they may modify state that is finalized in a
268+
// callback.
269+
if (m_emerge)
270+
m_emerge->stopThreads();
271+
272+
if (m_env) {
273+
MutexAutoLock envlock(m_env_mutex);
274+
275+
// Execute script shutdown hooks
276+
infostream << "Executing shutdown hooks" << std::endl;
277+
m_script->on_shutdown();
278+
279+
infostream << "Server: Saving environment metadata" << std::endl;
280+
m_env->saveMeta();
281+
}
282+
283+
// Stop threads
284+
if (m_thread) {
285+
stop();
286+
delete m_thread;
287+
}
288+
289+
// Delete things in the reverse order of creation
290+
delete m_emerge;
291+
delete m_env;
292+
delete m_rollback;
293+
delete m_banmanager;
294+
delete m_itemdef;
295+
delete m_nodedef;
296+
delete m_craftdef;
297+
298+
// Deinitialize scripting
299+
infostream << "Server: Deinitializing scripting" << std::endl;
300+
delete m_script;
301+
302+
// Delete detached inventories
303+
for (auto &detached_inventory : m_detached_inventories) {
304+
delete detached_inventory.second;
305+
}
306+
}
307+
308+
void Server::init()
309+
{
310+
infostream << "Server created for gameid \"" << m_gamespec.id << "\"";
311+
if (m_simple_singleplayer_mode)
312+
infostream << " in simple singleplayer mode" << std::endl;
186313
else
187-
infostream<<std::endl;
188-
infostream<<"- world: "<<m_path_world<<std::endl;
189-
infostream<<"- game: "<<m_gamespec.path<<std::endl;
314+
infostream << std::endl;
315+
infostream << "- world: " << m_path_world << std::endl;
316+
infostream << "- game: " << m_gamespec.path << std::endl;
190317

191318
// Create world if it doesn't exist
192-
if(!loadGameConfAndInitWorld(m_path_world, m_gamespec))
319+
if (!loadGameConfAndInitWorld(m_path_world, m_gamespec))
193320
throw ServerError("Failed to initialize world");
194321

195322
// Create server thread
@@ -202,8 +329,7 @@ Server::Server(
202329
std::string ban_path = m_path_world + DIR_DELIM "ipban.txt";
203330
m_banmanager = new BanManager(ban_path);
204331

205-
m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(
206-
m_path_world));
332+
m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world));
207333
std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods();
208334
// complain about mods with unsatisfied dependencies
209335
if (!m_modmgr->isConsistent()) {
@@ -214,10 +340,10 @@ Server::Server(
214340
MutexAutoLock envlock(m_env_mutex);
215341

216342
// Create the Map (loads map_meta.txt, overriding configured mapgen params)
217-
ServerMap *servermap = new ServerMap(path_world, this, m_emerge);
343+
ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge);
218344

219345
// Initialize scripting
220-
infostream<<"Server: Initializing Lua"<<std::endl;
346+
infostream << "Server: Initializing Lua" << std::endl;
221347

222348
m_script = new ServerScripting(this);
223349

@@ -279,74 +405,6 @@ Server::Server(
279405
m_csm_noderange_limit = g_settings->getU32("csm_flavour_noderange_limit");
280406
}
281407

282-
Server::~Server()
283-
{
284-
infostream << "Server destructing" << std::endl;
285-
286-
// Send shutdown message
287-
SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(CHATMESSAGE_TYPE_ANNOUNCE,
288-
L"*** Server shutting down"));
289-
290-
{
291-
MutexAutoLock envlock(m_env_mutex);
292-
293-
infostream << "Server: Saving players" << std::endl;
294-
m_env->saveLoadedPlayers();
295-
296-
infostream << "Server: Kicking players" << std::endl;
297-
std::string kick_msg;
298-
bool reconnect = false;
299-
if (getShutdownRequested()) {
300-
reconnect = m_shutdown_ask_reconnect;
301-
kick_msg = m_shutdown_msg;
302-
}
303-
if (kick_msg.empty()) {
304-
kick_msg = g_settings->get("kick_msg_shutdown");
305-
}
306-
m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN,
307-
kick_msg, reconnect);
308-
}
309-
310-
// Do this before stopping the server in case mapgen callbacks need to access
311-
// server-controlled resources (like ModStorages). Also do them before
312-
// shutdown callbacks since they may modify state that is finalized in a
313-
// callback.
314-
m_emerge->stopThreads();
315-
316-
{
317-
MutexAutoLock envlock(m_env_mutex);
318-
319-
// Execute script shutdown hooks
320-
infostream << "Executing shutdown hooks" << std::endl;
321-
m_script->on_shutdown();
322-
323-
infostream << "Server: Saving environment metadata" << std::endl;
324-
m_env->saveMeta();
325-
}
326-
327-
// Stop threads
328-
stop();
329-
delete m_thread;
330-
331-
// Delete things in the reverse order of creation
332-
delete m_emerge;
333-
delete m_env;
334-
delete m_rollback;
335-
delete m_banmanager;
336-
delete m_itemdef;
337-
delete m_nodedef;
338-
delete m_craftdef;
339-
340-
// Deinitialize scripting
341-
infostream << "Server: Deinitializing scripting" << std::endl;
342-
delete m_script;
343-
344-
// Delete detached inventories
345-
for (auto &detached_inventory : m_detached_inventories) {
346-
delete detached_inventory.second;
347-
}
348-
}
349-
350408
void Server::start()
351409
{
352410
infostream << "Starting server on " << m_bind_addr.serializeString()
@@ -916,38 +974,7 @@ void Server::AsyncRunStep(bool initial_step)
916974
}
917975
}
918976

919-
// Timed shutdown
920-
static const float shutdown_msg_times[] =
921-
{
922-
1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 45, 60, 120, 180, 300, 600, 1200, 1800, 3600
923-
};
924-
925-
if (m_shutdown_timer > 0.0f) {
926-
// Automated messages
927-
if (m_shutdown_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) {
928-
for (u16 i = 0; i < ARRLEN(shutdown_msg_times) - 1; i++) {
929-
// If shutdown timer matches an automessage, shot it
930-
if (m_shutdown_timer > shutdown_msg_times[i] &&
931-
m_shutdown_timer - dtime < shutdown_msg_times[i]) {
932-
std::wstringstream ws;
933-
934-
ws << L"*** Server shutting down in "
935-
<< duration_to_string(myround(m_shutdown_timer - dtime)).c_str()
936-
<< ".";
937-
938-
infostream << wide_to_utf8(ws.str()).c_str() << std::endl;
939-
SendChatMessage(PEER_ID_INEXISTENT, ws.str());
940-
break;
941-
}
942-
}
943-
}
944-
945-
m_shutdown_timer -= dtime;
946-
if (m_shutdown_timer < 0.0f) {
947-
m_shutdown_timer = 0.0f;
948-
m_shutdown_requested = true;
949-
}
950-
}
977+
m_shutdown_state.tick(dtime, this);
951978
}
952979

953980
void Server::Receive()
@@ -3398,16 +3425,13 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
33983425
{
33993426
if (delay == 0.0f) {
34003427
// No delay, shutdown immediately
3401-
m_shutdown_requested = true;
3428+
m_shutdown_state.is_requested = true;
34023429
// only print to the infostream, a chat message saying
34033430
// "Server Shutting Down" is sent when the server destructs.
34043431
infostream << "*** Immediate Server shutdown requested." << std::endl;
3405-
} else if (delay < 0.0f && m_shutdown_timer > 0.0f) {
3406-
// Negative delay, cancel shutdown if requested
3407-
m_shutdown_timer = 0.0f;
3408-
m_shutdown_msg = "";
3409-
m_shutdown_ask_reconnect = false;
3410-
m_shutdown_requested = false;
3432+
} else if (delay < 0.0f && m_shutdown_state.isTimerRunning()) {
3433+
// Negative delay, cancel shutdown if requested
3434+
m_shutdown_state.reset();
34113435
std::wstringstream ws;
34123436

34133437
ws << L"*** Server shutdown canceled.";
@@ -3428,9 +3452,7 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
34283452
SendChatMessage(PEER_ID_INEXISTENT, ws.str());
34293453
}
34303454

3431-
m_shutdown_timer = delay;
3432-
m_shutdown_msg = msg;
3433-
m_shutdown_ask_reconnect = reconnect;
3455+
m_shutdown_state.trigger(delay, msg, reconnect);
34343456
}
34353457

34363458
PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_version)
@@ -3518,7 +3540,7 @@ void dedicated_server_loop(Server &server, bool &kill)
35183540
}
35193541
server.step(steplen);
35203542

3521-
if (server.getShutdownRequested() || kill)
3543+
if (server.isShutdownRequested() || kill)
35223544
break;
35233545

35243546
/*

‎src/server.h

+21-7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
128128
~Server();
129129
DISABLE_CLASS_COPY(Server);
130130

131+
void init();
131132
void start();
132133
void stop();
133134
// This is mainly a way to pass the time to the server.
@@ -201,7 +202,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
201202
inline double getUptime() const { return m_uptime.m_value; }
202203

203204
// read shutdown state
204-
inline bool getShutdownRequested() const { return m_shutdown_requested; }
205+
inline bool isShutdownRequested() const { return m_shutdown_state.is_requested; }
205206

206207
// request server to shutdown
207208
void requestShutdown(const std::string &msg, bool reconnect, float delay = 0.0f);
@@ -348,9 +349,25 @@ class Server : public con::PeerHandler, public MapEventReceiver,
348349
std::mutex m_env_mutex;
349350

350351
private:
351-
352352
friend class EmergeThread;
353353
friend class RemoteClient;
354+
friend class TestServerShutdownState;
355+
356+
struct ShutdownState {
357+
friend class TestServerShutdownState;
358+
public:
359+
bool is_requested = false;
360+
bool should_reconnect = false;
361+
std::string message;
362+
363+
void reset();
364+
void trigger(float delay, const std::string &msg, bool reconnect);
365+
void tick(float dtime, Server *server);
366+
std::wstring getShutdownTimerMessage() const;
367+
bool isTimerRunning() const { return m_timer > 0.0f; }
368+
private:
369+
float m_timer = 0.0f;
370+
};
354371

355372
void SendMovement(session_t peer_id);
356373
void SendHP(session_t peer_id, u16 hp);
@@ -368,7 +385,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
368385
void SetBlocksNotSent(std::map<v3s16, MapBlock *>& block);
369386

370387

371-
void SendChatMessage(session_t peer_id, const ChatMessage &message);
388+
virtual void SendChatMessage(session_t peer_id, const ChatMessage &message);
372389
void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed);
373390
void SendPlayerHP(session_t peer_id);
374391

@@ -586,10 +603,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
586603
Random stuff
587604
*/
588605

589-
bool m_shutdown_requested = false;
590-
std::string m_shutdown_msg;
591-
bool m_shutdown_ask_reconnect = false;
592-
float m_shutdown_timer = 0.0f;
606+
ShutdownState m_shutdown_state;
593607

594608
ChatInterface *m_admin_chat;
595609
std::string m_admin_nick;

‎src/unittest/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set (UNITTEST_SRCS
2020
${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp
2121
${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp
2222
${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp
23+
${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp
2324
${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp
2425
${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp
2526
${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmap.cpp
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#include <server.h>
21+
#include "test.h"
22+
23+
#include "util/string.h"
24+
#include "util/serialize.h"
25+
26+
class FakeServer : public Server
27+
{
28+
public:
29+
// clang-format off
30+
FakeServer() : Server("fakeworld", SubgameSpec("fakespec", "fakespec"), true,
31+
Address(), true, nullptr)
32+
{
33+
}
34+
// clang-format on
35+
36+
private:
37+
void SendChatMessage(session_t peer_id, const ChatMessage &message)
38+
{
39+
// NOOP
40+
}
41+
};
42+
43+
class TestServerShutdownState : public TestBase
44+
{
45+
public:
46+
TestServerShutdownState() { TestManager::registerTestModule(this); }
47+
const char *getName() { return "TestServerShutdownState"; }
48+
49+
void runTests(IGameDef *gamedef);
50+
51+
void testInit();
52+
void testReset();
53+
void testTrigger();
54+
void testTick();
55+
};
56+
57+
static TestServerShutdownState g_test_instance;
58+
59+
void TestServerShutdownState::runTests(IGameDef *gamedef)
60+
{
61+
TEST(testInit);
62+
TEST(testReset);
63+
TEST(testTrigger);
64+
TEST(testTick);
65+
}
66+
67+
void TestServerShutdownState::testInit()
68+
{
69+
Server::ShutdownState ss;
70+
UASSERT(!ss.is_requested);
71+
UASSERT(!ss.should_reconnect);
72+
UASSERT(ss.message.empty());
73+
UASSERT(ss.m_timer == 0.0f);
74+
}
75+
76+
void TestServerShutdownState::testReset()
77+
{
78+
Server::ShutdownState ss;
79+
ss.reset();
80+
UASSERT(!ss.is_requested);
81+
UASSERT(!ss.should_reconnect);
82+
UASSERT(ss.message.empty());
83+
UASSERT(ss.m_timer == 0.0f);
84+
}
85+
86+
void TestServerShutdownState::testTrigger()
87+
{
88+
Server::ShutdownState ss;
89+
ss.trigger(3.0f, "testtrigger", true);
90+
UASSERT(!ss.is_requested);
91+
UASSERT(ss.should_reconnect);
92+
UASSERT(ss.message == "testtrigger");
93+
UASSERT(ss.m_timer == 3.0f);
94+
}
95+
96+
void TestServerShutdownState::testTick()
97+
{
98+
std::unique_ptr<FakeServer> fakeServer(new FakeServer());
99+
Server::ShutdownState ss;
100+
ss.trigger(28.0f, "testtrigger", true);
101+
ss.tick(0.0f, fakeServer.get());
102+
103+
// Tick with no time should not change anything
104+
UASSERT(!ss.is_requested);
105+
UASSERT(ss.should_reconnect);
106+
UASSERT(ss.message == "testtrigger");
107+
UASSERT(ss.m_timer == 28.0f);
108+
109+
// Tick 2 seconds
110+
ss.tick(2.0f, fakeServer.get());
111+
UASSERT(!ss.is_requested);
112+
UASSERT(ss.should_reconnect);
113+
UASSERT(ss.message == "testtrigger");
114+
UASSERT(ss.m_timer == 26.0f);
115+
116+
// Tick remaining seconds + additional expire
117+
ss.tick(26.1f, fakeServer.get());
118+
UASSERT(ss.is_requested);
119+
UASSERT(ss.should_reconnect);
120+
UASSERT(ss.message == "testtrigger");
121+
UASSERT(ss.m_timer == 0.0f);
122+
}

0 commit comments

Comments
 (0)
Please sign in to comment.