Почему я не могу отключить собственный 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
, очевидно, что это не исправило их для них.
Ответы
Ответ 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
Звучит как ленивая и/или безопасная реализация. Вместо того, чтобы проверять, задействованы ли другие потоки, это просто предполагает, что некоторые из них. Лучше бросить исключение безопасности, а не оставить потенциальную дыру в безопасности.