8026155: Enhance ForkJoin pool
authordl
Thu, 19 Dec 2013 10:31:59 +0000
changeset 22070 9a4f43c9b15a
parent 22010 a1ee9743f4ee
child 22071 8e1399f55471
8026155: Enhance ForkJoin pool Reviewed-by: chegar, alanb, ahgross
jdk/src/share/classes/java/util/concurrent/ForkJoinPool.java
jdk/src/share/classes/java/util/concurrent/ForkJoinWorkerThread.java
--- a/jdk/src/share/classes/java/util/concurrent/ForkJoinPool.java	Wed Jul 05 19:25:40 2017 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/ForkJoinPool.java	Thu Dec 19 10:31:59 2013 +0000
@@ -49,6 +49,9 @@
 import java.util.concurrent.RunnableFuture;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+import java.security.Permissions;
 
 /**
  * An {@link ExecutorService} for running {@link ForkJoinTask}s.
@@ -140,6 +143,9 @@
  * <li>{@code java.util.concurrent.ForkJoinPool.common.exceptionHandler}
  * - the class name of a {@link UncaughtExceptionHandler}
  * </ul>
+ * If a {@link SecurityManager} is present and no factory is
+ * specified, then the default pool uses a factory supplying
+ * threads that have no {@link Permissions} enabled.
  * The system class loader is used to load these classes.
  * Upon any error in establishing these settings, default parameters
  * are used. It is possible to disable or limit the use of threads in
@@ -501,6 +507,16 @@
      * task status checks) in inapplicable cases amounts to an odd
      * form of limited spin-wait before blocking in ForkJoinTask.join.
      *
+     * As a more appropriate default in managed environments, unless
+     * overridden by system properties, we use workers of subclass
+     * InnocuousForkJoinWorkerThread when there is a SecurityManager
+     * present. These workers have no permissions set, do not belong
+     * to any user-defined ThreadGroup, and erase all ThreadLocals
+     * after executing any top-level task (see WorkQueue.runTask). The
+     * associated mechanics (mainly in ForkJoinWorkerThread) may be
+     * JVM-dependent and must access particular Thread class fields to
+     * achieve this effect.
+     *
      * Style notes
      * ===========
      *
@@ -882,6 +898,7 @@
          */
         final void runTask(ForkJoinTask<?> task) {
             if ((currentSteal = task) != null) {
+                ForkJoinWorkerThread thread;
                 task.doExec();
                 ForkJoinTask<?>[] a = array;
                 int md = mode;
@@ -899,6 +916,8 @@
                         t.doExec();
                     }
                 }
+                if ((thread = owner) != null) // no need to do in finally clause
+                    thread.afterTopLevelExec();
             }
         }
 
@@ -1155,7 +1174,7 @@
      * Increment for seed generators. See class ThreadLocal for
      * explanation.
      */
-    private static final int SEED_INCREMENT = 0x61c88647;
+    private static final int SEED_INCREMENT = 0x9e3779b9;
 
     /*
      * Bits and masks for control variables
@@ -2084,12 +2103,10 @@
                                   ((c & ~AC_MASK) |
                                    ((c & AC_MASK) + AC_UNIT))));
                 }
-                if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) {
-                    (w.currentSteal = t).doExec();
-                    w.currentSteal = ps;
-                }
+                if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null)
+                    w.runTask(t);
             }
-            else if (active) {       // decrement active count without queuing
+            else if (active) {      // decrement active count without queuing
                 long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT);
                 if ((int)(nc >> AC_SHIFT) + parallelism == 0)
                     break;          // bypass decrement-then-increment
@@ -3282,8 +3299,7 @@
      */
     private static ForkJoinPool makeCommonPool() {
         int parallelism = -1;
-        ForkJoinWorkerThreadFactory factory
-            = defaultForkJoinWorkerThreadFactory;
+        ForkJoinWorkerThreadFactory factory = null;
         UncaughtExceptionHandler handler = null;
         try {  // ignore exceptions in accessing/parsing properties
             String pp = System.getProperty
@@ -3302,7 +3318,12 @@
                            getSystemClassLoader().loadClass(hp).newInstance());
         } catch (Exception ignore) {
         }
-
+        if (factory == null) {
+            if (System.getSecurityManager() == null)
+                factory = defaultForkJoinWorkerThreadFactory;
+            else // use security-managed default
+                factory = new InnocuousForkJoinWorkerThreadFactory();
+        }
         if (parallelism < 0 && // default 1 less than #cores
             (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
             parallelism = 1;
@@ -3312,4 +3333,38 @@
                                 "ForkJoinPool.commonPool-worker-");
     }
 
+    /**
+     * Factory for innocuous worker threads
+     */
+    static final class InnocuousForkJoinWorkerThreadFactory
+        implements ForkJoinWorkerThreadFactory {
+
+        /**
+         * An ACC to restrict permissions for the factory itself.
+         * The constructed workers have no permissions set.
+         */
+        private static final AccessControlContext innocuousAcc;
+        static {
+            Permissions innocuousPerms = new Permissions();
+            innocuousPerms.add(modifyThreadPermission);
+            innocuousPerms.add(new RuntimePermission(
+                                   "enableContextClassLoaderOverride"));
+            innocuousPerms.add(new RuntimePermission(
+                                   "modifyThreadGroup"));
+            innocuousAcc = new AccessControlContext(new ProtectionDomain[] {
+                    new ProtectionDomain(null, innocuousPerms)
+                });
+        }
+
+        public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
+            return (ForkJoinWorkerThread.InnocuousForkJoinWorkerThread)
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedAction<ForkJoinWorkerThread>() {
+                    public ForkJoinWorkerThread run() {
+                        return new ForkJoinWorkerThread.
+                            InnocuousForkJoinWorkerThread(pool);
+                    }}, innocuousAcc);
+        }
+    }
+
 }
--- a/jdk/src/share/classes/java/util/concurrent/ForkJoinWorkerThread.java	Wed Jul 05 19:25:40 2017 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/ForkJoinWorkerThread.java	Thu Dec 19 10:31:59 2013 +0000
@@ -35,6 +35,9 @@
 
 package java.util.concurrent;
 
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+
 /**
  * A thread managed by a {@link ForkJoinPool}, which executes
  * {@link ForkJoinTask}s.
@@ -61,6 +64,10 @@
      * completes. This leads to a visibility race, that is tolerated
      * by requiring that the workQueue field is only accessed by the
      * owning thread.
+     *
+     * Support for (non-public) subclass InnocuousForkJoinWorkerThread
+     * requires that we break quite a lot of encapulation (via Unsafe)
+     * both here and in the subclass to access and set Thread fields.
      */
 
     final ForkJoinPool pool;                // the pool this thread works in
@@ -80,6 +87,18 @@
     }
 
     /**
+     * Version for InnocuousForkJoinWorkerThread
+     */
+    ForkJoinWorkerThread(ForkJoinPool pool, ThreadGroup threadGroup,
+                         AccessControlContext acc) {
+        super(threadGroup, null, "aForkJoinWorkerThread");
+        U.putOrderedObject(this, INHERITEDACCESSCONTROLCONTEXT, acc);
+        eraseThreadLocals(); // clear before registering
+        this.pool = pool;
+        this.workQueue = pool.registerWorker(this);
+    }
+
+    /**
      * Returns the pool hosting this thread.
      *
      * @return the pool
@@ -131,21 +150,128 @@
      * {@link ForkJoinTask}s.
      */
     public void run() {
-        Throwable exception = null;
-        try {
-            onStart();
-            pool.runWorker(workQueue);
-        } catch (Throwable ex) {
-            exception = ex;
-        } finally {
+        if (workQueue.array == null) { // only run once
+            Throwable exception = null;
             try {
-                onTermination(exception);
+                onStart();
+                pool.runWorker(workQueue);
             } catch (Throwable ex) {
-                if (exception == null)
-                    exception = ex;
+                exception = ex;
             } finally {
-                pool.deregisterWorker(this, exception);
+                try {
+                    onTermination(exception);
+                } catch (Throwable ex) {
+                    if (exception == null)
+                        exception = ex;
+                } finally {
+                    pool.deregisterWorker(this, exception);
+                }
             }
         }
     }
+
+    /**
+     * Erases ThreadLocals by nulling out Thread maps
+     */
+    final void eraseThreadLocals() {
+        U.putObject(this, THREADLOCALS, null);
+        U.putObject(this, INHERITABLETHREADLOCALS, null);
+    }
+
+    /**
+     * Non-public hook method for InnocuousForkJoinWorkerThread
+     */
+    void afterTopLevelExec() {
+    }
+
+    // Set up to allow setting thread fields in constructor
+    private static final sun.misc.Unsafe U;
+    private static final long THREADLOCALS;
+    private static final long INHERITABLETHREADLOCALS;
+    private static final long INHERITEDACCESSCONTROLCONTEXT;
+    static {
+        try {
+            U = sun.misc.Unsafe.getUnsafe();
+            Class<?> tk = Thread.class;
+            THREADLOCALS = U.objectFieldOffset
+                (tk.getDeclaredField("threadLocals"));
+            INHERITABLETHREADLOCALS = U.objectFieldOffset
+                (tk.getDeclaredField("inheritableThreadLocals"));
+            INHERITEDACCESSCONTROLCONTEXT = U.objectFieldOffset
+                (tk.getDeclaredField("inheritedAccessControlContext"));
+
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    /**
+     * A worker thread that has no permissions, is not a member of any
+     * user-defined ThreadGroup, and erases all ThreadLocals after
+     * running each top-level task.
+     */
+    static final class InnocuousForkJoinWorkerThread extends ForkJoinWorkerThread {
+        /** The ThreadGroup for all InnocuousForkJoinWorkerThreads */
+        private static final ThreadGroup innocuousThreadGroup =
+            createThreadGroup();
+
+        /** An AccessControlContext supporting no privileges */
+        private static final AccessControlContext INNOCUOUS_ACC =
+            new AccessControlContext(
+                new ProtectionDomain[] {
+                    new ProtectionDomain(null, null)
+                });
+
+        InnocuousForkJoinWorkerThread(ForkJoinPool pool) {
+            super(pool, innocuousThreadGroup, INNOCUOUS_ACC);
+        }
+
+        @Override // to erase ThreadLocals
+        void afterTopLevelExec() {
+            eraseThreadLocals();
+        }
+
+        @Override // to always report system loader
+        public ClassLoader getContextClassLoader() {
+            return ClassLoader.getSystemClassLoader();
+        }
+
+        @Override // to silently fail
+        public void setUncaughtExceptionHandler(UncaughtExceptionHandler x) { }
+
+        @Override // paranoically
+        public void setContextClassLoader(ClassLoader cl) {
+            throw new SecurityException("setContextClassLoader");
+        }
+
+        /**
+         * Returns a new group with the system ThreadGroup (the
+         * topmost, parentless group) as parent.  Uses Unsafe to
+         * traverse Thread group and ThreadGroup parent fields.
+         */
+        private static ThreadGroup createThreadGroup() {
+            try {
+                sun.misc.Unsafe u = sun.misc.Unsafe.getUnsafe();
+                Class<?> tk = Thread.class;
+                Class<?> gk = ThreadGroup.class;
+                long tg = u.objectFieldOffset(tk.getDeclaredField("group"));
+                long gp = u.objectFieldOffset(gk.getDeclaredField("parent"));
+                ThreadGroup group = (ThreadGroup)
+                    u.getObject(Thread.currentThread(), tg);
+                while (group != null) {
+                    ThreadGroup parent = (ThreadGroup)u.getObject(group, gp);
+                    if (parent == null)
+                        return new ThreadGroup(group,
+                                               "InnocuousForkJoinWorkerThreadGroup");
+                    group = parent;
+                }
+            } catch (Exception e) {
+                throw new Error(e);
+            }
+            // fall through if null as cannot-happen safeguard
+            throw new Error("Cannot create ThreadGroup");
+        }
+    }
+
 }
+