Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple users writing at the same file

I have a project which is a Web API project, my project is accessed by multiple users (i mean a really-really lot of users). When my project being accessed from frontend (web page using HTML 5), and user doing something like updating or retrieving data, the backend app (web API) will write a single log file (a .log file but the content is JSON). The problem is, when being accessed by multiple users, the frontend became unresponsive (always loading). The problem is in writing process of the log file (single log file being accessed by a really-really lot of users). I heard that using a multi threading technique can solve the problem, but i don't know which method. So, maybe anyone can help me please. Here is my code (sorry if typo, i use my smartphone and mobile version of stack overflow):

public static void JsonInputLogging<T>(T m, string methodName)
{
    MemoryStream ms = new MemoryStream();
    DataContractJsonSerializer ser = new 
            DataContractJsonSerializer(typeof(T));
    ser.WriteObject(ms, m);
    string jsonString = Encoding.UTF8.GetString(ms.ToArray());
    ms.Close();
    logging("MethodName: " + methodName + Environment.NewLine + jsonString.ToString());
}


public static void logging (string message)
{
    string pathLogFile = "D:\jsoninput.log";
    FileInfo jsonInputFile = new FileInfo(pathLogFile);
    if (File.Exists(jsonInputFile.ToString()))
    {
        long fileLength = jsonInputFile.Length;
        if (fileLength > 1000000)
        {
            File.Move(pathLogFile, pathLogFile.Replace(*some new path*);
        }
    }
    File.AppendAllText(pathLogFile, *some text*);
}
like image 860
wulung_try Avatar asked Jul 29 '15 06:07

wulung_try


3 Answers

You have to understand some internals here first. For each [x] users, ASP.Net will use a single worker process. One worker process holds multiple threads. If you're using multiple instances on the cloud, it's even worse because then you also have multiple server instances (I assume this ain't the case).

A few problems here:

  • You have multiple users and therefore multiple threads.
  • Multiple threads can deadlock each other writing the files.
  • You have multiple appdomains and therefore multiple processes.
  • Multiple processes can lock out each other

Opening and locking files

File.Open has a few flags for locking. You can basically lock files exclusively per process, which is a good idea in this case. A two-step approach with Exists and Open won't help, because in between another worker process might do something. Bascially the idea is to call Open with write-exclusive access and if it fails, try again with another filename.

This basically solves the issue with multiple processes.

Writing from multiple threads

File access is single threaded. Instead of writing your stuff to a file, you might want to use a separate thread to do the file access, and multiple threads that tell the thing to write.

If you have more log requests than you can handle, you're in the wrong zone either way. In that case, the best way to handle it for logging IMO is to simply drop the data. In other words, make the logger somewhat lossy to make life better for your users. You can use the queue for that as well.

I usually use a ConcurrentQueue for this and a separate thread that works away all the logged data.

This is basically how to do this:

// Starts the worker thread that gets rid of the queue:
internal void Start()
{
    loggingWorker = new Thread(LogHandler)
    {
        Name = "Logging worker thread",
        IsBackground = true,
        Priority = ThreadPriority.BelowNormal
    };
    loggingWorker.Start();
}

We also need something to do the actual work and some variables that are shared:

private Thread loggingWorker = null;
private int loggingWorkerState = 0;
private ManualResetEventSlim waiter = new ManualResetEventSlim();
private ConcurrentQueue<Tuple<LogMessageHandler, string>> queue =
    new ConcurrentQueue<Tuple<LogMessageHandler, string>>();

private void LogHandler(object o)
{
    Interlocked.Exchange(ref loggingWorkerState, 1);

    while (Interlocked.CompareExchange(ref loggingWorkerState, 1, 1) == 1)
    {
        waiter.Wait(TimeSpan.FromSeconds(10.0));
        waiter.Reset();

        Tuple<LogMessageHandler, string> item;
        while (queue.TryDequeue(out item))
        {
            writeToFile(item.Item1, item.Item2);
        }
    }
}

Basically this code enables you to work away all the items from a single thread using a queue that's shared across threads. Note that ConcurrentQueue doesn't use locks for TryDequeue, so clients won't feel any pain because of this.

Last thing that's needed is to add stuff to the queue. That's the easy part:

public void Add(LogMessageHandler l, string msg)
{
    if (queue.Count < MaxLogQueueSize) 
    {
        queue.Enqueue(new Tuple<LogMessageHandler, string>(l, msg));
        waiter.Set();
    }
}

This code will be called from multiple threads. It's not 100% correct because Count and Enqueue don't necessarily have to be called in a consistent way - but for our intents and purposes it's good enough. It also doesn't lock in the Enqueue and the waiter will ensure that the stuff is removed by the other thread.

Wrap all this in a singleton pattern, add some more logic to it, and your problem should be solved.

like image 56
atlaste Avatar answered Nov 10 '22 02:11

atlaste


That can be problematic, since every client request handled by new thread by default anyway. You need some "root" object that is known across the project (don't think you can achieve this in static class), so you can lock on it before you access the log file. However, note that it will basically serialize the requests, and probably will have a very bad effect on performance.

like image 31
Felix Av Avatar answered Nov 10 '22 03:11

Felix Av


No multi-threading does not solve your problem. How are multiple threads supposed to write to the same file at the same time? You would need to care about data consistency and I don't think that's the actual problem here.

What you search is asynchronous programming. The reason your GUI becomes unresponsive is, that it waits for the tasks to complete. If you know, the logger is your bottleneck then use async to your advantage. Fire the log method and forget about the outcome, just write the file.

Actually I don't really think your logger is the problem. Are you sure there is no other logic which blocks you?

like image 29
greenhoorn Avatar answered Nov 10 '22 03:11

greenhoorn