During the development of a concurrent distributed application for a client, we repeated a common mistake of tail recursing in an asynchronous workflow using the do! construct. For example:
let agent execute =
new MailboxProcessor<_>(fun inbox ->
let rec loop() = async {
let! msg = inbox.Receive()
execute msg
do! loop()
}
loop())
This is problematic because using do! in this context leaks stack frames every time the workflows recurses (which is typically every time a message is processed). Moreover, F# uses a trampoline to implement this stack so the consequence is not a stack overflow but, rather, a gradual memory leak.
So if your concurrent applications appear to be leaking a few kilobytes for each message they process, search for instances of do! in tail position and replace them with return! like this:
let agent execute =
new MailboxProcessor<_>(fun inbox ->
let rec loop() = async {
let! msg = inbox.Receive()
execute msg
return! loop()
}
loop())