As a TeamB member,
I read a lot of newsgroup
message. I mean a lot of newsgroup messages. My particular
interest is web development and WebSnap, and I try to answer as many
questions as I can in those groups in particular. Many I can answer,
some I can't. But I do find that many of the same questions keep
popping up, and one of the questions that comes up over and over is
about how webmodules and webdatamodules are threaded. If you don't
completely understand how WebSnap threading works, you can run into
any number of problems with your application. The first part of this
article will cover the default
behavior of webmodules. The second section will discuss the ways that
you can change this default behavior, and the implications of doing
so. In addition, this discussion covers memory-resident web
applications such as Web App Debugger applications, ISAPI
applications, and Apache applications for Apache 2.0. CGI
applications work differently, because CGI applications execute once
for each request, and thus there is no pooling of webmodules that
takes place. Of course, that fact alone means that your application
will likely be managed in a different way. Anyway, let's get started
with a mind picture that illustrates how WebSnap threading works.
The Tool Checkout Room
Imagine you work in a furniture factory. You build wood furniture,
and you need, naturally, a bunch of tools. Now, the owner of this
furniture factory is pretty cheap, and even though there are twenty
woodworkers, he knows that all twenty of you won't be needing one of
those cool cordless drills all at once. Being cheap, he buys only ten
of them, and puts them in a little room with one of those doors that
swings open only at the upper half. If you want a drill, you have to
go to the checkout room and get a drill from the guy behind the door.
When you ask for a drill, you are given one. If no drill is
immediately available, you are told to check back later.
(Fortunately, your co-workers are very considerate and always check
their drills in immediately upon finishing with them). Seems pretty
normal. The unusual twist here is that when the owner set up this
little check out stand, there are no drills at all in the room, and
the first time someone comes and asks for a drill, the guy in the
room has to go out and buy a brand new one to give the first
customer. If that drill is returned, then he'll hand it out again,
but if there are no drills available, he'll go buy a new drill if he
needs one, until there are a total of ten drills in the system. If
drill requests are coming slowly, he may never have to buy more than
two or three drills. If they are really slow, he may only have to buy
one drill and continue to hand out that lone drill. But if things get
really busy, and people aren't returning the drills as fast as they
are asking for them, he'll have to buy new drills until he gets to
the limit, in this case ten. If all the drills are checked out,
people wanting drills will just have to come back later.
Now, there's a further twist with all of this. When receive a
drill, you have to be careful, because chances are that the drill you
get isn't going to be brand new. The guy behind the counter doesn't
do anything to the drill when it is turned in other than collect it
and hang it back on the shelf. While your co-workers are very good
about turning in the drills right away when they are done, they don't
really do anything to “reset” the drill when they do turn
them in, nor does the guy at the counter. Thus, when you get a drill,
you don't know what state it will be in. Maybe it will be set on
reverse. Maybe it will have a drill bit set in it that you don't
need. Maybe it will have the tension set too high or too low for your
needs. Thus, you'll need to reset the drill yourself to make sure
that it is ready to do what you need to do. You never should assume
that the drill is ready to be used for the specific task you have for
it.
Webmodules are Like Drills
Webmodules are like the drills in the above illustration. There
are a limited supply of them, and there is a “pool” of
them available for all your users who make HTTP requests. The first
requests for the webmodules are fulfilled by creating a new instance
of the webmodule until the set limit of total webmodules is reached.
They are doled out by the thread pool manager on a first come first
serve basis. Requests are honored as fast as possible using the
available instances of the webmodule in the pool. When a webmodule is
done responding to a request, it is returned to the pool where it is
cached. When a request is answered with a cached webmodule, the
webmodule is passed in the state that it was left off. (This
last statement is particularly important. I'll cover ways to deal
with this later). This means that any variables, properties and
fields that were set to non-default values during the course of
executing the code in the webmodule will remain that way when a
cached module is given out. Realizing all of this is very important,
as these facts will affect the way that you build your application
and manage the data that is specific to both your end user and your
datamodules themselves. This whole issue is complicated even further
by the different ways that CGI, Apache DSO, and ISAPI applications
handle data and session information.
Handling a “Drill” that needs “Resetting”
Whenever a request comes in, it usually gets handled by a
webmodule that has been “returned” in some unknown state
(The exception being those early requests that actually get
freshly-created webmodules.) You should never assume that
anything in a webmodule is as it “should” be when
processing a request. You should always assume that the
webmodule doesn't know anything when a request comes in. Therefore,
you'll always need to initialize the webmodule every single time it
gets used. I'd recommend doing it this way: give the webmodule an
Initialize
method, and call it first thing in the OnBeforeDispatchPage
events of the webmodule. This way, you'll be sure that as each
request comes in, the webmodule is all set up and ready to begin
doing whatever it is it needs to do before it starts the dispatch
process.
Here's an example. Frequently on the newsgroups, a developer will
report that their users are seeing each other's query results. This
happens, of course, because they are trying to use “unreset
drills”. They are using variables in a webmodule or
webdatamodule that they aren't resetting for each request, or that
they are setting in one request and then then expecting to be still
set in another request. Thus, the next user sees what they previous
user was doing. This is particularly likely to happen whenever the
developer redirects the page to a new location. A
Response.SendRedirect
or a DispatchPageName
call results in a new request, and if you set the SQL for a
query and then redirect, it is unlikely that the thread pooler
will give back the same module. Since you now have a different
module, you'll likely have someone else's string in the SQL property.
Thus, you need to set the SQL for every request, and never
assume from one page to another that the query holds the data that
you set it to before you redirected. (By the way, these same rules
apply for webdatamodules. If you set the SQL on a query on a
webdatamodule and then go to another page, you almost certainly won't
get the same webdatamodule back, and who knows what your new user
will see?)
The first thing I'd recommend to avoid this is to never use any
type of global variable of any sort, and even think twice about using
a field variable in the webmodule itself. Global variables are
obviously not good in a multi-threaded application – who the
heck can tell what webmodule is currently accessing or changing it?
You could probably use critical sections or something similar to
guard the data, but I would say that if you are feeling the need to
use critical sections in your WebSnap application, then you probably
want to rethink your design. Field variables in the webdatamodule
will work if you initialize them in every action handler and
page dispatch event, but if you forget, then things could get ugly.
Instead, I'd recommend that to as large a degree as possible, you
use Session variables and adapter fields. They are easy to use, will
always hold the proper data for the user, and don't need to be
managed on a a per request basis. You can use them in the OnGetValue
event of an adapter field to make accessing them even easier. For
instance, let's say you want to keep track of a user's name. Create a
field on an Adapter called NameField,
and make it's OnGetValue
handler look like this:
procedure THome.NameFieldGetValue(Sender: TObject; var Value: Variant);
begin
Result := Sessions.Values['Username'];
end;
Then, when you grab the user's name from where ever you get it, just
set
Sessions.Values['Username'] := MyUserNameVariable;
and that is it. Now, if you need to use the user's name in your
Delphi code, you can call NameField.Value.
If you want to put the user's name in your HTML, you can call the
adapterfield's value from server-side Javascript. And you never have
to worry about initializing or resetting the value for your user's
name. Once you've set it, it will always be there, part of your
session variable, waiting faithfully to be used (or at least until
the session expires.) The session management system will ensure that
each user gets the correct data. You can even use this technique for
constant values if you like. Some will object to the fact that these
are variant types, and thus slow, but I'd counter that they work
great, aren't that slow since they are almost always string or
integer values, and don't need converting all that much. Besides, I
always say that safety and clarity of code is better than fast, bad
design.
Changing the Defaults
As I
mentioned, the above discussion covered the default threading system
for WebSnap. The way threads are managed is actually determined in
the code of all of your webmodules and webdatamodules. You may have
noticed some code in the initialization section of your webmodules.
It usually looks something like this --
initialization
if WebRequestHandler <> nil then
WebRequestHandler.AddWebModuleFactory(TWebPageModuleFactory.Create(TAdapterPageProducerPage2,
TWebPageInfo.Create([wpPublished {, wpLoginRequired}], '.html'),
crOnDemand, caCache));
This code creates a factory object that will either
create a new webmodule or grab a cached (“returned drill”)
webmodule from the pool of webmodules of this particular type,
depending how you set it up. The key parts of this code that concerns
us in our quest to understand WebSnap threading are the last two
parameters. They determine two things – when an instance of a
webmodule will be created, and how that web module will be treated
once it has been used. These two parameters are of the types listed
below:
TWebModuleCreateMode = (crOnDemand, crAlways);
TWebModuleCacheMode = (caCache, caDestroy);
The first parameter, of type TWebModuleCreateMode
determines the way that this module will be created. When set to
crOnDemand,
a copy of the webmodule will only be created or made available when
needed and there is not a cached instance available. “When
needed” means that a specific request for this webmodule has
arrived. For instance, if you have a page called “About us”
and a page called “Contact Us” on your site, the “Contact
Us” page will not be created when a request for “About
Us” comes in, but will be created or found in the thread pool
when a request for “Contact Us” comes in. When this flag
is set to crAlways,
the webmodule will be created for every request to the
application, regardless of whether that module has been asked for
specifically or not. In the previous example, if “Contact Us”
had crAlways
set, it would be created when a request for the “About
Us” page came in. crAlways
should be used for modules that will participate in most or every
request that comes into your WebSnap application. An example would be
a webdatamodule that contains database information needed by all
pages.
Note: When you create a new module, the wizard gives you the
opportunity to set these parameters. However, once the module has
been created, you'll have to manually edit the code to change the
parameters.
The second parameter, of type TWebModuleCacheMode,
determines what happens to a webmodule after it has responded to a
request. The webmodule can either be cached or destroyed. Obviously,
caCache
causes the webmodule to be cached in the thread pool, and caDestroy
causes the webmodule to be destroyed when it has finished its task
The trick here, of course, if determining how these flags should
be set. Usually, the default is fine. In the default, the webmodules
are created as needed, and stored for later use. However, if you have
a webmodule or webdatamodule that is 'common' to most requests (as
most WebDatamodules will be) or
participates in a large percentage of requests, you might set the
crAlways
flag to ensure that is it created with every request, saving the
effort of having to specifically hunt up and create an instance of
the module. If a module is used rarely in an application, you might
set the caDestroy
so that infrequently used instances aren't hanging around taking up
memory. And obviously, some combinations would make much sense, such
as setting crAlways
and caDestroy.
If you are creating an instance of a module for every request, it
doesn't make much sense to destroy it every time as well.
One thing to note here – the main webmodule for your
application, the page that contains all the main WebSnap components
like the TSessionsService
and the TWebAppComponents
component, is of type TWebAppPageModule
as opposed to TWebPageModule,
and only takes a TWebModuleCacheMode
parameter. WebAppPageModules are always created, as they participate
in every request to the application. You can set caDestroy
for your main module, but I'd recommend against it, unless you have
an application that is so infrequently used that there's no point in
even storing it in memory.
That should cover it. Threading and thread pooling are integral to
WebSnap, and understanding how they work is key to a successful
WebSnap project. Generally, if you use Session variables and always
reset all your webmodules for every request, then you should be fine.
As your application grows, you should be able to tell the parts of
the application that should be cached, created on demand, or
destroyed when not being used. And now you should be able to properly manage your WebSnap webmodules to build a well designed WebSnap application.
Nick Hodges is the Chief Technology Officer for Lemanix
Corporation. Lemanix is a Borland
Solution Partner that specializes in enterprise solutions, training,
and consulting using Borland Delphi. Nick is a frequent author and
conference speaker, and a member or Borland's
TeamB. Nick lives
in St. Paul with his wife and three children. He's working on
building one of these.
Connect with Us