Wojciech Szabowicz
Wojciech Szabowicz

Reputation: 4198

f# and memmory usage in recursive

recently I am trying to learn more about memory profiling, so I have a simple application that receives messages from UDP. Now I am using standard Visual Studio 2019 memory profiler from here https://memprofiler.com/.

And from the looks of it I have small but constantly building up memory usage in this app, but I do not know if I'm on to something (nomenclature of memory profiling is quite new to me so I mostly do not knot what i am looking at).

So down to my problem and question data from profiler shows me this

enter image description here

Snapshot takers are about 10 minutes apart (but program runs for an hour and I took snapshot every 10 minutes, all process are stable only this part is increasing)

So if I understand correctly, problem is in procedure $UdpListener.listenerWatcher@76-5 that is part of code that looks like:

member this.Start (udpReceiver : UdpClient) (onMessageReceived : byte array -> IBus -> unit) = 
    async {
        try
            let! receiveResult = udpReceiver.ReceiveAsync() |> Async.AwaitTask
            let receiveBytes = receiveResult.Buffer
            this.Logger.Debug 
                (sprintf "Received info %A from remote end poin Ip:%s Port:%i" receiveBytes (receiveResult.RemoteEndPoint.Address.ToString()) receiveResult.RemoteEndPoint.Port)
            onMessageReceived receiveBytes bus
        with 
            | :? ObjectDisposedException -> return ()
            | :? SocketException -> return ()
            | ex -> 
                match ex.InnerException :? ObjectDisposedException with 
                | true -> 
                    this.Logger.Warn "Receiver is disposed or socked is closed, probably receiver restart" 
                    return ()
                | _  ->
                    this.Logger.Fatal(sprintf "Fatal error while starting listening, exception %A" ex)
                    raise (ex)
        }

member this.Watcher (ip: IPAddress, port : int32) (onMessageReceived : byte array -> IBus -> unit) =
    let rec listenerWatcher (udpReceiver : UdpClient) = 
        async {
            try
                do! this.Start udpReceiver onMessageReceived
                return! listenerWatcher (udpReceiver) 
            with | ex ->
                this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex)
                return ()
        }    
    (ip, port) |> this.CreateUdpReceiver |> listenerWatcher 

UdpReceiver is created once when watcher is started and it looks like:

 member private this.CreateUdpReceiver (ip: IPAddress, port : int32) : UdpClient =
    try
        let endpoint = IPEndPoint (ip, port)
        let receivingClient = new UdpClient()
        receivingClient.Client.Bind endpoint
        receivingClient.Client.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true)
        this.Logger.Debug (sprintf "Listener binded to Ip Adress:%s Port:%i" (endpoint.Address.ToString()) endpoint.Port)
        UdpMediator.Instance.InvokeAddUdpReceiver receivingClient
        receivingClient
    with | ex -> 
        this.Logger.Fatal(sprintf "Fatal error while stoping  udp clinet")
        raise (ex)

And InvokeAddUpdReceiver just adds created receiver outside so we can dispose him when not needed or needs to be refreshed.

So basically I can not pin point source of this leak, for me it looks OK but I might be blind to something.

Upvotes: 0

Views: 84

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

One potential source of issues that I can see in your code is the way the recursive call is implemented in the listenerWatcher function. You have:

let rec listenerWatcher (udpReceiver : UdpClient) = async {
  try
    do! this.Start udpReceiver onMessageReceived
    return! listenerWatcher (udpReceiver) 
  with ex ->
    this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) } 

The issue here is that the recursive call using return! is inside a try .. with block. This prevents the async runtime from treating this as a tail-recursive call. It has to keep a handler allocated for the exception handling and so it adds one more allocated object for each recursive call.

You should be able to fix this by moving the exception handling outside of the recursive loop:

let rec listenerWatcher (udpReceiver : UdpClient) = async {
  do! this.Start udpReceiver onMessageReceived
  return! listenerWatcher (udpReceiver) }

let startListenerWatcher udpReceiver = async {
  try 
    return! listenerWatcher udpReceiver
  with ex ->
    this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) } 

Or, given that you do not need to keep any state in the recursive call, you could just use a while loop instead of recursion:

let listenerWatcher (udpReceiver : UdpClient) = async {
  try 
    while true do 
      do! this.Start udpReceiver onMessageReceived
  with ex ->
    this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) } 

Upvotes: 3

Related Questions