Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

MTS (and COM+) and global variables

In new article, Jimmy Nilsson explains why you shouldn't use global variables in MTS components and in regular VB components that use a thread pool. On the way he also provides some neat tricks for changing the number of threads assigned to a MTS package and a "new" way to handle the connection string.


advertisement

Ever since you were a little kid youve known that you should be careful with global variables, right? In this article I will try to motivate you once again why you shouldnt use them. On the way I will give some other tips, such as how to change the number of threads for a package and a "new" solution for handling the connection string.

As I said, you all know that global variables are evil. Module level variables have slightly fewer drawbacks, but they still have basically the same problems. Even though you know about the problems (such as unexpected side-effects when you make code changes), you perhaps have a bunch of those variables anyway in your projects? You are not alone!



Perhaps you have heard that you definitely shouldnt use global variables in MTS components? But do you know why? Dont you hate being told what to do and not to do, without knowing the reason why? (Im also very sure that several of you havent heard that you should avoid using global variables in MTS components before, since it isnt mentioned that very often.)


THE PROBLEM

If you use global variables in your component that lives in MTS, you typically wont notice any problem with it in your tests. Perhaps you will never get any trouble. If you do get trouble, it will probably be when the production server has a load peak. (That is the worst time, right?) So what could happen? One possible problem could be that whatever an instance wrote to a global variable isnt there when the instance goes back again to read the variable. Another value is found instead. Only your imagination sets the limit of what can happen. Scary? Yes.

Why is this so? The reason for this very unwanted behaviour is that your MTS package will by default have 100 threads. When there are 100 activities or less, each activity "has" its own thread. Each thread on the other hand has its own global variables. Everybody is happy.

When there are more activities than threads however, the activities will be multiplexed over the threads. Several activities will share the very same global variables. The catastrophe could be near.


THE KEY TO TESTING THIS IN MTS

You can change the default number of threads for a package by following those steps:

  1. Locate HKLM/SOFTWARE/Microsoft/Transaction Server/Packages/{"the package GUID"} in the registry.
  2. Add a DWORD Value named "ThreadPoolMax".
  3. Give it a value between 0 and 0x7FFFFFFF.

Perhaps this information may also be of interest to you at other times when you think it would be better to change the number of threads from the default setting, because of performance reasons, for example.

As usual, take care when you play with the registry!


HEY, WHAT ABOUT WINDOWS 2000 AND COM+?

Im sorry that Im a bit retro here, but unfortunately it isnt as easy to force the problem in Windows 2000 as in MTS since there is no documented way (at least not today) to change the number of threads for COM+ applications.

I did a really fast test though and got the very same problem in Windows 2000 too. More on that later in this article. First the test in MTS.


THE REST OF A SIMPLE TEST

If you would like to see the "whole" problem in a small test, you can try it out by following these steps:

  1. Create a new project (ActiveX DLL).
  2. Change the name of the project to "GlobalVar" and the name of the class to "Test".
  3. Add a BAS module (the name doesnt matter) and paste in the following code:
  4. Option Explicit Global gstrName As String

  5. Paste the following code to the class "Test":

  6. Option Explicit

    Public Sub NameSet (ByVal vstrName As String) gstrName = vstrName End Sub
    Public Function NameGet() As String
      NameGet = gstrName
    End Function

  7. Build the DLL, set it to binary compatibility and register it in MTS.
  8. Set the number of threads per package to one, by following the instructions in the section "THE KEY TO TESTING THIS" above.
  9. Create a new project (Standard EXE).
  10. Add two text boxes and two command buttons to the form. Let their names be Text1, Text2, Command1 and Command2. Blank out the text property for both text boxes.
  11. Set a reference to the GlobalVar-DLL.
  12. Paste the following code into the forms class module:


  13. Option Explicit
    Private mobj As GlobalVar.Test

    Private Sub Command1_Click()
      mobj.NameSet Text1.Text
    End Sub

    Private Sub Command2_Click()
      Text2.Text = mobj.NameGet()
    End Sub

    Private Sub Form_Load()
      Set mobj = _
      CreateObject("GlobalVar.Test")
    End Sub

  14. Build the EXE.
  15. Start two instances of the EXE and watch the result of the global data being shared by the two instances of GlobalVar.Test You will see this, for example, when you set one value in the first client, then set another value in the second client and finally get the value from the first client. It will be the value set in client two!

A NECESSARY TEST?

Perhaps you think (like me) that it may be a good idea to always include a test in your integration test phase (or preferably earlier) when you lower the number of threads for your packages when you or your QA-team are doing tests? Hopefully you may find a nasty bug or two then, before the customer does.


WORKAROUNDS?

One typical workaround to limit problems with global variables is to use only module level variables (private variable in a BAS module) and then use a set-sub and a get-function to limit the ways of reaching the variable. Unfortunately that wont help with the problem I am talking about here at all. Yet another solution would be to use a static variable like this in a BAS module:

Public Function NameInfo (Byval vstr As String) As String
  Static sstrName As String
  If Len(vstr) > 0 Then
    sstrName = vstr
  End If

  NameInfo = sstrName
End Function

But once again, the problem is the same.

At first I thought that using synchronization objects such as mutexes could help to solve the problem, but they are not useful for creating different instances of global variables. Even if they were useful, you should be very, very cautious about creating serialization scenarios in your components, since they will hurt scalability a lot.

You can invent your own locking scheme for the global variables, but once again you will hurt scalability a lot. Perhaps you wonder if correctness isnt more important than high throughput? Of course, but most often you need both. I suggest that you try to find a different solution instead!

Well, myself I dont have any global variables in my MTS components. In my applications at least, they can be avoided (even if it sometimes means extra code, extra data sent over the net, extra roundtrips to the database and so on).

I also believe that since this is state that we are talking about here, it isnt taken care of by MTS very well, and that is no coincidence. In MTSs opinion, state is preferably held in the client or at the database server.


ALWAYS A PROBLEM?

You can of course also use this knowledge so that you benefit from it. In Sub Main you can set a global variable to a value that should be the same for all the users. (Dont forget to set the Startup Object for the DLL project in this case.) If the value is readonly and static through the life of the process, then there is no contention problem. A typical example could be the connection string. That could be a nice solution if you like to store the value in the registry and would like to limit the trips to the registry. This can even be better than using the SPM for storing the value since there are serialization-problems with the SPM. (See my article "The 19:th programming trap in MTS" at VB-2-The-Max.)

Observe also that if you use "Retain in memory" (which you definitely should!), then the Sub Main will only be executed the first time the thread is used for the life of the process. That way, the number of visits to the registry will be very low.


BACK TO WINDOWS 2000 AGAIN

I promised to discuss Windows 2000 and COM+ a bit further. I did some very quick tests on Windows 2000 Server with the very same component and test driver as I used above. Even though I didnt know how to set the number of threads per application (as you probably know, packages in MTS are called applications in COM+), I got the very same problem almost immediately. In my test, I fired up eight instances of the test driver. When I started the ninth test driver instance, its COM+ component reused the thread and the global variables of the eighth COM+ component As a matter of fact, the problem seem to be even worse for COM+ than for MTS!

I would like to point out once again that I did a very fast test here, but when I have had time for more experiments and/or when more information has been released about this, I would be happy to post a short notice about it at VB-2-The-Max.


ACTIVE X EXE SERVERS

BTW, please observe that the problem isnt only appearing with MTS packages and COM+ applications. You have the same problem with multithreaded ActiveX EXE servers that use a thread pool.


CONCLUSION

Im very sure that you now realize how hard some bugs can be to track down if you dont know about the problem with global variables in MTS/COM+ and/or if you dont know how to test it.

A special thanks to Troy Cambra at Microsoft for giving valuable information and inspiration for this article. Thanks, Troy!



   
Jimmy Nilsson is the owner of the Swedish consultant company JNSK AB (www.jnsk.se). He has been working with system development since late 1988 (with VB since version 1.0) and, in recent years, he has specialized in component-based development, mostly in the Microsoft environment. He has also been developing and presenting courses in database design, object-oriented design, etc. at a Swedish University for six years. Jimmy is the author of ".NET Enterprise Design with Visual Basic .NET and SQL Server 2000" and he often speaks at VSLive conferences.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date