Thread: Win32: Minimising desktop heap usage

Win32: Minimising desktop heap usage

From
Dave Page
Date:
A discussion on -general has been ongoing in which is was discovered
that 8.3 can exhaust the desktop heap allocated to a service with ~45
connections at which point the server will crash.

Desktop heap is allocated by user32.dll and shell32 on a per-process
basis from a 512KB pool for a non-interactive logon (on XP) and 3072KB
pool for an interactive logon. The attached patch removes our direct
dependencies on these DLLs, reducing the overhead from around 9.7KB per
backend to around 3.5KB. This is back in the range we had with 8.2.

Unfortunately, there are still indirect dependencies on user32.dll which
I don't think we'll be able to do anything about. If desktop heap is
still exhausted with larger installations, the allocated heap size can
be increased in the registry.

See
http://blogs.msdn.com/ntdebugging/archive/2007/01/04/desktop-heap-overview.aspx
for more info.

Regards, Dave
Index: src/backend/port/win32/signal.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/port/win32/signal.c,v
retrieving revision 1.18
diff -c -r1.18 signal.c
*** src/backend/port/win32/signal.c    5 Jan 2007 22:19:35 -0000    1.18
--- src/backend/port/win32/signal.c    23 Oct 2007 14:44:35 -0000
***************
*** 178,184 ****
      char        pipename[128];
      HANDLE        pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
--- 178,184 ----
      char        pipename[128];
      HANDLE        pipe;

!     snprintf(pipename, sizeof(pipename) - 1, "\\\\.\\pipe\\pgsignal_%u", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
***************
*** 251,257 ****
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", GetCurrentProcessId());

      for (;;)
      {
--- 251,257 ----
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     snprintf(pipename, sizeof(pipename) - 1, "\\\\.\\pipe\\pgsignal_%u", GetCurrentProcessId());

      for (;;)
      {
Index: src/port/kill.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/kill.c,v
retrieving revision 1.8
diff -c -r1.8 kill.c
*** src/port/kill.c    5 Jan 2007 22:20:02 -0000    1.8
--- src/port/kill.c    23 Oct 2007 14:44:35 -0000
***************
*** 38,44 ****
          errno = EINVAL;
          return -1;
      }
!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%i", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
--- 38,44 ----
          errno = EINVAL;
          return -1;
      }
!     snprintf(pipename, sizeof(pipename) - 1, "\\\\.\\pipe\\pgsignal_%u", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
Index: src/port/path.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.71
diff -c -r1.71 path.c
*** src/port/path.c    5 Jan 2007 22:20:02 -0000    1.71
--- src/port/path.c    23 Oct 2007 14:47:29 -0000
***************
*** 628,637 ****
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        tmppath[MAX_PATH];

      ZeroMemory(tmppath, sizeof(tmppath));
!     if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, tmppath) != S_OK)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;
--- 628,643 ----
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        *tmppath=0;

      ZeroMemory(tmppath, sizeof(tmppath));
!
!     /*
!      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
!      * will force us to link with shell32.lib which eats valuable desktop heap.
!      */
!     tmppath = getenv("APPDATA");
!     if (!tmppath)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;

Re: Win32: Minimising desktop heap usage

From
Tom Lane
Date:
Dave Page <dpage@postgresql.org> writes:
> [ get rid of wsprintf in favor of snprintf ]

+1 for not depending on nonstandard subroutines without need.
But please note the standard locution is snprintf(buf, sizeof(buf), ...
Not sizeof() - 1.

> !     char        *tmppath=0;

Please use NULL not 0 ... actually, since the variable is
unconditionally assigned to just below, I'd say this should just be
"char *tmppath;".  I don't like useless initializations: if the compiler
is not smart enough to discard them then they waste cycles, and they
also increase the risk of bugs-of-omission in future changes.  If
someone were to change things around so that tmppath didn't get assigned
to in one code path, then the compiler would complain about use of an
uninitialized variable --- *unless* you've written a useless
initialization such as the above, in which case the mistake might pass
unnoticed for awhile.  So don't initialize a local variable unless
you're giving it an actually meaningful value.

> !     /*
> !      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
> !      * will force us to link with shell32.lib which eats valuable desktop heap.
> !      */
> !     tmppath = getenv("APPDATA");

Hmm, is there any functional downside to this?  I suppose
SHGetSpecialFolderPath does some things that getenv does not...

Otherwise it looks good...

            regards, tom lane

Re: Win32: Minimising desktop heap usage

From
Dave Page
Date:
Tom Lane wrote:
> Dave Page <dpage@postgresql.org> writes:
>> [ get rid of wsprintf in favor of snprintf ]
>
> +1 for not depending on nonstandard subroutines without need.
> But please note the standard locution is snprintf(buf, sizeof(buf), ...
> Not sizeof() - 1.

Noted.

>> !     char        *tmppath=0;
>
> Please use NULL not 0 ... actually, since the variable is
> unconditionally assigned to just below, I'd say this should just be
> "char *tmppath;".  I don't like useless initializations: if the compiler
> is not smart enough to discard them then they waste cycles, and they
> also increase the risk of bugs-of-omission in future changes.  If
> someone were to change things around so that tmppath didn't get assigned
> to in one code path, then the compiler would complain about use of an
> uninitialized variable --- *unless* you've written a useless
> initialization such as the above, in which case the mistake might pass
> unnoticed for awhile.  So don't initialize a local variable unless
> you're giving it an actually meaningful value.

The downside is that we see a useless warning that, if sufficiently
multiplied, might make it hard to see something we are interested in.

In this case, not initialising it will also create the first 'accepted'
warning in VC++ in our code :-(. All the others come from Kerberos.

Modified in the attached patch however.

>> !     /*
>> !      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
>> !      * will force us to link with shell32.lib which eats valuable desktop heap.
>> !      */
>> !     tmppath = getenv("APPDATA");
>
> Hmm, is there any functional downside to this?  I suppose
> SHGetSpecialFolderPath does some things that getenv does not...

A good percentage of the special folder paths you might query with
SHGetSpecialFolderPath() are not actually in the environment. APPDATA
certainly is on XP, 2K3 and Vista though, and I've found MS KB articles
referring to it being on 2K as well.

Regards, Dave.
Index: src/backend/port/win32/signal.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/port/win32/signal.c,v
retrieving revision 1.18
diff -c -r1.18 signal.c
*** src/backend/port/win32/signal.c    5 Jan 2007 22:19:35 -0000    1.18
--- src/backend/port/win32/signal.c    23 Oct 2007 14:44:35 -0000
***************
*** 178,184 ****
      char        pipename[128];
      HANDLE        pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
--- 178,184 ----
      char        pipename[128];
      HANDLE        pipe;

!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
***************
*** 251,257 ****
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", GetCurrentProcessId());

      for (;;)
      {
--- 251,257 ----
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", GetCurrentProcessId());

      for (;;)
      {
Index: src/port/kill.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/kill.c,v
retrieving revision 1.8
diff -c -r1.8 kill.c
*** src/port/kill.c    5 Jan 2007 22:20:02 -0000    1.8
--- src/port/kill.c    23 Oct 2007 14:44:35 -0000
***************
*** 38,44 ****
          errno = EINVAL;
          return -1;
      }
!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%i", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
--- 38,44 ----
          errno = EINVAL;
          return -1;
      }
!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
Index: src/port/path.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.71
diff -c -r1.71 path.c
*** src/port/path.c    5 Jan 2007 22:20:02 -0000    1.71
--- src/port/path.c    23 Oct 2007 14:47:29 -0000
***************
*** 628,637 ****
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        tmppath[MAX_PATH];

      ZeroMemory(tmppath, sizeof(tmppath));
!     if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, tmppath) != S_OK)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;
--- 628,643 ----
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        *tmppath;

      ZeroMemory(tmppath, sizeof(tmppath));
!
!     /*
!      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
!      * will force us to link with shell32.lib which eats valuable desktop heap.
!      */
!     tmppath = getenv("APPDATA");
!     if (!tmppath)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;

Re: Win32: Minimising desktop heap usage

From
Tom Lane
Date:
Dave Page <dpage@postgresql.org> writes:
> Tom Lane wrote:
>> So don't initialize a local variable unless
>> you're giving it an actually meaningful value.

> The downside is that we see a useless warning that, if sufficiently
> multiplied, might make it hard to see something we are interested in.

[ looks again... ]  Actually, I think you just proved my point for me.
The ZeroMemory call should go away, no?  (Does this mean that the
Windows builds fail to detect dereferencing NULL?  Bad if so.)

>> Hmm, is there any functional downside to this?  I suppose
>> SHGetSpecialFolderPath does some things that getenv does not...

> A good percentage of the special folder paths you might query with
> SHGetSpecialFolderPath() are not actually in the environment. APPDATA
> certainly is on XP, 2K3 and Vista though, and I've found MS KB articles
> referring to it being on 2K as well.

OK, in that case seems no problem.

            regards, tom lane

Re: Win32: Minimising desktop heap usage

From
Gregory Stark
Date:
"Tom Lane" <tgl@sss.pgh.pa.us> writes:

>> !     /*
>> !      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
>> !      * will force us to link with shell32.lib which eats valuable desktop heap.
>> !      */
>> !     tmppath = getenv("APPDATA");
>
> Hmm, is there any functional downside to this?  I suppose
> SHGetSpecialFolderPath does some things that getenv does not...

The functional difference appears to be that the environment variable is set
on startup (or login?) and doesn't necessarily have the most up to date value
if it's been changed. But it's not something you're likely to change and you
can always adjust the environment variable manually to fix the problem.

--
  Gregory Stark
  EnterpriseDB          http://www.enterprisedb.com

Re: Win32: Minimising desktop heap usage

From
Dave Page
Date:
Tom Lane wrote:
> [ looks again... ]  Actually, I think you just proved my point for me.
> The ZeroMemory call should go away, no?

Yup, quite correct. v3 attached.

/D
Index: src/backend/port/win32/signal.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/port/win32/signal.c,v
retrieving revision 1.18
diff -c -r1.18 signal.c
*** src/backend/port/win32/signal.c    5 Jan 2007 22:19:35 -0000    1.18
--- src/backend/port/win32/signal.c    23 Oct 2007 16:02:41 -0000
***************
*** 178,184 ****
      char        pipename[128];
      HANDLE        pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
--- 178,184 ----
      char        pipename[128];
      HANDLE        pipe;

!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", (int) pid);

      pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
***************
*** 251,257 ****
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%d", GetCurrentProcessId());

      for (;;)
      {
--- 251,257 ----
      char        pipename[128];
      HANDLE        pipe = pgwin32_initial_signal_pipe;

!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", GetCurrentProcessId());

      for (;;)
      {
Index: src/port/kill.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/kill.c,v
retrieving revision 1.8
diff -c -r1.8 kill.c
*** src/port/kill.c    5 Jan 2007 22:20:02 -0000    1.8
--- src/port/kill.c    23 Oct 2007 16:02:41 -0000
***************
*** 38,44 ****
          errno = EINVAL;
          return -1;
      }
!     wsprintf(pipename, "\\\\.\\pipe\\pgsignal_%i", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
--- 38,44 ----
          errno = EINVAL;
          return -1;
      }
!     snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", pid);
      if (!CallNamedPipe(pipename, &sigData, 1, &sigRet, 1, &bytes, 1000))
      {
          if (GetLastError() == ERROR_FILE_NOT_FOUND)
Index: src/port/path.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/port/path.c,v
retrieving revision 1.71
diff -c -r1.71 path.c
*** src/port/path.c    5 Jan 2007 22:20:02 -0000    1.71
--- src/port/path.c    23 Oct 2007 16:04:42 -0000
***************
*** 628,637 ****
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        tmppath[MAX_PATH];

!     ZeroMemory(tmppath, sizeof(tmppath));
!     if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, tmppath) != S_OK)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;
--- 628,641 ----
      strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
      return true;
  #else
!     char        *tmppath;

!     /*
!      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
!      * will force us to link with shell32.lib which eats valuable desktop heap.
!      */
!     tmppath = getenv("APPDATA");
!     if (!tmppath)
          return false;
      snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
      return true;

Re: Win32: Minimising desktop heap usage

From
Dave Page
Date:
Gregory Stark wrote:
> "Tom Lane" <tgl@sss.pgh.pa.us> writes:
>
>>> !     /*
>>> !      * Note: We use getenv here because the more modern SHGetSpecialFolderPath()
>>> !      * will force us to link with shell32.lib which eats valuable desktop heap.
>>> !      */
>>> !     tmppath = getenv("APPDATA");
>> Hmm, is there any functional downside to this?  I suppose
>> SHGetSpecialFolderPath does some things that getenv does not...
>
> The functional difference appears to be that the environment variable is set
> on startup (or login?) and doesn't necessarily have the most up to date value
> if it's been changed. But it's not something you're likely to change and you
> can always adjust the environment variable manually to fix the problem.

If you're hacking the registry to change it then you've only got
yourself to blame if you don't update the environment as well imho.

I also wouldn't be at all surprised if many apps build paths containing
the value returned by SHGetSpecialFolderPath() (or %APPDATA%) at startup
and then use that value from then on rather than repeatedly calling the
API function. It's not like you'd expect the value to change at all
often, if ever.

/D


Re: Win32: Minimising desktop heap usage

From
Magnus Hagander
Date:
Tom Lane wrote:
> Dave Page <dpage@postgresql.org> writes:
>> Tom Lane wrote:
>>> So don't initialize a local variable unless
>>> you're giving it an actually meaningful value.
>
>> The downside is that we see a useless warning that, if sufficiently
>> multiplied, might make it hard to see something we are interested in.
>
> [ looks again... ]  Actually, I think you just proved my point for me.
> The ZeroMemory call should go away, no?  (Does this mean that the
> Windows builds fail to detect dereferencing NULL?  Bad if so.)

Windows builds don't fail to detect that in genereal. ZeroMemory,
however, has a protection specifically against being passed NULL input,
IIRC.

//Magnus


Re: Win32: Minimising desktop heap usage

From
Magnus Hagander
Date:
Dave Page wrote:
> Tom Lane wrote:
>> [ looks again... ]  Actually, I think you just proved my point for me.
>> The ZeroMemory call should go away, no?
>
> Yup, quite correct. v3 attached.

Great job tracking this down!

Patch looks good from here (after the fixes per Tom).

Applied, many thanks!

//Magnus