Groups | Blog | Home
all groups > dotnet remoting > september 2005 >

dotnet remoting : strange... Singleton object, but 2 different fields


David Krmpotic
9/17/2005 12:00:00 AM
Hello,

I have a simple app with server (a bunch of singletons) and clients that
connect to it.
Each client gets a unique ID (some integer) upon connection and then pings
the server regularily with it.
if there is no ping for some time, server concludes that client has died
(freeze, reboot) and releases its ID and any locks that client has put on
rows in tables.

The strange problem is that (if you look at the code),
GetHostNameByClientID(int clientID) constantly returns null from some
places... I found out that this hashtable - ID2HostName is somehow different
(?) in these calls than normal (referenced in Ping method for example). it
is empty, although ping shows name of the host nicely. I further proved that
this is a different object be checkinh the return from GetHashCode().

but when I put the declaration of the hashtable like this:

internal static readonly Hashtable ID2HostName= new Hashtable();

then it work! I don't know by what coincidence, but I stumbled upon this
article

http://www.yoda.arachsys.com/csharp/singleton.html

just few hours before experiencing this problem. I could imagine how this
could happen in the example mentioned there, but not in my case.. well..
maybe.. or obviously.. but I cannot explain it..
so that means that I should use internal static readonly for all the fields
on my SAOS like that? or prepare myself for terrible surprises!

thank you a lot for helping me clarify this strange thing.

take care!
David

=========================================

public class ConnectedClientsManager: MarshalByRefObject
{
private MyLib.UniqueIDGenerator uniqueIDGenerator = new UniqueIDGenerator;
private ID2AliveStatus = new Hashtable();
private Hashtable ID2HostName= new Hashtable();

private System.Threading.Timer checkForDeadClientsTimer;

public ConnectedClientsManager()
{

//we check for dead clients every 10s
//this means that dead client will be detected
//10 to 20s after it dies. After a client dies,
//this timer has to fire twice to detect it
//first time it just sets Alive flag for all clients to false
//and the next time it detects that it is still on file
//(there was no ping in the meantime)
int interval = 10000;
checkForDeadClientsTimer = new System.Threading.Timer(new
System.Threading.TimerCallback(CheckForDeadClients), null, interval,
interval);
}

public int ClientConnected(string hostName)
{
lock(this)
{
int ID = uniqueIDGenerator.GenerateNewID();

Console.WriteLine("Client " + hostName + " ("+ID+") connected!");

ID2AliveStatus.Add(ID, true);
ID2HostName.Add(ID, hostName);

Console.WriteLine("Conn "+ID2HostName.GetHashCode().ToString());

return ID;
}
}

public void ClientDisconnected(int clientID)
{
lock(this)
{
Console.WriteLine("Client " + ID2HostName[clientID] + " ("+clientID+")
disconnected!");

ID2AliveStatus.Remove(clientID);
ID2HostName.Remove(clientID);

this.uniqueIDGenerator.ReleaseID(clientID);
}
}

public string GetHostNameByClientID(int clientID)
{
lock(this)
{
return (string)ID2HostName[clientID];
}
}

public void Ping(int clientID)
{
lock(this)
{
ID2AliveStatus[clientID] = true;

Console.WriteLine("Pinged from " +
ID2HostName[clientID]/*ID2HostName[clientID]*/ + " (" + clientID + ")");
}
}

public ArrayList GetConnectedClientsList()
{
ArrayList list = new ArrayList();
foreach(string hostName in ID2HostName.Values)
{
list.Add(hostName);
}

return list;
}

private void CheckForDeadClients(object state)
{
lock(this)
{
ArrayList keys = new ArrayList();
foreach(int clientID in ID2AliveStatus.Keys)
{
keys.Add(clientID);
}

foreach(int clientID in keys)
{
if((bool)ID2AliveStatus[clientID])
{
ID2AliveStatus[clientID] = false; //sets alive status to false
}
else
{
TableLocks.GetProxy().ClientDied(clientID);
ClientDisconnected(clientID);
}
}
}
}

#region Remoting
public static void RegisterServerObject()
{
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ConnectedClientsManager),
"ConnectedClientsManager",
WellKnownObjectMode.Singleton );
}

private static ConnectedClientsManager proxy = null;

public static ConnectedClientsManager GetProxy()
{
//%%%% uncommenting this line doesn't make difference!%%%%
//lock(typeof(ConnectedClientsManager))
{
if(proxy == null)
{
proxy = (ConnectedClientsManager) Activator.GetObject(
typeof(ConnectedClientsManager),
"tcp://"+Global.ServerName+":45326/ConnectedClientsManager" );
}
}

return proxy;
}
}

//object never dies
public override object InitializeLifetimeService()
{
return null;
}
#endregion
}

Robert Jordan
9/18/2005 12:00:00 AM
[quoted text, click to view]

The code makes sense but it just hides a bug in your design.
You probably create a ConnectedClientsManager object as a CAO
from your client before the singleton has been created/accessed.
That's why you see different ID2HostName objects.

David Krmpotic
9/18/2005 10:52:07 AM
UPDATE:
I think I solved a problem by implementing RegisterServerObject() method
this way:


public static void RegisterServerObject()
{
proxy = new ConnectedClientsManager();
RemotingServices.Marshal(proxy, "ConnectedClientsManager");
}


does this make sense? is all of this normal? no more hidden surprises for me
now ;) ?

thank you again!



[quoted text, click to view]

David2112
9/20/2005 5:04:29 AM

[quoted text, click to view]

Thank you for your response.

With this change everything seems to work flawlesly. I am still
interested in finding the exact answer about what went wrong first.

I don't think I am creating any CAO on my client. As a matter of fact I
don't know anything about CAOs yet. Could I have done something without
knowing it?

How can I check?

All I do on the client is call the Proxy method everytime I want to use
the SAO object I created.

The method is here:

public static ConnectedClientsManager GetProxy()
{
//%%%% uncommenting this line doesn't make difference!%%%%
//lock(typeof(ConnectedClientsManager))
{
if(proxy == null)
{
proxy = (ConnectedClientsManager) Activator.GetObject(
typeof(ConnectedClientsManager),
"tcp://"+Global.ServerName+":45326/ConnectedClientsManager" );
}
}


return proxy;
}
}


I something in here that *was* causing problems?


Thank you

David
AddThis Social Bookmark Button