Search code examples
javamultithreadingloggingstringbuffer

Not seeing full data in the string buffer in mutli thread logging


I am using ThreadLocal StringBuffer for storing entire log to move it to kibana at the end of each test case execution. But when I am running cases in parallel, the log is loosing some entries. I am writing the log into file before adding it to StringBuffer and I see all entires in the log file but not in the StringBuffer.

Any ideas are much appreciated.

I tried String Builder but no luck. Tried to make synchronized still no luck. I tried writing the log entry into file before moving into string buffer and I see all entire there but not getting all entries in string buffer

public abstract class Messages {

    static ThreadLocal<StringBuffer> msg;
    public static void init() {
         msg = new ThreadLocal<StringBuffer>() {
            @Override
            protected StringBuffer initialValue() {
                return new StringBuffer();
            }
     };

     public static void addMsg(String msg) {
          msg.get().append(msg + "\r\n");
            System.out.println(msg);
    }
}

public class CallingClass(){
    public void callingMethod(String threadName){
       Messages.init();
       Messages.addMsg("Hi");
       Messages.addMsg("This");
       Messages.addMsg("Is");
       Messages.addMsg("Testing");
       Messages.addMsg("For");
       Messages.addMsg("Multi");
       Messages.addMsg("Thread");
       Messages.addMsg("UI");
       Messages.addMsg(threadName + "!!");
    }
}

From my cucumber tests, we call the above method callingMethod from each thread.

I am running 10 parallel threads and the result is different when I print the msg at the end from all 10 threads, I see for some threads it is missing the first few entries.

I tried making addMsg synchronized but still no luck. In a single thread execution, the log is proper and also when I am using debug from eclipse, the log is coming properly as expected.


Solution

  • Multiple threads call Messages.init().

    The first time we call init the previous value of null for msg is replaced with a new ThreadLocal.

    The next time we call init the previous value is replaced with a new copy of ThreadLocal, effectively discarding all previously used/created StringBuffer objects.

    And the same the next time ...

    There's no point in that init method and you should get rid of it (and all calls to it) and just define msg like this:

    static final ThreadLocal<StringBuffer> msg = new ThreadLocal<StringBuffer>() {
                @Override
                protected StringBuffer initialValue() {
                    return new StringBuffer();
                }
         };
    

    The whole point of ThreadLocal is that you only need one instance of it to give each thread their own StringBuffer.

    Also, if all access to the content is via those methods, then the synchronization/thread safety of StringBuffer isn't needed and you should use a StringBuilder instead.