Using native Java threading in Lucee

Just wanted to follow up here that Lucee 5 had a bug that prevented createDynamicProxy() from loading some core classes. This was fixed in 5.2.9.20 as part of this ticket: [LDEV-1778] - Lucee
I’m unclear on whether this bug affected all versions of Lucee 5 or was introduced at a later date,

I have done a quick test and the NoClass-Error is fixed in 5.2.9.20 and its running.
In 5.2.8.50 i still get the NoClass-Error like in my old topic https://lucee.daemonite.io/t/runnable-task-java-lang-noclassdeffounderror/526
I also dont get my workaround with the javaSettings running (maybe it was some kind of version-specifc ?!)

did you file a bug?

1 Like

As you pointed out yourself the bug is fixed in 5.2.9.20 and onwards. sadly we cannot fix bugs in a way that affects past versions :wink:

1 Like

I’m not really sure what your (@bdw429s ) goal is on this.
you wanna improve Lucee 4.5 on this?
i my opinion the best approach is to create a function createRunnable(udf) that handles everything.

The goal is the same it’s always been: To be able to use the native threading functionalities of the JVM to run CFML code. That is all. The reason the thread started out talking about Lucee 4.5 is because this thread is from quite a while ago and that was the version of Lucee that CommandBox and all our sites were still on at the time. All the same questions still apply to Lucee 5.

Its all fine :slight_smile:
I just wanted to may sure that LDEV-1778 is fixing the bug related to the threading.

what you can do with createDynamicProxy.

Where you did fail is to make the environment of the birthplace available to the component. Like i already did point out has a lot of open question, in my opinion it makes sense to make the application context available (when it still exists), where i have given you a easy workaround

index.cfm

jRunnable = createDynamicProxy( new Runnable(), [ 'java.lang.Runnable' ] );
t = createObject( 'java', 'java.lang.Thread' ).init( jRunnable );
t.start();

Runnable.cfc

component accessors="true" {

	public function init() {
		variables.ac=getPageContext().getApplicationContext();
	}

    function run() {
    	getPageContext().setApplicationContext(variables.ac);
        systemOutput( 'Thread has run.',1,1);
        systemOutput( getApplicationMetadata(),1,1);
        systemOutput( url ,1,1);
    }
    
}

but making the user enviroment data available (session,client scope) is much more in question, in theory that Proxy class can be executed long after the users session did end.
Also the context (stacktrace) not necessary is still available.

So again, you need to be more clear what you are expecting what is not working ATM.

Like i also set we can simplify all of the by providing a function createRunnable.

I need to be able to run a thread that executes in the context of the original request, just like CFThread does. It has access to all the normal scopes. And, of course, some of those scopes are no longer accessible once the parent thread has completed if a CFThread runs for a very long time but I think that’s fine. For example: In the case where I spin up a CFThread and expect to access things like the cgi scope I also know the thread will end before the request is over. And in the cases where I want to run a CFThread for a very long time, I would not expect to access the CGI scope inside of it. I would like this same functionality to be possible from CFML code run with native threading libraries in Java such as concurrent thread pools one-off runnables, or Java Streams.

In the event of CFThread, Lucee manages the creating and pooling of the threads so it can make sure it gets cleaned up. I understand that using an external thread pool such as the fork join pool in Java Streams might require me to manually associate the thread and clean it up at the end which I am ok with.

The problem with a simple createDynamicProxy() implementation is that thread runs outside of the application context so settings and CF Mappings do not exist inside that thread. This is a major problem especially if using a framework.

Your code sample shows a way to work around the application context, but such a thread would not have access to the other request based scopes which would be necessary when passing closures into cbstreams for parallel processing inside of a request where the thread would need full access to the request in the same manner a CFThread would have.

I’ve experimented with copying the page context which appears to make my threads have access to the original request but I don’t yet know how to properly clean up that thread so it is free to be placed back in a fork join pool with no references left in it.

And finally, my goal is to also have Adobe ColdFusion add this feature as well which necessitates some sort of top-level CFML support for associating and unassociating a thread with either an application or a specific parent request that both Lucee and Adobe CF can implement in the same way.

Does that clear up what I’m looking for and why the current suggestions are still falling short of what I need?

1 Like

when you execute a method of a Proxy class, Lucee first checks if the current thread has a PC. If so it simply uses that one, in our case that of course is not the case, so Lucee simply creates a PC on the spot and because it has no info of the birthplace that one is empty.

So how we could simulate cfthread-task what you in the end want to have. we store the necessary data (NOT PC) with the Proxy class.

This comes with 2 problems:

  1. unnecessary overhead - we have to store it this with every Proxy class, even the chance it is used that way is minimal. we also cannot take the chance that only Proxy classes that implement Runnable needs this
  2. environment - in your scenario you are expecting the birthplace environment, but that is not necessary what other users are expecting, so what is the right default environment

#1 is the same as every closure does in a similar way, so i think we are fine, #2 maybe needs an addional optional attribute with createDynProxy

I like the idea of an additional parameter to createDynamicProxy() or something like that. I agree that not every user has the same use case so I’d like to be able to control it.

This was what I needed to make sense of “Task” CFThread processes. It seems that Task threads run outside the original application context. But, I can re-write it to the original application using the aforementioned application-context setter.

I just wanted to say thank you! The tip about using the “application context” association:

getPageContext().setApplicationContext(variables.ac);

… was exactly what I need to figure out how to actually make CFThread[type="task"] useful. As a fun thought-experiment, I’ve generated a task thread that monitors and manages daemon threads running inside my ColdFusion application:

This allows me to restart CFThreads if they crash (such as when consuming a message queue, Redis list as in the above blog post).

task-thread-running-daemon-threads-lucee5

2 Likes

@bennadel check out the async manager we built into coldbox 6. We solved the thread context problems for Lucee and acf. And we have a much much more powerful (and fluent) api for daemon threads than cfthread.

I also baked the same solution into my rabbit mq sdk’s consumer threads.

3 Likes

Very cool, I will check it out :+1:

@bdw429s this is pretty interesting right here: RabbitSDK/Consumer.cfc at master · Ortus-Solutions/RabbitSDK · GitHub – I like that you found a way do associate the application context in both Lucee CFML and Adobe ColdFusion, hats off to you :smiley: It seems significantly easier in Lucee, which is cool.

I’m not super familiar with RabbitMQ (I somewhat get the basic concepts). But, picking through the code, it looks like the RabbitMQ Channel is responsible for keeping the Consumer thread going indefinitely, right? Meaning, if the Consumer throws an error or whatever, it looks like the Channel is managing that and keeping things going.

As an aside, I know nothing about WireBox, but it looks like it allows for name-spacing of properties. I really really wish we had that in our app (which is use FW/1 + DI/1). I’m starting to have to create really massive Component names in order to make them globally unique. This is cool!

I like that you found a way do associate the application context in both Lucee CFML and Adobe ColdFusion, hats off to you :smiley: It seems significantly easier in Lucee, which is cool.

Thanks. Lucee was easier since I had the benifit of being able to see the engine’s open source code. Lucee also tends to encapsulate better from what I’ve seen.

I’m not super familiar with RabbitMQ (I somewhat get the basic concepts ). But, picking through the code, it looks like the RabbitMQ Channel is responsible for keeping the Consumer thread going indefinitely, right? Meaning, if the Consumer throws an error or whatever, it looks like the Channel is managing that and keeping things going.

Um, more less. RabbitMQ is just a message queue for sending messages between applications. I only mentioned it because the java SDK for Rabbit uses a java thread pool to spin up the consumer threads. These threads suffer from the same issue as any native thread solution, which is they are not “attached” to their application nor page context so they forget who they are.

I know nothing about WireBox

It’s not that different than DI/1, just more robust and extensible.

but it looks like it allows for name-spacing of properties.

WireBox adds a layer of abstraction called “mappings” that work like recipes in your kitchen. When you go to a restaurant, you don’t order a slice of “flour, sugar, milk, oil, and eggs”. You order a slice of “cake”. Cake is the agreed name for the thing you want the chief to cook for you, and he has a a recipe card labeled “cake” on top that lists all the things going into it. The same with WireBox-- while the default name for a mapping is usually the name of the CFC you want, you can create a mapping in WireBox called “cake” and then tell WireBox exactly what that means. This is why/how WireBox can supply you with more than just CFC instances, but also static values, settings, java classes, and framework services. All it needs is the recipe to build it and the name you want to call it.

So as far as “namespaces”. Imagine, the chief now has the following recipe cards:

  • Chocolate cake
  • Angel food cake
  • Devils Food cake
  • Carrot cake

He ends all the cake recipes off with the same name to help sort and categorize them. This also helps prevent accidental duplicate names when he also has

  • Chocolate cake
  • Chocolate milk
  • Chocolate candy bar
  • Chocolate covered ants

WireBox does the same thing when it auto-registers CFCs from a module. The default name for a CFC is “nameOfCFC@nameOfModule”. Which is how you get a name like RabbitClient@rabbitsdk which is both self-describing plus avoids accidental overlap with another moudle having a CFC with the same name.

It’s really interesting stuff. I spent like an hour trying to dig through the ioc.cfc from FW/1 yesterday. Really, what I would love to do is be able to set a property attribute that defines:

  • The name of the injectable property (ie, what the object is called locally inside the target).
  • The class-path for the injectable definition.

So, image that I have a bunch of CFCs all in a folder like libs/app/feature, I’d love to have a CFC that has properties like:

property name="service" type="/libs.app.feature.FeatureService";
property name="gateway" type="/libs.app.feature.FeatureGateway";
property name="helper" type="/libs.app.feature.Helper";

This way, the CFCs that are “local” to a feature don’t have to have globally unique names. For example, I might have several CFCs that have the name Helper.cfc, and they would live within their own corners of the app.

Anyway, we’ve gone totally off topic at this point :smiley: But, this is something that is weighing on me lately because I’m starting to have CFC names that are absurdly long because they have to be unique.

@bennadel You chose the wrong Di engine then, because that is exactly how WireBox works. The name of the mapping you wish to inject doesn’t have to be the same as what variable you inject it into.

property name='yoYoMySweetInjectedInstance' inject='OfficialWireBoxMappingName';
property name='fileUtil' inject='InternalFileSystemUtilityService';
// Direct CFC path to file
property name='Helper' inject='libs.app.feature.Helper';

Oh man!!! I want, I want! I need, I need!

Unfortunately, that ship has almost certainly sailed. We have a boat-load (pun intended) of code that would have to be changed at this point. But, good to know I’m not crazy for wanting such things.

2 Likes