Hanging by a WebSnap Thread

By: Nick Hodges

Abstract: This article covers the basics of webmodule threading in both WebSnap and WebBroker, as well as some strategies for dealing with the traps that it can create.

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.


Server Response from: ETNASC02