Is it possible that a FOR loop is split across threads?... just wondering

Hi, I have a very random question. Is it possible that for some reason, even tough I have not written any code to specify it, my application uses 2 or even possibly more concurrent threads to process a FOR loop? I have a case here where I’ve been scratching my head for a couple hours trying to understand how this can be. I have a static list (exact same values every run) and I read it in a FOR loop (to process scenegraph stuff based on this list) and every time I run my app, I get the same numbers but each pair of 2 or sometimes 3 sequential numbers are switched together. Here’s a screenshot to visually explain what comes out of the FOR loop:

Some runs, all the 2000 integers are exactly the same. Some runs they’re switched in pairs like this. All the integers are the same throughout the whole list and on every run (comes from a static list), they’re just not in the same order. WTF? I always thought FOR loops are procedural and straightforward, how can this be? If it’s new that a CPU can split its execution accross different threads, how can I disable this by code?

Any idea or thoughts are welcome :stuck_out_tongue:

Thx for reading.

Your assessment of the problem or your diagnostics of it must be incorrect. A for loop is always single threaded.

Print the thread (Thread.currentThread()) as part of your output and you will prove it. If you see two threads in your output then you know it’s because you are running two threads and your loops are running at the same time.

Most likely, your list isn’t as static as you think it is. I don’t know how it’s built… I can’t even tell if both pictures are from the same program execution.

what kind of for loop is this?

if your iterating over a list while modifying it, you can get weird results if you wrongly assume nothing weird can happen.

Thank you guys. I was afraid FOR loops changed since 15 years ago when I learned C++ lol… well, that’s pretty weird.

Paul, I printed the thread inside the FOR loop and on every index I’m getting the same thread. So I’m probably left with something I overlooked like variable re-using maybe?

I’ll have to check every single variable part of this FOR loop, I’ll BRB.

OK… phew… there were a RANDOM MIX of 3 different exceptions not bubbling up (NullException, ConcurrentModificationException and NoSuchElementException) all bound to a single line which is: layer.collideWith(ray, results);

I have OBVIOUSLY made sure that layer, ray and results all are not null and even in the catch() I can always print their contents and they’re never null. The only thing I’m asking myself is: I’m doing the same thing earlier in that same FOR loop with another node and it’s working all the time (never throws an exception at all) AND the ray variable is the same, I’m doing this: Ray ray = new Ray(child.getLocalTranslation(), new Vector3f(0, -1, 0));

Could it be that raycasting a second time using the same ray variable can cause weird random exceptions like this? It’s pretty random. Is there like a caching feature in this function or something that would prevent it from going again a second time? If I comment out this line, the app never crashes, I tried 10 times in a row just to be sure. It’s 100% reliable.

@.Ben. said: Could it be that raycasting a second time using the same ray variable can cause weird random exceptions like this? It's pretty random. Is there like a caching feature in this function or something that would prevent it from going again a second time? If I comment out this line, the app never crashes, I tried 10 times in a row just to be sure. It's 100% reliable.

Can’t say 100% for sure, it might be. Can you make a small test case where this problem happens?

The last time we went down this particular rabbit hole, it turned out that you were doing collisions from multiple threads on the same scene graph objects or something. So let’s just rule that out first.

The ONLY way you get concurrent modification exceptions is if you are concurrently modifying something. Either you are modifying that data structure from inside the loop or you are modifying it from separate threads. Those are the only two options really. Since we can’t see the exception stack traces, anything we tell you us just frustrated guessing.

Edit: also if you are in a situation where exceptions are not getting seen then you need to fix that immediately. Either you are using a thread pool (see above with angry eyes) or you are swallowing exceptions somewhere. The latter of which is enough to get you physically abused in some dev shops.

Hi Normen. I’d like to do that, but I have over (no joke) 8000 lines of code implied in this project and I’m feeling like I left some extremely obvious detail behind and by making a smaller test case, I fear I’ll make the issue disappear. It’s getting so big so quickly…

Speaking of which, as Paul mentioned (Hi again Paul, sorry for frustating you! Really, I didn’t mean to haha… OK I’ll try to explain and put some code for you guys to actually understand what I’m up to, BTW I’m happily surprised you remember the last question you helped me with :D) I found out after over an hour of close looking at the code that THERE IS INDEED TWO CONCURRENT THREADS going on but they’re so fast (?) that when I print them, it never seems like they cross each other (execute at the same time). The exception happens even tough all the println()'s are always citing the same thread name (thread-1 for instance). BUT come to think about it again, it’s honestly stupid of me to have thought multithreading was not part of it. It’s now obvious it’s part of the problem, because of one of the implied exceptions name.

So that’s on the table. THERE IS a concurrent modification going on in a second thread but not always AND YES PAUL, now I have put TRY CATCH every god damn place, because yes, it was swallowing all the exceptions, that’s why I was lost at the beginning of this post, now I see all exceptions)… and it happens so fast that sometimes it’s not doing it (probably because the other thread is not messing with this variable at this point in time) it’s pretty much random, altough ALL THE DATA IS STATIC and always exactly the same on all runs. It’s in a text file, it’s not randomly generated.

OK so let’s skip to the actual thing:

What I don’t understand is how can a function throw a concurrent modification exception when that function actually creates all its used variables? I mean, let’s say 2 threads are calling the same function in a Future pattern exactly like mine:

[java]
private List<Future<Node>> arr_threads = new ArrayList<>();

            arr_threads.add(0, threadpool.submit(
            new Callable&lt;Node&gt;(){
                    @Override
                    public Node call() throws Exception {
                        try{
                            Node n = get_item(new Vector3f(locX, 0, locZ), true, new Vector3f(entering_side_x, 0, entering_side_z));
                            return n;
                        }
                        catch(Exception e){
                            return null;
                        }
                    }
                }
            ));

[/java]

…all is 100% well if I comment out the collideWith() function inside the get_item() function. By following the stack, it throws the concurrent modification exception while sorting the collideWith() results variable. But that variable is created inside the get_item() function, so how can it be shared among threaded calls? I’m pinpointing the problem but I don’t understand how this is possible and what’s the real fix to have collideWith() working on threaded calls.

Basically, inside the get_item() function, I create a Node and I attach lots of spatials on it and then I do some ray tracing to align them and it crashes there SOMETIMES and sometimes it’s fine and also like I said if I comment this part out, the WHOLE rest of that function (+500 lines of code) with a bunch of manipulations on spatials, materials and stuff all works PERFECTLY. Basically, the collideWith() function throws a ConcurrentException or sometimes a NoSuchElementException and even sometimes NULL exception even tough none of the variables listed here-after are NEVER null. So I’m 100% sure that results is RE-USED across all the running threads, but it doesn’t make sense to me, because I just instanciated right there from inside the get_item() function:

[java]
CollisionResults results = new CollisionResults();
Ray ray = new Ray(child.getLocalTranslation(), new Vector3f(0, -1, 0));
if(layer.getQuantity() > 0){
try{
layer.collideWith(ray, results);
}
catch(Exception e){
e.printStackTrace();
// ConcurrentException / NoSuchElementException… weird.
}
}
[/java]

Thank you so much for your support guys.

We can’t see the ConcurrentModifcationException so it’s hard for us to comment… and forgive me if I don’t fully trust your own analysis.

Also, is there are a reason you are using submit() and keeping the results? (This is why your exceptions weren’t showing up before because submit() requires you to check for them later.)

And just to prove the point, I’m also randomly even getting a IndexOutOfBoundsException on that same line, stating that the “results” variable is at INDEX:1 but SIZE:0 so this happens when it begins to sort the collideWith() results but midway, another thread resets this variable. It’s pretty obvious to me now. How can this be and how can I make absolutely sure instanciated variables from within the get_item() function (or any function actually for god’s sake!) remain alive only for the execution time and remain private to it at all time? (I’ve never seen variables from within a function actually be accessible and writable by another concurrent function call before… that seems pretty F*** up to me)

>> Also, is there are a reason you are using submit() and keeping the results?

Can you elaborate? I tried to follow the very LIMITED and FEW examples I found around here. If I’m doing it wrong, please advise me of what I could try instead?

Maybe this answers your question? : I’m using this code to know when the thread has finished processing (because sometimes it takes up to a second to do all those manipulations, that’s why I’m doing all those ray castings in a thread):

[java]
// Check for finished threads…
for(int i = 0; i < arr_threads.size(); i++) {
Future<Node> thread_out = arr_threads.get(i);

            if(thread_out != null &amp;&amp; thread_out.isDone()){
                try{
                    Node n_out = thread_out.get();
                    if(n_out != null){
                        // Do lots of stuff on the scene graph........
                    }
                }
                catch(InterruptedException | ExecutionException e){
                    e.printStackTrace();
                }
                
                // Remove thread
                arr_threads.remove(thread_out);
                
                // Breathe to next frame
                break;
            }
        }

[/java]

@.Ben. said: And just to prove the point, I'm also randomly even getting a IndexOutOfBoundsException on that same line, stating that the "results" variable is at INDEX:1 but SIZE:0 so this happens when it begins to sort the collideWith() results but midway, another thread resets this variable. It's pretty obvious to me now. How can this be and how can I make absolutely sure instanciated variables from within the get_item() function (or any function actually for god's sake!) remain alive only for the execution time and remain private to it at all time? (I've never seen variables from within a function actually be accessible and writable by another concurrent function call before... that seems pretty F*** up to me)

Well, clearly something is sharing something. BUT WE CAN"T SEE THE EXCEPTION HERE SO WE CAN"T VERIFY ANYTHING. Last time I’m going to say it. Stack trace or GTFO.

As to the other, if your callable is throwing exceptions then they should be thrown when you do Future.get() as an ExecutionException. Your catch() in that list bit of code should have printed it.

1 Like

Just throwing out there that a List is not guaranteed to be returned in the order added to. If you need this… try LinkedList.

I didn’t reaqd the thread in full… but it sounded like you were wondering why some of the results come back in a different order. If so… the above statement is what you were looking for. If not… sorry… it’s late and I’m tired =)

@t0neg0d said: Just throwing out there that a List is not guaranteed to be returned in the order added to. If you need this... try LinkedList.

I didn’t reaqd the thread in full… but it sounded like you were wondering why some of the results come back in a different order. If so… the above statement is what you were looking for. If not… sorry… it’s late and I’m tired =)

List is guaranteed to return things in the order added. You might be thinking of Collection… but List has very specific semantics.

@pspeed said: List is guaranteed to return things in the order added. You might be thinking of Collection... but List has very specific semantics.

Erm… right… Collection… were is my bed?

EDIT: Lord help me… I wasn’t even thinking collection… I’m over here in HashMap land if anyone needs me.

Regarding executing code, catching random exceptions caused by wrong programming, swallowing them and then returning some semi-valid looking results… two horror stories:

  1. Somebody was trying to update some structures but was not able to get it synchronized properly without chance of deadlocks. Instead, he put try/catch (ConcurrentModificationException) {Thread.sleep(70); try again}; Obviously data structure was already randomly half broken. This code is still there and handles billion dollar business.

  2. I was getting crazy by running some Swing code and having rendering fail in strange ways. It looked like some kind of NPE in event thread, but nothing was printed to the console. It turned out that one of the libraries provided by the company (non-UI, kind of database driver) was so buggy that it was throwing exceptions left and right. Original developer, when pressed for delivery, registered global exception handler which was just swallowing exceptions - not only in his code, but in entire application using his small library - and claimed success. Unfortunately, he left company before I learned about that, so I couldn’t fire him :wink:

Regarding the actual subject - don’t swallow exceptions. Don’t handle them. They are there for a reason. You might want to do some serious defensive coding when you deploy into embedded space without possibility of easy updating, but I don’t think it is a good approach during development. Trace exceptions, understand them and fix code so they are not thrown. Having “this exception is thrown once per 10000 calls, let’s ignore it for now” is road to madness.

@.Ben. said: ...

hi,

we dont want to read a books of text in this forum, just put all stacktraces here with some code causing them, do not explain your intentions, just put code, exceptions and BRIEF explantation here of what it is doing

30 lines of exceptions stack traces + 20 lines of code around them are better than 10 lines of your text describing what happens

no stack traces, no code, no brief = no help, because it is frustrating and difficult to help

edit:
i think you can think differently, you have concurrent modification exception, so
you run new thread that do collidewith on scenegraph that is in different thread, then you are doing it wrong. dont need to continue, beginning is wrong

a. data cannot be static if concurrent modification exception occurs, that means modification, that means dynamic data

in my opinion correct form could be:

  1. get all geometries you need to check for collision and put them into whatever list, while leaving them in scenegraph untouched
  2. give the linked list to another thread, so you iterating linked list and every iteration you run collideWith on geometry, even if geometry is detached in main thread, you can safely collide with it and no null exception occurs
  3. if you want to remove item from scenegraph from another thread, use app.enqueue() and no concurrent modification exception occurs

just suggestion, and i will be very frustrated if it was written in vain

Hi all, I’m SORRY @pspeed I didn’t pick up you wanted the stack trace yesterday, I was VERY tired after fiddling with this non stop for like 5 hours. Here it is for 1 run, but there are 4 different exceptions and it’s always random but as you can see I said, it’s always refering to the collision results, so keep in mind there are NULL, CONCURRENT, NO SUCH ELEMENT and INDEX OUT OF BOUNDS… it’s obvious “results” is getting modified from another thread.

[java]
java.util.NoSuchElementException
at java.util.ArrayList$Itr.next(ArrayList.java:834)
at java.util.Collections.sort(Collections.java:158)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:84)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame$3.call(Terrain.java:531)
at mygame$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at java.util.Collections.sort(Collections.java:158)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:84)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain$3.call(Terrain.java:531)
at mygame.Terrain$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
[/java]

This is what I got in the second run:

[java]
java.util.NoSuchElementException
at java.util.ArrayList$Itr.next(ArrayList.java:834)
at java.util.Collections.sort(Collections.java:158)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:84)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain.get_item(Terrain.java:110)
at mygame.Terrain$1.run(Terrain.java:351)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.lang.NullPointerException
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain$3.call(Terrain.java:531)
at mygame.Terrain$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$ListItr.set(ArrayList.java:900)
at java.util.Collections.sort(Collections.java:159)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:84)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain$3.call(Terrain.java:531)
at mygame.Terrain$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:635)
at java.util.ArrayList.get(ArrayList.java:411)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:88)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain$3.call(Terrain.java:531)
at mygame.Terrain$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at java.util.Collections.sort(Collections.java:158)
at com.jme3.collision.CollisionResults.getClosestCollision(CollisionResults.java:84)
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:404)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:456)
at com.jme3.scene.Mesh.collideWith(Mesh.java:917)
at com.jme3.scene.Geometry.collideWith(Geometry.java:408)
at com.jme3.scene.Node.collideWith(Node.java:494)
at com.jme3.scene.Node.collideWith(Node.java:494)
at mygame.Terrain.get_item(Terrain.java:539)
at mygame.Terrain$3.call(Terrain.java:531)
at mygame.Terrain$3.call(Terrain.java:523)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
[/java]

I have to attend to a meeting, I’ll be back in 2-3 hours, I’ll read other people’s comments too, I am very pleased you guys are trying to help me. Thank you so much for your time :slight_smile:

TTYL.

@.Ben.
What’s your end goal with this? (If I missed that in the posts sorry) Might be easier to help if people knew what you were trying to accomplish.

Specifically, what’s the original list for? (Did you ever specify the data type used, how you add to and how you are retrieving the info… or is this a moot point now?)
What’s the ray casting and then collideWith all about?

To me it looks like you are setting up your task to run over and over again? (Otherwise why use the scheduled thread pool executor)

…then I guess it’s possible then that the instance starts running before the previous one has been completed.

Whatever the case, more than one thread is hitting the same CollisionResults which is a huge no-no. Actually, given the teeny little code snippets provided I can’t see how that happens even if the same instance is being rerun.

There is something that we can’t see that is the issue.

And nope to all of that… and this is why the stack trace was important. (read: always include the stack trace)

You/we have been barking up the wrong tree and we could have figured that out right away from the stack trace (read again: always include the stack trace)

As stated in a previous thread, collisions against nodes are not thread safe. It’s not just the collision results but the internal collision data. BIHTree keeps an internal CollisionResults and that is the one getting the concurrent mod exceptions. So two different threads cannot call collideWith() on the same Geometry. Period.

You should really take a step back and look at the meta-problem, though. In general there should be no reason to do this level of pooled threading of collisions.

But anyway, it’s not your collision results that gets shared across threads. It’s the one internal to the Geometry’s collision data. If you want a pool of threads to hit against the scene graph then you will have to clone the scene graph and make sure the Geometry clones have their own copy of the internal collision data. I don’t think it’s worth it, personally. There are a dozen better ways to speed up collisions.