Server keeps running out of memory

Discussion in 'Plugin Development' started by jeussa, Nov 25, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    jeussa

    Hi dere everyone,

    I've got a little issue, I'm working on a server network and in order for the servers to communicate I pass messaged between them using the following class:

    Code:java
    1. package net.invasioncraft.api.network;
    2.  
    3. import java.io.DataInputStream;
    4. import java.io.DataOutputStream;
    5. import java.io.IOException;
    6. import java.lang.reflect.Method;
    7. import java.net.Socket;
    8. import java.util.ArrayList;
    9. import java.util.HashMap;
    10. import java.util.List;
    11. import java.util.Map;
    12.  
    13. import org.bukkit.Bukkit;
    14.  
    15. import net.invasioncraft.api.icAPI;
    16. import net.invasioncraft.api.config.VirtualConfigFile;
    17. import net.invasioncraft.api.network.events.DatabridgeCloseEvent;
    18. import net.invasioncraft.api.network.events.DatabridgePackageReceiveEvent;
    19. import net.invasioncraft.api.network.events.DatabridgePackageSendEvent;
    20. import net.invasioncraft.api.network.events.DatabridgeCloseEvent.Reason;
    21. import net.invasioncraft.api.network.events.DatabridgePingEvent;
    22. import net.invasioncraft.api.network.packages.ConfigPackage;
    23. import net.invasioncraft.api.network.packages.StringPackage;
    24. import net.invasioncraft.api.network.packages.icPackage;
    25.  
    26. public class DataBridge extends Thread{
    27.  
    28. private static String leaveMessage = "_socketClosed_",
    29. pingMessage = "_socketPing_",
    30. pingConfirmMessage = "_socketConfirmPing_",
    31. data = "_socketData_";
    32. private static char split = '|';
    33.  
    34. private static List<DataBridge> list = new ArrayList<DataBridge>();
    35. private static List<Class<? extends icPackage>> packageTypes = new ArrayList<Class<? extends icPackage>>();
    36.  
    37. private Socket socket;
    38. private DataInputStream input;
    39. private DataOutputStream output;
    40. private BridgeType type;
    41.  
    42. private boolean reading;
    43. private Map<String, Integer> ping;
    44.  
    45. public DataBridge(Socket socket, BridgeType type) throws IOException{
    46. this.socket = socket;
    47.  
    48. input = new DataInputStream(socket.getInputStream());
    49. output = new DataOutputStream(socket.getOutputStream());
    50.  
    51. this.type = type;
    52.  
    53. reading = true;
    54. ping = new HashMap<String, Integer>();
    55.  
    56. list.add(this);
    57. this.start();
    58. }
    59.  
    60. public void run(){
    61. while(true){
    62. if(reading){
    63. try{
    64. String message = input.readUTF();
    65. if(message == null)return;
    66.  
    67. final String[] messages = icAPI.stringUtil.split(message, split);
    68.  
    69. if(messages.length <= 0)return;
    70. String prefix = messages[0];
    71.  
    72. if(prefix.equals(leaveMessage)){
    73. DatabridgeCloseEvent event = new DatabridgeCloseEvent(this, Reason.REMOTE_SHUTDOWN);
    74. Bukkit.getPluginManager().callEvent(event);
    75. icAPI.console.info("Received leave-message from remote-address " + socket.getRemoteSocketAddress());
    76.  
    77. reading = false;
    78.  
    79. list.remove(this);
    80. input.close();
    81. output.close();
    82. socket.close();
    83. }
    84. else if(prefix.equals(pingMessage)){
    85. sendRaw(pingConfirmMessage + split + messages[1]);
    86. }
    87. else if(prefix.equals(pingConfirmMessage)){
    88. if(!ping.containsKey(messages[1]))return;
    89. rp(messages[1], ping.get(messages[1]));
    90. }else{
    91. String[] msgs = new String[messages.length-2];
    92. String typeId = messages[1];
    93.  
    94. for(int n = 0; n < msgs.length; n ++){
    95. msgs[n] = messages[n+2];
    96. }
    97.  
    98. if(prefix.equals(data)){
    99. icPackage icp = null;
    100. for(Class<? extends icPackage> cp : packageTypes){
    101. if(icp != null)break;
    102.  
    103. try{
    104. String s = (String)pid(cp);
    105.  
    106. if(s.equals(typeId)){
    107. Method a = cp.getDeclaredMethod("convert", new Class<?>[]{String[].class});
    108. a.setAccessible(true);
    109. icp = (icPackage)a.invoke(null, (Object)msgs);
    110. }
    111. }catch(Exception e){
    112. e.printStackTrace();
    113. }
    114. }
    115.  
    116. if(icp == null){
    117. icAPI.console.warn("Failed to get packageType! Given value: '" + typeId + "'");
    118. icAPI.console.warn("Not responding to message ...");
    119. }else{
    120. DatabridgePackageReceiveEvent event = new DatabridgePackageReceiveEvent(this, icp);
    121. Bukkit.getPluginManager().callEvent(event);
    122. }
    123. }else{
    124. icAPI.console.warn("==================================================");
    125. icAPI.console.warn("Invalid messageType registered at " + getClass().getPackage() + getClass().getName());
    126. icAPI.console.warn("Registered messageType: " + prefix);
    127. icAPI.console.warn("Not responding to message ...");
    128. icAPI.console.warn("==================================================");
    129. }
    130. }
    131. }catch(IOException e){
    132. }catch(Exception e){
    133. e.printStackTrace();
    134. }
    135. }
    136. }
    137. }
    138.  
    139. /**
    140.   * Closes the dataBridge
    141.   */
    142. public void close(){
    143. close(Reason.LOCAL_SHUTDOWN);
    144. }
    145. public void close(Reason reason){
    146. try{
    147. icAPI.console.info("Ending connection with address: " + socket.getRemoteSocketAddress());
    148. icAPI.console.info("Reason: " + reason.toString().toLowerCase());
    149. output.writeUTF(leaveMessage);
    150.  
    151. reading = false;
    152.  
    153. DatabridgeCloseEvent event = new DatabridgeCloseEvent(this, reason);
    154. Bukkit.getPluginManager().callEvent(event);
    155.  
    156. list.remove(this);
    157. input.close();
    158. output.close();
    159. socket.close();
    160. }
    161. catch(IOException e){
    162. icAPI.console.error("Failed to close dataBridge to remote address " + socket.getRemoteSocketAddress() + " !");
    163. e.printStackTrace();
    164. }
    165. }
    166.  
    167. /**
    168.   * Returns the dataInputStream
    169.   * @return
    170.   */
    171. public DataInputStream getDataInputStream(){
    172. return input;
    173. }
    174.  
    175. /**
    176.   * Returns the dataOutputStream
    177.   * @return
    178.   */
    179. public DataOutputStream getDataOutputStream(){
    180. return output;
    181. }
    182.  
    183. /**
    184.   * Returns the socket
    185.   * @return
    186.   */
    187. public Socket getSocket(){
    188. return socket;
    189. }
    190.  
    191. /**
    192.   * Returns the bridgeType
    193.   * @return
    194.   */
    195. public BridgeType getType(){
    196. return type;
    197. }
    198.  
    199. /**
    200.   * Sends a raw message
    201.   */
    202. private boolean sendRaw(String msg){
    203. try{
    204. output.writeUTF(msg);
    205. return true;
    206. }catch(Exception e){
    207. return false;
    208. }
    209. }
    210.  
    211. /**
    212.   * Sends a package
    213.   * @param package
    214.   * @return
    215.   */
    216. public boolean send(icPackage icpackage){
    217. DatabridgePackageSendEvent event = new DatabridgePackageSendEvent(this, icpackage);
    218. Bukkit.getPluginManager().callEvent(event);
    219.  
    220. if(event.isCancelled())return false;
    221.  
    222. return sendRaw(data + split + icpackage.getTypeId() + split + icpackage.toSendableString());
    223. }
    224. public boolean send(String message){
    225. return send(new StringPackage(message));
    226. }
    227. public boolean send(String[] messages){
    228. return send(new StringPackage(messages));
    229. }
    230. public boolean send(String title, VirtualConfigFile virtualConfigFile){
    231. return send(new ConfigPackage(title, virtualConfigFile));
    232. }
    233.  
    234. /**
    235.   * Returns the character used to spit
    236.   * message-parts
    237.   * @return
    238.   */
    239. public static char getSplit(){
    240. return split;
    241. }
    242.  
    243. /**
    244.   * Registers a new icPackage type
    245.   * @param icpackage
    246.   */
    247. public static boolean registerNewPackage(Class<? extends icPackage> icClass){
    248. String s = pid(icClass);
    249.  
    250. if(s == null)return false;
    251.  
    252. for(Class<? extends icPackage> icp : packageTypes){
    253. if(pid(icp).equals(s))return false;
    254. }
    255.  
    256. packageTypes.add(icClass);
    257. return true;
    258. }
    259.  
    260. public static void d(icAPI plugin){
    261. List<DataBridge> l = list;
    262. for(DataBridge db : l){
    263. db.close(Reason.API_SHUTDOWN);
    264. }
    265. }
    266.  
    267. public static void e(icAPI plugin){
    268. icAPI.scheduler.scheduleSyncRepeatingTask(new Runnable(){
    269. public void run(){
    270. for(DataBridge db : list){
    271. db.dp();
    272. }
    273. }
    274. }, 5*20, 10*20);
    275. icAPI.scheduler.scheduleSyncRepeatingTask(new Runnable(){
    276. public void run(){
    277. List<DataBridge> l = list;
    278. for(DataBridge db : l){
    279. db.ap();
    280. }
    281. }
    282. }, 1, 1);
    283. }
    284.  
    285. private static Integer currentPingId = null;
    286. private static String generatePingId(){
    287. if(currentPingId == null)currentPingId = 0;
    288. currentPingId = currentPingId + 1;
    289. return ""+currentPingId;
    290. }
    291.  
    292. private void dp(){
    293. String id = generatePingId();
    294. ping.put(id, 0);
    295. sendRaw(pingMessage + split + id);
    296. }
    297.  
    298. private void ap(){
    299. //icAPI.scheduler.scheduleSyncDelayedTask(new Runnable(){
    300. //public void run(){
    301. Map<String, Integer> edit = new HashMap<String, Integer>();
    302. for(String s : ping.keySet()){
    303. if(ping.get(s) >= 10*20){
    304. close(Reason.TIMED_OUT);
    305. return;
    306. }else{
    307. edit.put(s, ping.get(s)+1);
    308. }
    309. }
    310. for(String s : edit.keySet()){
    311. ping.put(s, edit.get(s));
    312. }
    313. //}
    314. //}, 1);
    315. }
    316.  
    317. private void rp(String id, double t){
    318. ping.remove(id);
    319.  
    320. DatabridgePingEvent event = new DatabridgePingEvent(this, ((t/20)*100), id);
    321. Bukkit.getPluginManager().callEvent(event);
    322. }
    323.  
    324. @Deprecated
    325. private static String pid(Class<? extends icPackage> c){
    326. try{
    327. Method a = c.getDeclaredMethod("getPackageTypeId");
    328. a.setAccessible(true);
    329. return (String)a.invoke(null);
    330. }catch(Exception e){
    331. e.printStackTrace();
    332. return null;
    333. }
    334. }
    335. }
    336.  


    However after a while the server keeps running out of memory, any ideas what may be causing this?

    Ps. it only happens on the serverSocket and NOT on the client. Here's the code I use only for servers:

    Code:java
    1. package net.invasioncraft.api.network;
    2.  
    3. import java.io.IOException;
    4. import java.net.ServerSocket;
    5. import java.net.SocketTimeoutException;
    6. import java.util.ArrayList;
    7. import java.util.List;
    8.  
    9. import org.bukkit.Bukkit;
    10.  
    11. import net.invasioncraft.api.icAPI;
    12. import net.invasioncraft.api.network.events.DatabridgeCreateEvent;
    13.  
    14. public class VirtualDataServer extends Thread{
    15.  
    16. private static List<VirtualDataServer> list = new ArrayList<VirtualDataServer>();
    17.  
    18. /**
    19.   * DataServers are used to allow connections from a client
    20.   */
    21.  
    22. private int port;
    23. private ServerSocket serverSocket;
    24. private boolean pending;
    25.  
    26. public VirtualDataServer(int port) throws IOException{
    27. this(port, 10000);
    28. }
    29. public VirtualDataServer(int port, int soTimeout) throws IOException{
    30. this.port = port;
    31. serverSocket = new ServerSocket(port);
    32. serverSocket.setSoTimeout(soTimeout);
    33. pending = true;
    34.  
    35. list.add(this);
    36. }
    37.  
    38. /**
    39.   * Returns the local port
    40.   * @return
    41.   */
    42. public int getPort(){
    43. return port;
    44. }
    45.  
    46. /**
    47.   * Returns the serverSocket
    48.   * @return
    49.   */
    50. public ServerSocket getServerSocket(){
    51. return serverSocket;
    52. }
    53.  
    54. @Override
    55. @SuppressWarnings("deprecation")
    56. public void destroy(){
    57. try{
    58. super.destroy();
    59. list.remove(this);
    60. }catch(Exception e){}
    61. }
    62.  
    63. public void run(){
    64. while(pending){
    65. try{
    66. DataBridge db = new DataBridge(serverSocket.accept(), BridgeType.SERVER);
    67. icAPI.console.info("Succesfully connected with client-address: " + db.getSocket().getRemoteSocketAddress());
    68.  
    69. DatabridgeCreateEvent event = new DatabridgeCreateEvent(db);
    70. Bukkit.getPluginManager().callEvent(event);
    71.  
    72. }catch(SocketTimeoutException e){
    73. }catch(IOException e){
    74. e.printStackTrace();
    75. break;
    76. }
    77. }
    78. }
    79.  
    80. public static void d(icAPI plugin){
    81. List<VirtualDataServer> l = list;
    82. for(VirtualDataServer vds : l){
    83. vds.destroy();
    84. }
    85. }
    86. }
    87.  


    Thanks for your time!
     
  2. Offline

    Skionz

    jeussa Try closing the ServerSocket onDisable? I really have no idea are you sure its your plugin causing it?
     
  3. Offline

    jeussa

    I'm actually using this as an API. The older version did not have this issue however it was very unstable so I redid the whole thing. Right now all that needs to be fixed is the server running out of memory.

    Ow btw, almost forgot to add:
    the reason it's running out of memory is because of one of the threads, not sure which one tho.
     
  4. Offline

    TGRHavoc

    jeussa
    A little tip, when creating a new thread don't use "while (true)" instead create a variable (e.g. Boolean running = true) then use "while ( running ) " then, when you want the thread to stop just make the variable false. If you want to know why you should do this instead of "Thread.stop()/Thread.destroy()" then read this by Sun.

    Edit: You could also do "while ( ! Thread.currentThread().isInterrupted() )" and then later call the "Thread.interrupt()" method. Similarly you could use the "while (true)" and then when you want to get rid of the thread call the "Thread.interrupt()" method.
     
  5. Offline

    Rocoty

    TGRHavoc I agree...almost. Use Thread.interrupted() so that the interrupted flag gets cleared. If you're checking for interruption in the current thread you should have a good reason not to use this method.

    jeussa There are some things I would like to point out. You're swallowing exceptions. Don't do that. You're only hiding useful information which, by chance, might help you debug this problem. I would also suggest you wrap the try blocks around the entire while loop. If something goes wrong you don't always want the connection to keep going, do you? I suppose it depends on the use case.

    EDIT: To figure out which thread runs out of memory, name them. That way the name of the thread will be displayed in the stack trace.
     
  6. Offline

    CubieX

    Code:
     while(pending){
    try{
    DataBridge db = new DataBridge(serverSoc
    This seems evil to me. Where do you set the "pending" varibale to FALSE?
     
    Skionz likes this.
  7. Offline

    jeussa

    With the code I provided, not. However I slightly worked on an update which sets pending to false when the databridge is closed. However the server is still running out of memory after some time.
     
  8. Offline

    xize

    jeussa

    Im not entirely sure but why do you have a static List of all VirtualServers plus it isn't volatile?, volatile keyword means that it can be modified by other threads.

    make sure those in that List got removed, also double test it with contains() in case for some reason ??? the VirtualServer you try to lookup in contains() is differentated than the one you would expect.

    a good example which I would like to show is why for example a List with a ItemStack could cause a memory leak, I know this may has nothing todo with ItemStack but maybe it still has todo with your own objects so please try reflect onto this:
    Code:
     
    public class Test {
            private List<ItemStack> items = new ArrayList<ItemStack>();
     
            public Test() {
                  ItemStack item = new ItemStack(Material.GLOWSTONE, 1);
                  items.add(item);
           }
     
           public boolean compair() {
                   ItemStack item = new ItemStack(Material.GLOWSTONE);
                   item.setAmount(2);
                   return items.contains(item); //this will return false
           }
    }
    
    now if you see I placed a ItemStack into the items list through the constructor.

    however when I would use the compair() method it will returns false even though the ItemStack looks smilliar.
    why? because since there is difference in the amount per stack this would not work and contains() will return false, now lets say this would happen to your VirtualServer List then it would be possible those elements never will be removed due a change in the object it self which triggers that equals sents a other result than you would/could expect.

    also the while(true) loop isn't that evil aslong its ran on a other thread plus theres a way to stop it again (I would preferly use a long sleep till it throws a exception or the deprecated stop() but yet ive to lookup why it is deprecated).

    but what I don't understand is why should there be more 'servers' than just one who handles all the requests?

    also please post the stacktrace of the permgen error sometimes it could give alot of information what could happen to.
     
  9. Offline

    mythbusterma

    xize

    That's not at all what volatile does, but nice try. We aren't saying the while(true) loop is evil, he was saying you need to check for interrupts, and yea the multiple servers thing is a good point.

    jeussa

    As for your code, it doesn't seem have very good object-oriented design at all. Why does each class have a static reference to a List of all instances of that class? That's normally where a manager comes in. Furthermore, the design doesn't look very thread-safe at all, and will likely error out with all the different ways you can violate the thread-safety of this design. Your constants should be static and final, otherwise you can inadvertently modify them, and you shouldn't be catching Exception.

    As for why it's running out of memory, have you tried making sure that one of your awful lists isn't overfilling with useless sockets? Or perhaps that the DataBridges are never actually removed because the threads are on infinite uninterruptable loops? Try using a Java Profiler to see where your issue is.
     
  10. Offline

    CubieX

    My point was, that he is never setting "pending" to FALSE which means, as soon as a connection has been closed client-side, the loop will again run and create another dataBridge-object instead of just doing ".accept()" for the next incoming connection.
    Depending on the traffic this may cause a rapid depletion of RAM, because the garbage collector is a pretty lazy dude by default.
    I see no reason to throw the socket object away after each transfer.
    It's just a guess. But the current implementation is surely not optimal.
     
  11. Offline

    mythbusterma

    CubieX

    I wasn't disagreeing with you, I agree wholeheartedly.
     
  12. Offline

    jeussa


    The code you see here is something I made to be used inside an API. Multiple plugins that use the API may need different ports, which is why I'm listing all pending serverSockets (or in this case VirtualDataServers containing the sockets) rather then creating a single one.

    Also I'm afraid I don't get any stacktraces but the message 'server stopped responding'


    I've slightly been editing my code so as soon as I use the close() method on a databridge pending will be set to false and after that I attempt to destroy the thread. Unfortunately without any success.

    Now my issue is only occuring on servers and not on clients, which would suggest the issue should be in the VirtualDataServer.java. However I'm still a nub when it comes to socket programming.
     
Thread Status:
Not open for further replies.

Share This Page