We were experiencing this frequently (we do a rolling restart of all of our
web servers every night).
There is a little edge case bug in the autowire method of BeanInjector,
essentially the double check lock fails if there are near simultaneous 1st
requests for an object. The check is a pure variable name existence check,
and the key that it checks is actually created before the object has been
autowired. So if there is a second request for an object before the first
request has finished autowiring, then that second request will succeed but
it won't have all of it's dependencies injected.
And if that request is for a transfer object it will be cached without it's
dependencies, so later objects will work but those first couple requested
objects that are cached will continue to fail until they fall out of the
cache.
We patched it locally and have been using the patched version in production
for a couple months now with no problems, and I believe that Brian is going
to roll the patch in soon.
If you can't wait I've attach the patch that we've been using.
On Sat, Mar 21, 2009 at 12:09 PM, Brian Kotek <[email protected]> wrote:
> Also, someone (I think it was Paul Marcotte) emailed me a patch for the
> observer that affected some edge cases involving simultaneous access to the
> injection logic, which I really need to incorporate and update the files
> (I'll actually commit to doing this today since I have some time and it's
> not a very major change).
>
>
>
> On Sat, Mar 21, 2009 at 3:07 PM, Brian Kotek <[email protected]> wrote:
>
>> Also, do you have something that is setting or clearing the variables
>> scope in your Decorator where your injected beans are stored, something like
>> <cfset variables.instance = StructNew() />?
>>
>>
>>
>> On Sat, Mar 21, 2009 at 12:15 PM, Bob Silverberg <
>> [email protected]> wrote:
>>
>>> What does your Coldspring config look like for Transfer and the
>>> BeanInjector, and what does your code that loads the CS bean factory look
>>> like?
>>>
>>>
>>> On Sat, Mar 21, 2009 at 11:55 AM, Brian G <[email protected]>wrote:
>>>
>>>>
>>>> A couple of weeks ago I released a big update to my app. When I roll
>>>> out new versions and restart ColdFusion on my two web servers, I'm
>>>> seeing service layer objects which are injected via Brian K's
>>>> TDOBeanInjectorObserver "disappear" in some of the first Transfer
>>>> objects that are loaded. My app has the following moving parts:
>>>>
>>>> * Transfer 1.1, Coldspring 1.2
>>>> * TDOBeanInjectorObserver in Coldspring
>>>> * All decorators extend AbstractBeanDecorator
>>>> * TransferSync for synchornizing my two node cluster
>>>>
>>>> My rollout process looks like:
>>>>
>>>> * Ant build script to check out and push code
>>>> * Restart ColdFusion
>>>>
>>>> When the server first comes up, it seems as though some Transfer
>>>> objects are not fully initialized and they're missing some of their
>>>> injected dependencies. What's interesting is that the object makes it
>>>> into the Transfer cache so it continues to throw errors when a method
>>>> on the decorator is called that accesses the injected service object.
>>>> Here's one such error but it occurs on any variety of objects (user,
>>>> event, others):
>>>>
>>>> Expression: Element CLUBMEMBERSERVICE is undefined in VARIABLES.
>>>>
>>>> * /var/www/pukka/api-prod/model/event/event.cfc (51, ??)
>>>> * /var/www/pukka/api-prod/model/event/event.cfc (207,
>>>> CF_UDFMETHOD)
>>>> * /var/www/pukka/msr-prod/views/registration/dsp.host.cfm (4,
>>>> CF_TEMPLATEPROXY)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/view/ViewRenderer.cfm
>>>> (29, CFINCLUDE)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/view/ViewRenderer.cfc
>>>> (50, CFMODULE)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/framework/ModelGlue.cfc
>>>> (450, CF_TEMPLATEPROXY)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/framework/ModelGlue.cfc
>>>> (340, CF_UDFMETHOD)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/framework/ModelGlue.cfc
>>>> (289, CF_UDFMETHOD)
>>>> * /var/www/pukka/api-prod/ModelGlue/unity/ModelGlue.cfm (126,
>>>> CF_TEMPLATEPROXY)
>>>> * /var/www/pukka/msr-prod/index.cfm (82, CFINCLUDE)
>>>>
>>>> Line 207 basically calls getClubMemberService() and line 51 is
>>>> returning variables.ClubMemberService (which was supposed to be
>>>> injected by setClubMemberService()).
>>>>
>>>> My update a couple of weeks ago changed a lot of bits; I switched from
>>>> my own bean injector to Brian Kotek's version, I updated the version
>>>> of TransferSync, I changed my decorators to all extend an
>>>> AbstractBeanDecorator... and of course I didn't see these issues in my
>>>> development environment. :)
>>>>
>>>> I've been able to correct it by doing a soft restart on my app (which
>>>> clears the Transfer cache, restarts Coldspring and my Model-glue apps/
>>>> etc). Sometimes it takes a couple of tries though to correct it.
>>>> Anyone seen this before? Any thoughts about where to poke?
>>>>
>>>>
>>>> Brian, Mr. Edge Case
>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> Bob Silverberg
>>> www.silverwareconsulting.com
>>>
>>>
>>>
>
>
> >
>
--~--~---------~--~----~------------~-------~--~----~
Before posting questions to the group please read:
http://groups.google.com/group/transfer-dev/web/how-to-ask-support-questions-on-transfer
You received this message because you are subscribed to the Google Groups
"transfer-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/transfer-dev?hl=en
-~----------~----~----~----~------~----~------~--~---
### Eclipse Workspace Patch 1.0
#P colspringutils
Index: BeanInjector.cfc
===================================================================
--- BeanInjector.cfc (revision 10)
+++ BeanInjector.cfc (working copy)
@@ -1,5 +1,5 @@
-<!---
-LICENSE
+<!---
+LICENSE
Copyright 2008 Brian Kotek
Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,61 +14,61 @@
See the License for the specific language governing permissions and
limitations under the License.
-File Name:
+File Name:
BeanInjector.cfc
-
-Version: 1.0
+
+Version: 1.0
-Description:
+Description:
This component will autowire any target component with matching beans
from ColdSpring. It is useful for injecting beans into
- "transient" or "per-request" components. It also has advantages over
using ColdSpring to manage the non-Singleton objects:
-
+ "transient" or "per-request" components. It also has advantages over
using ColdSpring to manage the non-Singleton objects:
+
1. ColdSpring goes through several phases of lookup and resolution when
beans are created. This adds performance overhead
when managing non-Singleton beans. This BeanInjector avoids that
overhead.
-
+
2. Since ColdSpring creates a fully-initialized component before it
returns it to you, you have only limited control over
- how that object is constructed (via the ColdSpring XML configuration).
Using this BeanInjector allows you to create and call
- the init() method on your components, allowing you full control over
how they are constructed.
-
+ how that object is constructed (via the ColdSpring XML configuration).
Using this BeanInjector allows you to create and call
+ the init() method on your components, allowing you full control over
how they are constructed.
+
This makes it much easier to create "rich" objects that can handle much
more business logic than objects that can't leverage
- ColdSpring. The dependencies are cached by the BeanInjector for
performance. After the first instance of object is created, all
+ ColdSpring. The dependencies are cached by the BeanInjector for
performance. After the first instance of object is created, all
subsequent objects of that type will have their dependencies injected
using cached information. The component is thread-safe.
Usage:
Usage of the BeanInjector is fairly straightforward. The ColdSpring XML
file might look like this:
-
+
<bean id="userService" class="components.userService">
<property name="beanInjector">
<ref bean="beanInjector" />
</property>
</bean>
-
+
<bean id="beanInjector" class="components.BeanInjector" />
-
+
<bean id="validatorFactory" class="components.ValidatorFactory"
/>
-
+
Next, I would create public setter method(s) for the bean(s) you want
to inject into my component, for example I might
have a User.cfc with the following method:
-
+
<cffunction name="setValidatorFactory" access="public"
returntype="void" output="false" hint="I set the ValidatorFactory.">
<cfargument name="validatorFactory" type="any"
required="true" hint="ValidatorFactory" />
<cfset variables.instance.validatorFactory =
arguments.validatorFactory />
</cffunction>
-
+
To autowire a new User inside my UserService, I would simply create the
User (either directly or with a factory) and then
call autowire() on the BeanInjector:
-
+
<cfset var user = CreateObject('component',
'components.User').init() />
- <cfset variables.beanInjector.autowire(user) />
-
- That's it. The User object would now be autowired and have the
ValidatorFactory injected into it. The end result is that
- any setters in your target object that have matching bean IDs in
ColdSpring will have those beans injected automatically.
- As an additional example, a bean with an ID of "productService" would
be autowired into a component that had a public setter
+ <cfset variables.beanInjector.autowire(user) />
+
+ That's it. The User object would now be autowired and have the
ValidatorFactory injected into it. The end result is that
+ any setters in your target object that have matching bean IDs in
ColdSpring will have those beans injected automatically.
+ As an additional example, a bean with an ID of "productService" would
be autowired into a component that had a public setter
method named setProductService(), and so on.
-
+
There is an optional constructor argument called "suffixList" that can
be supplied. This is a comma-delimited list
of propery name suffixes that will be allowed. If you specify a
suffixList, the Observer will only inject beans which
end in one of the suffixes in the list. For example, if you specify a
suffixList of "service", setter methods for
@@ -76,95 +76,99 @@
would NOT be called. This can be useful in rare situations where your
Transfer Object may have database-driven properties
that conflict with the names of ColdSpring beans. Most people probably
won't need to worry about this, but the option
is here in case the issue arises.
-
+
In case you have problems determining whether beans are being properly
injected into your Decorators, there is
an optional init() method argument called "debugMode". By default, this
is false. If you set it to true via the ColdSpring
XML config file, the component will trace successful dependency
injections to the debugging output. It will also
rethrow any errors that occur while trying to inject beans into your
Decorators. Obviously, ensure that this is
remains off in production.
-
- The autowire() method also has two optional arguments that can be used
for small performance increases:
-
- The first optional argument is "targetComponentTypeName". If the
calling code already knows the full type name of the target
- component, you can pass this string into the autowire() method to avoid
the need to look up the type name in the component
- metadata. For example, Transfer ORM Decorators already know their type,
so if you are autowiring a Decorator, you can pass in
+
+ The autowire() method also has two optional arguments that can be used
for small performance increases:
+
+ The first optional argument is "targetComponentTypeName". If the
calling code already knows the full type name of the target
+ component, you can pass this string into the autowire() method to avoid
the need to look up the type name in the component
+ metadata. For example, Transfer ORM Decorators already know their type,
so if you are autowiring a Decorator, you can pass in
the type name. The performance differnce is small, but every little bit
helps so I made this an option. In most cases, the
type won't be known by the calling code, so this argument won't be used.
-
+
The second optional argument is "stopRecursionAt". This is the full
type name of a superclass of the target component at which
you want dependency resolution lookup to stop. For example, Transfer
ORM Decorators inherit from the "transfer.com.TransferDecorator"
class. However, when autowiring Transfer Decorators, you don't want to
waste time trying to resolve dependencies at that level,
because from that parent class upwards, everything is managed by
Transfer. There are no custom properties for you to define at
that level. Looking for properties to autowire in those parent classes
would usually not cause any errors, but it is wasted time.
- By specifying a type name to stop the lookup recursion, you can save
some processing time and avoid looking at unnecessary
+ By specifying a type name to stop the lookup recursion, you can save
some processing time and avoid looking at unnecessary
parent classes. Unless the target component is part of some greater
framework, such as Transfer, using this optional argument
will usually be unnecessary.
-
+
--->
<cfcomponent name="BeanInjector" hint="">
-
+
<cffunction name="init" access="public" returntype="any"
hint="Constructor.">
<cfargument name="suffixList" type="string" required="false"
default="" />
<cfargument name="debugMode" type="boolean" required="false"
default="false" />
<cfset variables.DICache = StructNew() />
<cfset variables.debugMode = arguments.debugMode />
<cfset variables.suffixList = arguments.suffixList />
+ <cfset variables.loadedKey = createUUID()>
<cfreturn this />
</cffunction>
-
+
<cffunction name="autowire" access="public" returntype="any"
output="false" hint="">
<cfargument name="targetComponent" type="any" required="true" />
<cfargument name="targetComponentTypeName" type="any"
required="false" default="" hint="If the calling code already knows the type
name of the target component, passing this will provide a small speed
improvment because the type doesn't have to be looked up in the component
metadata." />
<cfargument name="stopRecursionAt" type="string"
required="false" default="" hint="When recursing the parent classes of the
target component, recusion will stop when it reaches this class name." />
<cfset var local = StructNew() />
-
+
<cfif not Len(arguments.targetComponentTypeName)>
<cfset local.typeName =
GetMetaData(arguments.targetComponent).name />
<cfelse>
<cfset local.typeName =
arguments.targetComponentTypeName />
</cfif>
-
+
<!--- If the DI resolution has already been cached, inject from
the cache. --->
- <cfif StructKeyExists(variables.DICache, local.typeName)>
+ <cfif StructKeyExists(variables.DICache, local.typeName) and
StructKeyExists(variables.DICache[local.typeName],variables.loadedKey)>
<cfset injectCachedBeans(arguments.targetComponent,
local.typeName) />
<cfelse>
-
+
<!--- Double-checked lock based on Object Type Name to
handle race conditions. --->
<cflock
name="Lock_BeanInjector_Exclusive_#local.typeName#" type="exclusive"
timeout="5" throwontimeout="true">
-
- <cfif StructKeyExists(variables.DICache,
local.typeName)>
+
+ <cfif StructKeyExists(variables.DICache, local.typeName) and
StructKeyExists(variables.DICache[local.typeName],variables.loadedKey)>
<cfset
injectCachedBeans(arguments.targetComponent, local.typeName) />
- <cfelse>
+ <cfelse>
<!--- Create a new cache element for
this component. --->
<cfset
variables.DICache[local.typeName] = StructNew() />
-
+
<!--- Get the metadata for the
component. --->
<cfset local.objMetaData =
GetMetaData(arguments.targetComponent) />
-
+
<!--- Recurse the inheritance tree of the
component and attempt to resolve dependencies. --->
<cfset
performDIRecursion(arguments.targetComponent, local.objMetaData,
local.typeName, arguments.stopRecursionAt) />
+ <cfset variables.DICache[local.typeName][variables.loadedKey]=true
/>
</cfif>
-
+
</cflock>
-
+
</cfif>
-
+
<cfreturn arguments.targetComponent />
</cffunction>
-
+
<cffunction name="injectCachedBeans" access="private" returntype="void"
output="false" hint="">
<cfargument name="targetComponent" type="any" required="true" />
<cfargument name="typeName" type="string" required="true" />
<cfset var thisProperty = "" />
<cfif StructCount(variables.DICache[arguments.typeName]) gt 0>
<cfloop
collection="#variables.DICache[arguments.typeName]#" item="thisProperty">
- <cfset injectBean(arguments.targetComponent,
thisProperty, variables.DICache[arguments.typeName][thisProperty]) />
- <cfif variables.debugMode><cftrace text="The
cached dependency #thisProperty# was successfully injected into
#arguments.typeName#." inline="false"></cfif>
+ <cfif thisProperty neq variables.loadedKey>
+ <cfset
injectBean(arguments.targetComponent, thisProperty,
variables.DICache[arguments.typeName][thisProperty]) />
+ <cfif variables.debugMode><cftrace
text="The cached dependency #thisProperty# was successfully injected into
#arguments.typeName#." inline="false"></cfif>
+ </cfif>
</cfloop>
</cfif>
</cffunction>
-
+
<cffunction name="injectBean" access="private" returntype="void"
output="false" hint="">
<cfargument name="targetObject" type="any" required="true" />
<cfargument name="propertyName" type="string" required="true" />
@@ -173,7 +177,7 @@
<cfinvokeargument name="#arguments.propertyName#"
value="#arguments.propertyValue#" />
</cfinvoke>
</cffunction>
-
+
<cffunction name="performDIRecursion" access="private"
returntype="void" output="false" hint="">
<cfargument name="targetObject" type="any" required="true" />
<cfargument name="metaData" type="struct" required="true" />
@@ -183,20 +187,20 @@
<cfset var propertyName = "" />
<cfset var thisSuffix = "" />
<cfset var suffixMatch = true />
-
+
<!--- If the metadata element has functions, attempt to resolve
dependencies. --->
<cfif StructKeyExists(arguments.metadata, 'functions')>
<cfloop from="1"
to="#ArrayLen(arguments.metaData.functions)#" index="thisFunction">
- <cfif
Left(arguments.metaData.functions[thisFunction].name, 3) eq "set"
+ <cfif
Left(arguments.metaData.functions[thisFunction].name, 3) eq "set"
and
Len(arguments.metaData.functions[thisFunction].name) gt 3
- and (not
StructKeyExists(arguments.metaData.functions[thisFunction], 'access')
- or
+ and (not
StructKeyExists(arguments.metaData.functions[thisFunction], 'access')
+ or
arguments.metaData.functions[thisFunction].access eq 'public')>
<cfset propertyName =
Right(arguments.metaData.functions[thisFunction].name,
Len(arguments.metaData.functions[thisFunction].name)-3) />
<cftry>
-
+
<cfif
getBeanFactory().containsBean(propertyName)>
-
+
<!--- If a suffix List
is defined, confirm that the property has the proper suffix. --->
<cfif
Len(variables.suffixList)>
<cfset
suffixMatch = false />
@@ -207,44 +211,44 @@
</cfif>
</cfloop>
</cfif>
-
+
<cfif suffixMatch>
-
+
<!--- Try to
call the setter. --->
<cfset
injectBean(arguments.targetObject, propertyName,
getBeanFactory().getBean(propertyName)) />
-
- <!--- If the
set was successful, add a cache reference to the bean for the current TDO
property. --->
+
+ <!--- If the
set was successful, add a cache reference to the bean for the current TDO
property. --->
<cfset
variables.DICache[arguments.originalTypeName][propertyName] =
getBeanFactory().getBean(propertyName) />
-
+
<cfif
variables.debugMode><cftrace text="The dependency #propertyName# was
successfully injected into #arguments.originalTypeName# and cached."
inline="false"></cfif>
-
+
</cfif>
-
+
</cfif>
-
+
<cfcatch type="any">
<!--- Bean injection
failed. --->
<cfif
variables.debugMode><cfrethrow /></cfif>
</cfcatch>
-
+
</cftry>
</cfif>
</cfloop>
</cfif>
-
- <!--- If the metadata element extends another component,
recurse that component. --->
+
+ <!--- If the metadata element extends another component,
recurse that component. --->
<cfif StructKeyExists(arguments.metadata, 'extends')>
<cfif not Len(arguments.stopRecursionAt) or
(Len(arguments.stopRecursionAt) and arguments.metadata.extends.name neq
arguments.stopRecursionAt)>
<cfset
performDIRecursion(arguments.targetObject, arguments.metaData.extends,
arguments.originalTypeName, arguments.stopRecursionAt) />
</cfif>
</cfif>
</cffunction>
-
+
<!--- Dependency injection methods for Bean Factory. --->
<cffunction name="getBeanFactory" access="public" returntype="any"
output="false" hint="I return the BeanFactory.">
<cfreturn variables.instance.beanFactory />
</cffunction>
-
+
<cffunction name="setBeanFactory" access="public" returntype="void"
output="false" hint="I set the BeanFactory.">
<cfargument name="beanFactory"
type="coldspring.beans.BeanFactory" required="true" />
<cfset variables.instance.beanFactory = arguments.beanFactory />