Possible race conditions when creating and updating a struct in the same request - coldfusion

女生的网名这么多〃 提交于 2019-12-11 02:39:30

问题


About a year ago I asked a question about errors I was getting in an app, that indicated a possible race condition:

Possible race condition creating Structs in ColdFusion

A year on, I'm still having issues with this and other apps where the same technique is employed and it seems that frankly, you cannot create and update a struct reliably in the same request. This of course seems ludicrous, so I must be doing something wrong - but I'd appreciate some help.

Here's an explanation of what's going on:

  • I have a function, instantiated in the Application scope, that accepts a comma separated list of IDs as a parameter.
  • The function hits the database requesting all the records with matching IDs
  • We then loop through the recordset, and create a locally scoped struct, dynamically named using the ID of the record, e.g. Local.myStruct['item_' & myrecordset.ID]
  • Finally we add data to this struct.

The issue is that very intermittently, the code will error on the last step, stating that the dynamically named struct that was created literally two lines previous, does not exist... Interestingly, the line of code in question is updating the struct, and so if it didn't exist, I'd expect it to simply create it - I wonder if using Array notation to set the value of dynamically named variables somehow behaves differently than using dot notation?

So, here's some code:

<cffunction name="GetPrices" access="public" returntype="struct">
    <cfargument name="ItemID"       type="string"   required="true" />
    <cfargument name="PriceBand"    type="string"   default="1"     />

    <cfset var Local = {} />

    <!--- Query the database for the part details --->
    <cfquery name="Local.GetParts" datasource="#this.sDSN#">
        SELECT  TOP 200
                [ItemID]
            ,   [Price1]
            ,   [Price2]
            ,   [Price3]
        FROM    [Items]

        WHERE   1 = 1

        <cfif ListLen(Arguments.ItemID) GT 1>
            AND ItemID IN (<cfqueryparam cfsqltype="cf_sql_integer" list="yes" value="#Arguments.ItemID#">)
        <cfelse>
            AND itemID = <cfqueryparam cfsqltype="cf_sql_integer" value="#Arguments.ItemID#">
        </cfif>
    </cfquery>

    <cfloop query="Local.GetParts">
        <cfset Local.ReturnStruct['item_' & Local.GetParts.ItemID] = {} />

        <cfset Local.ReturnStruct['item_' & Local.GetParts.itemID].CurrentPrice = Local.GetParts['price' & Arguments.PriceBand] />
    </cfloop>

    <cfreturn Local.ReturnStruct />

</cffunction>

The function would be called thus:

Variables.GetPrices = Application.com.Items.GetPrices(
        ItemID      = '8263,1996,324686,32,12746,297807,1763,37568,2359782,321,3525,563466,323'
    ,   PriceBand   = 2
)

As you can see, the function is varscoped, and in any case we're running CF10 where I'm lead to believe that an implicit Local scope is thread safe in a function. Var scoping is where we landed last year on this problem, but alas the problem persists. Can anyone offer any other suggestions?

Thanks

EDIT: As requested, here's an exmple of the errors I'm receiving:

Element item_61284 is undefined in a CFML structure referenced as part of an expression.

The item ID is different every time. It's probably worth noting that the code posted has been manipulated slightly for the purposes of the question - so the stacktrace below may show bits and pieces that can't be attributed to the code above.

I'm confident that nothing removed from the production code is relevant to the issue, as these symptoms occurr across tens of functions in multiple apps, wherever the same technique is used.

coldfusion.runtime.UndefinedElementException: Element item_61284 is undefined in a CFML structure referenced as part of an expression. at coldfusion.runtime.CfJspPage.ArrayGetAt(CfJspPage.java:974) at coldfusion.runtime.CfJspPage._arrayGetAt(CfJspPage.java:985) at coldfusion.runtime.CfJspPage._arrayGetAt(CfJspPage.java:980) at cfparts2ecfc1041715109$funcREDACTED.runFunction(REDACTED.cfc:665) at coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:472) at coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:405) at coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:368) at coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:55) at coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:321) at coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:518) at coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:660) at coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:469) at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2373) at REDACTEDcfm969331649.runPage(REDACTED.cfm:43) at coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:244) at coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:444) at coldfusion.runtime.CfJspPage._emptyTcfTag(CfJspPage.java:2799) at REDACTEDcfm522717774.runPage(REDACTED.cfm:83) at coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:244) at coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:444) at coldfusion.runtime.CfJspPage._emptyTcfTag(CfJspPage.java:2799) at cfapplication2ecfc1345221948$funcONREQUEST.runFunction(REDACTED\Application.cfc:573) at coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:472) at coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:405) at coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:368) at coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:55) at coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:321) at coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:220) at coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:655) at coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:444) at coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:414) at coldfusion.runtime.AppEventInvoker.invoke(AppEventInvoker.java:108) at coldfusion.runtime.AppEventInvoker.onRequest(AppEventInvoker.java:300) at coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:424) at coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:48) at coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40) at coldfusion.filter.PathFilter.invoke(PathFilter.java:112) at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:94) at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28) at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38) at coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:46) at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38) at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22) at coldfusion.filter.CachingFilter.invoke(CachingFilter.java:62) at coldfusion.CfmServlet.service(CfmServlet.java:219) at coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:42) at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at sun.reflect.GeneratedMethodAccessor63.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:97) at com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:437) at com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:311) at com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:192) at com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:472) at com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36) at sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:79) at sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:53) at com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:928) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:414) at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:204) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:539) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:298) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)


回答1:


Gary, I feel your pain because I too have experienced all kinds of crazy CF errors that "shouldn't be possible". I spent literally years battling intermittent failures in the Transfer ORM which I just recently learned was due to the fact that xmlSearch() is not thread-safe!

So, let me share a couple of strategies which will not solve your problem but will help you get closer to diagnosing the underlying cause of the issue:

Use duplicate() to eliminate thread-safety issues

The solution to my XML case above was to wrap the results of xmlSearch() in a duplicate(). It may not be the issue here but you might try breaking apart the generation of the struct key from the usage like:

<cfloop query="Local.GetParts">
    <cfset local.id = "item_" & duplicate(Local.GetParts.ItemID) />
    <cfset Local.ReturnStruct[local.id] = {CurrentPrice: Local.GetParts['price' & Arguments.PriceBand]} />
</cfloop>

Use a sanity check with cflog

This is probably my best strategy because it tells you the condition that CF is in when it's about to barf. Try something like:

<cfloop query="Local.GetParts">
    <cfset Local.ReturnStruct['item_' & Local.GetParts.ItemID] = {} />
    <cftry>
        <cfset Local.ReturnStruct['item_' & Local.GetParts.itemID].CurrentPrice = Local.GetParts['price' & Arguments.PriceBand] />
         <cfcatch type="any">
             <cflog file="crazy.log" text="GetParts: #currentRow#/#local.getParts.recordCount#, KeyCount: #structKeyCount(local.ReturnStruct)#, keys: #structKeyList(local.ReturnStruct)#" />
         </cfcatch>
    </cftry>
</cfloop>

<cfreturn Local.ReturnStruct />

The idea being that when you encounter an unexpected error, log everything you think that might help you deduce information about where you're at. Bonus: this won't actually throw an error to the user.

PS - one thing I noticed is if your query doesn't return results, Local.ReturnStruct would be undefined. I personally would define it at the start of the function like: <cfset local.returnStruct = {} />




回答2:


I don' think CFLOCK will do the trick here. Try this instead:

<cfloop query="Local.GetParts">
    <cfif !structKeyExists( Local.ReturnStruct, "item_" & Local.GetParts.ItemID )>
        <cfset Local.ReturnStruct['item_' & Local.GetParts.ItemID] = { CurrentPrice = 0 } />
    </cfif>
    <cfset structInsert( Local.ReturnStruct['item_' & Local.GetParts.itemID], CurrentPrice, Local.GetParts['price' & Arguments.PriceBand] )>
</cfloop>

Why use structInsert()?

structInsert(structure, key, value [, allowoverwrite ])

The default value for allowoverwrite is false. Using standard dot notation, you can't get this level of control. Is it possible that your query is returning one or more records with the same value for ItemID? This would also set the key CurrentPrice with a default value.




回答3:


I would try modifying this line:

<cfset Local.ReturnStruct['item_' & Local.GetParts.itemID].CurrentPrice = Local.GetParts['price' & Arguments.PriceBand] />

To:

<cfset Local.ReturnStruct['item_' & Local.GetParts.itemID] = {
    CurrentPrice = Local.GetParts['price' & Arguments.PriceBand]
}>

It will ask CF to do less magic for you (i.e. create an empty struct with key CurrentPrice).



来源:https://stackoverflow.com/questions/27408067/possible-race-conditions-when-creating-and-updating-a-struct-in-the-same-request

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!