Edit #1
I believe that I misrepresented my intent when writing this question by focusing too much on the issues that have arisen from our misuse/misunderstanding of locks. I am making this edit to try to clear my goal up.
I am looking to gain advice on whether or not performing some sort of static analysis and building a call graph once at the beginning of the lifetime of a server is a good idea. I am intending to use this analysis to solve lock issues, but that is another issue. Is this feasible, or is it too costly of an operation?
Original Post
I am currently tasked with finding a way to fix deadlock problems in a server that is constantly being maintained and developed on. I am wondering about the feasability on building some sort of map of function call trees representing the paths we might take given a request to the server. The trees do not have to be fully built, as I will explain below.
Setup
- The server is written in Java using the Spring framework.
- We are using SQL to communicate with our relational database. Inside of our database is a table marked
tb_lockthat is used as way to simplify locking resources that might exist in multiple tables across the database.
tb_lock looks something like this:
| function_nm |
|---|
| tb_member |
| tb_member^0 |
| tb_member^1 |
| CHANGE_BALANCE |
| CHANGE_BALANCE^1234 |
| ADD_MEMBER |
| ... |
Instead of having to lock multiple tables that are involved in, say, changing the balance for one (or many) account, we are able to just lock one row in this tb_lock table.
Deadlock Issues
Currently, as the developer sees fit, locks are grabbed at various places during code execution. This, obviously, currently leads to deadlock issues that are hard to debug (this could be remedied by adding logs that mark where, when, and by who a lock is grabbed or released).
Proposed Solution(s)
In order to solve this, to me there seems to be multiple possible solutions. In general, each solution involves changing from grabbing locks at multiple points in the code to grabbing all locks needed at the initial execution at the entry point.
- Force developers to manually trace back to entry points and add the locks they need to the list of locks that will be grabbed at the entry point.
This is obviously not a sustainable option. As code is refactored and grows, the locks that are grabbed need to be reanalyzed constantly. This option is also prone to human error.
- Through custom Java annotations, allow developers to continue not bothering about when or where they grab a lock, but make them annotate the locks that will be grabbed. When the server is first run, build some sort of map of trees to determine what locks we need to grab (based off of a possibility of use, not on the certainty of use) and grab these locks at the entry point.
In my opinion, this seems like a much more sustainable solution, as well as much less prone to human error. However, the problem now is whether or not this is feasible. If the server were full of regular old POJO's and had an easily determined call tree it would be easy, but using Spring framework, as well as things like reflection, tracking down the possible paths from an entry point to all annotations seems like a much more difficult task.
All in all, I am looking for advice on whether or not this is a feasible path to go down. Could I use some sort of analysis to build some sort of lock tree, or should I rely on other methods?