Sharing Data between Room Ext and Zone Ext - Java

Post here your questions about Actionscript and Java server side extensions development.

Moderators: Lapo, Bax

sanoysys
Posts: 10
Joined: 24 Mar 2009, 08:38
Location: Bengaluru, India
Contact:

Sharing Data between Room Ext and Zone Ext - Java

Postby sanoysys » 06 Jul 2011, 09:02

Hello, I am a Flash AS3 programmer and I have been using SFS1.x for a while now. I have written Server-side extensions in ActionScript all this time. But only recently I started writing extensions in Java, and I am completely new to Java.

The problem I have here is related to static variables. I want to create a global class which can hold global data which can be shared between Zone extension and the Room extensions in that zone.

Following below are the files:
Class: Test_Zone_Ext_1

Code: Select all

public class Test_Zone_Ext_1 extends AbstractExtension
{
   public void init()
   {
      trace("Hello from Test_Zone_Ext_1");
   }
   public void destroy()
   {
      trace("Bye bye! Test_Zone_Ext_1 is shutting down!");
   }
   public void handleRequest(String arg0, ActionscriptObject arg1, User arg2, int arg3)
   {
      // TODO Auto-generated method stub
   }
   public void handleRequest(String arg0, String[] arg1, User arg2, int arg3)
   {
      // TODO Auto-generated method stub
   }
   public void handleInternalEvent(InternalEventObject ieo)
   {
      String evtName = ieo.getEventName();
      if(evtName.equals("serverReady"))
      {
         init2();
      }
   }
   private void init2()
   {
      try
      {
         _helper = ExtensionHelper.instance();
         _zone = _helper.getZone(this.getOwnerZone());
         readConfig("javaExtensions/TestZoneData.xml");
         initRooms();
         trace("Created Rooms");
         trace("Zone Count1: "+Globals.getCount());
         trace("Zone Count2: "+Globals.getCount());
      }
      catch(Exception ex)
      {
         trace("[ERROR] Test_Zone_Ext_1::Init2: "+ex.getMessage()+", Stack: "+ex.getStackTrace());
      }
   }
   private void initRooms()
   {
      try
      {
         for(int i = 0; i < _roomsData.size(); ++i)
         {
            HashMap<String, String> params = _roomsData.get(i);
            Room room = _helper.createRoom(_zone, params, null, null, null, false, true, false);
            trace("Room Created: Id: "+room.getId()+", Name: "+room.getName()+", Ext Name: "+params.get("xtName"));
         }
      }
      catch(Exception ex)
      {
         trace("[ERROR] creating rooms: "+ex.getMessage());
      }
   }
   private void readConfig(String path)
   {
      try
      {
         DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
         Document doc = docBuilder.parse (new File(path));
         doc.getDocumentElement ().normalize ();
         //
         _roomsData = new ArrayList<HashMap<String, String>>();
         //
         Element roomsNode = (Element) doc.getElementsByTagName("Rooms").item(0);
         NodeList rooms = roomsNode.getElementsByTagName("Room");
         trace("Total Rooms: "+rooms.getLength());
         for(int i = 0; i < rooms.getLength(); ++i)
         {
            Element room = (Element) rooms.item(i);
            HashMap<String, String> roomParams = new HashMap<String, String>();
            roomParams.put("name", room.getAttribute("name"));
            roomParams.put("pwd", room.getAttribute("pwd"));
            roomParams.put("maxU", room.getAttribute("maxU"));
            roomParams.put("maxS", room.getAttribute("maxS"));
            roomParams.put("isGame", room.getAttribute("isGame"));
            roomParams.put("isLimbo", room.getAttribute("isLimbo"));
            roomParams.put("uCount", room.getAttribute("uCount"));
            roomParams.put("xtName", room.getAttribute("xtName"));
            roomParams.put("xtClass", room.getAttribute("xtClass"));
            _roomsData.add(roomParams);
         }
      }
      catch(SAXParseException ex1)
      {
         trace("[ERROR] Parsing error" + ", line " + ex1.getLineNumber () + ", uri " + ex1.getSystemId () + "MSG: "+ex1.getMessage());
      }
      catch(SAXException ex2)
      {
         Exception x = ex2.getException ();
         ((x == null) ? ex2 : x).printStackTrace ();
      }
      catch(ParserConfigurationException ex3)
      {
         trace("[ERROR] ParserConfigurationException: "+ex3.getMessage()+", Stack: "+ex3.getStackTrace());
      }
      catch(Throwable ex4)
      {
         trace("[ERROR] Exception: "+ex4.getMessage()+", Stack: "+ex4.getStackTrace());
      }
   }
   //
   private Zone _zone;
   private ExtensionHelper _helper;
   private ArrayList<HashMap<String, String>> _roomsData;
}


Class: Test_Room_Ext_1

Code: Select all

public class Test_Room_Ext_1 extends AbstractExtension
{
   public void init()
   {
      trace("Hello from Test_Room_Ext_1");
      trace("Room Count1: "+Globals.getCount());
      trace("Room Count2: "+Globals.getCount());
   }
   public void destroy()
   {
      trace("Bye bye! Test_Room_Ext_1 is shutting down!");
   }
   public void handleRequest(String arg0, ActionscriptObject arg1, User arg2, int arg3)
   {
      // TODO Auto-generated method stub
   }
   public void handleRequest(String arg0, String[] arg1, User arg2, int arg3)
   {
      // TODO Auto-generated method stub
   }
   public void handleInternalEvent(InternalEventObject ieo)
   {
      // TODO Auto-generated method stub
   }
}


Class: Globals

Code: Select all

public class Globals
{
   public Globals()
   {
      super();
   }
   public static int getCount()
   {
      return _var1++;
   }
   //
   private static int _var1 = 0;
}


File: TestZoneData.xml

Code: Select all

<Test_Zone debug="true">
   <Rooms>
      <Room name="Test_Room_1" pwd="" maxU="500" maxS="100" isGame="true" isLimbo="false" uCount="false" xtName="TRExt1" xtClass="Test_Room_Ext_1">
      </Room>
      <Room name="Test_Room_2" pwd="" maxU="500" maxS="100" isGame="true" isLimbo="false" uCount="false" xtName="TRExt2" xtClass="Test_Room_Ext_1">
      </Room>
      <Room name="Test_Room_3" pwd="" maxU="500" maxS="100" isGame="true" isLimbo="false" uCount="false" xtName="TRExt3" xtClass="Test_Room_Ext_1">
      </Room>
   </Rooms>
</Test_Zone>


Console Output:

Code: Select all

[ Test_Zone_Ext_1 ]: Total Rooms: 3
15:10:12.977 - [ INFO ] > Room Extension [ TRExt1 ] created!
[ Test_Room_Ext_1 ]: Hello from Test_Room_Ext_1
[ Test_Room_Ext_1 ]: Room Count1: 0
[ Test_Room_Ext_1 ]: Room Count2: 1
[ Test_Zone_Ext_1 ]: Room Created: Id: 2, Name: Test_Room_1, Ext Name: TRExt1
15:10:12.987 - [ INFO ] > Room Extension [ TRExt2 ] created!
[ Test_Room_Ext_1 ]: Hello from Test_Room_Ext_1
[ Test_Room_Ext_1 ]: Room Count1: 0
[ Test_Room_Ext_1 ]: Room Count2: 1
[ Test_Zone_Ext_1 ]: Room Created: Id: 3, Name: Test_Room_2, Ext Name: TRExt2
15:10:12.993 - [ INFO ] > Room Extension [ TRExt3 ] created!
[ Test_Room_Ext_1 ]: Hello from Test_Room_Ext_1
[ Test_Room_Ext_1 ]: Room Count1: 0
[ Test_Room_Ext_1 ]: Room Count2: 1
[ Test_Zone_Ext_1 ]: Room Created: Id: 4, Name: Test_Room_3, Ext Name: TRExt3
[ Test_Zone_Ext_1 ]: Created Rooms
[ Test_Zone_Ext_1 ]: Zone Count1: 0
[ Test_Zone_Ext_1 ]: Zone Count2: 1


From the output we can clearly see that the static variable "_var1" of class Globals has been created 4 times: once for zone extension and thrice for the room extensions. So it is impossible to share data between Room extensions and Zone extensions using this method.

I don't know what is causing the SFS or JRE to create separate static variables for different extensions. Is there a way we can instruct SFS or JRE to create only one static variable and not so many?
sanoysys
Posts: 10
Joined: 24 Mar 2009, 08:38
Location: Bengaluru, India
Contact:

Postby sanoysys » 07 Jul 2011, 06:15

I think the problem is same as the one "zood" mentioned in this thread: http://www.smartfoxserver.com/forums/viewtopic.php?t=1985 But I don't see anyone suggesting a solution to it.

As zood mentions the problem occurs when you create a static zone and add dynamic rooms to it and try to share data between the respective extensions using static variables. For some reason the extensions are segregated and different sets of static variables are created for different extensions.

Please someone look into it.
User avatar
BigFIsh
Posts: 1698
Joined: 25 Feb 2008, 19:26
Location: New Zealand

Postby BigFIsh » 07 Jul 2011, 23:49

I am able to reproduce the behaviour you're seeing. It is indeed very odd.. :shock:

I'll carry on investigating and see if I can find a solution to this problem.
Smartfox's forum is my daily newspaper.
Sarevok
Posts: 75
Joined: 12 Apr 2011, 22:12

Postby Sarevok » 08 Jul 2011, 00:07

Problem is in class loader and it can be easily fixed. You have to make sure that:

1) ./javaExtensions/ folder is in your classpath.
2) <AutoReloadExtensions>true</AutoReloadExtensions> is NOT set for your zone nor globaly in config.xml

Side effect is that you will not be able to reload extensions at runtime, but we can live without it :D

After that you will have only one instance of global class. Also you should consider to use singleton and to make it thread safe, if you plan to write to those global vars, and not only to read them.
User avatar
BigFIsh
Posts: 1698
Joined: 25 Feb 2008, 19:26
Location: New Zealand

Postby BigFIsh » 08 Jul 2011, 00:17

Sarevok, I have tried what you said but it made no difference. Maybe you can clarify what you meant by including ./javaExtension/ in the classpath. My full classpath for my Zone extension is: com.sfs.ZoneExtension, and at compile time the compiled class (not jar) gets copied to ./javaExtension/

The problem seems to be related to dynamically attaching an extension to a room at run time.
Smartfox's forum is my daily newspaper.
Sarevok
Posts: 75
Joined: 12 Apr 2011, 22:12

Postby Sarevok » 08 Jul 2011, 00:40

You have to add ./javaExtension/ to classpath. How you do it depends if you run smartfox as server or as daemon/service.

If you run it as server, then you go to the server folder, and open start.bat file in text editor. There you should add javaExtension in classpath. Here is excerpt of my start.bat file:

@echo off
"..\jre\bin\java.exe" -server -cp "./;./sfsExtensions/;./javaExtensions;./javaExtensions/MyExtension.jar;lib/activation.jar;lib/commons-beanutils.jar;

...and so on


If you run your smartfox as daemon, then you have to configure that wrapper.conf file so that mentioned folder is in classpath.

Try to do it, it should solve the problem. :)

p.s. in excerpt from my start.bat (start.sh in linux) only important part is the bolded one. The one after it is just a junk extension I forgot to celan up :D

edit: I bolded wrong thing in first try, now it is corrected :?
User avatar
BigFIsh
Posts: 1698
Joined: 25 Feb 2008, 19:26
Location: New Zealand

Postby BigFIsh » 08 Jul 2011, 01:02

Ah, I understand now.

Adding ./javaExtensions/ to the classpath did indeed solve the problem (and I couldn't believe it). Thanks for sharing this solution with us. :)
Smartfox's forum is my daily newspaper.
Sarevok
Posts: 75
Joined: 12 Apr 2011, 22:12

Postby Sarevok » 08 Jul 2011, 01:13

You are welcome. :)

I actually found this solution in sfs documentation, few months ago:
http://www.smartfoxserver.com/docs/inde ... ons.htm#9a

Make sure that the javaExtensions/ folder is not comprised in your classpath. By default SmartFoxServer includes this folder in the classpath so that all extension code is loaded at boot-time by the JVM system class loader. While this can be useful for sharing classes across multiple extensions (e.g. Singletons etc...) it will prevent dynamic reloading of the classes. If you remove the javaExtensions/ folder from the classpath, SmartFoxServer will use a separate class loader for each Extension and allowing dynamic reloading.


I had problem with my database connection pool singleton that was leaking connections, and was crashing database eventually. After some debugging I found that multiple instances of singleton were loaded in memory, which should never happen. Then I checked smartfox documentation and found solution in above link :)
sanoysys
Posts: 10
Joined: 24 Mar 2009, 08:38
Location: Bengaluru, India
Contact:

Postby sanoysys » 08 Jul 2011, 06:48

Thanks Sarevok and BigFIsh for your help. The solution you mentioned works! :)

I hope gotoAndPlay().it is aware of this bug and fixes it up in the next release.
Sarevok
Posts: 75
Joined: 12 Apr 2011, 22:12

Postby Sarevok » 08 Jul 2011, 08:37

This is not a bug, it is kind of feature. :)
When you delete javaExtensions folder from your classpath you are enabling feature called "dynamic extension reloading". That way your server extension is reloaded automatically every time you make change to it (no need to restart the server). This feature is very useful for debugging because it can be pain to restart server all the time during the extension development. But when development phase is over, you should disable it, to make sharing data between extension available :)
sanoysys
Posts: 10
Joined: 24 Mar 2009, 08:38
Location: Bengaluru, India
Contact:

Postby sanoysys » 05 Aug 2011, 10:50

I get it now Sarevok... Thanks for your reply :)

Return to “Server Side Extension Development”

Who is online

Users browsing this forum: No registered users and 14 guests