> OK, thanks for all this info so far guy's. Now then,
> am I correct in saying that I should be scoping all
> of my argument var's to local var's within each
> of my functions within my cfc's? Now, is it
> acceptable, in instances where I pass in an
> argument collection, to copy the argument collection
> to a new local collection i.e. duplicate the argument
> structure to a new local structure?
No, if a variable is an argument it's already local to the function,
so there's no need to recreated it locally, and actually unless you
recreated it into a var'ed structure, you'll get an error, i.e. this
produces an error:
<cffunction ...>
<cfargument name="x">
<cfset var x = arguments.x>
</cffunction>
Using duplicate to create a copy of the arguments scope won't gain you
anything, but it will inflate the server's memory consumption and time
to execute pages, and since CFC's can't be duplicated it will also
prevent you from being able to pass a CFC as an argument to another
method. So... I would say under no circumstances should you duplicate
the arguments scope.
> Can someone give me a brief idiot's guide on how
> CFC's and individual functions behave for each
> user? i.e. when initializing in the application
> scope the CFC is available application wide to
> each user, does this mean that only 1 instance
> of the CFC ever exists or is there 1 for each
> user session? Is each call to the CFC session
> independent or is this where I need to be
> understanding the whole locking/race condition
> issue, each call is awaiting for another call
> to finish first. Bear in mind that so far all
> my CFC's do is handle database interaction.
> Am I even making any sense at all, I do not
> know, I am so confused ;-(
Like all other variables, a CFC in a scope has only one instance for
all users affected by that scope:
<---SERVER--------------------------------->
<---APPLICATION-------------><-APPLICATION->
<---SESSION------><-SESSION->
<REQUEST><REQUEST>
A session represents an individual user, so a CFC should only be
created in the session scope if it contains data specific to an
individual user -- and maybe not even then, depending on your
architectural needs, it might be better to have member-data CFC's
stored in the application scope so that they can be updated by another
user (such as changing the member's permissions/roles). A CFC created
in the application scope will have one instance for every
user-session/request associated with that application. A CFC created
in the server scope will have one instance for every request to that
server, irrespective of application or session.
A race condition occurs when a shared resource (such as an application
or server variable or in some cases database information) can be
modified by multiple requests simultaneously (this is the normal
state) and one set of modifications must occur in a specific and
uninterupted order:
For example: You have this table structure in your database:
MEMBER <----MEMBER_ROLE----> ROLE
These tables are joined by foreign key constraints, so that if a
memberid or roleid is inserted into the member_role table it must also
exist in the member or role table respectively or the database will
raise an error.
Now you have some common CF code to manage the data in these tables:
<cfquery name="deletememberroles" ...>
delete from member_role where roleid = #form.roleid#
</cfquery>
<cfquery name="deleterole" ...>
delete from role where roleid = #form.roleid#
</cfquery>
And in a separate template:
<cfquery name="deletememberroles" ...>
delete from member_role where memberid = #form.memberid#
</cfquery>
<cfloop index="x" list="#form.roleid#">
<cfquery name="insertrole" ...>
insert into member_role
(memberid,roleid)
values
(#form.memberid#,#x#)
</cfquery>
</cfloop>
Now these 2 templates would create a race condition because in both
cases, the collection of queries being executed must be executed in
order and uninterrupted. Otherwise, if the member-update process which
is inserting the new roleid's is interrupted by the role-delete
process (in a separate request from another user), you might end up
with this situation:
<cfquery name="deletememberroles" ...>
delete from member_role where memberid = #form.memberid#
</cfquery>
<cfquery name="deletememberroles" ...>
delete from member_role where roleid = #form.roleid#
</cfquery>
<cfquery name="deleterole" ...>
delete from role where roleid = #form.roleid#
</cfquery>
<cfloop index="x" list="#form.roleid#">
<cfquery name="insertrole" ...>
insert into member_role
(memberid,roleid)
values
(#form.memberid#,#x#)
</cfquery>
</cfloop>
The last query (within the loop) may then produce an error if the form
values contain the deleted roleid. In this case there are several
methods that can be used to eliminate this race condition, including
CFTRANSACTION, CFLOCK and CFTRY (which doens't actually eliminate the
race-condition, but allows the process to ignore the error).
<cftransation isolation="serializeable">
<cfquery name="deletememberroles" ...>
delete from member_role where memberid = #form.memberid#
</cfquery>
<cfquery name="deletememberroles" ...>
delete from member_role where roleid = #form.roleid#
</cfquery>
</cftransaction>
----------
<cfquery name="deleterole" ...>
delete from role where roleid = #form.roleid#
</cfquery>
<cfloop index="x" list="#form.roleid#">
<cftransaction isolation="serializeable">
<cfquery name="getrole" ...>
select * from role where roleid = #x#
</cfquery>
<cfif getRole.recordcount>
<cfquery name="insertrole" ...>
insert into member_role
(memberid,roleid)
values
(#form.memberid#,#x#)
</cfquery>
</cfif>
</cftransaction>
</cfloop>
In the code above, the cftransaction tag creates a SQL transaction
which will serialize all access to the requested role and member_role
tables, so that each transaction must end before the next begins. This
way when you execute the getRole query in the 2nd transaction, you can
be certain that the role exists in the role table before inserting it
into the member_role table.
Or you could use something like this:
<cflock name="myapp_rolelock_#form.roleid#">
<cfquery name="deletememberroles" ...>
delete from member_role where memberid = #form.memberid#
</cfquery>
<cfquery name="deletememberroles" ...>
delete from member_role where roleid = #form.roleid#
</cfquery>
</cflock>
----------
<cfquery name="deleterole" ...>
delete from role where roleid = #form.roleid#
</cfquery>
<cfloop index="x" list="#form.roleid#">
<cflock name="myapp_rolelock_#x#">
<cfquery name="getrole" ...>
select * from role where roleid = #x#
</cfquery>
<cfif getRole.recordcount>
<cfquery name="insertrole" ...>
insert into member_role
(memberid,roleid)
values
(#form.memberid#,#x#)
</cfquery>
</cfif>
</cflock>
</cfloop>
This is similar to the cftransaction approach, but serializes any
arbitrary block of cflock code with the same name attribute (not just
database access). You have to be careful with your lock names because
they're server-wide (although they have nothing at all to do with the
server scope). So if your lock name isn't descriptive enough, it could
cause performance problems when it serializes access to lots of
unrelated code.
Or lastly:
<cfloop index="x" list="#form.roleid#">
<cftry>
<cfquery name="insertrole" ...>
insert into member_role
(memberid,roleid)
values
(#form.memberid#,#x#)
</cfquery>
<cfcatch></cfcatch>
</ctry>
</cfloop>
You normally wouldn't want to use cftry-cfcatch for "flow-control" or
race-conditions in an application, but in this case, if the database
raises an error it won't insert the record, so everything is as it
should be once the error is raised, and user-roles shouldn't be
deleted with any significant frequency, so the exception will rarely
be thrown anyway, so it's reasonable to assume that this try-catch
won't degrade the server's performance.
s. isaac dealey 954.522.6080
new epoch : isn't it time for a change?
add features without fixtures with
the onTap open source framework
http://www.fusiontap.com
http://coldfusion.sys-con.com/author/4806Dealey.htm
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
Logware (www.logware.us): a new and convenient web-based time tracking
application. Start tracking and documenting hours spent on a project or with a
client with Logware today. Try it for free with a 15 day trial account.
http://www.houseoffusion.com/banners/view.cfm?bannerid=67
Message: http://www.houseoffusion.com/lists.cfm/link=i:4:217286
Archives: http://www.houseoffusion.com/cf_lists/threads.cfm/4
Subscription: http://www.houseoffusion.com/lists.cfm/link=s:4
Unsubscribe:
http://www.houseoffusion.com/cf_lists/unsubscribe.cfm?user=11502.10531.4
Donations & Support: http://www.houseoffusion.com/tiny.cfm/54