Hooking into message loop of dbx DataSnap user session

五迷三道 提交于 2019-12-11 01:59:55

问题


Is there a way to hook into the WndProc of a dbx user session?

Background: dbx DataSnap uses Indy components for TCP communication. In its simplest form, a DataSnap server is an Indy TCP server accepting connections. When a connection is established, Indy creates a thread for that connection which handles all requests for that connection.

Each of these user connections consume resources. For a server with a couple hundred simultaneous connections, those resources can be expensive. Many of the resources could be pooled, but I don't want to always acquire and release a resource each time it is needed.

Instead, I'd like to implement a idle timer. After a thread finishes with a resource, the timer would start. If the thread accesses the resource before the timer has elapsed, the resource would still be "assigned" to that thread. But if the timer elapses before the next access, the resource would be released back to the pool. The next time the thread needs the resource, another resource would be acquired from the pool.

I haven't found a way to do this. I've tried using SetTimer but my timer callback never fires. I assume this is because Indy's WndProc for the thread isn't dispatching WM_TIMER. I have no control of the "execution loop" for this thread, so I can't easily check to see if an event has been signaled. In fact, none of my code for this thread executes unless the thread is handling a user request. And in fact, I'm wanting code to execute outside of any user request.

Solutions to the original question or suggestions for alternative approaches would be equally appreciated.


回答1:


We tried to implement something to share resources across user threads using TCP connections (no HTTP transport, so no SessionManager), but ran into all sorts of problems. In the end we abandoned using individual user threads (set LifeCycle := TDSLifeCycle.Server) and created our own FResourcePool and FUserList (both TThreadList) in ServerContainerUnit. It only took 1 day to implement, and it works very well.

Here's a simplified version of what we did:

TResource = class
  SomeResource: TSomeType;
  UserCount: Integer;
  LastSeen: TDateTime;
end;

When a user connects, we check FResourcePool for the TResource the user needs. If it exists, we increment the resource's UserCount property. When the user is done, we decrement the UserCount property and set LastSeen. We have a TTimer that fires every 60 seconds that frees any resource with a UserCount = 0 and LastSeen greater than 60 seconds.

The FUserList is very similar. If a user hasn't been seen for several hours, we assume that their connection was severed (because our client app does an auto-disconnect if the user has been idle for 90 minutes) so we programmatically disconnect the user on the server-side, which also decrements their use of each resource. Of course, this means that we had to create a session variable ourselves (e.g., CreateGUID();) and pass that to the client when they first connect. The client passes the session id back to the server with each request so we know which FUserList record is theirs. Although this is a drawback to not using user threads, it is easily managed.




回答2:


James L maybe had nailed it. Since Indy thread does not have an message loop, you have to rely in another mechanism - like read-only thread-local properties (like UserCount and / or LastSeem in his' example) - and using main thread of the server to run a TTimer for liberating resources given some rule.

EDIT: another idea is create an common data structure (example below) which is updated each time an thread finishes its' job.

WARNING: coding from mind only... It may not compile... ;-)

Example:

TThreadStatus = (tsDoingMyJob, tsFinished);

TThreadStatusInfo = class
private
  fTStatus : TThreadStatus;
  fDTFinished : TDateTime;
  procedure SetThreadStatus(value: TThreadStatus);
public
  property ThreadStatus: TThreadStatus read fTStatus write SetStatus;
  property FinishedTime: TDateTime read fDTFinished;
  procedure FinishJob ;
  procedure DoJob;
end

procedure TThreadStatusInfo.SetThreadStatus(value : TThreadStatus)
begin
  fTStatus = value;
  case fTStatus of 
    tsDoingMyJob :
       fDTFinished = TDateTime(0);
    tsFinished:
       fDTFinished = Now;
  end;
end;

procedure TThreadStatusInfo.FinishJob;
begin
  ThreadStatus := tsFinished;
end;

procedure TThreadStatusInfo.DoJob;
begin
  ThreadStatus := tsDoingMyJob;
end;

Put it in a list (any list class you like), and make sure each thread is associated with a index in that list. Removing items from the list only when you won't use that number of threads anymore (shrinking the list). Add an item when you create a new thread (example, you have 4 threads and now you need an 5th, you create a new item on main thread).

Since each thread have an index on the list, you don't need to encapsulate this write (the calls on T on a TCriticalSection.

You can read this list without trouble, using an TTimer on main thread to inspect the status of each thread. Since you have the time of each thread's finishing time you can calculate timeouts.



来源:https://stackoverflow.com/questions/9010281/hooking-into-message-loop-of-dbx-datasnap-user-session

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!