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.
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.
Example:
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.
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 ownFResourcePool
andFUserList
(bothTThreadList
) in ServerContainerUnit. It only took 1 day to implement, and it works very well.Here's a simplified version of what we did:
When a user connects, we check
FResourcePool
for theTResource
the user needs. If it exists, we increment the resource'sUserCount
property. When the user is done, we decrement theUserCount
property and setLastSeen
. We have aTTimer
that fires every 60 seconds that frees any resource with aUserCount = 0
andLastSeen
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 whichFUserList
record is theirs. Although this is a drawback to not using user threads, it is easily managed.