Search code examples
coldfusionapache-axis

CF10/CF11 web services using Axis2 restarting existing Java objects


Original post:

This is truly bizarre.

I have a web service that has been working since CFMX (that is, since CF6). We recently upgraded to CF10. (Not my choice, I lobbied for CF11.) We have users that required the old WSDL because they generated stubs for their WS client based on old-format WSDL. So I added wsversion="1" to the cfcomponent tag. We get old-style WSDL, but webservices.log says "Using Axis 2 ...". That's unexpected, but not what I'm referring to as truly bizarre. Read on.

The web service is stateless. I hear that session control is possible with CF web services nowadays, but we're not doing that. No Application.cfc, no cfapplication tag. So each cfinvoke is its own, fully self-contained execution, right? Or so I thought. I'm seeing things in subsequent invocations of the web service that were defined in previous invocations of the web service. It's as if the same memory is being reused.

For example, I use the existence of a variable (with IsDefined) to detect a condition. It occurs in invocation 1. No problem, I fix it. But then the variable still exists in invocation 2!

I define a UDF unconditionally, because it wouldn't have been defined earlier in the invocation. On the next invocation, it blows up complaining that I attempted to define that UDF twice! Same file! That UDF name defined only once in only that one file!

I conditionally define an array and ArrayAppend trace messages into it in one invocation, and I see the same array still exists and still contains those same trace messages in subsequent invocations! (Hard to argue with same timestamps reappearing.)

All of these symptoms persist until we restart CF Server. Not nice. Not nice at all.

It's as if we're restarting old, dirty instantiations of the CFM files' classes, rather than doing a new ClassName() to get our own fresh copies unique to subsequent invocations.

Enable trusted cache is No. Save class files is No. You'd think those 2 alone would require a recompilation of .cfm to .java, then .java to .class, then .class to memory (starting over from scratch on every request), wouldn't you?

This never occurred before, but it's occurring consistently under CF10. Did my sysadmin miss something in the CF10 release documentation?

UPDATE (same day, 5:40 PM EST):

As you see in the comments and my responses, below, this problem proved to be Axis 2 only. The wsversion="1" that I thought was in the CFC got edited out, probably by some other well-meaning soul trying to solve this same problem.

Because this CF10 behavior was so horrendous, I got surprisingly quick permission to deploy 2 experimental web services. I now have proof, a minimalistic example that Axis 2 has the problem (at least, at our site, with our config):

/experiments/cf10axis1.cfc:

<cfcomponent displayname="Axis 1 Service" wsversion="1">
<cffunction name="IsFooDefined" access="remote" returntype="string" output="No">
    <cfset Variables.Foo = IsDefined("Variables.Foo")>
    <cfreturn Variables.Foo>
</cffunction>
</cfcomponent>

/experiments/cf10axis2.cfc:

<cfcomponent displayname="Axis 2 Service" wsversion="2">
<cffunction name="IsBarDefined" access="remote" returntype="string" output="No">
    <cfset Variables.Bar = IsDefined("Variables.Bar")>
    <cfreturn Variables.Bar>
</cffunction>
</cfcomponent>

/experiments/call_both_cf10_webservices.cfm:

<cfinvoke webservice     = "http://((snip))/experiments/cf10axis1.cfc?wsdl"
          method         = "IsFooDefined"
          refreshWSDL    = "No"
          returnvariable = "Variables.Answer1">
</cfinvoke>
<cfinvoke webservice     = "http://((snip))/experiments/cf10axis2.cfc?wsdl"
          method         = "IsBarDefined"
          refreshWSDL    = "No"
          returnvariable = "Variables.Answer2">
</cfinvoke>
<cfoutput>
Variables.Answer1 = #Variables.Answer1#.<br/>
Variables.Answer2 = #Variables.Answer2#.<br/>
</cfoutput>

First call returns

Variables.Answer1 = NO.
Variables.Answer2 = NO.

All subsequent calls return

Variables.Answer1 = NO.
Variables.Answer2 = YES.

Can't get much more definitive proof than that.

Damn I love how cleanly StackOverflow renders CFML. Really sweet.

Confirmed:

This originally happened in CF10 on Sun Sparc Solaris. I just now ran the same minimalistic test (above) in CF11 on a MacBook Pro and got the exact same result. I've taken the question mark off of this post's Title, and I've made it say CF10/CF11. It's no longer in question.

I don't consider it a CF bug. It's Axis2 that's doing this.

This has serious implications as to how people code, but I can't find any mention of it on the web. Just because you didn't do something in an invocation, doesn't mean it's not that way in memory. Abort flags remain set. You can't use cfparams; you have to used cfsets. Every UDF has to be subordinate to cfif not IsDefined(). The mind boggles.

How am I the first person to discover this? CF11 is out. Is no one using Axis2 in CF?

If I weren't such a naturally happy guy, I'd be majorly depressed right now. :-)


Solution

  • dom_watson (Dominic Watson) posted a workable solution, above. I wish I could give him credit for the correct answer, but I can't figure out how to do that. (Yes, I'm a StackOverflow newb. Yes, I apologize.)

    At first I wasn't hopeful. After all, files cfincluded by Axis2 web services were affected by the caching too, even though they were compiled to separate Java class files. It seemed totally dreadworthy that CreateObject() would pick up an in-memory copy of the object.

    That didn't happen.

    /experiments/cf10axis2proxy.cfc (Axis2 web service):

    <cfcomponent displayname="Axis2Proxy" wsversion="2">
    <cffunction name="IsImpDefined" access="remote" returntype="string" output="No">
        <cfset Variables.ImplObject = CreateObject("component", "experiments.cf10axis2impl")>
        <cfreturn Variables.ImplObject.IsImpDefined()>
    </cffunction>
    </cfcomponent>
    

    /experiments/cf10axis2impl.cfc (note, not even a web service, access is "public"):

    <cfcomponent displayname="Axis2Implementation">
    <cffunction name="IsImpDefined" access="public" returntype="string" output="No">
        <cfset Variables.Imp = IsDefined("Variables.Imp")>
        <cfreturn Variables.Imp>
    </cffunction>
    </cfcomponent>
    

    Added to /experiments/call_both_cf10_webservices.cfm (see above):

    <cfinvoke webservice     = "http://((snip))/experiments/cf10axis2proxy.cfc?wsdl"
              method         = "IsImpDefined"
              refreshWSDL    = "Yes"
              returnvariable = "Variables.Answer3">
    </cfinvoke>
    

    Also added Variables.Answer3 to the cfoutput display, of course.

    First execution yielded

    Variables.Answer1 = NO.
    Variables.Answer2 = NO.
    Variables.Answer3 = NO.
    

    and all subsequent executions yielded

    Variables.Answer1 = NO.
    Variables.Answer2 = YES.
    Variables.Answer3 = NO.
    

    So it's tested and confirmed (under CF11, despite the file names). dom_watson's suggestion makes a cfinvoke of an Axis2 web service behave the way https://wikidocs.adobe.com/wiki/display/coldfusionen/cfinvoke says cfinvoke behaves:

    • Transiently instantiates a component or web service and invokes a method on it.

    (emphasis added).

    Thank you, Dominic.