views:

133

answers:

4

I have an asmx web service that should only be allowed to respond to 1 client at once.

In otherwords if the service is getting called by client A, and server B calls, I would like B to pend until A is finished then B can get serviced.

If this is too complicated then at bare minimum calls from B should fail with a user defined error during the time A is engaging the service.

The reason is that the service relies heavily on IO operations and XML serialization, so it is crucial that the service does not get called simultaneously by more than 1 client.

Thanks in advance

+1  A: 

I don't know how it's implemented in .net, but I guess you want to implement a lock object which is 'owned' by a request when it is being serviced and cannot be given to more than one request at a time. In Java, I might synchronize on some global object.

Be careful though, watch for the usual concurrency issues... say for a naive implementation, one request checks for the lock and finds it available, then sleeps and a second request checks for the lock and takes it, then the first request wakes, thinking the lock is free and bad things happen). Also make sure you handle the case that request processing crashes leaving the lock in place.

As the usage pattern of making a non-concurrent web-based system is taken (certainly in the Java servlets world) as a bad practise, I'd suggest you throw an error back in the case where the service is already in use rather than block as it might be difficult to judge the impact of blocking.

Brabster
You mean flag an asp.net global application variable perhaps?
JL
Like I said, I'm not sure of the details for asp.net as I don't work in that language, but you need some object that is visible for all requests that can be used to advertise that a request is being serviced. The request could then check the object and only continue if no other requests are underway. Be careful though, watch for the possibility of deadlock and make sure you handle the case that request processing crashes leaving the lock in place.
Brabster
+3  A: 
static object _LockObject = new object();

void WebServiceCall()
{
    lock(_LockObject)
    {
        // Do work...
    }
}

Create a static object that you call lock() on. The lock() statement will prevent other calls from executing the code inside until the first execution that got the lock completes.

Note that depending on your timeout settings B might fail with a timeout depending on how long A takes to complete.

Update: Yes you can use the Monitor class in place of lock(). You could use the Monitor.TryEnter() method to check if the object is already locked or not (ie: if you wanted to return an error instead of waiting).

More details:

From http://msdn.microsoft.com/en-us/library/aa664735(VS.71).aspx:

A lock statement of the form

lock (x) ...

where x is an expression of a reference-type, is precisely equivalent to

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

From http://msdn.microsoft.com/en-us/library/de0542zz.aspx:

Use Enter to acquire the Monitor on the object passed as the parameter. If another thread has executed an Enter on the object, but has not yet executed the corresponding Exit, the current thread will block until the other thread releases the object.

So it's just by design that the code knows to block and not skip. You could skip if you wanted by using the Monitor.TryEnter() method.

Cory Charlton
Excuse my ignorance but this means nothing to me, can you elaborate? thanks
JL
@JL: Added some more details. Let me know if you have a specific question you'd like more detail on.
Cory Charlton
Is it also possible to determine the lock state of an object?
JL
@JL: Yup, see my update.
Cory Charlton
Ok last question, how does the above code know to wait, and not just skip the code block?
JL
@JL: More details. Basically the code knows to wait because another piece of code/web service call has already executed Monitor.Enter() on the _LockObject but has not yet called Monitor.Exit()
Cory Charlton
Why the downvote?
Cory Charlton
A: 

I don't know why you want to do so, but anyway, that can be a valid scenario. Try to look how Linux's APT package manager acquires lock:

To prevent multiple instances of package manager from spawning, for a package manager to function, it needs to lock the lock file and write it's PID in it.

Similarly, you can create a file in the Virtual Host's root. When a client is connected, lock that file, i,e write something in it. When, it finishes, make the file empty. Before trying to lock it, try to see if something is there inside. If yes, then return back the error message to the client.

Manish Sinha
This is a good answer, but you're forgetting about one important point - restart ability. If the ws thread dies, how do you take action to ensure the file is reset, otherwise you could end up with a situation where the service call is locked to ALL clients.
JL
Oops! I didn't think about that. If the client doesn't release the lock(clear the file), the person has to manually clear it up. The first answer to this question(above) is right. Cheers!!
Manish Sinha
+2  A: 

I understand the requirement to only process one request at a time but I don't think only allowing one request at a time is the answer.

Some of the answers suggested blocking the request. Although this will work on a small scale, this will result in problems such as time-outs and the ability to scale to more servers.

An alternative ist that you service each request as they come in and place it on a queue for later processing. This queue can be processed one task at a time (or depending on how busy your server is).

The original requesters can be notified in a couple of ways off completion. One way would be to poll to see if the original request has completed or if it still in the queue for processing, perhaps using a generated token (such as GUID).

Philip Fourie
+1 Given the additional info now available, I agree that queuing requests sounds like the right think to do.
Brabster