https://forums.embarcadero.com/thread.jspa?messageID=389618
Thread: DataSnap - detect disgraceful disconnect
Reply to this Thread Search Forum Watch this Thread Back to Thread List
Replies: 6 - Last Post: Sep 8, 2011 5:14 AM Last Post By: Mathew DeLong
Mathias Burbach
Posts: 38
Registered: 12/8/99
DataSnap - detect disgraceful disconnect Reply Headers
Posted: Jul 14, 2011 1:51 AM
datasnap , detect , disgraceful , disconnect
X-Source-Client: web
NNTP-Posting-Host: 120.146.135.230
X-Posting-Host: 120.146.135.230
Hello Folks!
How can I detect a disgraceful disconnect in a DataSnap server? We want to build a logon/logoff audit table and need to detect when a client process gets killed or - even worse - a network cable is unplugged accidentally.
When running the server in the Delphi debugger I can see an exception being raised if I kill the process of the client application without disconnecting first. Here is the call stack at the time:
:75df9617 KERNELBASE.RaiseException + 0x54
IdStack.TIdStack.RaiseSocketError(10054)
IdStack.TIdStack.RaiseLastSocketError
IdStack.TIdStack.CheckForSocketError(-1)
IdStackBSDBase.TIdStackBSDBase.Receive(???,???)
IdSocketHandle.TIdSocketHandle.Receive(???)
IdIOHandlerStack.TIdIOHandlerStack.ReadDataFromSource(???)
IdIOHandler.TIdIOHandler.ReadFromSource(False,0,False)
IdIOHandlerStack.TIdIOHandlerStack.Connected
IdTCPConnection.TIdTCPConnection.Connected
IdCustomTCPServer.TIdCustomTCPServer.DoExecute(???)
IdContext.TIdContext.Run
IdTask.TIdTask.DoRun
IdThread.TIdThreadWithTask.Run
IdThread.TIdThread.Execute
Classes.ThreadProc($17C9CC0)
System.ThreadWrapper($1821050)
:76681174 kernel32.BaseThreadInitThunk + 0x12
:77bfb3f5 ntdll.RtlInitializeExceptionChain + 0x63
:77bfb3c8 ntdll.RtlInitializeExceptionChain + 0x36
But none of the components on the TServerContainer or the TServerContainer itself would allow me to detect the lost socket connection. The TDSServer.OnError event is only triggered if the error occurred during a server method call. But that may not be the case.
What would you recommend to detect such a lost socket connection?
Thanks for a short answer in advance.
Salut,
Mathias
Mathias Burbach
Posts: 38
Registered: 12/8/99
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 2, 2011 12:27 AM in response to: Mathias Burbach
datasnap , detect , disgraceful , disconnect , delphi , xe2
X-Source-Client: web
NNTP-Posting-Host: 120.146.135.230
X-Posting-Host: 120.146.135.230
Hello Folks!
I have revisited this problem, now that I am a happy owner of Delphi XE2 and heard Damien Bootsma talking about improvements in DataSnap in version XE2 at the Sydney Delphi XE2 World Tour. I have created an event handler for the new TDSTCPServerTransport.OnDisconnect property. Now when the client connection is closed disgracefully I end up in this event handler. Here is the call stack at the time:
UServerContainer.TServerContainer1.DSTCPServerTransport1Disconnect(($30663C0))
Datasnap.DSTCPServerTransport.TDSTCPServerTransport.DoOnDisconnect(TIdContextPeer($3075150) as IIPContext)
IndyPeerImpl.TIdTCPServerPeer.LOnDisconnectEvent(???)
IdCustomTCPServer.TIdCustomTCPServer.DoDisconnect(???)
IdCustomTCPServer.TIdCustomTCPServer.ContextDisconnected($30BAEF0)
IdContext.TIdContext.AfterRun
IdTask.TIdTask.DoAfterRun
IdThread.TIdThreadWithTask.AfterRun
IdThread.TIdThread.Execute
System.Classes.ThreadProc($3029D40)
System.ThreadWrapper($3081110)
:764c3677 kernel32.BaseThreadInitThunk + 0x12
:76ec9d72 ntdll.RtlInitializeExceptionChain + 0x63
:76ec9d45 ntdll.RtlInitializeExceptionChain + 0x36
The problem now is that the session has been deleted already and even trying to access TDSSessionManager.GetThreadSession would raise another exception. I checked some of the properties of the Event: TDSTCPDisconnectEventObject. For example I type casted Event.Connection as TIdTCPConnection, but most of the properties of TIdTCPConnection are empty (e.g. BoundIP).
How am I supposed to figure which client connection turned bad?
A sample project and the CodeSite log are available for download from here:
http://www.maranatha-consulting.com/Delphi/LostConnection.zip
Thanks for a short answer in advance.
Salut,
Mathias
Edited by: Mathias Burbach on Sep 2, 2011 5:31 PM
Mathew DeLong
Posts: 43
Registered: 9/18/08
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 2, 2011 6:07 AM in response to: Mathias Burbach
X-Source-Client: web
NNTP-Posting-Host: 10.40.30.224
X-Posting-Host: 10.40.30.224
On the TDSTCPServerTransport component you can set the value of the "KeepAliveEnablement" to "kaEnabled" and then set the "KeepAliveTime" to the number of milliseconds you want to wait before sending KeepAlive packets to the client. The KeepAliveInterval property doesn't need to be changed unless you want to... this property is the delay between KeepAlive packet attempts. The number of attempts is determined by the operating system. (Windows, I believe, is 10 attempts.) If you don't enable KeepAlive, the proper events I talk about below will probably not be fired.
Once this is configured, you can register a session event on your application to determine the session which has been closed. You'd do that like this:
TDSSessionManager.Instance.AddSessionEvent(
procedure(Sender: TObject; const EventType: TDSSessionEventType; const Session: TDSSession)
begin
if (EventType = SessionClose) and (Session <> nil) then
//Handle this case
end);
If you want to handle the connection itself closing, you can add an OnConnect event to theTDSTCPServerTransport component, which keeps a list of the connections, and their associated SessionIDs. Like this:
procedure TForm1.ServerTransportConnectEvent(Event: TDSTCPConnectEventObject);
begin
//FConnections: TObjectDictionary<TIdTCPConnection,TDSTCPChannel>;
System.TMonitor.Enter(FConnections);
try
FConnections.Add(TIdTCPConnection(Event.Connection), Event.Channel); //Event.Channel has a SessionId property
finally
System.TMonitor.Exit(FConnections);
end;
end;
Then you can add an OnDisconnect event like this:
procedure TCMServerForm.CMServerTransportDisconnectEvent(Event: TDSTCPDisconnectEventObject);
begin
System.TMonitor.Enter(FConnections);
try
FConnections.Remove(TIdTCPConnection(Event.Connection));
finally
System.TMonitor.Exit(FConnections);
end;
end;
In the OnDisconnect procedure you will know the session ID of the connection being closed, because it is stored in the FConnections map.
I hope that helps.
--
Thanks,
Mat DeLong (Embarcadero)
Mathias Burbach
Posts: 38
Registered: 12/8/99
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 3, 2011 12:03 AM in response to: Mathew DeLong
X-Source-Client: web
NNTP-Posting-Host: 120.146.135.230
X-Posting-Host: 120.146.135.230
Hello Mathew,
Thanks for your reply.
This answer certainly helped to identify the TIdTCPConnection and the Channel of the lost connection. However my CodeSite log file reveals that the Event.Channel.SessionId is not set yet in the TDSTCPServerTransport.OnConnect event. The comment on the Event.Channel.SessionId property confirms that "This will not be populated until a Read has been made.".
Anyhow lateron in the process of connecting the client app/user I need to relate a TDSSessionManager.GetThreadSession.Id with the already stored TDSTCPChannel.SessionId. I may not see the wood for the trees but it seems I am unable to find a way to conclude from a DSConnectEventObject: TDSConnectEventObject to an Event: TDSTCPConnectEventObject.
My idea would be to store the logon in TDSAuthenticationManager.OnConnect if user name & password are fine but then use the TDSTCPChannel.SessionId as the primary key in the database for my connection log table. That way I will be able to log the disconnect under both circumstances (graceful & disgraceful disconnect).
I have amended my sample project LostConnection and added the CodeSite log for the scenario of a lost connection after introducing the FConnections: TObjectDictionary<TIdTCPConnection,TDSTCPChannel>. See also here:
http://www.maranatha-consulting.com/Delphi/LostConnection.zip
I would appreciate your directions in getting "this riddle" solved.
Salut,
Mathias
Mathew DeLong
Posts: 43
Registered: 9/18/08
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 6, 2011 5:31 AM in response to: Mathias Burbach
X-Source-Client: web
NNTP-Posting-Host: 10.40.30.224
X-Posting-Host: 10.40.30.224
You should be able to use an Authentication Manager component, and in the OnUserAuthentication event you wold have access to the current session being used by the connecting user. This is like an OnConnect event. You could use that session ID (getting the thread session from the SessionManager class,) to look up in a map of stored channels (as I mentioned before) to find the user's connection.
Hope that helps,
--
Thanks,
Mat DeLong (Embarcadero)
Mathias Burbach
Posts: 38
Registered: 12/8/99
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 7, 2011 3:40 PM in response to: Mathew DeLong
datasnap , detect , disgraceful , disconnect , delphi , xe2
X-Source-Client: web
NNTP-Posting-Host: 120.146.135.230
X-Posting-Host: 120.146.135.230
Hello Mathew,
Thanks for your reply.
Mathew DeLong wrote:
You should be able to use an Authentication Manager component, and in the OnUserAuthentication event you wold have access to the current session being used by the connecting user. This is like an OnConnect event. You could use that session ID (getting the thread session from the SessionManager class,) to look up in a map of stored channels (as I mentioned before) to find the user's connection.The difficulty for me seems to be to get the mapping between the Event.Channel: TDSTCPChannel with the DSConnectEventObject.ChannelInfo: TDBXChannelInfo done. The Event.Channel.SessionId is not related to the TDSSessionManager.GetThreadSession.Id.
Instead I came up with a slightly different idea. I observed that the TThread.CurrentThread.ThreadID is unique for each connection as long as the user is connected maybe even the server is running. So I created a dictionary like this:
TConnectionPK = Cardinal;
FConnections: TDictionary<TThreadID,TConnectionPK>;
In the TDSAuthenticationManager.OnUserAuthenticate event - if the user/password combination is valid - I log the connection in the DB getting a new primary key value from the DB server and store it together with the ThreadID in the dictionary:
TMonitor.Enter(FConnections);
try
FConnectionPK := LogUserConnect(User);
FConnections.Add(TThread.CurrentThread.ThreadID, FConnectionPK);
finally
TMonitor.Exit(FConnections);
end;
I can then remove the ThreadID when the TCP channel signals a disconnect in TDSTCPServerTransport.OnDisconnect:
TMonitor.Enter(FConnections);
try
if FConnections.TryGetValue(TThread.CurrentThread.ThreadID, ConnectionPK) then
begin
LogUserDisconnect(ConnectionPK);
FConnections.Remove(TThread.CurrentThread.ThreadID);
end;
finally
TMonitor.Exit(FConnections);
end;
Is the assumption correct that a particular ThreadID is not assigned again until the user has either disconnected gracefully or lost its connection, which causes the TDSTCPServerTransport.OnDisconnect to be triggered? If that's the case I have found my unique identifier for a connection (e.g. TThread.CurrentThread.ThreadID) and can move on.
Salut,
Mathias
Mathew DeLong
Posts: 43
Registered: 9/18/08
Re: DataSnap - detect disgraceful disconnect Reply Headers
Posted: Sep 8, 2011 5:14 AM in response to: Mathias Burbach
X-Source-Client: web
NNTP-Posting-Host: 10.40.16.198
X-Posting-Host: 10.40.16.198
The issue is with this: "TDSSessionManager.GetThreadSession.Id". Unfortunately, this is somewhat confusingly named. The TDSSession's "Id" is intended for internal use only. The "SessionId" you care about is actually called "Name" on the TDSSession instance. Use the Name property, and everything should work fine for you.
--
Thanks,
Mat DeLong (Embarcadero)