I just ran into a funny little issue when playing around with runAsync(). If I run this code a few times (maybe 4 or 5 times, maybe 15 times), my server runs out of threads:
After I run that page a few times, the Lucee server stops being able to create new threads and all page requests end with this:
Error (java.lang.OutOfMemoryError)
lucee.runtime.exp.NativeException: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:957)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1367)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at java.util.concurrent.Executors$DelegatedExecutorService.submit(Executors.java:681)
at lucee.runtime.future.Future._then(Future.java:56)
at lucee.runtime.functions.thread.RunAsync.call(RunAsync.java:27)
at run_async_speed630.async_cfm$cf.udfCall(/run-async-speed/async.cfm:19)
at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106)
at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344)
at lucee.runtime.type.UDFImpl.call(UDFImpl.java:212)
at lucee.runtime.type.EnvUDF.call(EnvUDF.java:109)
at lucee.runtime.functions.closure.Map._inv(Map.java:312)
I was trying to compare speed of parallel iteration (.each( op, true, 20 )) to the speed of runAsync() to see if there was any difference. Ironically, parallel iteration doesn’t seem to have any issues at all; but, this code with runAsync() consistently drains the server of all threads.
@bennadel Yes, the cfengine param is the one. You can start any version of Lucee so long as it’s been pulled into ForgeBox (happens daily). The best part is, you can just use tab completion to find what you want. For example, if you type
server start cfengine=lucee@5.3.8
and hit tab, you’d see all the possible snapshots.
In this case, you can also just run this:
@bennadel I forget where, but I someone was talking about this same error the other day. If you have FusionReactor, can you take a look at Resrources > Threads and see how many threads are running on the JVM. Then go to Resources > Thread Activity and back up the chart to see what the trend of thread counts looks like.
This is just a guess, but it’s possible the single executor thread pools that Lucee uses for runasync aren’t being shutdown and de-allocated.
Actually, I just realized you had a code sample at the top. I tossed that in a 5.3.7 server I had running and hit refresh about 20 times. I wasn’t able to get an error, but I can see in FR that there are 1000 threads created for each request, and a portion of them say in the ‘waiting’ state for a few seconds before they are all removed. (The yellow parts of the graph) The code you have creates a pretty wasteful creation of threads I think, and depending on your memory spaces and JVM config, I could see a scenario in which calling that too often would create more threads than the JVM could handle.
Obviously, your code here is just for the sake of example, but I’d never use a pattern like that in real life. The cbstreams library for instance, allows you to loop async over a collection of any size, but with a fixed thread pool. Which is more less what the arrayEach in parallel will do as well.
I’d be interested in seeing what this graph looks like on your machine leading up to the error you reported. And I’ll re-iterate-- the Lucee runAsync() BIF creates a NEW single executor thread pool for every execution! I don’t think it’s architected very well for any sort of regular usage IMO.
I was just curious to see if there was any substantive performance difference between using parallel iteration and runAsync() … and I figured the easiest way to test would be to create a scenario in which there was likely more requests than threads, to see which ones can drain and complete first.
To your point, I would never actually do this in “real life”