0

I have created a basic logging class, in it currently is a 2 dimension array which stores the log information of up to 100 events (then starts over-writing the information so I only keep the last 100 events in it).

On a log event of a certain level, this array data is dumped into a database on an AsyncTask thread.

At the moment the array is defined at the class level, so the AsyncTask thread is accessing the same array that the GUI thread is accessing (which is giving me NullPointerExceptions, as it should).

I need a way to make a copy of the values of the array as it is in the instant the AsyncTask thread starts, so that this background thread can read all the data and send it to the database correctly - this is the point I'm stuck at.

// Defined in the class
private static String[][] logHistory = new String[100][6];


// Class to log errors to a database
private static class asyncLog extends AsyncTask<Void,Void,Void>
{
    // This is the background thread
    @Override
    protected Void doInBackground(Void... params)
    {
        Thread.currentThread().setName("LogToDB.asyncLog.doInBackground");              

        // Make a copy of the array in its current state
        final String[][] logTemp = new String[100][6];
        for (int i = 0; i < 100; i++) 
        {
              for (int j = 0; j < 6; j++) 
              {
                logTemp[i][j]=logHistory[i][j];
              }
        }

        // ...snip
    }
}

Clearly the final modifier is wrong here, since I am still getting the exception

I would like to know:

  • Is this the right way to do what I am trying to do?
  • If not, what is a better way?
  • How to have a string array copied in a background thread so that regardless of what changes the GUI thread make to the original array, the copy always has the same values until the thread ends and it is destroyed

I have seen lots of examples of coping 1D arrays, but these are mostly int data types (eg this one, and as I understand strings, they are always references :/

EDIT As requested, see stack trace:

08-23 09:17:35.570: E/AndroidRuntime(8030): FATAL EXCEPTION: LogToDB.asyncLog.doInBackground
08-23 09:17:35.570: E/AndroidRuntime(8030): java.lang.RuntimeException: An error occured while executing doInBackground()
08-23 09:17:35.570: E/AndroidRuntime(8030):     at android.os.AsyncTask$3.done(AsyncTask.java:299)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.FutureTask.run(FutureTask.java:239)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.lang.Thread.run(Thread.java:856)
08-23 09:17:35.570: E/AndroidRuntime(8030): Caused by: java.lang.NullPointerException
08-23 09:17:35.570: E/AndroidRuntime(8030):     at coty.production.downtimeaquisition.LogToDB.Insert(LogToDB.java:393)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at coty.production.downtimeaquisition.LogToDB.access$10(LogToDB.java:387)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at coty.production.downtimeaquisition.LogToDB$asyncLog.doInBackground(LogToDB.java:558)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at coty.production.downtimeaquisition.LogToDB$asyncLog.doInBackground(LogToDB.java:1)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at android.os.AsyncTask$2.call(AsyncTask.java:287)
08-23 09:17:35.570: E/AndroidRuntime(8030):     at java.util.concurrent.FutureTask.run(FutureTask.java:234)
08-23 09:17:35.570: E/AndroidRuntime(8030):     ... 4 more

LogToDB.java:558 is the call to the Insert method

LogToDB.java:387 is the Insert method

LogToDB.java:393 is the highlighted line:

Statement stmt=null;

try 
{
    stmt=dbConn.createStatement(); // This line
    stmt.executeUpdate(sql);
}  
Community
  • 1
  • 1
iabbott
  • 873
  • 1
  • 8
  • 23
  • 1
    Wait, why are you supposed to be getting NPEs? That part of your question makes no sense at all. – chrylis -cautiouslyoptimistic- Aug 23 '13 at 08:06
  • Try other threads !!! http://stackoverflow.com/questions/9106131/how-to-clone-a-multidimensional-array-in-java or http://stackoverflow.com/questions/5617016/how-do-i-copy-a-2-dimensional-array-in-java for example – An-droid Aug 23 '13 at 08:08
  • @chrylis because I am getting 4 or 5 logging threads at once, which I assumed to mean that the array has been updated multiple times, meaning the older threads would not have the correct data any more – iabbott Aug 23 '13 at 08:09
  • The OP understands not just to copy the reference to the top-level array itself; the problem is that there's no particularly elegant way to copy multidimensionals (and all of the SO answers I've found managed to miss `Arrays#copyOf`, even called iteratively). – chrylis -cautiouslyoptimistic- Aug 23 '13 at 08:09
  • 4
    You're making incorrect assumptions. A final modifier on a field doesn't cause exceptions to be thrown. Post the exact and complete stack trace of the exception and ask us why it's thrown and how to fix it. Your array copy is correct. – JB Nizet Aug 23 '13 at 08:11
  • 3
    Side note: Don't use a multi-dimensional array to contain logging data. Create a class, e.g. `LogItem` that contains the information required for each logging event and store these in a `List` (or maybe an array). – Duncan Jones Aug 23 '13 at 08:11
  • I think you're using the wrong data structure for the task. The idea is to have a circular buffer and then dump the whole thing when the app generates an "alarm" signal, defined as a log message of sufficiently high level? – chrylis -cautiouslyoptimistic- Aug 23 '13 at 08:11
  • @chrylis yes that is right – iabbott Aug 23 '13 at 08:13
  • @DuncanJones thanks, I didn't think of that, but will I not run into the same issue with the List of LogItems in the 2nd thread? – iabbott Aug 23 '13 at 08:14
  • @iabbott: you will at least have the possibility to synchonize access to the data. Encapsulation is key when dealing with threads. – JB Nizet Aug 23 '13 at 08:18
  • @JBNizet i have added the stack trace – iabbott Aug 23 '13 at 08:30
  • 2
    So, what could be null at this line? `dbConn` is the only possibility. So you forgot to initialize `dbConn`. – JB Nizet Aug 23 '13 at 08:42
  • Or `dbConn` got initialized in a separate thread and isn't `volatile`. – chrylis -cautiouslyoptimistic- Aug 23 '13 at 08:46
  • @JBNizet damn.. that was it - i had initialised it but before this thread was started...it seems it was being closed before it should, so I shuffled the open/close code around so now the thread can open and close a local connection var :) silly me – iabbott Aug 23 '13 at 10:16

1 Answers1

1

I'm going to assume that what you're trying to do is to have a "phantom log" of events that only gets dumped in the event of some undesired condition, so that you're not cluttering up your main log but can see details whenever some error happens. If that's the case, here's how I'd go about it:

  1. Define a class LogMessage that contains the necessary information about your message, usually a String and a log level (should be an enum), possibly with a field for a Throwable. See log4j and slf4j for examples of what this interface usually looks like.
  2. Define a fixed-length buffer class; unfortunately, while the 1.6 concurrent APIs have interfaces that support fixed-length queues, I wasn't able to find a nice, efficient implementation. The best option depends greatly on the frequency of your reads and dumps, but I'd start by doing something like allocating a fixed-size array, using an AtomicInteger as an array index. The standard (unlimited-sized) implementations are based on the paper "Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms". The reference to this array would need to be volatile or backed by an AtomicReference.
  3. When the "funnel" API that's tracking log levels sees a log message of suitable importance, it swaps out the entire log structure atomically and fires off a job to dump it. If you're logging to a facility that would get confused by multiple log threads, you might even submit the entire log buffer into a BlockingQueue that a single long-running dump thread reads from.
chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • 1
    i marked this as answer since it helped me track the real problem down, and made my code more reliable. Thanks to all who helped :) – iabbott Aug 23 '13 at 10:17