Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Jul. 2009) with: Sean Schroeder

Bidirectional Data Exchange With ColdFusion's CFThread Tag

By Ben Nadel on
Tags: ColdFusion

In the past, I've talked about bidirectional data exchange with ColdFusion's CFThread tags; however, those discussions were always centered around either passing data into a tag at the start of its execution or, passing data out of a tag at the end of its execution. In order to get ready for some further research, however, I wanted to run some experiments to see if I could create bidirectional data exchange with a CFThread tag in the midst of its execution. What I found was that you can do this - but it requires a data exchange bridge.

When you run an asynchronous thread using ColdFusion's CFThread tag, you can use the "Thread" scope in order to pass data back into the calling context. The values stored in this Thread scope become accessible in the calling context via the thread object - the variable created as a result of the CFThread "name" attribute. As such, my first instinct was to try and alter the thread object from the calling context in hopes that these externally-set values would become available internally to the thread:

  • <!--- Run a thread in the current page context. --->
  • <cfthread
  • name="runner"
  • action="run">
  •  
  • <!---
  • Store an index value into the thread scope. This will give
  • the external calling context access to the "index" variable.
  • --->
  • <cfset thread.index = 1 />
  •  
  • <!---
  • Sleep the thread so that the execution context has time to
  • attempt thread access while the thread is still "running."
  • --->
  • <cfthread
  • action="sleep"
  • duration="#(2 * 1000)#"
  • />
  •  
  • </cfthread>
  •  
  •  
  • <!---
  • Sleep a little bit to make sure the above thread actually started
  • executing. Remember, there is not guarantee as to when threads
  • actually execute.
  • --->
  • <cfthread
  • action="sleep"
  • duration="100"
  • />
  •  
  •  
  • <!---
  • Now that the thread is running (most likely) and is probably
  • sleeping internally, let's try to update the index value from
  • the outside.
  • --->
  • <cfset runner.index = 2 />

As you can see here, my asynchronous thread is storing an "index" key in its Thread scope. Then, in the calling context, after the thread has commenced execution, I am trying to override that initial index value. Unfortunately, this throws the following ColdFusion error:

Error in modifying the thread scope. This scope can be modified only by the owner thread.

After getting this error, my next thought was to store a ColdFusion struct inside of the Thread scope and then pass data back and forth using this intermediary struct. Since this struct is one level removed from the Thread scope, the hope was that it could be altered without raising any permissions problems:

  • <!--- Run a thread in the current page context. --->
  • <cfthread
  • name="runner"
  • action="run">
  •  
  • <!---
  • This time, rather than storing output directly in the
  • thread scope, we are going to create an intermediary
  • "exchange" object. Since we cannot edit the thread scope
  • externally, the exchange object will provide a tunnel
  • into a shared, mutatable scope.
  • --->
  • <cfset thread.exchange = {} />
  •  
  • <!---
  • Now, rather than storing the index in the thread scope,
  • let's store it in exchange object.
  • --->
  • <cfset thread.exchange.index = 1 />
  •  
  • <!---
  • Sleep this thread so the external page has time to act
  • on the shared, exchange object.
  • --->
  • <cfthread
  • action="sleep"
  • duration="#(1 * 1000)#"
  • />
  •  
  • <!--- Increment the shared index. --->
  • <cfset thread.exchange.index++ />
  •  
  • </cfthread>
  •  
  •  
  • <!---
  • Sleep a little bit to make sure the above thread actually started
  • executing. Remember, there is not guarantee as to when threads
  • actually execute.
  • --->
  • <cfthread
  • action="sleep"
  • duration="100"
  • />
  •  
  •  
  • <!---
  • Now that the thread is running (most likely) and is probably
  • sleeping internally, let's try to update the index value from
  • the outside using the shared exchange object.
  • --->
  • <cfset runner.exchange.index = 100 />
  •  
  • <!---
  • Rejoin the thread (notice that the rest of the thread is
  • incrementing the shared index value).
  • --->
  • <cfthread
  • action="join"
  • name="runner"
  • />
  •  
  • <!--- Now, output the final index value. --->
  • <cfoutput>
  •  
  • Index: #runner.exchange.index#
  •  
  • </cfoutput>

This example definitely has a lot more going on in it in order to demonstrate the concept; however, the premise is simple. Since ColdFusion structs are passed around by reference, storing one within the Thread scope should create an object accessible by both the thread context and the execution context. And, as long as ColdFusion doesn't mind this shared object being mutated from the outside, it should provide a tunnel for bidirectional communication.

When we run this CFThread example, we get the following output:

Index: 101

It worked! The execution context overrode the initial index value, setting it to be 100. Then, the asynchronous thread came out of its slumber and incremented that shared index reference, resulting in a new value of 101.

ColdFusion clearly doesn't like it if you try to alter the Thread scope outside of a given CFThread instance; however, ColdFusion doesn't appear to have any problem with you altering a complex object stored within that same Thread scope. We can then leverage this behavior in order to create bidirectional data exchange between the asynchronous thread and its execution context. The trick here is just to pass data around through some soft of agreed-upon intermediary channel.




Reader Comments

@Tim,

Threads are just really cool. I'm working on a little proof-of-concept right now that I'll blog about shortly - might be crazy, might be awesome!

Reply to this Comment

I am using the threading in conjunction with cfprint, (Adam Lehman thinks we may be the only shop that USES cfprint) and needed a way to to supply some data to the thread already created after it started. I will let you know how it goes.

Reply to this Comment

This is probably actually a bug in cfthread!

Modifying a thread's state at runtime is incredibly dangerous as the caller has no idea what state the thread is in so you could end up putting your threads into a broken, invalid state. It's the reason why object attributes to cfthread are deep copied. If they were references, or even a shallow copy, the objects can be changed without the thread knowing about it.

I'd say if you really think you need this sort of 'feature', your design is wrong!

George.

Reply to this Comment

Won't be the first time I was wrong, nor the last. It will still be interesting to see where this leads, learning about it won't hurt anyone.

Reply to this Comment

@George,

I have to say that I disagree. CFThread provides encapsulation of logic in the same way that CFCs provide encapsulation a logic. To say that altering a thread's state is dangerous is much akin to saying that calling a method on a CFC is dangerous. As long as you build your threads to accept this, you should be as safe as building your CFCs to accept this.

Heck, if I delete a CFC function at run time from an external context:

<cfset structDelete( cfc, method ) />

... then I'll definitely be shooting myself in the foot. But as long as I am doing something that *both* contexts agree on, then I can't see why this would be considered dangerous.

Just as any relationship, communication is key :)

Reply to this Comment

@Ben,

It's completely different.

It's like your var-scoped variables in a cffunction being modified mid-call by an outside process.

Threads aren't designed as a means to solely encapsulate logic - that's what methods in a CFC are for. A thread is a bunch of logic which runs effectively as a process by itself. You *really* shouldn't have shared scopes in threads; if you really, *really* want to do it, have methods on objects which can be called from a thread but not without *extremely* fastidious locking, which introduces massive complexity and runtime overheads.

In a highly transactional application, concurrent processing with threads is a big enough headache as it is, without having to code your threads to cater for variables in an unpredictable state.

I'd put money on the fact that if this was reported as a bug, it'd be fixed.

I'd really suggest reading Java Concurrency In Practice. It's obviously Java-centric but there is a lot of great general-purpose threading info in there as well.

The fact you can delete entire functions from a CFC at runtime is proper dodgy as well but that's a whole different discussion.

George.

Reply to this Comment

@George,

I don't think the parallel holds. When CFThread gets compiled, it actually runs as a Function - it has its own local scope, arguments scope, attributes scope etc.. I am *not* talking about modifying those on the fly and I agree that modifying locally-scoped variables would lead to some crazy behavior; in fact, this is why var-scoping in a CFC is so critical (and why extensions like VarScopper are so popular).

Neither Thread nor Thread.exchange are local variables in the same sense. The Thread scope is designed to allow data to be passed back to the calling context. Then, I took that concept and augmented it with the "exchange" object. Remember, in order for this to work, *both* sides of the divide have to agree that this exchange object is meant for bi-directional data transfer; if they didn't agree, the CFThread body would simply use the Thread scope to pass back data.

Also, think about it from the other side for a moment - the values of the thread object change throughout the duration of the async thread and the calling context has to deal with this... it has to be *designed* to deal with this. If the [thread].status property can change to many possible values, from your point of view, it kind of sounds like the calling context should never make use of the values since they are not predictable at any given time? But, in fact, those values are very useful as long as the calling context understands how they work and how they can be used.

As far as being able to mutate component methods on the fly from an external context, this is actually a very powerful feature and does wonders for things like dependency injection. I should hope they never get rid of it.

Reply to this Comment

@George,

... not to say there isn't a lot I can and should learn about the difficulties of concurrency; but I think there is nothing inherently wrong with this approach.

Reply to this Comment

Thanks for posting this example, Ben. I plan to put something like this to use. I want to spawn up a thread to insert several records (possibly 1000s) into a database incrementing a counter upon each insert. While this thread is running I plan to check the counter with a while loop and wait for say at least 100 records to be inserted, then return some data, while the thread keeps running until completed in the bg.

Hopefully I can make it happen.

Reply to this Comment

@Pat,

That's an interesting idea. Can I ask why you want to wait for 100 records to run before you return data. If you're gonna go asynchronous, why not go all the way?

Reply to this Comment

Long story short. I've built a web interface where users can run SQL queries against a very large db (over 12 billion records). In a nutshell it works like this. Request comes in. We run query against large db residing on a separate db server. We then use the query's results to populate a temp table on our web server. A jdbc connection is stored in the client's session so that the client can access the data in the temp table as necessary (ie run sub queries against the smaller resultset). I have an ExtJS buffered grid w/paging on the front end to display the records 100 at a time.

So what I'm trying to achieve is a way to insert the first 100 records into the temp table, then return that first 100 so that the user can begin viewing his/her results, and continue temp table continues to be populated in the background.

Right now its working for us without the threading; however, the user has to wait for the temp table to be completely populated before anything is returned.

Its kind of a crapshoot for us. These users are writing queries that can return well over 10,000 at a time. So, we are trying to keep the waiting time to a minimum and improve the overall user experience.

Reply to this Comment

@Pat,

12 billion records? I had to read that a few times just to make sure I wasn't going crazy :) That's just bananas! But, it gives me an idea for something - I wonder if we can incorporate push-technology into threading. Technically, not a problem; but I wonder if we can make this graceful. I know you're rocking ExtJS, but I was just brainstorming (with myself) about some sort of augmented jQuery AJAX method that would allow a push-response. Something like:

$.ajax({
type: "post",
url: ".....",
push: true,
success: function( response ){ .... }
});

Here, the push:true would denote tha the page can completely let the AJAX request run and complete without triggering the success. Then, the success would be triggered via PUSH events.

... anyway, gotta run to conference sessions. But, i'll do some more thinking on this. I know it might not be right for your situation, but I'm feeling inspired.

Reply to this Comment

Both sets of sample code produce the error:

This scope can be modified only by the owner thread.

in CF 9,0,1,274733

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.