2014-05-21 21 views
7

Tôi đã chơi đùa với gói UNIX của conduit-extra, về cơ bản cho phép tạo một máy chủ dễ dàng bằng cách sử dụng các ổ cắm miền UNIX, cụ thể là sử dụng runUnixServer funciton.Cách dọn dẹp tài nguyên chính xác bằng cách sử dụng ResourceT là gì?

Vấn đề là sau khi chức năng tồn tại, nó sẽ không dọn dẹp tệp ổ cắm, điều đó có nghĩa là nó cần được dọn dẹp thủ công. Đây là một ví dụ đơn giản, về cơ bản tạo ra một máy chủ echo.

main :: IO() 
main = do 
    let settings = serverSettings "foobar.sock" 
    runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad)) 

Tôi đã google một chút và thấy rằng cách chính xác để xử lý tài nguyên ở đây là sử dụng gói resourcet. Mặc dù vấn đề là hầu hết các API trong tài nguyên mong đợi tôi tự phân bổ tài nguyên, đây không phải là trường hợp của runUnixSever, không trả về bất kỳ thứ gì.

Lúc đầu, tôi nghĩ tôi có thể sử dụng register, đăng ký một chức năng mà loại bỏ các tập tin, chẳng hạn như sau

main :: IO() 
main = runResourceT $ do 
    register $ removeLink "foobar.sock" 
    let settings = serverSettings "foobar.sock" 
    liftIO $ runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad)) 

Có một vấn đề với cách tiếp cận này mặc dù, ít nhất là như xa như các tài liệu cho allocate nói:

Điều này gần giống với việc gọi phân bổ và sau đó đăng ký hành động phát hành, nhưng điều này xử lý đúng cách mặt nạ ngoại lệ không đồng bộ.

Điều này có nghĩa là tự nó không xử lý ngoại lệ không đồng bộ? Nếu vậy, có thể đó là một vấn đề khi một trong những xử lý sinh ra bởi các runUnixServer (tài liệu nói nó sinh ra một sợi cho mỗi khách hàng) làm tăng một lỗi?

Giải pháp thứ ba và cuối cùng mà tôi đưa ra là sử dụng allocate, để đảm bảo rằng các ngoại lệ không đồng bộ được xử lý đúng cách (tôi không chắc liệu nó có thực sự cần thiết trong trường hợp này) hay không.

main :: IO() 
main = runResourceT $ do 
    allocate (return 1) (const $ removeLink "foobar.sock") 
    let settings = serverSettings "foobar.sock" 
    liftIO $ runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad)) 

Nhưng đây thực sự là giải pháp tốt nhất? Vì tôi đang tạo một giá trị mà tôi sẽ không bao giờ sử dụng (return 1) và sau đó sử dụng một hàm const để bỏ qua giá trị đó trong trình hoàn thiện.

Trả lời

9

Trước khi giải quyết các câu hỏi resourcet:

  1. resourcet là không cần thiết trong trường hợp này. Bạn chỉ có thể sử dụng chức năng finally cho một cái gì đó như thế này, ví dụ: runUnixServer settings (\ad -> ...) cuối cùng là removeLink "foobar.sock".
  2. Điều này thực sự trông giống như hành vi có vấn đề. Các mô hình được chấp nhận chung trong ống dẫn là, nếu bạn phân bổ một nguồn tài nguyên, bạn có trách nhiệm làm sạch nó lên. Tôi đã không viết mã socket unix, vì vậy có thể có một lý do cho tác giả đã làm nó khác nhau ở đây. Nhưng nó đáng để mở một báo cáo lỗi.

Điều đó nói rằng, mã ban đầu của bạn với register là tốt. Vấn đề duy nhất tôi thấy là nếu một ngoại lệ được ném trước khi foobar.sock được tạo, mặc dù giải pháp finally của tôi cũng dễ bị tấn công.

Các bình luận về phân bổ vs đăng ký đã làm với mã mà trông giống như sau:

handle <- openFile fp ReadMode 
register $ hClose handle 

Mã này là dễ bị tổn thương đến một ngoại lệ async bị ném giữa openFileregister cuộc gọi. Vì bạn không phân bổ một tài nguyên như thế này, register là tốt.

+0

Cảm ơn bạn đã phản hồi cực nhanh! –

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