SPM, SPM, Egg, Chips and SPM

The Shared Property Manager is basically a mechanism for accessing shared memory in a structured, controlled manner. Actually, calling it “shared memory” is stretching things a little, as we’re talking about memory shared between individual instances within the same server, rather than the more enticing prospect of memory shared between different processes on the same machine. However, this isn’t quite as restrictive as it might seem at first, as we shall see later on.

Shared properties are single, named or numbered VARIANT-type data items, grouped together in named, er, groups. The SPM is accessed by means of three COM interfaces:

ISharedPropertyGroupManager

ISharedPropertyGroup

ISharedProperty

The names get shorter as you get near the action.

It’s actually pretty simple to use. First of all, you get hold of an interface to the shared property group manager, ISharedPropertyGroupManager. This has the following methods:

Method Parameters
CreatePropertyGroup [in] BSTR name,
[in,out] LONG* plIsoMode,
[in,out] LONG* plRelMode,
[out] VARIANT_BOOL* pfExists,
[out] ISharedPropertyGroup** ppGroup
get_Group [in] BSTR name,
[out] IsharedPropertyGroup** ppGroup
get__NewEnum [out] IUnknown** ppEnumerator

(Whilst we’re at it, notice that get__NewEnum() interface — remember the enumerator we implemented in Chapter 1? They pop up all over the place…)

Using this, you can either create a new property group or get access to an existing one, in the form of an ISharedPropertyGroup interface. This has the following methods:

Method Parameters
CreateProperty [in] BSTR name,
[out] VARIANT_BOOL* pfExists,
[out] ISharedProperty** ppProp
CreatePropertyByPosition [in] INT index,
[out] VARIANT_BOOL* pfExists,
[out] ISharedProperty** ppProp
get_Property [in] BSTR name,
[out] ISharedProperty** ppProp
get_PropertyByPosition [in] INT index,
[out] ISharedProperty** ppProp

This enables us to get access to the properties in the group. We can either create new properties, or get access to existing properties. These can either be named or numbered, according to taste. However, you can’t get access to a named property by number, or a numbered property by name. Either way, what we end up with is an ISharedProperty interface. This has the following methods,

Method Parameters
get_Value [out] VARIANT* pVal
put_Value [in] VARIANT value

which are pretty self-explanatory.

I must confess that I struggled a little to put together a decent application to demonstrate how SPM locks shared memory, in a way that could easily show what happened without SPM. What I wanted to do was generate an easily reproducible race condition that could be avoided by using SPM. What I ended up with was a very dull component that controls access to a simple flat file. Still, it will do the trick. We’re going to develop a very simple component, called, not surprisingly, FlatFile. This has a very simple interface:

Method Parameters Description
Write [in] int value Write value, twice to the flat file “c:\race.txt”

Now, that’s what I call a simple interface. This is the entire code for the object (in project NospamServer):

STDMETHODIMP CFlatFile::Write(int value)
{
   WriteToFile ("c:\\race.txt", value);
   WriteToFile ("c:\\race.txt", value);
   return S_OK;
}

void CFlatFile::WriteToFile (LPSTR lpszFile, int value)
{
   char szValue[128];
   sprintf (szValue, "%d\r\n", value);

   HANDLE hFile = CreateFile (lpszFile, GENERIC_WRITE, 0, NULL,
                        OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL +
                        FILE_FLAG_WRITE_THROUGH, NULL);

   SetFilePointer (hFile, 0, 0, FILE_END);

   ULONG nWritten;
   WriteFile (hFile, szValue, strlen (szValue), &nWritten, NULL);

   CloseHandle (hFile);
}

The Client

For our client, we’ll use a very simple Visual Basic application, based around a form with one text box, one button and one timer. Here’s the code:

Dim flat As FlatFile
Dim counter As Integer

Private Sub Form_Load()

   Timer1.Enabled = False
   Timer1.Interval = 1
   counter = 1000
   Set flat = New FlatFile

End Sub

Private Sub cmdStart_Click()

   Timer1.Enabled = True

End Sub
Private Sub Timer1_Timer()

   flat.Write (txtValue.Text)
   counter = counter - 1

   If (counter <= 0) Then
      MsgBox ("Finished!")
      Timer1.Enabled = False
   End If

End Sub

Let’s see what happens if we set off a couple of EXE clients, both using this component to write to c:\race.txt. Our client will cycle around on a timer, writing the same number to the file 1000 times. If you try this out, not surprisingly you get a pretty random set of data in the output file. You’ll see a whole load of 1s, then a couple of 2s, then a few pairs of 1s, then maybe a glut of 2s and so on. There is no consistent pattern to it at all — it's lumpy. This is simply because as soon as the first client is held up by the write to file, the second one grabs the CPU and has its go.

However, let’s see what happens when we add some SPM-style locking. We’ll create a clone of our project in the new project SpamServer. This is the new version of the Write method:

STDMETHODIMP CFlatFile::Write(int value)
{
   CComPtr<IObjectContext> pObjectContext = NULL;
   GetObjectContext (&pObjectContext);

   CComPtr<ISharedPropertyGroupManager> pSpamMgr;
   CComPtr<ISharedPropertyGroup> pSpamGroup;
   CComPtr<ISharedProperty> pSpamProp;

   if (pObjectContext)
   {
      pObjectContext->CreateInstance
                     (CLSID_SharedPropertyGroupManager,
                     IID_ISharedPropertyGroupManager,
                     reinterpret_cast(void**)&pSpamMgr);

      LONG lIsolationMode = LockMethod;
      LONG lReleaseMode = Process;
      VARIANT_BOOL bExists = VARIANT_FALSE;

      pSpamMgr->CreatePropertyGroup (L"LockGroup", &lIsolationMode,
                              &lReleaseMode, &bExists,
                               &pSpamGroup);
      pSpamGroup->CreateProperty (L"LockProperty", &bExists,
                           &pSpamProp);
   }

   WriteToFile ("c:\\race.txt", value);
   WriteToFile ("c:\\race.txt", value);

   return S_OK;
}

What are we doing here? We are instantiating the Shared Property Manager, creating a property group (called LockGroup), and creating a property within that (called LockProperty). If you try to create a property group or property, and it already exists, SPM treats the request as a get_ type request — it will tell you that it already exists by setting the bExists flag.

Let’s see what happens now. We install the component to run under MTS, as we did with Negotiator, except that don’t need to specify anything about the component requiring a transaction. This is all we need to do to get a valid object context. We also need to change our clients to reference the SpamServer type library rather than the NospamServer one. This time, when we run two clients, we get a nice neat sequence of one or more pairs of 1s, then one or more pairs of 2s, then one or more pairs of 1s again. The creation of that shared property is acting as a lock. There is only one resource available here, and each client must wait its turn.

Where would we use this feature? We would use it wherever there is a need to share state between two separate instances of the same MTS object, in other words, where there was a need for some interaction between the two instances. The SPM can therefore be used to cache DBMS lookup tables and counters in order to avoid hot spots.

© 1998 by Wrox Press. All rights reserved.