• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

traintastic / traintastic / 21101426787

17 Jan 2026 09:52PM UTC coverage: 28.005% (+0.01%) from 27.995%
21101426787

push

github

reinder
[traintastic] fixed restart crash (out of memory)

12 of 38 new or added lines in 11 files covered. (31.58%)

5 existing lines in 2 files now uncovered.

8165 of 29156 relevant lines covered (28.0%)

193.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/server/src/traintastic/traintastic.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2019-2026 Reinder Feenstra
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
 */
21

22
#include "traintastic.hpp"
23
#include <fstream>
24
#include <boost/uuid/nil_generator.hpp>
25
#include <boost/uuid/string_generator.hpp>
26
#include <boost/uuid/uuid_io.hpp>
27
#include <archive.h>
28
#include <zlib.h>
29
#include <version.hpp>
30
#include <traintastic/copyright.hpp>
31
#include <traintastic/utils/str.hpp>
32
#include "../core/eventloop.hpp"
33
#include "../network/server.hpp"
34
#include "../core/attributes.hpp"
35
#include "../core/method.tpp"
36
#include "../core/objectproperty.tpp"
37
#include "../world/world.hpp"
38
#include "../world/worldlist.hpp"
39
#include "../world/worldloader.hpp"
40
#include "../log/log.hpp"
41
#include "../log/logmessageexception.hpp"
42
#include "../lua/getversion.hpp"
43

44
using nlohmann::json;
45

46
constexpr std::string_view versionCopyrightAndLicense{
47
  "<h2>Traintastic v" TRAINTASTIC_VERSION " <small>"
48
#ifdef TRAINTASTIC_VERSION_EXTRA_NODASH
49
  TRAINTASTIC_VERSION_EXTRA_NODASH
50
#else
51
  TRAINTASTIC_CODENAME
52
#endif
53
  "</small></h2>"
54
  "<p>" TRAINTASTIC_COPYRIGHT "</p>"
55
  "<p>This program is free software; you can redistribute it and/or"
56
  " modify it under the terms of the GNU General Public License"
57
  " as published by the Free Software Foundation; either version 2"
58
  " of the License, or (at your option) any later version.</p>"
59
  "<p>This program is distributed in the hope that it will be useful,"
60
  " but WITHOUT ANY WARRANTY; without even the implied warranty of"
61
  " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
62
  " GNU General Public License for more details.</p>"
63
  "<p><a href=\"https://traintastic.org\">traintastic.org</a></p>"
64
};
65

66
std::shared_ptr<Traintastic> Traintastic::instance;
67

68
Traintastic::Traintastic(const std::filesystem::path& dataDir) :
×
69
  m_restart{false},
×
70
  m_dataDir{std::filesystem::absolute(dataDir)},
×
NEW
71
  m_signalSet(EventLoop::ioContext()),
×
72
  about{this, "about", std::string(versionCopyrightAndLicense), PropertyFlags::ReadOnly},
×
73
  settings{this, "settings", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
×
74
  version{this, "version", TRAINTASTIC_VERSION_FULL, PropertyFlags::ReadOnly},
×
75
  world{this, "world", nullptr, PropertyFlags::ReadWrite,
×
76
    [this](const std::shared_ptr<World>& /*newWorld*/)
×
77
    {
78
      if(world)
×
79
        world->destroy();
×
80
      return true;
×
81
    }},
82
  worldList{this, "world_list", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
×
83
  newWorld{*this, "new_world",
×
84
    [this]()
×
85
    {
86
#ifndef NDEBUG
87
      std::weak_ptr<World> weakWorld = world.value();
×
88
#endif
89
      world = World::create();
×
90
#ifndef NDEBUG
91
      assert(weakWorld.expired());
×
92
#endif
93
      Log::log(*this, LogMessage::N1002_CREATED_NEW_WORLD);
×
94
      world->edit = true;
×
95
      settings->lastWorld = "";
×
96
    }},
×
97
  loadWorld{*this, "load_world",
×
98
    [this](const std::string& _uuid)
×
99
    {
100
      boost::uuids::uuid uuid;
101
      try
102
      {
103
        uuid = boost::uuids::string_generator()(_uuid);
×
104
      }
105
      catch(const std::exception&)
×
106
      {
107
        uuid = boost::uuids::nil_generator()();
×
108
        Log::log(*this, LogMessage::E1001_INVALID_WORLD_UUID_X, _uuid);
×
109
      }
×
110

111
      if(!uuid.is_nil())
×
112
        loadWorldUUID(uuid);
×
113
    }},
×
114
  closeWorld{*this, "close_world",
×
115
    [this]()
×
116
    {
117
#ifndef NDEBUG
118
      std::weak_ptr<World> weakWorld = world.value();
×
119
#endif
120
      world = nullptr;
×
121
#ifndef NDEBUG
122
      assert(weakWorld.expired());
×
123
#endif
124
      settings->lastWorld = "";
×
125
      Log::log(*this, LogMessage::N1028_CLOSED_WORLD);
×
126
    }},
×
127
  restart{*this, "restart",
×
128
    [this]()
×
129
    {
130
      if(Attributes::getEnabled(restart))
×
131
      {
132
        m_restart = true;
×
133
        exit();
×
134
      }
135
    }},
×
136
  shutdown{*this, "shutdown",
×
137
    [this]()
×
138
    {
139
      if(Attributes::getEnabled(shutdown))
×
140
        exit();
×
141
    }}
×
142
{
143
  if(!std::filesystem::is_directory(m_dataDir))
×
144
    std::filesystem::create_directories(m_dataDir);
×
145

146
  //Register signal handlers to shutdown gracefully
147
  m_signalSet.add(SIGINT);
×
148
  m_signalSet.add(SIGTERM);
×
149
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
150
  m_signalSet.add(SIGBREAK); //Windows uses SIGBREAK instead of SIGTERM
151
#endif
152

153
  m_signalSet.async_wait(&Traintastic::signalHandler);
×
154

155
  m_interfaceItems.add(about);
×
156
  m_interfaceItems.add(settings);
×
157
  m_interfaceItems.add(version);
×
158
  m_interfaceItems.add(world);
×
159
  m_interfaceItems.add(worldList);
×
160
  m_interfaceItems.add(newWorld);
×
161
  m_interfaceItems.add(loadWorld);
×
162
  m_interfaceItems.add(closeWorld);
×
163
  Attributes::addEnabled(restart, false);
×
164
  m_interfaceItems.add(restart);
×
165
  Attributes::addEnabled(shutdown, false);
×
166
  m_interfaceItems.add(shutdown);
×
167
}
×
168

169
void Traintastic::importWorld(const std::vector<std::byte>& worldData)
×
170
{
171
  try
172
  {
173
#ifndef NDEBUG
174
    std::weak_ptr<World> weakWorld = world.value();
×
175
#endif
176
    world = WorldLoader(worldData).world();
×
177
#ifndef NDEBUG
178
    assert(weakWorld.expired());
×
179
#endif
180
    Log::log(*this, LogMessage::N1026_IMPORTED_WORLD_SUCCESSFULLY);
×
181
  }
×
182
  catch(const LogMessageException& e)
×
183
  {
184
    throw e;
×
185
  }
×
186
  catch(const std::exception& e)
×
187
  {
188
    throw LogMessageException(LogMessage::C1011_IMPORTING_WORLD_FAILED_X, e.what());
×
189
  }
×
190
}
×
191

192
Traintastic::RunStatus Traintastic::run(const std::string& worldUUID, bool simulate, bool online, bool power, bool run)
×
193
{
194
  static const std::string boostVersion = std::string("boost ").append(std::to_string(BOOST_VERSION / 100000)).append(".").append(std::to_string(BOOST_VERSION / 100 % 100)).append(".").append(std::to_string(BOOST_VERSION % 100));
×
195
  Log::log(*this, LogMessage::I1001_TRAINTASTIC_VX, std::string_view{TRAINTASTIC_VERSION_FULL});
×
196
  Log::log(*this, LogMessage::I1006_X, boostVersion);
×
197
  Log::log(*this, LogMessage::I1007_X, std::string_view{"nlohmann::json " STR(NLOHMANN_JSON_VERSION_MAJOR) "." STR(NLOHMANN_JSON_VERSION_MINOR) "." STR(NLOHMANN_JSON_VERSION_PATCH)});
×
198
  Log::log(*this, LogMessage::I1008_X, std::string_view{archive_version_details()});
×
199
  Log::log(*this, LogMessage::I1009_ZLIB_X, std::string_view{zlibVersion()});
×
200
  Log::log(*this, LogMessage::I9002_X, Lua::getVersion());
×
201

202
  settings = std::make_shared<Settings>(m_dataDir);
×
203
  Attributes::setEnabled(restart, settings->allowClientServerRestart);
×
204
  Attributes::setEnabled(shutdown, settings->allowClientServerShutdown);
×
205

206
  worldList = std::make_shared<WorldList>(worldDir());
×
207

208
  if(!worldUUID.empty())
×
209
  {
210
    loadWorld(worldUUID);
×
211
    if(!world)
×
212
      return ExitFailure;
×
213
  }
214
  else if(settings->loadLastWorldOnStartup && !settings->lastWorld.value().empty())
×
215
    loadWorld(settings->lastWorld.value());
×
216

217
  try
218
  {
219
    m_server = std::make_shared<Server>(
×
220
#ifndef NO_LOCALHOST_ONLY_SETTING
221
      settings->localhostOnly,
×
222
#else
223
      false,
224
#endif
225
      settings->port, settings->discoverable);
×
226
  }
227
  catch(const LogMessageException& e)
×
228
  {
229
    Log::log(Server::id, e.message(), e.args());
×
230
    return ExitFailure;
×
231
  }
×
232

233
  if(world)
×
234
  {
235
    if(simulate)
×
236
      world->simulation = true;
×
237

238
    if(online)
×
239
      world->online();
×
240

241
    if(power && run)
×
242
      world->run();
×
243
    else if(power)
×
244
      world->powerOn();
×
245
  }
246

247
  try
248
  {
249
    EventLoop::exec();
×
250
  }
251
  catch(const std::exception& e)
×
252
  {
253
    Log::log(id, LogMessage::F1008_EVENTLOOP_CRASHED_X, e.what());
×
254
    return ExitFailure;
×
255
  }
×
256

257
  return m_restart ? Restart : ExitSuccess;
×
258
}
259

260
void Traintastic::exit()
×
261
{
NEW
262
  m_signalSet.cancel();
×
263

264
  if(m_restart)
×
265
    Log::log(*this, LogMessage::N1003_RESTARTING);
×
266
  else
267
    Log::log(*this, LogMessage::N1004_SHUTTING_DOWN);
×
268

269
  if(settings->autoSaveWorldOnExit && world)
×
270
    world->save();
×
271

272
  EventLoop::stop();
×
273
}
×
274

275
void Traintastic::loadWorldUUID(const boost::uuids::uuid& uuid)
×
276
{
277
  if(const WorldList::WorldInfo* info = worldList->find(uuid))
×
278
    loadWorldPath(info->path);
×
279
  else
280
    Log::log(*this, LogMessage::E1002_WORLD_X_DOESNT_EXIST, to_string(uuid));
×
281
}
×
282

283
void Traintastic::loadWorldPath(const std::filesystem::path& path)
×
284
{
285
  try
286
  {
287
#ifndef NDEBUG
288
    std::weak_ptr<World> weakWorld = world.value();
×
289
#endif
290
    world = WorldLoader(path).world();
×
291
#ifndef NDEBUG
292
    assert(weakWorld.expired());
×
293
#endif
294
    settings->lastWorld = world->uuid.value();
×
295
    Log::log(*this, LogMessage::N1027_LOADED_WORLD_X, world->name.value());
×
296

297
    if(world->onlineWhenLoaded)
×
298
    {
299
      world->online();
×
300
    }
301

302
    if(world->powerOnWhenLoaded)
×
303
    {
304
      if(world->runWhenLoaded)
×
305
      {
306
        world->run();
×
307
      }
308
      else
309
      {
310
        world->powerOn();
×
311
      }
312
    }
313

314
  }
×
315
  catch(const LogMessageException& e)
×
316
  {
317
    Log::log(*this, e.message(), e.args());
×
318
  }
×
319
  catch(const std::exception& e)
×
320
  {
321
    Log::log(*this, LogMessage::C1001_LOADING_WORLD_FAILED_X, e.what());
×
322
  }
×
323
}
×
324

325
void Traintastic::signalHandler(const boost::system::error_code& ec, int signalNumber)
×
326
{
327
  if(ec)
×
328
    return;
×
329

330
#define SIGNAL_NAME_CASE(x) \
331
  {\
332
    case x:\
333
      val = #x;\
334
      break;\
335
  }
336

337
  const char* val = "Unknown signal";
×
338
  switch(signalNumber)
×
339
  {
340
    SIGNAL_NAME_CASE(SIGINT)
×
341
    SIGNAL_NAME_CASE(SIGTERM)
×
342
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
343
    SIGNAL_NAME_CASE(SIGBREAK); //Windows uses SIGBREAK instead of SIGTERM
344
#endif
345
  }
346

347
#undef SIGNAL_NAME_CASE
348

349
  Log::log(*Traintastic::instance, LogMessage::N1001_RECEIVED_SIGNAL_X, std::string_view{val});
×
350
  instance->exit();
×
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc