Shutdown.java 18 KB

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