Reputation: 4198
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
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
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