7

long-time reader, first-time asker here. I have a service which writes data to a database in batches. It contains a buffer which is being watched by a separate thread. Whenever the buffer reaches a certain size or a certain period of time has elapsed since the last write, the thread writes the data to the DB.

In Java, whenever there is an exception in the monitor thread, it just silently dies and never does anything again.

Is there an established best practice of handling exceptions in such threads? The thread must be running at all times and should print out an exception to the log if one occurs.

There are some potential solutions to this that come to mind:

  1. Wrap the entire methods in try/catch blocks, catch all exceptions and have a Logger print the messages. (Way too ugly for my taste)
  2. Register an uncaught exception handler and deal with the exceptions there. (maybe?)
  3. Do not use a Thread, but a Callable or a Runnable instead. (Not sure if this is suitable for always-running threads)
  4. Anything else?
9
  • You need to decide which exceptions can be handled and which should cause the whole setup to be dismantled and shutdown gracefully. Handled exceptions don't halt the thread and can simply log. Unhandled exceptions should use a finally clause to properly close resources, but otherwise should not be "handled", just raised. The thread that started the setup can verify the existence of exceptions and log or retry or whatever accordingly.
    – Neil
    Mar 28, 2018 at 14:30
  • @Neil: The problem is that standard Threads do not propagate exceptions back to the thread that created them, so they are not even logged. The thread just dies silently. I think that's what Callable is for, it allows you to check for exceptions.
    – JohnEye
    Mar 28, 2018 at 14:45
  • 1
    What about a simple run(){try{doTask();}catch(Exception unexpected){/*log it, etc*/}}?
    – dagnelies
    Mar 28, 2018 at 16:34
  • @dagnelies: Yes, but that's identical to my solution 1, isn't it?
    – JohnEye
    Mar 28, 2018 at 17:00
  • basically, yes ...it's just that instead of wrap entire methodS, you just have to override doTask from SafeThread. I mainly don't see what's ugly about that
    – dagnelies
    Mar 28, 2018 at 17:30

2 Answers 2

6

Firstly, I didn't understand what you need the monitor thread for and what is it's purpose. The only reason I could think of is if the monitor thread is a continually running thread that just watches the buffer and periodically calls the database write. If that is the purpose, I would rather do it with schedulers, e.g. schedule a write every n seconds and write the whole buffer to the database. Then you just need to approximate how much seconds you need the scheduler rate to be in order for the buffer not to overflow.

As for the exception handling in threads, I would first retry the write. Ultimately, if everything fails, first thing you should do is salvage the data so it doesn't get lost.

The way exceptions are meant to be handled is to identify the exceptions that can occur and then only handle those specific exceptions. You should never do things like catch (Exception e). Since the DB writer service is a general service, I would have it propagate the exception to the caller and then have the caller handle the exception, since the service cannot know what behavior the caller wants.

As for how you should handle those specific exceptions is up to you and hardly anyone can help you with that. If you cannot handle the exceptions in finally block and they need developer's attention, I would log them or notify the developer through alternate communication channel, e.g. email, Slack.

3
  • You know, the schedulers might be the right way to do it, I'll just need to figure out how to properly make them work in predetermined intervals or whenever a buffer is full. But they do seem to abstract away all the stuff I don't want to handle myself quite nicely.
    – JohnEye
    Mar 28, 2018 at 14:27
  • Well, your code was already kind of working like a scheduler. Database writes can generally work in the ballpark of 10000 writes per second, so that will hardly be your bottleneck. I'd say you need to take more care of JVM memory. Your formula should be something like scheduler rate = {DB max writes/s} ÷ {max incoming requests/s} ÷ {overflow security factor, 5 or 10} for data of average size of 1kB. If any more than that, you most likely need to optimize by memory size.
    – leonz
    Mar 28, 2018 at 14:53
  • "you just need to approximate how much seconds you need the scheduler rate to be in order for the buffer not to overflow" That is a terrible suggestion. The rate could vary drastically. Instead you want your code to automatically adapt to higher and lower data rates. I suspect the OP is already doing this: check the size of the buffer and the elapsed time at a frequent interval (many times per second) so it responds quickly. Checking the conditions is cheap, doing the DB write is expensive. This should work great as long as you either have a loop with a sleep or are scheduling the thread.
    – Tony
    Jan 4 at 18:14
4

You never mentioned if the thrown exceptions MUST be handled/logged by the main thread or not. I'll assume "it doesn't matter, I just need to log the exception and avoid thread corruption".

If my assumption is correct, then you might simply need to put the try/catch on the Runnable's run method, but such method must be running on an infinite loop and sleep once in a while:

final Thread t = new Thread(new Runnable(){
    @Override
    public void run(){
          long lastTimeFlushed = getCurrentTime();
          while(true){
                try{
                     if(bufferLimitExceeded() || timeoutExceeded(lastTimeFlushed)){
                           flushToDB();
                           lastTimeFlushed = getCurrentTime();
                     } 
                     Thread.sleep(500);
                }catch(final InterruptedException ex){
                     // you'll need to decide whether to ignore or not thread's interruption
                     // This example is in case you decide not to ignore it
                     Thread.currentThread().interrupt();
                }catch(final Exception ex){
                     logError(ex);
                } 
          } 
     } 
});

t.start();

This way, your thread won't die and you would only need to add try/catch in a single place.

Disclaimer: I wrote this using my phone. Sorry if there are typos (especially in the code)

Not the answer you're looking for? Browse other questions tagged or ask your own question.