The scenario of one client getting and holding for a prolonged period an object reference to an instance of a SingleUse instancing top-level class in an executable means that no blocking problems will occur. As we’ve already seen, however, this scenario would not support any hardware-saving argument that might be used to bolster the case for building a distributed n-tiered system. Because every client here has a corresponding server, the memory (RAM) that might have been put into client PCs will have to be put into a server machine to support the occurrences of each instance of a SingleUse creatable Garbage Server needed by each client. (See Figure 2-10.) In fact, this kind of configuration will probably cost more in memory since even the lightest and least fully functioned server executable uses large amounts of memory. You also need to consider the expense of the server machine. The issue is that each time a client requests a Garbage Server, one will be created (since it is a SingleUse instancing server), and thus eventually the object server will not have enough resources remaining to create new instances.
Figure 2-10 The SingleUse option for distributed servers
Admittedly, the reality is that if connections to objects are broken as soon as practicable (for instance, after every task) and if a task doesn’t take a long time, or if asynchronous communications (notifications) between servers and clients for longer-running services are used, the ratio of servers to clients might be lower. Certainly that lower ratio is what we have to aim for, and as a result we might want to design our objects to be stateless (which we’ll get into later).
Of course, if we can scale the hardware sufficiently to cope with additions of extra clients, scalability isn’t a problem, just a cost. But if there is no control of the number of users that can be logged onto the server at one time, there is no control of the creation of objects, and eventually the objects can proliferate and choke the server machine that hosts them, unless some control is reestablished over how many objects can exist at any given time.
Size of an ActiveX Process
Standard received wisdom says that an ActiveX server executable uses 500 KB of memory even for an empty server process that is running idle with all memory pages that can be being swapped out. I did a little experiment and made a server—a very simple three-class project with no fancy code, compiled as a SingleUse, multithreaded EXE—that is included on the companion CD (CHAP02\ServerEXE\LawTrialEXE.vbp). I compiled it and called it from a client (also included on the CD: CHAP02\ServerEXE\DataTestEXE.vbp). Running the server on Microsoft Windows NT Server 4, on a 64-MB RAM 200-MHz Pentium Pro, I found in the Windows NT Task Manager that the out-of-process server had memory usage of 2092 KB and virtual memory size of 432 KB with one client using it, and that each client had a memory usage of 2460 KB and 452 KB. (See Figure 2-11.)
Pretty hungry! People don’t believe you when you tell them this. I then made the server into a DLL (CHAP02\ServerDLL\LawTrialDLL.vbp) and made a client (CHAP02\ServerDLL\DataTestDLL.vbp) for that and found that each client now had a memory usage of between 2180 KB and 2196 KB and a virtual memory size of 424 KB. (See Figure 2-11.) The minor discrepancy is almost certainly due to the number of elements created.
This seems to be a strong argument for procurator processes. (See the “Procurators or Process Managers” section on page 78.) I include the code on the companion CD, and you can test this yourself if you are running version 4 of We also get splatted by the problem of slow start-up of an object instance (which we’ve already talked about).
Figure 2-11 Checking out memory usage in Windows NT 4 Server’s Task Manager
Windows NT, using the Task Manager, which is often handier than the Performance Monitor for quick tests such as this.
Figure 2-12 The MultiUse instancing option for distributed servers
This configuration would appear to get around the number of loaded instances and memory usage problems on the server since each client gets its own instance; but that instance is provided by one loaded set of code, and each instance has its own copy of global data to work on. Remember this, because it means you can’t use this model to share global data between clients.
Where many clients are requesting services from a server that is satisfying all those requests with a single-threaded application (as Visual Basic 4 had to do, and Visual Basic 5 can do), the issue is blocking. Even on a preemptive multitasking operating system such as Windows NT, when a request is being serviced by one loaded instance of code, another request cannot be serviced. This is because ActiveX has to handle the possibility that a thread might be interrupted before it has completed its work and that the process that gets the processor next might call the same server. It handles this by blocking—serializing requests so that any other request is accepted by the server but cannot be run until the outstanding request, which is in the works already, is satisfied. If all requests were quickly turned around, the faster your server machine, the more requests it can get through, and the less of an issue blocking is. However, if the server machine is underpowered in relation to the peaks of requests made of it or if some of the requests are long running and others short, clients might have to wait for a long time to have their requests serviced. As you know, users don’t like not knowing how long something is going to take, and they really don’t like finding out that the same task can take different amounts of time on different occasions.