Given the following agent, which is a simple caching mechanism:
type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) = let agent = MailboxProcessor.Start(fun inbox -> let rec loop (cache : Map<'a, 'b>) = async { let inline flush() = flushCont cache loop Map.empty if cache.Count > size then return! flush() let! msg = inbox.Receive() match msg with | Add (key, value) -> if cache.ContainsKey key then return! loop cache else return! loop (cache.Add(key, value)) | ForceFlush -> return! flush() } loop Map.empty) member x.AddIfNotExists key value = Add(key,value) |> agent.Post member x.ForceFlush() = agent.Post ForceFlush
This agent will continue to occupy memory (it seems that memory is not freed when flushCont called).
Given the same code, but with a slight change:
type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) = let agent = MailboxProcessor.Start(fun inbox -> let rec loop (cache : Map<'a, 'b>) = async { let inline flush() = flushCont cache loop Map.empty let! msg = inbox.Receive() match msg with | Add (key, value) -> if cache.ContainsKey key then return! loop cache else let newCache = cache.Add(key, value) if newCache.Count > size then return! flush() else return! loop (cache.Add(key, value)) | ForceFlush -> return! flush() } loop Map.empty) member x.AddIfNotExists key value = Add(key,value) |> agent.Post member x.ForceFlush() = agent.Post ForceFlush
I have moved an expression that decides when to collapse in the case of the Add join. This causes the memory to be freed as expected.
What is wrong with the first approach, since it leaks memory?
f #
ebb
source share