This bug seems extremely fundamental to be a real bug so my initial thoughts are that I'm doing something completely wrong. An issue like this would have been discovered by now.
Brian Carr and I were working on some front end development for our project that leverages Quicksilver and Lightwire when we came across an bug in the UI. Whenever a form was submitted that contained form validation errors, it appeared to be returning correctly the first time. However if you were to submit the same form again, we noticed the error messages would continue to duplicate and stack. Initially we thought there was an issue with Quicksilver not configuring the beans correctly or perhaps we tagged our Errors object with a singleton annotation. After some digging into our bean configuration annotations, everything was annotated correctly.
So next step was to switch our dependency injection provider to ColdSpring, which Quicksilver allows you to do with one line. What happened to our surprise? It worked! The errors were displaying correctly. Ok, so now we know that the issue is Lightwire specific but didn't know whether it was a Quicksilver or Lightwire problem so we dug into Quicksilver's injection provider service for Lightwire. Perhaps we're wiring something incorrectly. After a good amount of investigation we verified Quicksilver was setting configuring beans correctly for Lightwire consumption.
Our next step was to completely bypass Quicksilver altogether. If we can configure these beans using just Lightwire and a scratch pad and still seeing this error then we can safely deduce that it was a problem in Lightwire. So here is what we found...
First, here is the jist of the classes involved. Both are TRANSIENT objects.
Simply, a BusinessServiceResponse has an Error object. For this example, the hashcode member is a unique hash that is attached to each unique instance.
Here is the code that we are using to test:
- Line 4 - 14 Configure the two transients - Errors and BusinessServiceResponse
- Line 16-20 Configure setter dependency. BusinessServiceResponse has an Errors property to be injected with the Errors transient object.
- Line 24-38 On the first iteration we are getting the bean for the first time then displaying the hashcode on the BusinessServiceResponse and the Errors. It's important to note that Errors hashcode is accessed through the BusinessServiceResponse on line 37.
Here are the results:
It appears that errors is being handled as a transient as the hashcode are identical across all three BusinessServiceResponse creations. Now that we know what the problem was, we dug into the Lightwire code. We found the issue in Lightwire.cfc. It loads setter dependencies as singletons by default??? Nooo, can't be.
On line 273, its creating each bean as a singleton. After replacing line 273 with an singleton/transient check like so:
It worked. Let's rerun our tests.
Hashcodes are different meaning now they are transients as well.
*** UPDATE 01/09/10 *** Do NOT use the code I posted as a fix as there has since been an official patch to fix this issue. Grab it from here. Many thanks to Peter Bell and Brian Rinaldi for their help and quick response on this!
Please guys, help me see the light...
11 comments:
My hope is that we're using it incorrectly - I happen to really like lightwire as it's performance far exceeds that of ColdSpring.
Micky, Have you pinged Peter about this? If so, what did he say and if not, why not? For the record, I sent it off to him via IM and hopefully he'll address it soon.
Hi Guys,
Well, clearly you're doing something that isn't being done that often, or it would have come up by now. I'm hip deep in a project today, but a quick skim seems like everything you are saying is correct. I'll review this tomorrow, and whether there is an issue or not, I'll work up a blog posting. If there *is* an issue, I'll patch, upload, and get with Luis Majano on the ColdBox version. I'll also ask others to get the word out.
Thanks for the great investigation. Will keep you posted!
@Peter, @Brian R-
Thanks for the quick response to this guys!
Peter, I speak for Brian Carr and myself when I say we are huuuuge fans of Lightwire. It's our dependency framework of choice for CF. Very lightweight and fast. Definitely let us know what you find so we can update our end.
Looks like we will still use Lightwire as our default injection provider after all :)
Peter, what do you mean by "you're doing something that isn't being done that often"? Most developers rarely set other transients as dependencies?
Hi Micky,
Transient issue replicated, documented and patched. http://appgen.pbell.com/2010/01/09/critical-lightwire-error-in-setter-injection-for-transients/ MANY thanks for catching that - great job and much appreciated!
I can *not* replicate the singleton issue at all (and I did quite a bit of testing). If you can come up with any replicable code, please post and/or prod me and I'll check it out ASAP, but I'm wondering if there really was an issue there or not.
Many thanks again for catching the transient error!
Peter
Please note, the error does NOT apply to the modified version of LightWire within ColdBox.
@Peter-
Thank you so much for your quick response, help and fix! I have updated this post to with instructions to check out your blog post about this issue with links to download the patch. Also with directions NOT to use my code fix.
I will be in Boston for most of next week on a business trip so I will try to replicate the singleton error with the new patch. Hopefully it was related to this error and all is well now. Will definitely keep you posted.
Thanks Peter!
I used Lightwire on several projects and never had this issue. I've injected Service objects into transients and then got the transient via the injected Service. I'm always interested in how people build applications, to learn and improve my code, so can I ask if you skip the Service Layer?
Thanks :)
John-
Do I skip the service layer? Well yes and no, haha. I skip service layers when using dependency injection because my dependency injection framework of choice is essentially the "service" layer right? At the end of the day, CS and Lightwire are factory (service) objects.
In my experience, when I ask the factory for a transient object like User, and User has composite transient objects attached to it like Address and PhoneNumber, the framework should know how to resolve those dependencies for you via your configuration. It should know that hey User, here is your Address object that also has these other dependencies injected into it - transient and/or singletons. All this should be independent of any custom service layer.
However, I agree that if what your talking about is creating your own abstract factory in between your injection framework and API then I think we're both on the same page! That's the most flexible way to go imho. But that kind of architecture is a bit advanced and the core framework should not preclude transients to be injected with other transients.
@Peter-
No singleton issue with the new patches release. It could have very well been a problem in my end.
Post a Comment