我有一个Scotty / WAI应用程序,其中一个端点发送一个由元素列表构建的大型Text输出.这是相关代码:
import Data.Text.Lazy as L
import Data.Text.Lazy.Encoding as E
class (Show csv) => ToCSV csv where
toCSV :: csv -> L.Text
toCSV = pack . show
instance (ToCSV c) => ToCSV [c] where
toCSV [] = empty
toCSV (c:cs) = toCSV c <> "\n" <> toCSV cs
get "/api/transactions" $accept "text/csv" $do
purp <- selectPurpose
txs <- allEntries <$> inWeb (listTransactions purp)
setHeader "Content-Type" "text/csv"
raw $E.encodeUtf8 $toCSV txs
据我所知Scotty’s documentation,输出应该是懒惰地构建并通过线路发送而无需在内存中构建整个text / bytestring.然而,这不是我观察到的行为:当我调用此端点时,服务器开始占用内存,我推断它正在构建整个字符串,然后一次发送它.
我错过了什么吗?
编辑1:
我写了一个doStream函数,它应该逐个发送结果BS的块:
doStream :: Text -> W.StreamingBody
doStream t build flush = do
let bs = E.encodeUtf8 t
mapM_ (\ chunk -> build (B.fromByteString chunk)) (BS.toChunks bs)
flush
但实际上它仍然在内存中构建整个输出……
编辑2:
实际上,这种方式流式传输工作正常.虽然服务器进程仍占用大量内存,但在发送每个块时实际上可能是垃圾收集.我将尝试更深入地分析内存使用情况,以了解这种消费来自何处.
编辑3:
我试图将堆限制为2GB,但这会导致进程崩溃.在整个转换过程中会保留一些内存……
最佳答案 看一下Web.Scotty.Trans中的
“stream”函数.它的目的是对将数据刷新到套接字之前生成的数据大小进行更细粒度的控制.
你用StreamingBody参数调用它,它实际上是类型的函数(Builder – > IO()) – > IO() – > IO().
所以你写了一个函数:
doMyStreaming send flush =
...
在其中发送和刷新数据,然后使用doMyStreaming作为参数调用流函数,而不是调用“raw”.