I have to fix some old java code which contains a nasty bug:
When a user logs in the code checks his permissions by querying an LDAP server which takes a second or two.
When another user logs in during that timeframe it seems that the permission check for the first user is continued with the permissions for the second user which is of course a catastrophic bug. It seems that data in the first thread is overwritten by the second thread.
There are a lot of static variables and methods scattered throughout the code. I have no idea whether there is a good reason for making those static or whether I could just make them dynamic.
Can you recommend a strategy how to make this code threadsafe, or a tutorial regarding this general class of problems?
Here are some details on what happens. First, an example of the standard flow:
2012-12-11 15:07:14,146 INFO [TP-Processor20] [MyListener] handleLoginEvent Login Event: username=[USER1]
2012-12-11 15:07:14,865 INFO [TP-Processor20] [MyListener] doInHibernate Group Maps Array has 3 maps inside.
2012-12-11 15:07:14,865 INFO [TP-Processor20] [MyListener] doInHibernate External Group NAME=[*] USER=[USER1] is a member? true
...
2012-12-11 15:07:16,036 INFO [TP-Processor20] [MyListener] doInHibernate External Group NAME=[ou:GROUP-A] USER=[USER1] is a member? false
...
2012-12-11 15:07:16,068 INFO [TP-Processor20] [MyListener] doInHibernate External Group NAME=[ou:GROUP-Z] USER=[USER1] is a member? false
TP-Processor20 done.
2012-12-11 15:07:33,099 INFO [TP-Processor9] [MyListener] handleLoginEvent Login Event: username=[USER1]
2012-12-11 15:07:33,677 INFO [TP-Processor9] [MyListener] doInHibernate Group Maps Array has 3 maps inside.
2012-12-11 15:07:33,677 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[*] USER=[USER1] is a member? true
...
2012-12-11 15:07:33,755 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[ou:GROUP-A] USER=[USER1] is a member? false
...
2012-12-11 15:07:33,786 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[ou:GROUP-Z] USER=[USER1] is a member? false
TP-Processor9 done.
Here is a log excerpt of the problem. Note how the variable holding the user name is different after the second log in.
2012-12-11 15:07:53,082 INFO [TP-Processor9] [MyListener] handleLoginEvent Login Event: username=[USER2]
2012-12-11 15:07:53,661 INFO [TP-Processor9] [MyListener] doInHibernate Group Maps Array has 3 maps inside.
2012-12-11 15:07:53,676 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[*] USER=[USER2] is a member? true
note the handleLoginIvent from another user
2012-12-11 15:07:53,676 INFO [TP-Processor1] [MyListener] handleLoginEvent Login Event: username=[USER1]
...
note that the USER= value has changed to that of TP-Processor1. Also, the "is a member" test returns now true which is incorrect for user USER1. It is actually user USER2 who is a member of that group.
2012-12-11 15:07:53,832 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[ou:GROUP-A] USER=[USER1] is a member? true
...
2012-12-11 15:07:53,989 INFO [TP-Processor9] [MyListener] doInHibernate External Group NAME=[ou:GROUP-Z] USER=[USER1] is a member? false
TP-Processor9 done
2012-12-11 15:07:54,286 INFO [TP-Processor1] [MyListener] doInHibernate Group Maps Array has 3 maps inside.
2012-12-11 15:07:54,286 INFO [TP-Processor1] [MyListener] doInHibernate External Group NAME=[*] USER=[USER1] is a member? true
...
2012-12-11 15:07:54,364 INFO [TP-Processor1] [MyListener] doInHibernate External Group NAME=[ou:GROUP-A] USER=[USER1] is a member? false
...
2012-12-11 15:07:54,551 INFO [TP-Processor1] [MyListener] doInHibernate External Group NAME=[ou:GROUP-Z] USER=[USER1] is a member? false
Try removing every static or instance variable, and replace them with local variables passed as arguments from method to method instead. Your code will thus become stateless, hence thread-safe. For example, replace the following
by
Another, perhaps better option, would be to restart from scratch. Because multi-threaded code using lots of static variables might not be worth preserving, and probably contains lots of other bugs or bad practices.