Re: Battles with CountedCompleter

Shevek via Concurrency-interest <[email protected]> Tue, 29 Sep 2020 12:03:01 -0700
Newsgroups gmane.comp.java.jsr.166-concurrency
Message-ID <[email protected]>
On 9/29/20 2:03 AM, Romain Colle wrote:
> Hi Shevek,
> 
> It might help to perform the recursive split as a fork() within the 
> parent task code instead of using external submits.
> 
> Looking at ForkJoinPool.awaitJoin(), it seems that a waiting task only 
> tries to steal from queues at odd indexes (worker internal queues), 
> which makes sense since they are the only queues where they should be 
> able to find subtasks.
> Therefore a running task will never try to steal from a submission 
> queue, and the only way to achieve parallelism is for them to compensate 
> and park themselves.

I think this was indeed it, and an excellent explanation of why. We now 
have:

     private static boolean isCurrentForkJoinPool(@Nonnull Executor 
executor) {
         Thread t = Thread.currentThread();
         if (!(t instanceof ForkJoinWorkerThread))
             return false;
         return ((ForkJoinWorkerThread) t).getPool() == executor;
     }

     @Nonnull
     private <V, T extends ManagedAction<V>> T submit(@Nonnull T task) {
         joinTask.addToPendingCount(1);
         if (joinTask.getPendingCount() > callerRunsPendingCountThreshold) {
             // joinTask.helpComplete(2);
             task.invoke();
         } else if (isCurrentForkJoinPool(executor)) {
             task.fork(); // NEW CODE HERE
         } else {
             executor.execute(task);
         }
         return task;
     }

which seems to resolve the issue. I wasn't sure until now why, and I 
think you just reminded me of something I knew once in distant history, 
but forgot.

This is all in the interests of attempting to create backpressure within 
a heavily contended FJP - I've considered using an additional call to 
something like helpComplete(1) when we hit backpressure, to reduce 
maximum latency, but we're not latency critical, we're a throughput 
application. Is there any other advice?

Thank you.

S.

> For more details see:
> https://github.com/openjdk/jdk/blob/6bddeb709d1d263d0d753909cabce7e755e7e27d/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java#L356-L360
> https://github.com/openjdk/jdk/blob/6bddeb709d1d263d0d753909cabce7e755e7e27d/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java#L1699
> 
> Let me know if this helps.
> Thanks,
> Romain
> 
> 
> On Fri, Sep 25, 2020 at 2:04 AM Shevek via Concurrency-interest 
> <[email protected] 
> <mailto:[email protected]>> wrote:
> 
>     I have been doing battle with CountedCompleter, and I'm stuck at a
>     point
>     where I'm doing something like this:
> 
>     doInParallel(Iterable<thing> tasks) {
>         CountedCompleter joinTask = new CountedCompleter();
>         for (some unknown number of things)
>             pool.submit(new CountedCompleter(parent, ...));
>         joinTask.tryComplete();
>         joinTask.join();
>     }
> 
>     The objective is to have a recursively-safe construct like:
>     try (ForkJoinScope scope = new ForkJoinScope(pool, ...)) {
>           for (whatever)
>               scope.execute(task);
>     } // close() calls join()
> 
>     Any subtask may itself repeat this pattern. The trouble I'm having is
>     that sometimes I get a lot of threads blocked here:
> 
>     "ForkJoinPool-1-worker-6" #19 daemon prio=5 os_prio=0
>     tid=0x00007f39e5075800 nid=0xd0c in Object.wait() [0x00007f39515f7000]
>        java.lang.Thread.State: WAITING (on object monitor)
>     at java.lang.Object.wait(Native Method)
>     at
>     java.util.concurrent.ForkJoinTask.internalWait(ForkJoinTask.java:311)
>               - locked <0x0000000477c78528> (a
>     org.compilerworks.common.util.concurrent.ForkJoinScope$JoinTask)
>     at java.util.concurrent.ForkJoinPool.awaitJoin(ForkJoinPool.java:2058)
>             at
>     java.util.concurrent.ForkJoinTask.doJoin(ForkJoinTask.java:390)
>     at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:719)
> 
>     What I can't work out is why these blocked threads aren't helping? 6 of
>     the threads in my 12-thread pool are blocked, and 6 are working.
>     Eventually, every so often, one of them seems to unblock. I'm trying to
>     trace the logic in the code to work out why the blocked threads don't
>     simply steal other work and do it. I've tried unit testing my
>     wrapper/controller code twenty ways up and it doesn't block in tests,
>     but it fails in application.
> 
>     Inspection of the heap of a blocked task shows (for example)
> 
>     * joinTask.pending=32
>     * 33 ForkJoinTask instances have a pointer to this joinTask as the
>     completer.
>     * joinTask and its children are in the correct ForkJoinPool
>     * Java 1.8.0_252
> 
>     Can anybody please help? I'm happy to submit exact code as there are a
>     couple of nuances to what I'm doing which might be relevant.
> 
>     I'm willing to be polite, but not effusive about the documentation of
>     CountedCompleter and similar, so it's entirely possible that I'm using
>     an API wrong.
> 
>     Code is attached.
> 
>     Thank you.
> 
>     S.
>     _______________________________________________
>     Concurrency-interest mailing list
>     [email protected]
>     <mailto:[email protected]>
>     http://cs.oswego.edu/mailman/listinfo/concurrency-interest
> 
_______________________________________________
Concurrency-interest mailing list
[email protected]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest