Memory leak using Powershell Remote Calls in C#
Asked Answered
P

2

10

I have a windows service that is doing a lot of exchange remote calls to get some server information. I noticed that as longs as the time passes the memory used by the service starts growing until a memory exception is thrown. I have searched and it looks like there is a known memory leak in the System.Management.Automation that does not dispose all the memory of the Runspace created while calling the close and/or dispose method. I reviewed a post that suggest using the CreateOutOfProcessRunspace of the RunspaceFactory but not sure how to use it.

Here is how the issue can be reproduced: (System.Management.Automation dll referenced)

for (int i = 0; i < 1000; i++)
{
    var runspace = RunspaceFactory.CreateRunspace();
    runspace.Open();
    runspace.Close();
    runspace.Dispose();
}

If you run this code, you will see how the memory is incremented. Due to the requirements, keeping a connection open as much as possible is not a good solution.

Do you know how I can fix this issue, even using the CreateOutOfProcessRunspace method of the RunspaceFactory or how to propery dispose the memory?

Thanks in advance

EDIT

I was using the V3 and change the runspace creation to use the CreateRunspacePool method and it looks like the leak is gone. Thanks so much for your help!

Phthisis answered 23/4, 2014 at 3:20 Comment(2)
Yes, it looks like you hit a problem. Perhaps there is a workaround. What do you use this runspace for?Whaler
@Manuel Why does the title say it's remote calls that are the problem? Your snippet only creates a Runspace, it doesn't actually do any PS calls, remote or local.Potbelly
W
4

I can see the problem in PS v3.0 but not in PS v2.0. Here is the code I use to see this (all examples are in PowerShell):

for() {
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()
    $runspace.Close()
    $p = Get-Process -Id $PID
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
}

It looks like handles and memory are leaking in v3.0 in the code above.

As far as v2.0 does not have this problem, one possible workaround may be to start the service using PS v2.0, i.e. PowerShell.exe -Version 2.0.

If this is not possible I can think of two more workarounds. One of them is not to create runspaces directly but use [powershell] instead. For example, this code does not show the leak in v3.0:

for() {
    $ps = [powershell]::Create()
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}

Another workaround, if it is applicable, may be use of [runspacefactory]::CreateRunspacePool(). This way also does not show the leak:

$rs = [runspacefactory]::CreateRunspacePool()
$rs.Open()
for() {
    $ps = [powershell]::Create()
    $ps.RunspacePool = $rs
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}
#$rs.Close() # just a reminder, it's not called here due to the infinite loop

The last one also works much faster because the runspace is kind of reused.

Whaler answered 23/4, 2014 at 4:43 Comment(3)
I'm not seeing any issues on PowerShell v4 on .NET 4.5.1. The first runspace open pushes the private bytes to 13,892K (after a GC) and after the 1000 iters (and a final GC) the privates bytes is at 14,076K. Not much of a bump ~184K. Handle count and thread count is stable too.Calvaria
Thank you for your answers! I will try these workarounds and let you knowPhthisis
I was using the V3 and change the runspace creation to use the CreateRunspacePool method and it looks like the leak is gone. Thanks so much :)Phthisis
C
3

I was also facing the same issue when I was using v1 of System.Management.Automation. But the issue was solved with v3 of System.Management.Automation and changing the code to use the CreateOutOfProcessRunspace method

Here is the code

            using (PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(4, 0), null, null, false))
        {

            using (var runspace = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(new string[0]), instance))
            {
                runspace.Open();

                using (PowerShell powerShellInstance = PowerShell.Create())
                {
                    powerShellInstance.Runspace = runspace;

                    var filePath = GetScriptFullName(powerShellScriptType);
                    powerShellInstance.Commands.AddScript(File.ReadAllText(filePath));

                    var includeScript = GetIncludeScript();
                    powerShellInstance.AddParameters(new List<string>
                {
                    userName,
                    plainPassword,
                    includeScript
                });
                    Collection<PSObject> psOutput = powerShellInstance.Invoke();

                    // check the other output streams (for example, the error stream)
                    if (powerShellInstance.Streams.Error.Count > 0)
                    {
                        // error records were written to the error stream.
                        // do something with the items found.
                        var exceptions = "";
                        foreach (var error in powerShellInstance.Streams.Error)
                        {
                            exceptions += error.Exception + "\n";
                        }

                        throw new InvalidPowerShellStateException(exceptions);

                    }
                    return psOutput;
                }
            }
        }
Crymotherapy answered 14/9, 2015 at 10:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.