Subject: Long startup time on windows (with simple fix)
From: Vladimir Šironja <[email-masked]>
To: [email-masked]
Date: Mon, 11 Nov 2024 12:30:54 +0100
List-Id: <debbugs-submit.debbugs.gnu.org>

Hello,
I have (*had!) a very annoying occurrence of long startup times on
windows 10 & 11.
Ended up with emacs 29.4 but tried several 28 and 26 versions with no
difference.
It takes sometimes around 30 secs for emacs to come up.
But not always, let's say a third of strarts were normal within a second.
Tried excluding relevant directories from virus check; no difference.
Also tried starting without init files (-Q); no difference.
So compiled with debug symbols gcc version 14.2.0 on msys2.
(Had to set manually add "#define HAVE_SYS_WAIT_H 1" into src/config.h
- but that's another issue, already handled several times in this
group.)

In gdb-debugger caught the hanging operation:

Thread 1 (Thread 19568.0x24c0):
#0  0x00007ff9e2331244 in ntdll!ZwAlpcSendWaitReceivePort () from
C:\WINDOWS\SYSTEM32\ntdll.dll
#1  0x00007ff9e0a4b032 in RpcBindingFromStringBindingW () from
C:\WINDOWS\System32\rpcrt4.dll
#2  0x00007ff9e0a47efb in RpcBindingFromStringBindingW () from
C:\WINDOWS\System32\rpcrt4.dll
#3  0x00007ff9e0a66c19 in RPCRT4!I_RpcSendReceive () from
C:\WINDOWS\System32\rpcrt4.dll
#4  0x00007ff9e0a66b5f in RPCRT4!NdrSendReceive () from
C:\WINDOWS\System32\rpcrt4.dll
#5  0x00007ff9e0af07da in RPCRT4!NdrClientCall3 () from
C:\WINDOWS\System32\rpcrt4.dll
#6  0x00007ff9e0aef69b in RPCRT4!NdrClientCall3 () from
C:\WINDOWS\System32\rpcrt4.dll
#7  0x00007ff9e11c6046 in SECHOST!LookupAccountSidLocalW () from
C:\WINDOWS\System32\sechost.dll
#8  0x00007ff9e11c5d4f in SECHOST!LookupAccountSidLocalW () from
C:\WINDOWS\System32\sechost.dll
#9  0x00007ff9e11dead3 in SECHOST!LookupAccountSidLocalA () from
C:\WINDOWS\System32\sechost.dll
#10 0x00007ff9e0d9bce0 in LookupAccountSidA () from
C:\WINDOWS\System32\advapi32.dll
#11 0x00007ff63569a990 in lookup_account_sid (lpSystemName=0x0,
Sid=0x4b414a8, Name=0xbfe740 "\030D╦5\001", cbName=0xbfe320,
DomainName=0x0, cbDomainName=0xbfe328, peUse=0xbfe314) at w32.c:706
#12 0x00007ff63569d35f in init_user_info () at w32.c:2319
#13 0x00007ff63569f151 in init_environment (argv=0xd315b0) at w32.c:3198
#14 0x00007ff6354b93de in main (argc=1, argv=0xd315b0) at emacs.c:2120

Ok, so noticed that it if s_pfn_Lookup_Account_Sid (LookupAccountSid)
invoked for the *second* time, it blocks for around half a minute
causing the delay.
However, when lucky, the function is invoked only once and it just
works as always.

I didn't analyse the cause for multiple invocations, but added a
simple naive workaround to test the theory - which simply caches the
results of LookupAccountSid.

from w32.c:

struct lookup_account_sid_cache {
    BOOL ret;
    LPTSTR Name;
    LPSTR DomainName;
    SID_NAME_USE peUse;
  };

static BOOL WINAPI
lookup_account_sid (LPCTSTR lpSystemName,
        PSID Sid,
        LPTSTR Name,
        LPDWORD cbName,
        LPTSTR DomainName,
        LPDWORD cbDomainName,
        PSID_NAME_USE peUse)
{
  static struct lookup_account_sid_cache cache = { 0, NULL, NULL, 0 };

  static LookupAccountSid_Proc s_pfn_Lookup_Account_Sid = NULL;
  HMODULE hm_advapi32 = NULL;
  if (is_windows_9x () == TRUE)
    {
      return FALSE;
    }
  if (g_b_init_lookup_account_sid == 0)
    {
      g_b_init_lookup_account_sid = 1;
      hm_advapi32 = LoadLibrary ("Advapi32.dll");
      s_pfn_Lookup_Account_Sid = (LookupAccountSid_Proc)
        get_proc_addr (hm_advapi32, LookupAccountSid_Name);
    }
  if (s_pfn_Lookup_Account_Sid == NULL)
    {
      return FALSE;
    }

  BOOL ret;

  if(!cache.ret) {
   ret = s_pfn_Lookup_Account_Sid (
          lpSystemName,
          Sid,
          Name,
          cbName,
          DomainName,
          cbDomainName,
          peUse);

    if(ret) {
      cache.ret = ret;
      cache.Name = Name && *Name ? strdup(Name) : NULL;
      cache.DomainName = DomainName && *DomainName ? strdup(DomainName) : NULL;
      cache.peUse = peUse ? *peUse : 0;
    }
  }
  else {
    ret = cache.ret;
    if(cache.Name) {
      strncpy(Name, cache.Name, *cbName);
      *cbName = strlen(cache.Name);
    }
    if(cache.DomainName && cbDomainName && *cbDomainName > 0) {
      if(DomainName) strncpy(DomainName, cache.DomainName, *cbDomainName);
      *cbDomainName = strlen(cache.DomainName);
     }
    if(peUse) *peUse = cache.peUse;
  }
  return ret;
}

Tried about 30 times since the fix: works like a charm.
Please change the code - to invoke the offending winapi function only
once, since it obviously punishes the second caller. Or perhaps a
caching mechanism as above.

Cheers to fellow emacsers!
And thx for all the good work form the team

Bye
Vlado