Serializing a request for an ASP page containing COM objects

M

Michael D. Kersey

Anthony said:
It's not just Application.Lock that's involved here. For the duration of
the application lock nothing else can use the application lock not even a
simple read.

A call to Application.Lock on an ASP page will not prevent another ASP
page (or another copy of the same ASP page) from reading data from the
Application object. In other words, Application.Lock locks out (other)
writers but not readers.
If the COM object performs an operation that requires any appreciable
elapsed time (regardless of whether the it's a CPU hog or not) then the
application can be significantly affected

Yes. Primarily because requests for the ASP page(s) using the COM object
will be serialized for any critical code section.
as requests stall trying to read
the application object whilst it's locked by this operation.

No. They will be able to read data from the Application object unless
they call the Lock() method. Again, Application.Lock locks out (other)
writers but not readers.
Not for this job they didn't. Lock is there to avoid race conditions when
modifying multiple values.


If all you have is a hammer everything looks like a nail. Many ASP
developers have more tools in their tool box.

Sure, but in this case we have a nail and we have a hammer. Why stop to
build your own hammer when a Microsoft hammer is provided?

Are we _afraid_ to use the big, bad Microsoft hammer? Maybe it will
_explode_ if we hit the nail! Maybe it is _radioactive_, will irradiate
the metal in the nail, the nail will fatigue prematurely and our house
will fall down! Maybe we'll die of radiation poisoning! Oooooooohhh! Who
knows _what_ _might_ _happen_!

Most scary of all, it might actually work! The Microsoft hammer may
strike the nail and drive it into the wood. Jeez!
It isn't rocket science to
create a small COM wrapper round a mutex and now you have the right tool for
the job.

Please feel free to post the code here for all to see. Please make sure
it's thoroughly debugged as is the Application.Lock/Unlock code I
provided earlier. I invite Max to compare running his application using
your to-be-provided code versus Application.Lock/Unlock.
Using Lock in this way is just setting yourself up for problems in
the future.

IMO this is making a mountain of a molehill. Max can test the
Application.Lock and .Unlock in 10 minutes easily; it's 2 lines of code.
 
A

Anthony Jones

Michael D. Kersey said:
A call to Application.Lock on an ASP page will not prevent another ASP
page (or another copy of the same ASP page) from reading data from the
Application object. In other words, Application.Lock locks out (other)
writers but not readers.

Ok do this test (I've done it and I know the results).

You'll need a small COM component to expose the Sleep API call, if not do
something else that doesn't hog the CPU but takes some time to complete.
(Like copying a large file across a network)


Page1.asp

Application("Thingy") = "Hello World"


Page2.asp

Application.Lock

MyComponent.Sleep 10000 ' 10 Seconds

Application.Unlock

Response.Write "Done"


Page3.asp

Response.Write Application("Thingy")



Hit Page1 to wake up the app and initialise Thingy.

Now using two separate instances of IExplorer (we need to simulate two
different sessions). Hit Page2.asp with one then immediately hit Page3.asp
with the other.

If you are correct Page3 should return immediately but of course it doesn't.
Page3 can't complete until Page2 releases the Lock. The lesson here is
don't believe everything you read in the documentation. Test it for
yourself.


Yes. Primarily because requests for the ASP page(s) using the COM object
will be serialized for any critical code section.


No. They will be able to read data from the Application object unless
they call the Lock() method. Again, Application.Lock locks out (other)
writers but not readers.

Do the test and see.
Sure, but in this case we have a nail and we have a hammer. Why stop to
build your own hammer when a Microsoft hammer is provided?

Are we _afraid_ to use the big, bad Microsoft hammer? Maybe it will
_explode_ if we hit the nail! Maybe it is _radioactive_, will irradiate
the metal in the nail, the nail will fatigue prematurely and our house
will fall down! Maybe we'll die of radiation poisoning! Oooooooohhh! Who
knows _what_ _might_ _happen_!

Most scary of all, it might actually work! The Microsoft hammer may
strike the nail and drive it into the wood. Jeez!

Hmm... there can't be agreement here when you believe application.lock to
be something it is not. Did you understand my earlier statement 'Lock is
there to avoid race conditions'? Have you read my other posts in this
thread detailing when and why you need to use Lock?
Please feel free to post the code here for all to see. Please make sure
it's thoroughly debugged as is the Application.Lock/Unlock code I
provided earlier. I invite Max to compare running his application using
your to-be-provided code versus Application.Lock/Unlock.

Fine. It's not like I've got anything better to do. Watch this space.
IMO this is making a mountain of a molehill. Max can test the
Application.Lock and .Unlock in 10 minutes easily; it's 2 lines of code.

I'm sure it would work assessing its impact on the rest of the application
would be another matter. What affect it has in production isn't so easy.

Hmm.. and test it again when other changes are made to the applicaiton
because this code can affects the WHOLE application and again and again...
 
A

Anthony Jones

Max said:
The COM object is very complex. It is single threaded and also maintains
state in the process between calls (using global variables and the like!)
and there are multiple calls to it from our customers web page. Yes I know
that is not how a good COM object is written but there is lots and lots of
legacy code in it!!). It cleans up on PageEnd so that the next request will
be treated correctly. Normally it would not run for a long time, however it
reads and executes byte code that is provided by our customers so "anything
may happen". It is however multiuser safe so that 2 or more processes do
not interfer with each other.

I was hoping that the idea to wrap it in a process lock would protect the
process state and to use a web garden would help with performance.

Ouch. Do you have other uses for the application object? If not then you
could use it as single mutex to protect the COM object as Michael suggested.
After all this misuse pales compared to the COM object you've described.

However since Michael continues to challange my POV I'll post some VB6 code
to wrap a Mutex when I get the chance.

Anthony.
 
A

Anthony Jones

However since Michael continues to challange my POV I'll post some VB6 code
to wrap a Mutex when I get the chance.

So here it is. Create a VB6 dll project called MutexFactory.
Set project properties Unattended Execution and Retain in Memory
Keep the Threading Model Apartment Threaded.

Add the following Mutex.cls Class Module set it's Instancing to
PublicNotCreatable.
Obviously you'll have undo the line wraps created by the reader.


Option Explicit
Private Const mcsModule = "Mutex."

Private Const ERROR_ALREADY_EXISTS = 183&
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const FORMAT_MESSAGE_IGNORE_INSERTS = &H200
Private Const LANG_NEUTRAL = &H0
Private Const WAIT_OBJECT_0 As Long = &H0
Private Const WAIT_TIMEOUT As Long = 258&
Private Const WAIT_ABANDONED As Long = &H80
Private Const WAIT_FAILED As Long = -1
Private Const HRESULT_WIN32 As Long = &H8007000
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA"
(lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As
String) As Long
Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long)
As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long)
As Long
Private Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA"
(ByVal dwFlags As Long, lpSource As Any, ByVal dwMessageId As Long, ByVal
dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long,
Arguments As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle
As Long, ByVal dwMilliseconds As Long) As Long

Public Enum OwnershipState
TimedOut = 0
Succeeded
TakenAbandoned
End Enum

Private mhMutex As Long
Private mlOwnershipCnt As Long

Friend Sub Init(rsName As String)

mhMutex = CreateMutex(ByVal 0&, 0, rsName)

If mhMutex = 0 Then ApiRaise Err.LastDllError, mcsModule & "Init"

End Sub

Public Function GetOwnership(ByVal Timeout As Long) As OwnershipState

Dim lResult As Long

lResult = WaitForSingleObject(mhMutex, Timeout)

Select Case lResult
Case WAIT_FAILED
ApiRaise Err.LastDllError, mcsModule & "GetOwnership"
Case WAIT_OBJECT_0
mlOwnershipCnt = mlOwnershipCnt + 1
GetOwnership = Succeeded
Case WAIT_TIMEOUT
GetOwnership = TimedOut
Case WAIT_ABANDONED
mlOwnershipCnt = mlOwnershipCnt + 1
GetOwnership = TakenAbandoned
End Select

End Function

Public Function Release() As Boolean

Dim lResult As Long

lResult = ReleaseMutex(mhMutex)

If lResult <> 0 Then
mlOwnershipCnt = mlOwnershipCnt - 1
Else
ApiRaise Err.LastDllError, mcsModule & "Release"
End If

End Function

Private Sub Class_Terminate()

Dim i As Long

If mhMutex <> 0 Then

For i = 1 To mlOwnershipCnt
ReleaseMutex mhMutex
Next

CloseHandle mhMutex
mhMutex = 0

End If

End Sub

Public Sub ApiRaise(ByVal vlError As Long, rsSource As String)
Err.Raise vlError And &HFFFF& Or HRESULT_WIN32, rsSource, ApiError(vlError)
End Sub

Private Function ApiError(ByVal vlError As Long) As String

Dim s As String
Dim lCnt As Long

s = String(256, 0)

lCnt = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM Or
FORMAT_MESSAGE_IGNORE_INSERTS, _
ByVal 0&, vlError And &HFFFF&, LANG_NEUTRAL, s, Len(s),
ByVal 0&)

If lCnt <> 0 Then ApiError = Left$(s, lCnt)

End Function




Add other Class called Factory leave it's instancing set to Multiuse:-

Option Explicit
Private Declare Sub SleepApi Lib "kernel32" Alias "Sleep" (ByVal
dwMilliseconds As Long)

Public Function GetMutex(ByVal Name As String) As Mutex
Set GetMutex = New Mutex
GetMutex.Init Name
End Function

Public Sub Sleep(ByVal Milliseconds As Long)
SleepApi Milliseconds
End Sub


I've included the sleep method which helps with testing.

Compile this up.

Usage:

Create an instance of the Factory object ("MutexFactory.Factory")

oMF = Server.CreateObject("MutexFactory.Factory")


To get a mutex call the Factory GetMutex method with a unique name for the
resource you want to protect. The INSTANCE_META_PATH server variable can be
useful if there are multiple instances of your app on the server. Be
careful not to exceed MAX_PATH I recommend you don't make the string longer
than 255.

oMutex = oMF.GetMutex("Unique_Name")


To enter serialise a chunk of code you first need to take ownership of the
Mutex.
The GetOwnership method takes a timeout value in milliseconds. -1 is
indefinite (not recommended).

lResult = oMutex.GetOwnership(5000) ' Wait for 5 seconds to get ownership

0 = Timed out.
1 = Successfully acquired ownership of Mutex
2 = Aquired ownership of a Mutex that had been abandoned (the previously
owning thread terminated before releasing the mutex)

If ownership has been aquired the Mutex should be released as soon as is
possible.

oMutex.Release()

Multiple successful calls to GetOwnership should be matched by an equal
number of calls to Release.


Example:-

This page is serialises all calls to it without impacting any other pages
(apart from consuming worker threads that might otherwise be processing
other requests)

'Serialised.asp
<html>
<head>
<title>Serialised</title>
</head>
<body>
<%
Dim oMF: Set oMF = Server.CreateObject("MutexFactory.Factory")
Dim oMutex: Set oMutex = oMF.GetMutex(ScriptName)
Dim lOwnershipState

lOwnershipState = oMutex.GetOwnership(7000)

If lOwnershipState Then
Response.Write "Doing work<br />"
oMF.Sleep 5000
Response.Write "Finished"
oMutex.Release()
Else
Response.Write "Timeout trying to own mutex"
End If

Function ScriptName()
ScriptName = Request.ServerVariables("INSTANCE_META_PATH") &
Request.ServerVariables("SCRIPT_NAME")
End Function

%>
</body>
</html>

If the amount of work involved is significant it may be worth setting the
timeout low and responding back to the client asking them to try again later
(or if you can use a javascript solution to retry all the better). This
would avoid consuming threads if many requests want this resource on an
otherwise busy site.

I hope you find this useful.

Anthony.
 
M

Michael D. Kersey

Anthony said:
penalty

It's not just Application.Lock that's involved here. For the duration of
the application lock nothing else can use the application lock not even a
simple read.

A call to Application.Lock on an ASP page will not prevent another ASP
page (or another copy of the same ASP page) from reading data from
OR WRITING DATA TO the Application object. In other words,
Application.Lock locks out only (other) pages that use Application.Lock.
If the COM object performs an operation that requires any appreciable
elapsed time (regardless of whether the it's a CPU hog or not) then the
application can be significantly affected

Yes. Primarily because requests for the ASP page(s) using the COM object
will be serialized for any critical code section.
as requests stall trying to read
the application object whilst it's locked by this operation.

No. They will be able to read data from the Application object unless
they call the Lock() method. Again, Application.Lock locks out only
(other) pages that use Application.Lock.
Not for this job they didn't. Lock is there to avoid race conditions when
modifying multiple values.


If all you have is a hammer everything looks like a nail. Many ASP
developers have more tools in their tool box.

Sure, but in this case we have a nail and we have a hammer. Why stop to
build your own hammer when a Microsoft hammer is provided?

Most scary of all, it might actually work! The Microsoft hammer may
strike the nail and drive it into the wood. Jeez!
It isn't rocket science to
create a small COM wrapper round a mutex and now you have the right tool for
the job.

Please feel free to post the code here for all to see. Please make sure
it's thoroughly debugged as is the Application.Lock/Unlock code I
provided earlier. I invite Max to compare running his application using
your to-be-provided code versus Application.Lock/Unlock.
Using Lock in this way is just setting yourself up for problems in
the future.

IMO this is making a mountain of a molehill. Max can test the
Application.Lock and .Unlock in 10 minutes easily; it's 2 lines of code.
 
M

Max

Thank you Anthony and Michael for your suggestions and code examples. I
have learnt quite a lot from all the replies to my posts. I will test you
suggestions and hopefully get the COM object functioning correctly.

Thanks again to everyone who reponded to my post.

Max
 
M

mdkersey

I tried your suggested test (below). And what I said directly above is
totally, utterly wrong. As you stated when the Application.Lock method
is called, it locks out _all_ attempts to read or write from/to the
Application object by any page other than the caller.
Ok do this test (I've done it and I know the results).

You'll need a small COM component to expose the Sleep API call, if not do
something else that doesn't hog the CPU but takes some time to complete.
(Like copying a large file across a network)


Page1.asp
Application("Thingy") = "Hello World"

Page2.asp
Application.Lock
MyComponent.Sleep 10000 ' 10 Seconds
Application.Unlock
Response.Write "Done"

Page3.asp
Response.Write Application("Thingy")

Hit Page1 to wake up the app and initialise Thingy.

Now using two separate instances of IExplorer (we need to simulate two
different sessions). Hit Page2.asp with one then immediately hit Page3.asp
with the other.

If you are correct Page3 should return immediately but of course it doesn't.
Page3 can't complete until Page2 releases the Lock.
Yep!

The lesson here is
don't believe everything you read in the documentation.

While you've provide me a way to weasel out, upon reviewing the
documentation online, I believe I never understood it completely. The
Application.Lock documentation online states:

doc> The Lock method blocks other clients from modifying the
doc> variables stored in the Application object, ensuring that only
doc> one client at a time can alter or access the Application
doc> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
doc> variables.

And that "one client" is the client who has the Lock.

Thanks.
 
M

Michael D. Kersey

Anthony Jones suggested ASP test programs revealed that I had a critical
misunderstanding of the Application.Lock/Unlock methods:
Anthony Jones wrote:
<snipped>
AJ> > It's not just Application.Lock that's involved here.
AJ> > For the duration of the application lock nothing
AJ> > else can use the application lock not even a
AJ> > simple read.MK> A call to Application.Lock on an ASP page will not prevent another ASP
MK> page (or another copy of the same ASP page) from reading data from
MK> OR WRITING DATA TO the Application object. In other words,
MK> Application.Lock locks out only (other) pages that use Application.Lock.

My statement directly above is wrong. As Anthony Jones correctly points
out, when the Application.Lock method is called, it locks out _all_
attempts to read or write from/to the Application object by any page
other than the caller.

<snipped>
AJ> > as requests stall trying to read
AJ> > the application object whilst it's locked by this operation.MK> No. They will be able to read data from the Application object unless
MK> they call the Lock() method. Again, Application.Lock locks out only
MK> (other) pages that use Application.Lock.

And my above statement is also wrong for the same reason.

<snipped>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top