I implemented an HTTP server in c# .NET:
public class HttpServer
{
private HttpListener listener;
public HttpServer()
{
listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8080/");
}
public void Start()
{
lock(this) {
listener.Start();
AsyncProcessing(listener);
}
}
public void Stop()
{
lock (this) {
listener.Stop();
}
}
private void AsyncProcessing(HttpListener listener)
{
if (listener == null)
return;
listener.BeginGetContext(new AsyncCallback(Callback), listener);
}
private void Callback(IAsyncResult result)
{
HttpListenerContext context = null;
lock(this) {
HttpListener listener = (HttpListener)result.AsyncState;
if (!listener.IsListening)
return;
context = listener.EndGetContext(result);
AsyncProcessing(listener);
}
/* handle request */
}
}
I have some questions about this implementation:
- I added some locks here and there to prevent race conditions, but I’m confused: Are those really needed? I read in the documentation that all public non-static methods are NOT thread safe. Why haven’t I seen code where this fact is considered?
- How does the HttpListenerContext behave? Does he have some sort of connection to the HttpListener? Or can I use multiple HttpListenerContexts concurrently?
- I heard HttpListener wouldn’t be ready for production systems, but I’ve never seen an argument supporting this claim. Is it true or not?
- Are there other things I should consider which I haven’t mentioned?
Thanks for your ideas
Bear in mind that I’m no expert in multithreading, so you should take care to verify, as best as you can, anything I say.
If anyone else knows, and would like to just steal my entire answer and just edit in or correct the details, feel free to do that.
Let’s deal with your questions one by one:
Well, yes and no. Locks are typically used to prevent multiple threads to access the same data structure at the same time, since it would corrupt the data structure. Consider sorting an array on one thread and inserting an element in the middle in another, timing those two threads correct would corrupt the contents of the array.
Now, in your code you are locking on
thiswhich is never a good idea. Outside code might also take a lock on the same object, and that’s out of your control, so in the interest of creating production ready code, I would not do that.If you need locks in your code, I would construct specific lock objects and use those.
In other words, add this:
and then everywhere you have
lock(this)replace it withlock(_Lock)instead. This way you can also have multiple locks, if needs be.As for actually needing locks, I’m not 100% sure on that. The thing I’m not sure about is that you’re locking before calling Stop, and you’re locking in the callback and inside the lock you check if the listener is still running.
This will prevent you from stopping the listener after accepting a request, but before you’ve actually processed the request. In other words, it sounds like you would prevent closing the server with open requests still being handled.
But, no, you wouldn’t prevent that, because you might stop the server after leaving the locked section in the callback, but during or before the commented code has fully executed, so you would still have that problem.
However It will also mean you’ve effectively serialized some of the callback method, the part where you call EndGetContext and restart the BeginGetContext cycle. Whether this is a good pattern or not, I don’t know.
Here I will make a guess. That class has no reference back to the listener class, or, it has a thread-safe way of cooperating with it.
It wouldn’t be much of a thread-based http listener system if every access to the request/response data has to be serialized.
In any case, if in doubt, check the documentation of the methods and/or properties on the context class that you’re accessing, and if you need to take steps to ensure thread safety, the documentation will say so.
(see comment on question)
Multi-threaded code is hard to write. Judging by your question I would venture a guess that you haven’t done a lot of it, and to be honest, though I have done a lot of it, I still feel like I’m on thin ice.
My advice is thus as follows: