1

I have a utility application to spawn (or respawn) other processes, set up the console window and its title and icon and connect the standard handles to file-like objects (file, named pipe, socket).

In case of any failure this application has to terminate immediately and as fast as possible.

if ( !success )
  ExitProcess( 1 );

Problems come with "Window 10 parallel library loading support in ntdll.dll".

As described in this answer and in this article this feature prevents the fast fail.

Programs which execute in less than 30 seconds will appear to hang due to ntdll!TppWorkerThreadwaiting for the idle timeout before the process terminates.

image

image

Even if I set MaxLoaderThreads in the Registry, the tread pool is created when I call LoadLibrary.

image: MaxLoaderThreads in Registry

My Question is, how to terminate the process without waiting for the loader-threads, without calling ZwCreateUserProcess directly and without modifying the Registry on the target machine.

My idea is to have a replacement for ExitProcess which calls TerminateProcess as follows (and it seems to work).

VOID WINAPI ExitProcessNow( UINT uExitCode )
{
  if ( TerminateProcess( GetCurrentProcess(),
                         uExitCode ) )
    ExitThread( uExitCode );
  else
    ExitProcess( uExitCode );
}

EDIT Some clarification

Assume the following program:

#include <Windows.h>
#ifndef _DEBUG
#pragma comment ( linker, "/ENTRY:EntryPoint" )
#else
#define EntryPoint main
#endif

#define NUMBER_OF_WORKER_THREADS  3

DWORD __stdcall WorkerThreadProc( PVOID __unused )
{
  for ( ;; )
    Sleep( 1000 );
}

void EntryPoint()
{
  for ( int ii = NUMBER_OF_WORKER_THREADS; ii; --ii )
    CloseHandle( CreateThread( NULL, 0, WorkerThreadProc, NULL, 0, NULL ) );
  Sleep( 10000 );
  // For demonstration do not call ExitProcess.
  // ExitProcess( 0 );
}

The initial thread starts three workers, waits 10 seconds and then exits. The workers are running indefinitely. If _DEBUG is not defined, there is no CRT code which calls ExitProcess at the end of main() and the process continues running with three threads at WorkerThreadProc.

This was the situation I observed. My process and/or a spawned process where running with three threads with an entry point at ntdll!Tp....

My code definitively called ExitProcess (after replacing ExitProcess by TerminateProcess it worked).

The point is, in cases when the program has to exit it has to exit immediately, independent of any locks in any library or any thread. Maybe it has something to do with the parallel loading or not, maybe there is as deadlock in my or the spawned code, it doesn't matter. The program has to exit and the process has to become signaled.

The main use case of this program is to respawn other programs when they exit for any reason as /sbin/init does when configured with respawn in /etc/inittab.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • if you call `ExitProcess` - process exit **just**. parallel library loading of course not affect this. your quote related to case when `ExitProcess` **not called**. so you go in **wrong direction**. you not need any replacement for `ExitProcess`. problem in something else – RbMm Jan 08 '20 at 11:49
  • Sometimes processes hang around after ending the program (also e.g. Excel) and I see threads at ntdll.dll!TpReleaseWork. Maybe this is a bug or happens in unexpected conditions, but it happens. To work around this situation I'll use TerminateProcess() to kill myself in a fast-fail-condition. – Daniel Hammerschmidt Jan 08 '20 at 14:03
  • in what your question at all ? absolute unclear. and how here parallel loader related ? – RbMm Jan 08 '20 at 14:04

2 Answers2

0

My Question is, how to terminate the process without waiting for the loader-threads

simply call ExitProcess as usual. this call just terminate process, and of course not wait for loader threads.

Programs which execute in less than 30 seconds will appear to hang due to ntdll!TppWorkerThreadwaiting for the idle timeout before the process terminates.

this is wrong or poorly worded. this is related to case when process not call ExitProcess but simply return from entry point (or terminate self thread).

process exit if:

  • TerminateProcess called (with no error) - unconditionally cause a process to exit. no any user mode cleanup.
  • ExitProcess called. however because here exist user mode cleanup - it can deadlock under some conditions.
  • all threads exit in process. the your quote related exactly to this case. if your "single threaded" application just exit from entry point without call ExitProcess - process exit only in case no other threads in process. with parallel loader exist working threads which usually live up to 30 seconds if no tasks. as result process will exit not immediately. but after 30 seconds. but again - this is only if process not call ExitProcess direct.

in your case - process (spawn7.exe ) :

  • or not call ExitProcess (in this case it can exit after 30 seconds)
  • or deadlock in call ExitProcess - in this case need look it call-stack and debug it

OP say that

Even if I call ExitProcess (withing the 30 seconds while the thread pool ist active) the process is still there with the three threads from ntdll.dll but without my EntryPoint.

this is false and can not be. if ExitProcess called - first what it do - terminate all of the threads in the process, except the calling thread. or deadlock even before this (in call RtlLockHeap for main process heap). so will be:

  • or still exist "EntryPoint" thread in process
  • or only single thread in process (again your "EntryPoint" thread)

if "still there with the three threads from ntdll.dll but no other" - i can definitely say - ExitProcess not called (or hooked)

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • ExitProcess does not terminate the process. It does things that maybe results in ending all threads and finally the process itself ([see the Remaks section of ExitProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess#remarks)). Even if I call ExitProcess (withing the 30 seconds while the thread pool ist active) the process is still there with the three threads from ntdll.dll but without my EntryPoint. If it where as simple as "simply call ExitProcess as usual" I wouln't have asked. – Daniel Hammerschmidt Jan 08 '20 at 12:42
  • @DanielHammerschmidt `ExitProcess` **terminate** process or **deadlock**. parallel loader not affect this at all – RbMm Jan 08 '20 at 12:44
  • @DanielHammerschmidt -exist some error in your code or tests. if `ExitProcess` deadlock - will be still visible thread, which call `ExitProcess` and **no other threads**. if *process is still there with the three threads from ntdll.dll but without my EntryPoint.* - this mean that `ExitProcess` **not called**. because first what `ExitProcess` do - **terminate All of the threads in the process, except the calling thread** – RbMm Jan 08 '20 at 12:48
  • @DanielHammerschmidt - and i not need see Remarks for exitprocess - i perfect know all this. you not say true. from your description follows that you not call `ExitProcess`. attach say debugger for process and look, are it called. – RbMm Jan 08 '20 at 13:02
  • As I noted at top if my question, I need to fast fail `if ( !success ) ExitProcess(1);`. So **I do call ExitProcess**. My question is not about how to repair a faulty condition, my question is how to end, exit or terminate that process in case of a faulty condition immediately if ExitProcess doesn't do the job. BTW: The Remarks section in the vendors documentation says: "Therefore, if you do not know the state of all threads in your process, it is better to call TerminateProcess than ExitProcess." – Daniel Hammerschmidt Jan 08 '20 at 13:52
  • @DanielHammerschmidt - you say not true in some place. or you **not call** `ExitProcess` or *process is still there with the three threads from ntdll.dll but without my EntryPoint* - this is false - this can not be if you call `ExitProcess`. so you need better describe situation. and all this not related to parallel loader. `ExitProcess` is normal call. we must call it in most case. only corrupted state of application or bad written 3-rd party dll can cause deadlock in call `ExitProcess`. and now unclear at all - about what you ask ? – RbMm Jan 08 '20 at 14:00
  • @DanielHammerschmidt - if you **call** `ExitProcess` but process not exit - it deadlock. will be **single** thread in process. and paste here it call-stack for begin. otherwise - you **not call** `ExitProcess` – RbMm Jan 08 '20 at 14:02
  • @DanielHammerschmidt - and *if ExitProcess doesn't do the job.* - `ExitProcess` (if it not hooked, modified) - **never** return. it or terminate process or deadlock. – RbMm Jan 08 '20 at 14:03
  • I don't know what you mean. My programm is a single threaded GUI-Application. After parsing the command line from PEB it opens files, named pipes or sockets, allocates a console window or attaches to another one, and starts the worker process. On any failure and after the worker is signaled I call ExitProcess and need to get signaled immediately (as my main thread is). Threre is always only one thread visible (and the loaders thread pool) (debugger, Process Explorer). Everything works as expected, except (sometimes) ExitProcess (but my thread is signaled in that case). – Daniel Hammerschmidt Jan 08 '20 at 14:28
  • @DanielHammerschmidt - again - your question/problem absolute unclear. from your latest comment look like bugs in your code. as result it some time deadlock/hungs – RbMm Jan 08 '20 at 14:33
  • @DanielHammerschmidt - and you still not want understand - if you call `ExitProcess` - or thread which do this call **deadlock** and not exit (and it will be **single** thread in process). or you **not call** `ExitProcess` at all, your thread **exit**. – RbMm Jan 08 '20 at 14:36
  • @DanielHammerschmidt - what you need - **debug** your process and exactly describe what happens without errors – RbMm Jan 08 '20 at 14:45
0

I cannot reproduce this at the moment, maybe it depends on the day(or night)time.

This situation (three threads in ntdll.dll!TpReleaseWork after exit) not only (sometimes) appears to my program "spawn(7).exe", but also (sometimes) to other (well-known) applications (I can see it in Process Explorer) which I cannot modify. Might be an odd situation in the "new" Feature "Window 10 parallel library loading support in ntdll.dll". Maybe I missed an update, maybe whatever.

Anyway, I need to "fail fast" and because I use the Processes for synchronisation (WaitForSingleObject) I now have two helper funtions.

ExitProcessNow to "kill" myself and WaitForProcessOrMainThread to wait for the target process (or its main thread) recently created with a call to CreateProcess.

VOID
__stdcall
ExitProcessNow( UINT uExitCode )
{
  if ( TerminateProcess( GetCurrentProcess(),
                         uExitCode ) )
    ExitThread( uExitCode );
  else
    ExitProcess( uExitCode );
}

VOID
__stdcall
WaitForProcessOrMainThread( PPROCESS_INFORMATION pProcessInformation,
                            BOOL fWaitForThread )
{
  if ( !fWaitForThread )
    WaitForSingleObject( pProcessInformation->hProcess, INFINITE );
  else
  {
    DWORD ExitCode = 0;
    WaitForSingleObject( pProcessInformation->hThread, INFINITE );
    GetExitCodeThread( pProcessInformation->hThread, &ExitCode );
    TerminateProcess( pProcessInformation->hProcess, ExitCode );
  }
  CloseHandle( pProcessInformation->hThread );
  CloseHandle( pProcessInformation->hProcess );
  pProcessInformation->hThread = NULL;
  pProcessInformation->hProcess = NULL;
}

BTW:

Interestingly, Windows 10 contains a default entry for chrome.exe with MaxLoaderThreads set to 1 to disable parallel loading. Windows 10 Parallel Loading Breakdown

There might be a reason for it. ;-)

  • 1
    you go in wrong direction. parallel loader here unrelated. exist some error in your code, when you not call `ExitProcess` or something else. chrome use *Untrusted Mandatory Level* for process token and other strong restrictions. with such token - impossible load any dll. so working loader threads will fail in chrome. because this it and disabled. only first chrome thread start on *Low Mandatory Level* and less restricted token. only this thread can load dlls and initialize process. then it drop token – RbMm Jan 08 '20 at 16:48
  • all your posted here code - not resolve error in your code, but try hide it. this is wrong way. if thread call `ExitProcess` - it or really terminate process or deadlock. in second case `WaitForSingleObject( pProcessInformation->hThread, INFINITE );` - not help you. you will be wait infinite. from other side - if `pProcessInformation->hThread` exit but process not exit- this 100% mean - it **not call** `ExitProcess`. `TerminateProcess( GetCurrentProcess(), uExitCode ) )` - never return - so any code after it senseless. all what you try todo wrong – RbMm Jan 08 '20 at 16:52
  • I don't know what try to tell me. I told you, there are other programs out there with this behavior. I told - in my question - that TerminateProcess( -1, 0 ) works in situations where ExitProcess( 0 ) does not. I told you, I don't care about the situation, I just need do terminate in case of any error and I need to detect if others (or its main thread) terminate. My posted code does exactly what it shold do: it detects when the callee or its main thread terminates and it terminates its process immediately in case of an error. – Daniel Hammerschmidt Jan 08 '20 at 17:13
  • BTW: I do not know what I have to "spawn". I dont care. I have to make sure it's running and have to restart it if it fails. – Daniel Hammerschmidt Jan 08 '20 at 17:15
  • *I told you, there are other programs out there with this behavior* - this is wrong - other programs just exit when need to exit. *I told - in my question - that TerminateProcess( -1, 0 ) works in situations where ExitProcess( 0 ) does not* - and i told you that you **mistake**. you **not call** `ExitProcess`. *My posted code* - wrong – RbMm Jan 08 '20 at 17:18
  • I guess, you're right. I guess, you know exactly every version and build of Windows in every possible environmet and how it behaves in every configuration. I guess, watchdogs - either hardare or software - are not neccessary. I guess nobody has ever seen a BSOD. I guess you know exactly what I see or have seen when I inspect processes on my computer. So I have to thank you again for your time. – Daniel Hammerschmidt Jan 08 '20 at 18:57