7

tôi không có cách nào để giải thích thế này, nhưng tôi thấy hiện tượng này trong mã của người khác:SecurityException từ I/O mã trong một dòng song song

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); 
      } 
     }); 
    } 
} 

Khi chạy với người quản lý an ninh được kích hoạt, chỉ đơn thuần gọi parallel() trên luồng hoặc parallelStream() khi nhận luồng từ bộ sưu tập, dường như đảm bảo rằng tất cả các nỗ lực thực hiện I/O sẽ ném SecurityException. (Nhiều khả năng, kêu gọi bất kỳ phương pháp mà thể ném SecurityException, sẽ ném.)

Tôi hiểu rằng parallel() có nghĩa là nó sẽ được chạy trong thread khác mà có thể không có đặc quyền tương tự như những gì chúng tôi bắt đầu với , nhưng tôi đoán tôi nghĩ rằng khuôn khổ sẽ chăm sóc điều đó cho chúng tôi.

Xóa cuộc gọi tới parallel() hoặc parallelStream() trong suốt quá trình mã hóa tránh rủi ro. Chèn AccessController.doPrivileged cũng sửa lỗi, nhưng không an toàn với tôi, ít nhất là không phải trong mọi tình huống. Có lựa chọn nào khác?

+2

Vui lòng cung cấp dấu vết ngăn xếp của ngoại lệ bạn nhận được –

+2

Đồng thời, vui lòng thêm mã 'SecurityManager' để chúng tôi có thể tái tạo chính xác vấn đề của bạn. –

+1

Điều này có thể thực hiện với các luồng song song bằng cách sử dụng chung pool join. Làm thế nào nó hoạt động [nếu bạn cung cấp hồ bơi của riêng bạn] (http://stackoverflow.com/a/22269778/829571)? – assylias

Trả lời

6

Thực thi luồng song song sẽ sử dụng khung Fork/Join, cụ thể hơn là nó sẽ sử dụng Fork/Join common pool. Đây là một chi tiết thực hiện, nhưng như được quan sát trong trường hợp này các chi tiết như vậy có thể bị rò rỉ theo những cách bất ngờ.

Lưu ý rằng hành vi tương tự cũng có thể xảy ra khi thực hiện tác vụ không đồng bộ bằng cách sử dụng CompletableFuture.

Khi trình quản lý bảo mật có mặt, nhà máy luồng của hồ bơi chung Fork/Join được đặt thành nhà máy tạo ra chủ đề vô thưởng vô phạt. Chủ đề vô thưởng vô phạt không có quyền được cấp cho nó, không phải là thành viên của bất kỳ nhóm chủ đề nào được xác định và sau khi nhiệm vụ Fork/Join cấp cao nhất đã hoàn thành việc thực hiện tất cả các địa chỉ luồng (nếu được tạo). Hành vi như vậy đảm bảo các nhiệm vụ Fork/Join được tách biệt với nhau khi chia sẻ chung nhóm.

Đây là lý do tại sao trong ví dụ một SecurityException được ném, có lẽ:

java.lang.SecurityException: Không thể tạo tập tin tạm thời hoặc thư mục

Có hai quanh công việc tiềm năng. Tùy thuộc vào lý do mà người quản lý bảo mật sử dụng, mỗi công việc xung quanh có thể làm tăng nguy cơ không an toàn.

Công việc đầu tiên, tổng quát hơn là đăng ký nhà máy Thread/Join thông qua thuộc tính hệ thống để nói cho khung Fork/Join biết nhà máy luồng mặc định sẽ là gì cho nhóm chung. Ví dụ ở đây là một nhà máy đề thực sự đơn giản:

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

Mà có thể được đăng ký với sở hữu hệ thống sau:

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

Hành vi của MyForkJoinWorkerThreadFactory hiện tại tương đương với ForkJoinPool.defaultForkJoinWorkerThreadFactory.

Công việc thứ hai, cụ thể hơn, xung quanh là tạo một nhóm Fork/Join mới. Trong trường hợp này, ForkJoinPool.defaultForkJoinWorkerThreadFactory sẽ được sử dụng cho các nhà thầu không chấp nhận đối số ForkJoinWorkerThreadFactory. Bất kỳ thực thi luồng song song nào cũng sẽ cần được thực hiện từ bên trong một tác vụ được thực hiện từ bên trong nhóm đó. Lưu ý rằng đây là chi tiết triển khai và có thể có hoặc không hoạt động trong các bản phát hành trong tương lai.

+0

Câu trả lời hay. Cũng lưu ý rằng chuỗi gửi có thể được sử dụng làm chuỗi công nhân trong khung F/J có thể có tác động ở đây: http://coopsoft.com/ar/Calamity2Article.html#submit – edharned

+0

Thú vị, thậm chí là xây dựng một chuỗi mới ForkJoinPool mà không tùy biến bất cứ thứ gì tạo ra một hồ bơi mang lại cho tôi toàn quyền. Nó chỉ là trường hợp mà đến từ commonPool() đã được "đầu độc" bởi nhà máy tùy chỉnh của họ. API luồng không xuất hiện để cho tôi khả năng chọn nhóm nào được sử dụng, vì vậy tôi đoán chúng tôi sẽ chỉ phải tránh sử dụng nó và sử dụng các tiện ích thực hiện song song hiện có của chúng tôi. – Trejkaz

+0

Trejkaz, bạn là chính xác, tôi bị mất rằng khi nhãn cầu mã. Đã cập nhật câu trả lời để sửa lỗi. –

2

Bạn lo lắng về việc AccessController.doPrivileged là không cần thiết. Nó không làm giảm an ninh nếu được thực hiện ngay. Các phiên bản dùng một luận cứ hành động đơn lẻ sẽ thực hiện thao tác trong hoàn cảnh của bạn, bỏ qua người gọi của bạn nhưng có những phương pháp quá tải, có một cuộc tranh cãi thêm, một bối cảnh ghi lại trước đó:

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

    if(parallel) 
    { 
     stream = stream.parallel(); 
     AccessControlContext ctx=AccessController.getContext(); 
     actualAction=name -> AccessController.doPrivileged(
      (PrivilegedAction<?>)()->{ createFile.accept(name); return null; }, ctx); 
    } 
    else actualAction = createFile; 

    stream.forEach(actualAction); 
} 

Điểm mấu quan trọng đầu tiên là AccessControlContext ctx=AccessController.getContext(); tuyên bố nó ghi lại ngữ cảnh bảo mật hiện tại của bạn bao gồm mã số người gọi hiện tại của bạn. (Hãy nhớ rằng các quyền có hiệu lực là giao lộ của tất cả các bộ của tất cả người gọi). Bằng cách cung cấp đối tượng bối cảnh kết quả ctx vào phương thức doPrivileged trong các Consumer bạn đang thiết lập lại ngữ cảnh, nói cách khác, PrivilegedAction sẽ có cùng quyền như trong trường hợp một luồng của bạn.

Các vấn đề liên quan