Implementing .NET pooling

This topic explains how to create and configure a pool on the client machine and how to use the pooling support methods.

Implementation overview

You must create your assembly and develop your application right from the start with pooling in mind. You’ll need to do the following:

1. (optional, but recommended) Write pooling support methods (activate, initialize, etc.) and add them to the SMC. See Using the pooling support methods.
2. Create your assembly. If you wrote pooling support methods, include the interface that contains them in the assembly. Select the “Support pooling” checkbox in the Component Information dialog box in Workbench. (Or, if you are working from the command line, specify the ‑p option when you run the batch file.) See Building an assembly in Workbench or Building an assembly from the command line.
3. Write your client‑side code. See Writing code that uses pooled objects.
4. Run regsvcs.exe to create a pool. See Creating a pool.
5. (optional) Set the desired options in the xfNetLink .NET Configuration Utility. See Setting options in the config file .
6. Use the Component Services utility to configure the pool. See Configuring the pool in Component Services.
7. Start the pool, and then run your application. See Starting the pool.

Creating a pool

The pool may be created automatically when you build your project in Visual Studio. If it is not, run the regsvcs.exe utility from a command prompt to create the pool. You will also need to use this utility to set up the pool when you deploy your completed application at a customer site. The regsvcs utility is distributed with the .NET Framework.

For example:

regsvcs assembly.dll

where assembly is the name of your Synergy assembly. You must run this command for each Synergy assembly that uses pooling. This command creates a COM+ application (named with the assembly name) and adds the procedural classes in your assembly to the application.

Configuring the pool

Once a pool has been created, it can be configured. Most of the configuration is done in Component Services, but there are two settings in the config file that affect pooling as well. If you need to change the configuration after the pool is running, see Changing the pool configuration.

Setting options in the config file

There are two settings specific to pooling—initialize time‑out and pool return—that can be set in the application configuration file. You may want to override the default values for these settings. To do so, run the xfNetLink .NET Configuration Utility and specify the desired settings. You can create settings at the default class level or for individual classes. See Creating and editing configuration files for more information about specifying settings.

Configuring the pool in Component Services

1. From Administrative Tools, select Component Services. (On Windows Vista, run %windir%\system32\comexp.msc.)
2. In the tree in the left pane, expand the Component Services node and its subnodes until you see the COM+ Applications node. Expand this node and locate the node named for your Synergy assembly. (See figure 1.)

1. The Component Services window, showing a COM+
application with components.

3. Expand the node for your application, and then expand the Components subnode to display the individual components. There is a component for every class in the assembly. If you created multiple copies of the same class (gencs ‑i option), you will see a component for each copy of the class.
4. Highlight an individual component node (e.g., ConsultIt.AppLogin.1 in figure 1) and select Action > Properties.
5. In the Properties dialog box go to the Activation tab. (See figure 2.)

2. Setting properties for a pooled component .

6. Select the “Enable object pooling” checkbox (it may already be selected), and then enter values for the following:

Minimum pool size

Enter the number of objects you want created at pool start‑up. The pool will never drop below this size.

Maximum pool size

Enter the maximum number of objects that you want in the pool at any one time. When deciding how large to make the pool, keep in mind how many xfServerPlus licenses you have available and how many objects you have pooled. The maximum pool size value for all pooled objects should not exceed the number of available xfServerPlus licenses.

Creation timeout

Enter the length of time, in milliseconds, that a request should be queued when all objects in the pool are in use and the pool is at its maximum size. If the request is not satisfied within the specified time, an error is thrown.

7. Select the “Component supports events and statistics” checkbox. This option enables you to view statistics about the pooled object, such as the pool size and the number of objects in use, in the Component Services window.
8. If the “Enable Just In Time Activation” checkbox is selected, clear it.
9. Click OK in the Properties dialog box. Repeat steps 4 through 9 above to configure each component.
Tip

If you have problems with the pool shutting down unexpectedly, try selecting the “Leave running when idle” option on the Advanced tab of the Properties dialog box that is accessed from the COM+ application node (“ConsultPro” in figure 1). Alternatively, you may want to select “Enable idle shutdown” and increase the value in the minutes field. The pool will wait indefinitely for the first connection, but once a connection to the pool has been made, this timer starts counting.

There are numerous other pooling settings that you can configure. The recommended configuration will depend upon your specific application. Refer to your COM+ documentation for details on additional settings.

Changing the pool configuration

The pool size and creation time‑out values are read when the pool is started. If you change these values in the Component Services utility, you must stop and restart the pool for the changes to take effect.

The xfNetLink .NET Configuration Utility settings (host, port, pool return, etc.) are read each time a new object is created and added to the pool. You should restart the pool after changing these settings to ensure that all objects in the pool are using the new settings.

Because restarting the pool will deactivate any objects currently in use, you may want to check the pool status before stopping the pool. To check the status, in Component Services highlight the Components node under your COM+ application node and select View > Status. The number of objects in use displays in the Activated column.

Starting the pool

1. Highlight the node for your COM+ application.
2. Select Action > Start.

If you do not start the pool manually, it will be populated (that is, started) the first time a client requests an object. Note, however, that this means that the first user will have to wait for the pool to start and the objects to be initialized. Because the primary goal of pooling is to improve performance at start‑up, we recommend manually starting the pool.

Tip

(Windows Vista) If you get a permissions error when attempting to start the pool, it may be related to role‑based security. To turn off this security check, right‑click on the node for the COM+ application you created and select Properties. In the Properties dialog box, go to the Security tab and clear the “Enforce access checks for this application” box. Click OK in the dialog box.

Using the pooling support methods

xfNetLink .NET enables you to access pooling support methods defined by the COM+ API. There are five pooling support methods—Initialize, Activate, Deactivate, CanBePooled, and Cleanup —which are called automatically at specific times during the lifetime of the pooled object. This enables you to specify that certain actions be performed at certain times during the object’s creation and use.

To use the pooling support methods

1. Write Synergy routines that perform the desired tasks. You can have several routines of the same type, if necessary (e.g., you might want different initialize routines for a customer object and an order object). The Synergy routines can be named anything you like.
2. Add the routines to the SMC by attributing the Synergy code, running dbl2xml, and then importing the XML file. (Or by entering data using the MDU.) Because these methods are called automatically, the method name must be one of the following: Initialize, Activate, Deactivate, CanBePooled, Cleanup.
Note

If you are attributing your code, you may need to use the name property of the xfMethod attribute to specify the correct pooling support method name; else, the method name will default from the Synergy routine name.

If you have more than one routine of the same type (e.g., two initialize routines), you will need to use the id property of the xfMethod attribute to specify a unique method ID (because the method ID defaults from the method name).

You can find more information about the name and id properties in xfMethod attribute.

3. Include the Synergy routines when you build your ELB.
4. Generate classes and build the assembly as you normally would, being sure to include the interface that contains the pooling support methods. Select the “Support pooling” checkbox in the Component Information dialog box in Workbench. (Or, if you are building the assembly from the command line, specify the ‑p option when you run the batch file.)

Pooling support methods

The methods are listed below in the order in which they are called during the object’s lifetime.

Initialize()

status = Initialize()

status – a ^VAL value that indicates whether the initialization was a success. Returns 0 for success or 1 for failure.

If the return value is 1, the object is destroyed, an error is logged in the client‑side log (if logging is turned on) and the Windows event log, and no other objects in the pool are created. When this happens, the pool will have been started, but there will be no objects in it. To recover from this state, you must stop the pool, fix the problem with the Initialize() method, rebuild the ELB, and then restart the pool.

Initialize() is called when the object is created. Use this method to prepare the environment by opening files, initializing global data, and so forth. Because Initialize() is called when the object is created, this method gets called only once per object, even if the object is reused. Compare with Activate().

Activate()

Activate()

Activate() is called when the object is requested by a client. This method can be used for code that should be executed when the object is actually used.

Both Activate() and Initialize() can be used for similar purposes—preparing the environment before using the object. The primary difference between them is that Activate() is called every time the object is allocated to a client, whereas Initialize() is called only once when the object is created.

Deactivate()

Deactivate()

Deactivate() is called when an object is released by the client. It can be used to reset the environment to a known state before an object is returned to the pool. Note that because objects can be reused, this method may be called multiple times. Compare with Cleanup().

CanBePooled()

status = CanBePooled()

status – a ^VAL value that indicates whether the object should be discarded or reused. Returns 0 if the object should be discarded; returns 1 if the object should be returned to the pool for reuse.

The CanBePooled() method is called after Deactivate(), and can be used to determine at runtime if an object can be re‑used. For example, if Deactivate() encountered an error, CanBePooled() could indicate that the object should be discarded. Or, Deactivate() could be written to check how much effort is required to clean up an object before returning it to the pool. If the effort is excessive, and it would be more efficient to discard the object and create a new one, CanBePooled() would return 0.

CanBePooled() overrides the pool return setting in the application configuration file (see pool return in the Class Settings table).

Cleanup()

Cleanup()

Cleanup() is called by the object’s disconnect() method. If the object’s connection is shared, it is called after the final disconnect(). It can be used to do the final object cleanup, such as closing files. This method is called only when objects are going to be discarded (i.e., pool return is set to false or CanBePooled() returns 0). If the object is going to be reused, use the Deactivate() method to perform cleanup‑type activities.

The Cleanup() method is also called when socket communication with the client is unexpectedly lost. When the pool is created, the Cleanup() method is automatically registered with the XFPL_REGCLEANUP routine on the server. (This routine must be in your SMC; see XFPL_REGCLEANUP for more information.) Then, if there is a fatal error that causes xfServerPlus to lose socket communication with the client, xfServerPlus calls the Cleanup() routine before shutting down.

Although Cleanup() and Deactivate() can be used for similar purposes, there are two fundamental differences between them:

Writing code that uses pooled objects

The code that you write when using pooling will look somewhat different than code for use without pooling, which was described in Using your Synergy .NET assembly: The basics. Because the pooled objects already have a connection to xfServerPlus, you do not need to call the connect() method. The disconnection from xfServerPlus is also handled by the pool, so you do not need to call disconnect().

You must add a reference in your Visual Studio project to System.EnterpriseServices. The xfNetLink .NET classes use this namespace.

If your client application is written in C#, when using pooling you should instantiate the object with a using statement as shown in the example below. Instantiating the object in this manner means that it exists only within the scope of the using statement. When the using statement ends, the object is released, and the Deactivate() and CanBePooled() methods are called. Failure to instantiate the object with a using statement can result in problems with the object being properly released and returned to the pool for reuse.

For example,

string userID = "MFranklin";
string password = "abc123";
try 
{
    using (AppLogin userSess = new AppLogin())
    {
        userSess.login(userID, password);
    }
} 
catch (Exception e) 
{
    //Error handling code
} 
Note

If you are using ASP, instantiating the object within a using statement limits you to using the object on a single ASP page. If your application requires that the object have session scope (that is, you need to maintain state across several ASP pages), we do not recommend using pooling, as it is difficult to ensure that the object will be properly released, returned to the pool, and reused.

The following code shows the same example written in VB.NET.

Dim userID As New String("MFranklin")
Dim password As New String("abc123")
Try
  Using userSess As New AppLogin()
    userSess.login(userID, password)
  Catch Ex As Exception
    ' Error handling code
  End Using
End Try

The following C# example shows how to use multiple copies of the same class in conjunction with pooling. (See Using multiple copies of the same class for more information about this feature.) In this example, our client application routes the user to a particular xfServerPlus port based upon a customer ID entered on the logon screen. We generated two instances of the AppLogin class, AppLogin and AppLogin1. We’ll use the generated C# interface (IAppLogin) to access methods in these classes so that all users can use the same client application, even though they are using different servers. As in the example above, we instantiate the object with the using statement.

//Class fields
private string userID;
private string password;
//Method to set up the classes to create instances at each port
private void Button_Click(object sender,
  System.EventArgs e)
{
  //Class name consists of "NameSpace.Class,Assembly"
  //Create instance for xfSP port 2356
  string className = "ABCComputers.AppLogin,ConsultIt";
  signOn(className);
  .
  .
  .
  //Create instance for xfSP port 4550
  className = "ABCComputers.AppLogin1,ConsultIt";
  signOn(className);
  .
  .
  .
}
//Method to create C# interface and use it with pooling
static void signOn(string className)
{
  using (IAppLogin userSess = getIAppLogin(className))
  {
      //Call the Synergy method using the C# interface
      userSess.login(userID, password);
  }
}
//Method to create an instance of the correct class and cast it
// to the C# interface
static IAppLogin getIAppLogin(string className)
{
  //Dynamically create an instance 
  Type tp = Type.GetType(className);
  Object obj = Activator.CreateInstance(tp);
  //Assign the object to the C# interface
  IAppLogin x = (IAppLogin)obj;
  return x;
}