26

Q: I have a windows service that spawns a lot of child processes. There seems to be some limit, around 100, where the process cannot be launched. The CreateProcess() call comes back with a pid, but then the process just fails to materialize. If I run our server as a console application instead, this limit goes away. Also, If I set the DETACHED_PROCESS flag, this limit more than doubles. However, I get failures if I set DETACHED_PROCESS and call CreateProcesssWithLogonW().

Wheezil
  • 3,157
  • 1
  • 23
  • 36

2 Answers2

40

This is a "desktop heap" problem. A very good discussion can be found here:

Desktop Heap Overview and Desktop Heap, Part 2

Note that this only applies to programs that are running as services, because the default desktop heap size for services is so much smaller than that for applications.

In our case, we were able to launch about 100 child processes before running out of resources without the change. With the change, this number can be increased considerably.

This is the answer we've given to our end users on our knowledgebase:

WARNING: this affects the desktop heap of all services! Do not make it larger than necessary or you will push the system to consume more resource and you may bump up against problems in the total available desktop heap size.

If you find that you cannot open more than about 100 total projects, even on a very large RAM server, you may have run into a limit of the Windows "desktop heap size".

The problem is that service sessions under windows (where the services run) have less of this "desktop heap" space available for creating windows.

The short version is:

  • Services get smaller desktop heaps than interactive sessions.

  • Desktop heap size limits the number of windows

  • Each sub-server creates one or more “windows” even if we can’t see them.

Solution:

  1. Backup your registry before making any changes!

  2. Run regedit.exe as administrator

  3. Edit the registry value:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows
    
  4. You will see a string like:

    %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16
    

The critical bit is:

SharedSection=1024,20480,768

The second number (20480) is the size for interactive sessions. The third number (768) is the size of non-interactive (services) sessions. Note how the third number is 26x smaller than the second. Experimentally, we found that changing this to:

SharedSection=1024,20480,2048

Increased the project limit from 106 to 270, almost perfectly scaling with the heap size. Pick a value that reflects the maximum number of projects that you expect to be opened simultaneously by all users on the system. Do not make this value larger than necessary, and no larger than 8192, as each service in your system will consume more of a precious resource.

You will need to reboot for these new settings to take effect.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Wheezil
  • 3,157
  • 1
  • 23
  • 36
  • 1
    I don't understand the language in all of these 'desktop heap' writeups that seem to apply to forking services, but this answer saved our hide. – covener Jan 27 '15 at 21:56
  • 3
    Incredibly enough, the same limitations were imposed on Windows 2008 Server Web Edition (Vista Core), Windows 2008 Server R2 (Windows 7 core), and Windows 2012 Server R2 (Windows 8.1 core). I changed mine to 1024,20480,4096 and upped the max threads from 16 to 32 on all these servers with no ill effects on resource usage, but a marked increased ability in services running on those servers, to spawn other processes simultaneously and successfully. Thanks! – Graphic Equaliser Aug 11 '15 at 10:40
  • +1 for the very clear description and workaround. For the record, though, if you're creating this many child processes in a service, you're doing it wrong. :-) [Granted, if you're porting code from UNIX it may not be worth the effort to re-architect it.] – Harry Johnston Jul 14 '16 at 21:24
  • Harry, I totally appreciate what you are saying. Unfortunately we need process-level isolation because we have a system that interfaces to user-specified database drivers and other libraries of various levels of quality and stability. We learned the hard way that attempting to host more than one "project" per process led to all kinds of bad and unpredictable interactions. Being able to kill a process is our escape hatch. – Wheezil Jul 15 '16 at 21:36
  • The linked to article suggests that this was all changed in Windows Vista, but my* Windows 7 Enterprise machine (on which I'm having a possibly similar problem) has the same configuration, as default. Can anyone further clarify what the crack is here? *Technically not mine. I don't voluntarily own Windows machines. – Toby Wilson Aug 07 '17 at 15:22
  • I think it is slightly different in quantities but the fundamental problem is the same. – Wheezil Aug 08 '17 at 18:15
1

We had many Remote Desktop servers we needed to test this on, so we wrote a powershell script to query AD for our RD servers and then apply this registry change. Enjoy

   $newValue = "%SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,2048 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16"
$origValue = "%SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16"

if ($update) {
    Clear-Variable update
}

$updateConfirm= [System.Windows.Forms.MessageBox]::Show("Update Desktop Heap Limitation to 2048?" , "Status" , 4) 
if ($updateConfirm -eq "YES" ) {
    $update = $true
} else { 
    $revertConfirm= [System.Windows.Forms.MessageBox]::Show("Revert Desktop Heap Limitation to 768?" , "Status" , 4) 
    if ($revertConfirm -eq "YES" ) {
        $update = $false
    } 
}

if (($updateConfirm -ne "YES") -and ($revertConfirm -ne "YES")) {
    Write-Host "User did not specify whether to update or revert Desktop Heap Limitation. Exiting Setup."
    Read-Host "Press Enter to exit."
    break
}

#Import Active Directory PowerShell module
if (Test-Path C:\Users\${env:USERNAME}\Documents\WindowsPowerShell\Modules\ActiveDirectory\ActiveDirectory.psm1) {
    Import-Module ActiveDirectory -prefix AD
} else {
    $s = New-PSSession -computerName DC01WDC01
    Invoke-command { import-module ActiveDirectory } -session $s
    Export-PSSession -session $s -commandname *-AD* -outputmodule ActiveDirectory -allowclobber
    Import-Module ActiveDirectory -prefix AD
    Remove-PSSession -session $s
}

$servers =  Get-ADADComputer -Filter {(Name -Like "RDS*")}  | Select -Expand Name

foreach ($server in $servers) {
    Write-Host "Working on $server" -ForegroundColor Magenta
    if(!(Test-Connection -ComputerName $server -Count 1 -quiet)) {
        Write-Warning "$server : Offline"
        Continue
    }

    if ($update -eq $true) {
        Invoke-Command -ComputerName $server -ArgumentList $newValue -ScriptBlock {
            Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" -Name "Windows" -Value $args[0]
            $result = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" | SELECT Windows
            if ($result.Windows -like "*SharedSection=1024,20480,2048*") {
                Write-Host "Successfully reverted Desktop Heap Limit to 2048" -ForegroundColor Green
            } else {
                Write-Warning "Update to registry value unsuccessful on $env:ComputerName" 
            }              
        }
    } elseif ($update -eq $false) {
        Invoke-Command -ComputerName $server -ArgumentList $origValue -ScriptBlock {
            Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" -Name "Windows" -Value $args[0]
            $result = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" | SELECT Windows    
            if ($result.Windows -like "*SharedSection=1024,20480,768*") {
                Write-Host "Successfully reverted Desktop Heap Limit to 768" -ForegroundColor Green
            } else {
                Write-Warning "Update to registry value unsuccessful on $env:ComputerName" 
            }       
        }
    }
}
Shaun
  • 33
  • 2
  • 9
  • Unfortunately the setting didn't work for us in our situation, but maybe the script helps someone else with a large group of machines to test on. :) – Shaun Dec 09 '16 at 01:35
  • Worked for me, but had to reboot after making changes for it to take effect. – user3725395 Jul 06 '17 at 06:52