Using native Java threading in Lucee

I had the need to run a cfthread from inside another cfthread in Lucee recently. @micstriit mentioned that this was already possible in Lucee 5.1, however my project was using Lucee 4.5 and unable to upgrade at the moment.

It is fairly easy to fire off a Java thread by creating a CFC with a “void run()” method and using createDynamicProxy() to create it as an instance of “java.lang.Runnable”. Then it can be passed into a java.lang.Thread() constructor and started with “start()”. This works well, but the thread runs outside of the page context and doesn’t get access to settings like CF mappings and the “thread” scope which makes it difficult to use.

In a Slack conversation with Micha he said,

when you create a new thread and you wanna run CFML code in that thread, you need to create a PageContext for that thread and register that PageContext with that thread and you also need to register the ConfigWeb object with that thread.

I don’t know what it takes “register” a package context or a configWeb object with a thread, so I’m creating this post here so he can hopefully explain that and the answer will be documented for others.

1 Like

Are you sure this works? I tried it and I get:

ce 	lucee.runtime.exp.NativeException: class java.lang.Runnable is invalid or doesn't exist
 	at lucee.commons.lang.PhysicalClassLoader.findClass(PhysicalClassLoader.java:146)
 	at lucee.commons.lang.PhysicalClassLoader.loadClass(PhysicalClassLoader.java:127)
 	at lucee.commons.lang.PhysicalClassLoader.loadClass(PhysicalClassLoader.java:107)
 	at java.lang.ClassLoader.defineClass1(Native Method)
 	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
 	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
 	at lucee.commons.lang.PhysicalClassLoader._loadClass(PhysicalClassLoader.java:163)
 	at lucee.commons.lang.PhysicalClassLoader.loadClass(PhysicalClassLoader.java:158)
 	at lucee.transformer.bytecode.util.JavaProxyFactory.createProxy(JavaProxyFactory.java:311)
 	at lucee.runtime.functions.other.CreateDynamicProxy._call(CreateDynamicProxy.java:95)
 	at lucee.runtime.functions.other.CreateDynamicProxy.call(CreateDynamicProxy.java:44)
 	at java_executor_test_cfm855$cf.call(/java-executor-test.cfm:8)
 	at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:904)
 	at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:819)
 	at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:814)
 	at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)
 	at application_cfc$cf.udfCall(/Application.cfc:6)
 	at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:105)
 	at lucee.runtime.type.UDFImpl._call(UDFImpl.java:337)
 	at lucee.runtime.type.UDFImpl.call(UDFImpl.java:224)
 	at lucee.runtime.ComponentImpl._call(ComponentImpl.java:697)
 	at lucee.runtime.ComponentImpl._call(ComponentImpl.java:580)
 	at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1903)
 	at lucee.runtime.listener.ModernAppListener.call(ModernAppListener.java:422)
 	at lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:224)
 	at lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)
 	at lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2265)
 	at lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2257)
 	at lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2225)
 	at lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:881)
 	at lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)
 	at lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:62)
 	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
 	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
 	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
 	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
 	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
 	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
 	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
 	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
 	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
 	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
 	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
 	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:670)
 	at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2508)
 	at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2497)
 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
 	at java.lang.Thread.run(Thread.java:745)

Hi @kliakos sorry for the lack of example in my first post. Here is the code to try this out on your own.

Create a component called myRunnable.cfc with the following code

component {
	
	function run() {
		systemOutput( 'Starting thread.', true );
		sleep( 2000 );
		systemOutput( 'Stopping thread.', true );
	}
	
}

Then run this code from another file:

myRunnable = createDynamicProxy( new myRunnable(), 'java.lang.Runnable' );
myThread = createObject( 'java', 'java.lang.Thread' ).init( myRunnable );
myThread.start();

You’ll see this in your console log:

Starting thread.
Stopping thread.
1 Like

Do you have this.javaSettings in your Application.cfc?

I figured out my problem was this ( actually the lack of it ). Found the solution from here

https://lucee.daemonite.io/t/runnable-task-java-lang-noclassdeffounderror/526

Nope, no javasettings defined in my Application.cfc. What version of Lucee are you running this on? I’m on 4.5

I am on 5.1

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