Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Looping with threads - java.lang.NoSuchMethodException: coldfusion.runtime.CFDummyComponent.<init>()

New Here ,
Feb 07, 2022 Feb 07, 2022

I'm working with an application running on ColdFusion 2018 and Java 11. It uses threads to make multiple http requests at once to reduce the wait time of loading data. About once a month, sometimes more, we start to get error reports of java.lang.NoSuchMethodException: coldfusion.runtime.CFDummyComponent.<init>() on the line of code that creates a thread. I haven't found any pattern to it and the error seems to just keep happening for every request that goes through the affected code path after it's first triggered until the server is restarted. This is occurring in production but I haven't been able to reproduce it locally so I have a feeling it's related to the number of users of the system. Can anyone offer a suggestion on what could be the cause? Please see the related code and error dump snippets below.

 

threadService.cfc

/*
	 * Runs all supplied threads and returns the results in a structure where each key
	 * 		is the name of a thread and contains the results of running the thread.
	 * @threads An array of thread definition structs. Must contain all of these keys:
	 *			data - A struct of arguments passed to the thread's closure function
	 *			closure - A function that returns another function. The returned function
	 *				is run inside of the thread and can accept the data key as an argument
	 *			name - The thread name
	*/
	public struct function runAll(required array threads) {
		var threadResults = {};
		var runnableThreads = [];

		for (var threadDefinition in threads) {
			arrayAppend(runnableThreads, variables.beanFactory.injectProperties("ThreadBean", {
					data: threadDefinition.attributes,
					factory: threadDefinition.closure,
					name: threadDefinition.name
				})
			);
		}
		
		for (var thread in runnableThreads) {
			thread.run();
		}
		
		threadJoin();
		
		for (var thread in runnableThreads) {
			structInsert(threadResults, thread.getName(), thread.getResult());
		}
		
		return threadResults;
	}

 

ThreadBean.cfc

public void function run() {
		var threadName = createUUID();
		var closure = variables.factory();

		thread name=local.threadName doForItem=closure threadData=variables.data { // this is the line the error occurs on
			try {
				thread.result = doForItem(threadData);
			}
			catch (any ex) {
				variables.error = ex;
			}
		}

		variables.thread = cfthread[local.threadName];
	}

 

K23044110lq9e_0-1644265727653.pngexpand image

 

319
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 07, 2022 Feb 07, 2022

I recommend you convert this from CFSCRIPT to regular CFML and see if you have the same problem. My personal experience has been that there isn't a one-to-one correlation between them when it comes to error handling. This kind of sucks as a recommendation, but maybe this will be addressed in future CF releases.

 

Dave Watts, Eidolon LLC

Dave Watts, Eidolon LLC
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 08, 2022 Feb 08, 2022

I see two possible inconsistencies:

  1.  closure = variables.factory() = doForItem. Yet doForItem is a function, whereas factory is the result of an invoked function. Unless, of course, factory() returns a closure, which may work as a function.
    In any case, factory and data are in variables scope. So you could simplify the code logic by keeping their interaction out of the thread scope. Something like:
     
    public void function run() {
    		var threadName = createUUID();
    		var closure = variables.factory();
                    var threadResult = local.closure(variables.data);
    
    		thread name=local.threadName threadResult=local.threadResult { 
    			try {
    				thread.result = threadResult ;
    			}
    			catch (any ex) {
    				variables.error = ex;
    			}
    		}
    
    		variables.thread = cfthread[local.threadName];
    }​
  2. The current line of code

    threadJoin();

    joins the main execution thread to the currently running thread, if there is one. This goes against the expectation in runAll(). A natural assumption is that all the threads coming in as the function's argument will be joined. 
    That is, something like:
    public struct function runAll(required array threads) {
    		var threadResults = {};
    		var runnableThreads = [];
    		var listOfThreadNames = "";
    
    		for (var threadDefinition in threads) {
    			arrayAppend(runnableThreads, variables.beanFactory.injectProperties("ThreadBean", {
    					data: threadDefinition.attributes,
    					factory: threadDefinition.closure,
    					name: threadDefinition.name
    				})
    			);
    			
    			listOfThreadNames = listAppend(listOfThreadNames, threadDefinition.name);
    		}
    		
    		for (var thread in runnableThreads) {
    			thread.run();
    		}
    		
    		threadJoin(listOfThreadNames);
    		
    		for (var thread in runnableThreads) {
    			structInsert(threadResults, thread.getName(), thread.getResult());
    		}
    		
    		return threadResults;
    }
    ​

     
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Feb 08, 2022 Feb 08, 2022

Calling variables.factory actually does return a closure that contains the logic to be run in the thread. So I think calling local.closure outside of the thread would mean there's no work for the thread to do.

 

Joining the threads by name seems like a good practice, thank you for the suggestion.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Feb 08, 2022 Feb 08, 2022

I may have found an important piece of puzzle. There appears to be a trend that the error occurs when a service object is passed into the thread attributes. The thread service is wired into other services, which also have their own dependent services. There are cases where a service that calls threadService.runAll() passes one of its dependencies into a thread's data for the thread's closure to use. I initially thought the error was saying that the java level thread object couldn't be instantiated but closer examination of the stack trace has me rethinking that. I see it goes through duplicate, which suggests to me that the exception happened when the thread attributes were being duplicated to pass into the thread, and the services being passed in actually don't have an init method defined. Still, I'm not sure how to explain why the application can be fine for several weeks then all of a sudden get stuck on this error. I also don't know how to verify my analysis because I still haven't been able to get this error to come up running controlled test cases.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 09, 2022 Feb 09, 2022
 

I may have found an important piece of puzzle. There appears to be a trend that the error occurs when a service object is passed into the thread attributes. The thread service is wired into other services, which also have their own dependent services. There are cases where a service that calls threadService.runAll() passes one of its dependencies into a thread's data for the thread's closure to use.
...
I see it goes through duplicate, which suggests to me that the exception happened when the thread attributes were being duplicated to pass into the thread, and the services being passed in actually don't have an init method defined. Still, 


By K23044110lq9e

 

I was also thinking along those lines. I would strongly advise you to avoid using the variables scope in a thread that is within a function within a component. Otherwise, once you start chaining method calls, it will quickly become unclear which context you are in.  

 

For example, within ThreadBean.cfc, ColdFusion may assume that the context of the variables scope is ThreadBean.cfc. In other words, ColdFusion may assume that variables.factory and variables.data are defined within the confines of ThreadBean.cfc. Customarily, in the init() of the component.

 

If ThreadBean.cfc doesn't define variables.factory and variables.data in this way, then these variables may be null at some point. The result will be an error like the one you got (likely cause: a closure value of null).

 

Suppose the variables-scoped  variables are indeed defined elsewhere. If I were forced to use them in my local function, then I would have the caller pass them in as arguments.

 

I noticed something else. Do I understand correctly that a thread of the same name is defined twice in run()? If so why?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Feb 09, 2022 Feb 09, 2022

ThreadBean.cfc is used as a transient, so each instance of it represents a separate thread. Factory and data are properties defined in the cfc and are just referenced directly without using the getter functions.

 

The actual name of the thread ColdFusion uses to run it in TheadBean.cfc is the result of a call to createUuid() so names for multiple threads in a request should be unique. The name used in the thread service is just for passing the results back to the calling function in a struct. It may not be unique per call but at this point will throw an exception if it's not.

 

During my testing, I tried sending in the function from the service that the thread is calling instead of passing the entire service and I noticed that the variables scope inside the function does get polluted between calls to functions in different threads. This makes sense according to adobe's documentation on thread scopes. But the variables scopes of the services look the way they should when I pass the services in. However, it's just me testing and the issue has been occurring in production where we have many more users so maybe there's a race condition in play. I believe not passing the services into the thread attributes is probably the answer as well but I don't have a clear explanation of why or a test that will prove it. And rewriting things so that the logic is defined in a reusable way is a bit difficult so I would hate to have to spend a lot of time on it and end up with it not being the right solution.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 09, 2022 Feb 09, 2022

ThreadBean.cfc is used as a transient, so each instance of it represents a separate thread. Factory and data are properties defined in the cfc and are just referenced directly without using the getter functions.

 

By K23044110lq9e

 

Ah, OK. That's clear. Thanks for the explanation.

 

The actual name of the thread ColdFusion uses to run it in TheadBean.cfc is the result of a call to createUuid() so names for multiple threads in a request should be unique. The name used in the thread service is just for passing the results back to the calling function in a struct. It may not be unique per call but at this point will throw an exception if it's not.

 

I was thinking more of the scenario where 2 or more threads have simultaneous access to run(). My guess is that, after run() completes, variables.thread will contain only one of them. And in no particular order, too, they being asynchronous threads. Is that what you intended? 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 10, 2022 Feb 10, 2022

 

During my testing, I tried sending in the function from the service that the thread is calling instead of passing the entire service and I noticed that the variables scope inside the function does get polluted between calls to functions in different threads. This makes sense according to adobe's documentation on thread scopes. But the variables scopes of the services look the way they should when I pass the services in. However, it's just me testing and the issue has been occurring in production where we have many more users so maybe there's a race condition in play. I believe not passing the services into the thread attributes is probably the answer as well but I don't have a clear explanation of why or a test that will prove it. And rewriting things so that the logic is defined in a reusable way is a bit difficult so I would hate to have to spend a lot of time on it and end up with it not being the right solution.


By K23044110lq9e

 

Suggestion for test:

 

 

 

public void function run() {
	
	var threadName = createUUID();
	
	try {
		var closure = variables.factory();

		thread name=local.threadName doForItem=closure threadData=variables.data {		
			thread.result = doForItem(threadData);		
		}

		variables.thread = cfthread[local.threadName];
	}
	
	catch (any ex) {
		
		variables.error = ex;
		/* Dump error as html file, which you can study at your convenience */
		writeDump(var=ex, format="html", output="#expandPath('threadBean_run_error.html')#");
	}
}

 

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 13, 2022 Feb 13, 2022

Oh, could you please share the contents of the error-dump.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Feb 18, 2022 Feb 18, 2022
LATEST

Any revelation from the dump?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Resources