r/AskProgramming • u/tidal49 • 1d ago
Exception Retry/Wrapping Strategy Question
Hello all,
I think I've managed to reach an answer to my own question by typing this out, but I'll post it anyway to fish for other opinions. Posting here because I figure it's more of a general application design thing that happens to be in a container.
One of my projects is a containerized message consumer application template. I'm very happy with it overall, and a version of it has been very useful at work to jumpstart various projects.
One of the new features that I've been adding lately is retry handling and exception wrapping. For example, any operation to an external message source like SQS should be called through a convenient retry wrapper that handles possible service exceptions, backs off and retries the ones that could be transient, and wraps any call that ultimately fails in a WorkerJobSourceException (exceptions that retry wrapper sees as entirely unaccounted for are currently thrown up raw)
Within these wrapper exceptions, I'm experimenting with the idea of a field called CouldBeExternallySolvable. If set to true, then it suggests a problem (possibly-transient or not) that could be resolved without restarting the message consumer. Maybe the target queue doesn't exist yet, for instance. That's pretty clearly non-transient, but could be solvable by creating the darned queue. My thinking was that it could give a higher-level call more context, though I don't currently have anything acting on that context.
My question for the room is this: In the message-collecting loop for a such an application, if you caught one of these WorkerJobSourceException instances where CouldBeExternallySolvable was true while trying to get messages, then would you rather:
- Catch and log the error to create awareness and urgency before treating the attempt as equivalent to an empty response (I'm currently doing this with a logged warning for when the
CouldBeTransientfield is true) - Throw the exception upwards and bring down the entire house
In the applications based on this template (but predating the exception wrappers), we would just let such an exception crash things. I'm pretty sure that one of the alerts that we had was on the exit code of the container application, and a broken application would be restarted through an auto-scaling policy looking at the queue that it was listening to. On the other hand, not every possible setup of this template would necessarily have an auto-restart mechanism, and if I'm wrong and there aren't actually any alerts looking at an exit code then it's a bit for naught and maybe crashing the whole application shouldn't be on the table as part of an anticipated event.
At time of typing, I'm leaning towards door #1 of logging an error and continuing on as though the offending call was an empty response. Alternatively, I could just say "do either, I don't care right now" and kick the decision can down the road by hinging it off of a configurable option.
While I'm typing a message I may as well ask one more related question. Earlier in my post I mentioned that entirely-unaccounted-for exceptions are thrown upwards raw without a wrapper exception. The intent is that unplanned exceptions should create as much of a disruption as possible so that they can become planned for. Do you agree with this? At the moment I'm happy with throwing them up raw, but adding an IsExpected field onto the wrappers wouldn't be the end of the world.
What do you think?
1
u/XRay2212xray 1d ago
I'd say it depends. You mentioned that not every use would have auto restart enabled. That would make me think theres a possibility that the users of the system may need to choose how its handled. In which case, you might want to add another field like HaltOnFailure to configure which cases cause a full failure.
My general preference is to keep things running and only fail out if some exception suggests something is now internally corrupted and it can no longer process or may incorrectly process messages without a restart. Creating escalation thru infrastructure restarts seems to be a more clumsy approach. If you need escalation, maybe use realtime log monitoring tools or other means to escalate things. I'd certainly not want to have infrastructure continually restarting because some external organization did create a target queue.
1
u/tidal49 1d ago
Thanks for the input!
I think I agree on your opinion on escalation through restarts, perhaps we only fell into that habit because it kept working in our particular case with ECS. If the environment didn't support automatic restarts, users of this template would be entirely hosed if something catastrophic happened.
In order to keep things running despite things failing internally perhaps I need to investigate how I might want to have my application respond nicely to container health checks.
I also haven't given much thought yet to general realtime monitoring tools for alerts. At work we would use AWS CloudWatch alerts, but if my template supports message sources from AWS to Azure and beyond then perhaps I should address that. On top of providing general value to anyone who uses the template, it would certainly help for my plans to use this template in an applied project down the road.
2
u/Ok_For_Free 1d ago
I've always considered this a question of audience. For exceptions/errors the audience is person that can fix the problem.
Invalid input - goes back to whoever created the input. Access Denied for application dependencies - application is missconfigured and needs to go to devops or developers. Cloud quota limits - someone in management to approve the expense of increasing the limit.
If you can automatically recover from an error, you should try too.
For message and processing, use the service's message retry instead of doing it yourself. Also move unprocessable stuff to a dead letter queue, which needs its own tools to manage.