Shutdown.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /*
  2. * Copyright © 2004-2019 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver;
  20. import java.util.concurrent.TimeUnit;
  21. import org.slf4j.Logger;
  22. import org.slf4j.LoggerFactory;
  23. import com.l2jserver.gameserver.config.Config;
  24. import com.l2jserver.UPnPService;
  25. import com.l2jserver.commons.database.ConnectionFactory;
  26. import com.l2jserver.gameserver.data.sql.impl.ClanTable;
  27. import com.l2jserver.gameserver.data.sql.impl.OfflineTradersTable;
  28. import com.l2jserver.gameserver.datatables.BotReportTable;
  29. import com.l2jserver.gameserver.instancemanager.ClanHallSiegeManager;
  30. import com.l2jserver.gameserver.instancemanager.CastleManorManager;
  31. import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
  32. import com.l2jserver.gameserver.instancemanager.GlobalVariablesManager;
  33. import com.l2jserver.gameserver.instancemanager.GrandBossManager;
  34. import com.l2jserver.gameserver.instancemanager.ItemAuctionManager;
  35. import com.l2jserver.gameserver.instancemanager.ItemsOnGroundManager;
  36. import com.l2jserver.gameserver.instancemanager.QuestManager;
  37. import com.l2jserver.gameserver.instancemanager.RaidBossSpawnManager;
  38. import com.l2jserver.gameserver.model.L2World;
  39. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  40. import com.l2jserver.gameserver.model.entity.Hero;
  41. import com.l2jserver.gameserver.model.olympiad.Olympiad;
  42. import com.l2jserver.gameserver.network.L2GameClient;
  43. import com.l2jserver.gameserver.network.SystemMessageId;
  44. import com.l2jserver.gameserver.network.gameserverpackets.ServerStatus;
  45. import com.l2jserver.gameserver.network.serverpackets.ServerClose;
  46. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  47. import com.l2jserver.gameserver.util.Broadcast;
  48. /**
  49. * This class provides the functions for shutting down and restarting the server.<br>
  50. * It closes all open client connections and saves all data.
  51. * @version $Revision: 1.2.4.5 $ $Date: 2005/03/27 15:29:09 $
  52. */
  53. public class Shutdown extends Thread {
  54. private static final Logger LOG = LoggerFactory.getLogger(Shutdown.class);
  55. private static Shutdown _counterInstance = null;
  56. private int _secondsShut;
  57. private int _shutdownMode;
  58. private static final int SIGTERM = 0;
  59. private static final int GM_SHUTDOWN = 1;
  60. private static final int GM_RESTART = 2;
  61. private static final int ABORT = 3;
  62. private static final String[] MODE_TEXT = {
  63. "SIGTERM",
  64. "shutting down",
  65. "restarting",
  66. "aborting"
  67. };
  68. /**
  69. * This function starts a shutdown count down from Telnet (Copied from Function startShutdown())
  70. * @param seconds seconds until shutdown
  71. */
  72. private void SendServerQuit(int seconds) {
  73. SystemMessage sysm = SystemMessage.getSystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECONDS);
  74. sysm.addInt(seconds);
  75. Broadcast.toAllOnlinePlayers(sysm);
  76. }
  77. public void startTelnetShutdown(String IP, int seconds, boolean restart) {
  78. LOG.warn("IP: {} issued shutdown command. {} in {} seconds!", IP, MODE_TEXT[_shutdownMode], seconds);
  79. if (restart) {
  80. _shutdownMode = GM_RESTART;
  81. } else {
  82. _shutdownMode = GM_SHUTDOWN;
  83. }
  84. if (_shutdownMode > 0) {
  85. switch (seconds) {
  86. case 540:
  87. case 480:
  88. case 420:
  89. case 360:
  90. case 300:
  91. case 240:
  92. case 180:
  93. case 120:
  94. case 60:
  95. case 30:
  96. case 10:
  97. case 5:
  98. case 4:
  99. case 3:
  100. case 2:
  101. case 1:
  102. break;
  103. default:
  104. SendServerQuit(seconds);
  105. }
  106. }
  107. if (_counterInstance != null) {
  108. _counterInstance._abort();
  109. }
  110. _counterInstance = new Shutdown(seconds, restart);
  111. _counterInstance.start();
  112. }
  113. /**
  114. * This function aborts a running countdown
  115. * @param IP IP Which Issued shutdown command
  116. */
  117. public void telnetAbort(String IP) {
  118. LOG.warn("IP: {} issued shutdown ABORT. {} has been stopped!", IP, MODE_TEXT[_shutdownMode]);
  119. if (_counterInstance != null) {
  120. _counterInstance._abort();
  121. Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
  122. }
  123. }
  124. /**
  125. * Default constructor is only used internal to create the shutdown-hook instance
  126. */
  127. protected Shutdown() {
  128. _secondsShut = -1;
  129. _shutdownMode = SIGTERM;
  130. }
  131. /**
  132. * This creates a countdown instance of Shutdown.
  133. * @param seconds how many seconds until shutdown
  134. * @param restart true is the server shall restart after shutdown
  135. */
  136. public Shutdown(int seconds, boolean restart) {
  137. if (seconds < 0) {
  138. seconds = 0;
  139. }
  140. _secondsShut = seconds;
  141. if (restart) {
  142. _shutdownMode = GM_RESTART;
  143. } else {
  144. _shutdownMode = GM_SHUTDOWN;
  145. }
  146. }
  147. /**
  148. * This function is called, when a new thread starts if this thread is the thread of getInstance, then this is the shutdown hook and we save all data and disconnect all clients.<br>
  149. * After this thread ends, the server will completely exit if this is not the thread of getInstance, then this is a countdown thread.<br>
  150. * We start the countdown, and when we finished it, and it was not aborted, we tell the shutdown-hook why we call exit, and then call exit when the exit status of the server is 1, startServer.sh / startServer.bat will restart the server.
  151. */
  152. @Override
  153. public void run() {
  154. if (this == getInstance()) {
  155. TimeCounter tc = new TimeCounter();
  156. TimeCounter tc1 = new TimeCounter();
  157. try {
  158. UPnPService.getInstance().removeAllPorts();
  159. LOG.info("UPnP Service: All ports mappings deleted ({}ms).", tc.getEstimatedTimeAndRestartCounter());
  160. } catch (Exception e) {
  161. LOG.warn("Error while removing UPnP port mappings!", e);
  162. }
  163. try {
  164. if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS) {
  165. OfflineTradersTable.getInstance().storeOffliners();
  166. LOG.info("Offline Traders Table: Offline shops stored({}ms).", tc.getEstimatedTimeAndRestartCounter());
  167. }
  168. } catch (Exception e) {
  169. LOG.warn("Error saving offline shops!", e);
  170. }
  171. try {
  172. disconnectAllCharacters();
  173. LOG.info("All players disconnected and saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  174. } catch (Exception e) {
  175. // ignore
  176. }
  177. // ensure all services are stopped
  178. try {
  179. GameTimeController.getInstance().stopTimer();
  180. LOG.info("Game Time Controller: Timer stopped({}ms).", tc.getEstimatedTimeAndRestartCounter());
  181. } catch (Exception e) {
  182. // ignore
  183. }
  184. // stop all thread pools
  185. try {
  186. ThreadPoolManager.getInstance().shutdown();
  187. LOG.info("Thread Pool Manager: Manager has been shut down({}ms).", tc.getEstimatedTimeAndRestartCounter());
  188. } catch (Exception e) {
  189. // ignore
  190. }
  191. try {
  192. LoginServerThread.getInstance().interrupt();
  193. LOG.info("Login Server Thread: Thread interruped({}ms).", tc.getEstimatedTimeAndRestartCounter());
  194. } catch (Exception e) {
  195. // ignore
  196. }
  197. // last byebye, save all data and quit this server
  198. saveData();
  199. tc.restartCounter();
  200. // saveData sends messages to exit players, so shutdown selector after it
  201. try {
  202. GameServer.gameServer.getSelectorThread().shutdown();
  203. LOG.info("Game Server: Selector thread has been shut down({}ms).", tc.getEstimatedTimeAndRestartCounter());
  204. } catch (Exception e) {
  205. // ignore
  206. }
  207. // commit data, last chance
  208. ConnectionFactory.getInstance().close();
  209. LOG.info("ConnectionFactory: Database connection has been shutdown({}ms).", tc.getEstimatedTimeAndRestartCounter());
  210. // server will quit, when this function ends.
  211. if (getInstance()._shutdownMode == GM_RESTART) {
  212. Runtime.getRuntime().halt(2);
  213. } else {
  214. Runtime.getRuntime().halt(0);
  215. }
  216. LOG.info("The server has been successfully shut down in {}seconds.", TimeUnit.MILLISECONDS.toSeconds(tc1.getEstimatedTime()));
  217. } else {
  218. // gm shutdown: send warnings and then call exit to start shutdown sequence
  219. countdown();
  220. // last point where logging is operational :(
  221. LOG.warn("GM shutdown countdown is over. {} NOW!", MODE_TEXT[_shutdownMode]);
  222. switch (_shutdownMode) {
  223. case GM_SHUTDOWN:
  224. getInstance().setMode(GM_SHUTDOWN);
  225. System.exit(0);
  226. break;
  227. case GM_RESTART:
  228. getInstance().setMode(GM_RESTART);
  229. System.exit(2);
  230. break;
  231. case ABORT:
  232. LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_AUTO);
  233. break;
  234. }
  235. }
  236. }
  237. /**
  238. * This functions starts a shutdown countdown.
  239. * @param activeChar GM who issued the shutdown command
  240. * @param seconds seconds until shutdown
  241. * @param restart true if the server will restart after shutdown
  242. */
  243. public void startShutdown(L2PcInstance activeChar, int seconds, boolean restart) {
  244. if (restart) {
  245. _shutdownMode = GM_RESTART;
  246. } else {
  247. _shutdownMode = GM_SHUTDOWN;
  248. }
  249. LOG.warn("GM: {}({}) issued shutdown command. {} in {} seconds!", activeChar.getName(), activeChar.getObjectId(), MODE_TEXT[_shutdownMode], seconds);
  250. if (_shutdownMode > 0) {
  251. switch (seconds) {
  252. case 540:
  253. case 480:
  254. case 420:
  255. case 360:
  256. case 300:
  257. case 240:
  258. case 180:
  259. case 120:
  260. case 60:
  261. case 30:
  262. case 10:
  263. case 5:
  264. case 4:
  265. case 3:
  266. case 2:
  267. case 1:
  268. break;
  269. default:
  270. SendServerQuit(seconds);
  271. }
  272. }
  273. if (_counterInstance != null) {
  274. _counterInstance._abort();
  275. }
  276. // the main instance should only run for shutdown hook, so we start a new instance
  277. _counterInstance = new Shutdown(seconds, restart);
  278. _counterInstance.start();
  279. }
  280. /**
  281. * This function aborts a running countdown.
  282. * @param activeChar GM who issued the abort command
  283. */
  284. public void abort(L2PcInstance activeChar) {
  285. LOG.warn("GM: {}({}) issued shutdown ABORT. {} has been stopped!", activeChar.getName(), activeChar.getObjectId(), MODE_TEXT[_shutdownMode]);
  286. if (_counterInstance != null) {
  287. _counterInstance._abort();
  288. Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
  289. }
  290. }
  291. /**
  292. * Set the shutdown mode.
  293. * @param mode what mode shall be set
  294. */
  295. private void setMode(int mode) {
  296. _shutdownMode = mode;
  297. }
  298. /**
  299. * Set shutdown mode to ABORT.
  300. */
  301. private void _abort() {
  302. _shutdownMode = ABORT;
  303. }
  304. /**
  305. * This counts the countdown and reports it to all players countdown is aborted if mode changes to ABORT.
  306. */
  307. private void countdown() {
  308. try {
  309. while (_secondsShut > 0) {
  310. switch (_secondsShut) {
  311. case 540:
  312. SendServerQuit(540);
  313. break;
  314. case 480:
  315. SendServerQuit(480);
  316. break;
  317. case 420:
  318. SendServerQuit(420);
  319. break;
  320. case 360:
  321. SendServerQuit(360);
  322. break;
  323. case 300:
  324. SendServerQuit(300);
  325. break;
  326. case 240:
  327. SendServerQuit(240);
  328. break;
  329. case 180:
  330. SendServerQuit(180);
  331. break;
  332. case 120:
  333. SendServerQuit(120);
  334. break;
  335. case 60:
  336. LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN); // avoids new players from logging in
  337. SendServerQuit(60);
  338. break;
  339. case 30:
  340. SendServerQuit(30);
  341. break;
  342. case 10:
  343. SendServerQuit(10);
  344. break;
  345. case 5:
  346. SendServerQuit(5);
  347. break;
  348. case 4:
  349. SendServerQuit(4);
  350. break;
  351. case 3:
  352. SendServerQuit(3);
  353. break;
  354. case 2:
  355. SendServerQuit(2);
  356. break;
  357. case 1:
  358. SendServerQuit(1);
  359. break;
  360. }
  361. _secondsShut--;
  362. int delay = 1000; // milliseconds
  363. Thread.sleep(delay);
  364. if (_shutdownMode == ABORT) {
  365. break;
  366. }
  367. }
  368. } catch (InterruptedException e) {
  369. // this will never happen
  370. }
  371. }
  372. /**
  373. * This sends a last byebye, disconnects all players and saves data.
  374. */
  375. private void saveData() {
  376. switch (_shutdownMode) {
  377. case SIGTERM:
  378. LOG.info("SIGTERM received. Shutting down NOW!");
  379. break;
  380. case GM_SHUTDOWN:
  381. LOG.info("GM shutdown received. Shutting down NOW!");
  382. break;
  383. case GM_RESTART:
  384. LOG.info("GM restart received. Restarting NOW!");
  385. break;
  386. }
  387. TimeCounter tc = new TimeCounter();
  388. // Seven Signs data is now saved along with Festival data.
  389. if (!SevenSigns.getInstance().isSealValidationPeriod()) {
  390. SevenSignsFestival.getInstance().saveFestivalData(false);
  391. LOG.info("SevenSignsFestival: Festival data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  392. }
  393. // Save Seven Signs data before closing. :)
  394. SevenSigns.getInstance().saveSevenSignsData();
  395. LOG.info("SevenSigns: Seven Signs data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  396. SevenSigns.getInstance().saveSevenSignsStatus();
  397. LOG.info("SevenSigns: Seven Signs status saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  398. // Save all raidboss and GrandBoss status ^_^
  399. RaidBossSpawnManager.getInstance().cleanUp();
  400. LOG.info("RaidBossSpawnManager: All raidboss info saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  401. GrandBossManager.getInstance().cleanUp();
  402. LOG.info("GrandBossManager: All Grand Boss info saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  403. ItemAuctionManager.getInstance().shutdown();
  404. LOG.info("Item Auction Manager: All tasks stopped({}ms).", tc.getEstimatedTimeAndRestartCounter());
  405. Olympiad.getInstance().saveOlympiadStatus();
  406. LOG.info("Olympiad System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  407. Hero.getInstance().shutdown();
  408. LOG.info("Hero System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  409. ClanTable.getInstance().storeClanScore();
  410. LOG.info("Clan System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  411. // Save Cursed Weapons data before closing.
  412. CursedWeaponsManager.getInstance().saveData();
  413. LOG.info("Cursed Weapons Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  414. // Save all manor data
  415. if (!Config.ALT_MANOR_SAVE_ALL_ACTIONS) {
  416. CastleManorManager.getInstance().storeMe();
  417. LOG.info("Castle Manor Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  418. }
  419. ClanHallSiegeManager.getInstance().onServerShutDown();
  420. LOG.info("CHSiegeManager: Siegable hall attacker lists saved!");
  421. // Save all global (non-player specific) Quest data that needs to persist after reboot
  422. QuestManager.getInstance().save();
  423. LOG.info("Quest Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  424. // Save all global variables data
  425. GlobalVariablesManager.getInstance().storeMe();
  426. LOG.info("Global Variables Manager: Variables saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  427. // Save items on ground before closing
  428. if (Config.SAVE_DROPPED_ITEM) {
  429. ItemsOnGroundManager.getInstance().saveInDb();
  430. LOG.info("Items On Ground Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
  431. ItemsOnGroundManager.getInstance().cleanUp();
  432. LOG.info("Items On Ground Manager: Cleaned up({}ms).", tc.getEstimatedTimeAndRestartCounter());
  433. }
  434. // Save bot reports to database
  435. if (Config.BOTREPORT_ENABLE) {
  436. BotReportTable.getInstance().saveReportedCharData();
  437. LOG.info("Bot Report Table: Successfully saved reports to database!");
  438. }
  439. try {
  440. Thread.sleep(5000);
  441. } catch (InterruptedException e) {
  442. // never happens :p
  443. }
  444. }
  445. /**
  446. * This disconnects all clients from the server.
  447. */
  448. private void disconnectAllCharacters() {
  449. for (L2PcInstance player : L2World.getInstance().getPlayers()) {
  450. // Logout Character
  451. try {
  452. L2GameClient client = player.getClient();
  453. if ((client != null) && !client.isDetached()) {
  454. client.close(ServerClose.STATIC_PACKET);
  455. client.setActiveChar(null);
  456. player.setClient(null);
  457. }
  458. player.deleteMe();
  459. } catch (Exception e) {
  460. LOG.warn("Failed logour char {}", player, e);
  461. }
  462. }
  463. }
  464. /**
  465. * A simple class used to track down the estimated time of method executions.<br>
  466. * Once this class is created, it saves the start time, and when you want to get the estimated time, use the getEstimatedTime() method.
  467. */
  468. private static final class TimeCounter {
  469. private long _startTime;
  470. protected TimeCounter() {
  471. restartCounter();
  472. }
  473. protected void restartCounter() {
  474. _startTime = System.currentTimeMillis();
  475. }
  476. protected long getEstimatedTimeAndRestartCounter() {
  477. final long toReturn = System.currentTimeMillis() - _startTime;
  478. restartCounter();
  479. return toReturn;
  480. }
  481. protected long getEstimatedTime() {
  482. return System.currentTimeMillis() - _startTime;
  483. }
  484. }
  485. /**
  486. * Get the shutdown-hook instance the shutdown-hook instance is created by the first call of this function, but it has to be registered externally.<br>
  487. * @return instance of Shutdown, to be used as shutdown hook
  488. */
  489. public static Shutdown getInstance() {
  490. return SingletonHolder._instance;
  491. }
  492. private static class SingletonHolder {
  493. protected static final Shutdown _instance = new Shutdown();
  494. }
  495. }