来自并行流中的 I/O 代码的 SecurityException [英] SecurityException from I/O code in a parallel stream

查看:35
本文介绍了来自并行流中的 I/O 代码的 SecurityException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个我没办法解释,但是我在别人的代码中发现了这个现象:

I have no way to explain this one, but I found this phenomenon in somebody else's code:

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.stream.Stream;

import org.junit.Test;

public class TestDidWeBreakJavaAgain
{
    @Test
    public void testIoInSerialStream()
    {
        doTest(false);
    }

    @Test
    public void testIoInParallelStream()
    {
        doTest(true);
    }

    private void doTest(boolean parallel)
    {
        Stream<String> stream = Stream.of("1", "2", "3");
        if (parallel)
        {
            stream = stream.parallel();
        }
        stream.forEach(name -> {
            try
            {
                Files.createTempFile(name, ".dat");
            }
            catch (IOException e)
            {
                throw new UncheckedIOException("Failed to create temp file", e);
            }
        });
    }
}

在启用安全管理器的情况下运行时,仅在流上调用 parallel() 或在从集合中获取流时调用 parallelStream() 似乎可以保证所有执行 I/O 的尝试都会抛出 SecurityException.(很可能,调用任何可以抛出SecurityException抛出的方法.)

When run with the security manager enabled, merely calling parallel() on a stream, or parallelStream() when getting the stream from a collection, seems to guarantee that all attempts to perform I/O will throw SecurityException. (Most likely, calling any method which can throw SecurityException, will throw.)

我了解 parallel() 意味着它将在另一个线程中运行,该线程可能与我们开始使用的权限不同,但我想我认为框架会小心对我们来说.

I understand that parallel() means that it will be running in another thread which might not have the same privileges as the one we started with, but I guess I thought that the framework would take care of that for us.

在整个代码库中删除对 parallel()parallelStream() 的调用可以避免风险.插入 AccessController.doPrivileged 也可以修复它,但对我来说听起来并不安全,至少在所有情况下都不是.还有其他选择吗?

Removing calls to parallel() or parallelStream() throughout the codebase avoids the risk. Inserting an AccessController.doPrivileged also fixes it, but doesn't sound safe to me, at least not in all situations. Is there any other option?

推荐答案

并行流执行将使用 Fork/Join 框架,更具体地说,它将使用 Fork/Join 公共池.这是一个实现细节,但正如在这种情况下观察到的那样,这些细节可能会以意想不到的方式泄露出去.

Parallel stream execution will use the Fork/Join framework, more specifically it will use the Fork/Join common pool. This is an implementation detail, but as observed in this case such details can leak out in unexpected ways.

请注意,使用 CompletableFuture 异步执行任务时也会发生相同的行为.

Note that the same behaviour can also occur when executing a task asynchronously using CompletableFuture.

当存在安全管理器时,Fork/Join 公共池的线程工厂被设置为创建无害线程的工厂.这样的 innocuous 线程没有授予它任何权限,不是任何已定义线程组的成员,并且在顶级 Fork/Join 任务完成执行后,所有线程本地(如果已创建)清除.这种行为可确保 Fork/Join 任务在共享公共池时彼此隔离.

When a security manager is present the thread factory of the Fork/Join common pool is set to a factory that creates innocuous threads. Such an innocuous thread has no permissions granted to it, is not a member of any defined thread group, and after a top-level Fork/Join task has completed its execution all thread locals (if created) are cleared. Such behaviour ensures Fork/Join tasks are isolated from each other when sharing the common pool.

这就是示例中抛出 SecurityException 的原因,可能是:

This is why in the example a SecurityException is thrown, probably:

java.lang.SecurityException: 无法创建临时文件或目录

java.lang.SecurityException: Unable to create temporary file or directory

有两种可能的解决方法.根据安全经理使用的原因,每个解决方法都可能增加不安全的风险.

There are two potential work arounds. Depending on the reasons a security manager utilized, each work around may increase the risk of being insecure.

第一个更通用的解决方法是通过系统属性注册 Fork/Join 线程工厂,以告诉 Fork/Join 框架公共池的默认线程工厂应该是什么.例如这里是一个非常简单的线程工厂:

The first, more general, work around is to register a Fork/Join thread factory via a system property to tell the Fork/Join framework what the default thread factory should be for the common pool. For example here is a really simple thread factory:

public class MyForkJoinWorkerThreadFactory
        implements ForkJoinPool.ForkJoinWorkerThreadFactory {
    public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return new ForkJoinWorkerThread(pool) {};
    }
}

可以使用以下系统属性注册:

Which can be registered with the following system property:

-Djava.util.concurrent.ForkJoinPool.common.threadFactory=MyForkJoinWorkerThreadFactory

-Djava.util.concurrent.ForkJoinPool.common.threadFactory=MyForkJoinWorkerThreadFactory

MyForkJoinWorkerThreadFactory 的行为目前等同于ForkJoinPool.defaultForkJoinWorkerThreadFactory.

The behaviour of MyForkJoinWorkerThreadFactory is currently equivalent to that of ForkJoinPool.defaultForkJoinWorkerThreadFactory.

第二个更具体的解决方法是创建一个新的 Fork/Join 池.在这种情况下,ForkJoinPool.defaultForkJoinWorkerThreadFactory 将用于不接受 ForkJoinWorkerThreadFactory 参数的构造函数.任何并行流执行都需要在从该池中执行的任务中执行.请注意,这是一个实现细节,在未来的版本中可能会或可能不会起作用.

The second, more specific, work around is to create a new Fork/Join pool. In this case the ForkJoinPool.defaultForkJoinWorkerThreadFactory will be utilized for constructors not accepting a ForkJoinWorkerThreadFactory argument. Any parallel stream execution would need to be performed from within a task executed from within that pool. Note that this is an implementation detail and may or may not work in future releases.

这篇关于来自并行流中的 I/O 代码的 SecurityException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆