Подтвердить что ты не робот

Почему я не могу отключить собственный ExecutorService под SecurityManager?

В соответствии с менеджером безопасности по умолчанию, если я создаю ExecutorService (ThreadPoolExecutor в этом случае), я не могу его закрыть, shutdown() просто вызывает checkPermission("modifyThread") и, таким образом, сразу же умирает:

import java.util.concurrent.*;

class A {
    public static void main( String[] args) {
        Thread ct = Thread.currentThread();
        System.out.println("current thread: " + ct);
        ct.checkAccess(); // we have access to our own thread...
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(
            1, // one core thread
            1, // doesn't matter because queue is unbounded
            0, TimeUnit.SECONDS, // doesn't matter in this case
            new LinkedBlockingQueue<Runnable>(), /* unbound queue for
                                                  * our single thread */
            new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    // obviously never gets called as we don't add any work
                    System.out.println("making thread");
                    return new Thread(r);
                }
            }
        );
        tpe.shutdown(); // raises security exception
    }
}

Sun JDK:

$java -Djava.security.manager A текущий поток: Thread [main, 5, main] Исключение в потоке "main" java.security.AccessControlException: доступ запрещен (java.lang.RuntimePermission modifyThread)   в java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)   в java.security.AccessController.checkPermission(AccessController.java:546)   в java.lang.SecurityManager.checkPermission(SecurityManager.java:532)   в java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1094)   в A.main(A.java:22)

OpenJDK:

$java -Djava.security.manager A текущий поток: Thread [main, 5, main] Исключение в потоке "main" java.security.AccessControlException: доступ запрещен (java.lang.RuntimePermission modifyThread)       в java.security.AccessControlContext.checkPermission(AccessControlContext.java:342)       в java.security.AccessController.checkPermission(AccessController.java:553)       в java.lang.SecurityManager.checkPermission(SecurityManager.java:549)       в java.util.concurrent.ThreadPoolExecutor.checkShutdownAccess(ThreadPoolExecutor.java:711)       в java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1351)       в A.main(A.java:22)

Почему??????? Что являются последствиями безопасности создания пула потоков, который управляет только вам и отключает его? Это ошибка в реализациях, или я что-то не хватает?

Посмотрим, что спецификация для ExecutorService.shutdown говорит...

Инициирует упорядоченное завершение работы, в котором выполняются ранее поставленные задачи, но новые задачи не будут приняты. Вызов не имеет дополнительного эффекта, если он уже выключен.

Броски: SecurityException - если диспетчер безопасности существует и завершение работы этого ExecutorService может работать с потоками, которые вызывающему не разрешено изменять, поскольку он не поддерживает RuntimePermission ( "modifyThread" ) или метод проверки безопасности проверяет доступ.

Это... примерно так же неопределенно, как и получается. Спектр говорит ничего о любых "потоках системы", которые производятся в течение жизненного цикла ExecutorService, и, кроме того, он позволяет вам создавать свои собственные темы, что является доказательством того, что быть не "системными потоками", когда вы это делаете. (Как сделано выше в моем примере)

Похоже, что разработчики Java SE увидели, что возможно shutdown поднять SecurityException, поэтому они были такими же: "О, хорошо, я просто добавлю случайную проверку безопасности на соответствие"...

Дело в том, что чтение через источник OpenJDK (openjdk-6-src-b20-21_jun_2010), оказывается, что поток только любой создан, это вызов вашего поставляемого ThreadFactory (который никогда не вызывается в тестовом файле, так как я не создаю никакой работы, и я не звоню prestartCoreThread или preStartAllCoreThreads). Таким образом, проверка безопасности выполняется без видимых причин в OpenJDK ThreadPoolExecutor (как это делается в sun-jdk-1.6, но у меня нет источника):

/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * @throws SecurityException {@inheritDoc}
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

checkShutdownAccess вызывается перед тем, как что-либо сделать...

/**
 * If there is a security manager, makes sure caller has
 * permission to shut down threads in general (see shutdownPerm).
 * If this passes, additionally makes sure the caller is allowed
 * to interrupt each worker thread. This might not be true even if
 * first check passed, if the SecurityManager treats some threads
 * specially.
 */
private void checkShutdownAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkPermission(shutdownPerm);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                security.checkAccess(w.thread);
        } finally {
            mainLock.unlock();
        }
    }
}

Как вы можете видеть, он безоговорочно вызывает checkPermission(shutdownPerm) в диспетчере безопасности. shutdownPerm определяется как...   закрытый статический окончательный RuntimePermission shutdownPerm =       новый RuntimePermission ( "modifyThread" );

... что совершенно не имеет смысла, насколько я могу сказать, потому что modifyThread подразумевает доступ к системным потокам, а там нет нити системы в игре здесь на самом деле, нет нити, потому что я не отправлял никаких работ или предварований, и даже если бы были потоки, они были бы моими потоками, потому что я прошел через ThreadFactory. Спецификация ничего не говорит о магическом умирании, кроме этого, если задействованы системные потоки (они отсутствуют), может быть SecurityException.

В принципе, почему я не могу просто удалить строку, которая проверяет доступ к системным потокам? Я не вижу никаких последствий для безопасности, требующих этого. И как никто другой не сталкивался с этой проблемой??? Я видел сообщение о проблеме, где они "разрешили" эту проблему, изменив вызов на shutdownNow на shutdown, очевидно, что это не исправило их для них.

4b9b3361

Ответ 1

Это довольно просто: вы не можете сделать это в группе основного потока. Он частично разработан для апплетов. Почему? Вы можете свободно использовать PrivilegedAction для выключения вызова, если это проблема. Имейте в виду, что Thread.interrupt() как невинный он может выглядеть также throws SecurityException.

Чтобы ответить на вопрос: просто убедитесь, что вы предоставили свой собственный код разрешениям, и вы счастливы. Альтернативно, "modifyThread" также может быть предоставлен свободно, он используется в основном апплетами.

Как и для ненадежного кода: ну, ненадежный код даже не должен обрабатывать w/threads вне его ThreadGroup, поэтому предоставите API для создания ThreadPool и разрешите выключение для такого, созданного вызывающим. Вы можете предоставить разрешение на основе вызывающего абонента.

Надеюсь, это немного помогло (количество вопросительных знаков ясно показывает отчаяние и крайнее раздражение)

    /*
     * Conceptually, shutdown is just a matter of changing the
     * runState to SHUTDOWN, and then interrupting any worker
     * threads that might be blocked in getTask() to wake them up
     * so they can exit. Then, if there happen not to be any
     * threads or tasks, we can directly terminate pool via
     * tryTerminate.  Else, the last worker to leave the building
     * turns off the lights (in workerDone).
     *
     * But this is made more delicate because we must cooperate
     * with the security manager (if present), which may implement
     * policies that make more sense for operations on Threads
     * than they do for ThreadPools. This requires 3 steps:
     *
     * 1. Making sure caller has permission to shut down threads
     * in general (see shutdownPerm).
     *
     * 2. If (1) passes, making sure the caller is allowed to
     * modify each of our threads. This might not be true even if
     * first check passed, if the SecurityManager treats some
     * threads specially. If this check passes, then we can try
     * to set runState.
     *
     * 3. If both (1) and (2) pass, dealing with inconsistent
     * security managers that allow checkAccess but then throw a
     * SecurityException when interrupt() is invoked.  In this
     * third case, because we have already set runState, we can
     * only try to back out from the shutdown as cleanly as
     * possible. Some workers may have been killed but we remain
     * in non-shutdown state (which may entail tryTerminate from
     * workerDone starting a new worker to maintain liveness.)
     */

Ответ 2

Звучит как ленивая и/или безопасная реализация. Вместо того, чтобы проверять, задействованы ли другие потоки, это просто предполагает, что некоторые из них. Лучше бросить исключение безопасности, а не оставить потенциальную дыру в безопасности.