{"id":4743,"date":"2013-04-05T07:00:00","date_gmt":"2013-04-05T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/04\/05\/how-do-i-wait-until-all-processes-in-a-job-have-exited\/"},"modified":"2013-04-05T07:00:00","modified_gmt":"2013-04-05T07:00:00","slug":"how-do-i-wait-until-all-processes-in-a-job-have-exited","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130405-00\/?p=4743","title":{"rendered":"How do I wait until all processes in a job have exited?"},"content":{"rendered":"<p><P>\nA customer was having trouble with job objects,\nspecifically, the customer found that a\n<CODE>Wait&shy;For&shy;Single&shy;Object<\/CODE>\non a job object was not completing\neven though all the processes in the job had exited.\n<\/P>\n<P>\nThis is probably the most frustrating part of job objects:\nA job object does not become signaled when all processes\nhave exited.\n<\/P>\n<BLOCKQUOTE CLASS=\"q\">\nThe state of a job object is set to signaled when all of its processes\nare terminated\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/ms684161(v=vs.85).aspx\">\nbecause the specified end-of-job time limit has been exceeded<\/A>.\nUse <B>Wait&shy;For&shy;Single&shy;Object<\/B>\nor\n<B>Wait&shy;For&shy;Single&shy;Object&shy;Ex<\/B>\nto monitor the job object for this event.\n<\/BLOCKQUOTE>\n<P>\nThe job object becomes signaled only if the end-of-job time limit\nhas been reached.\nIf the processes exit without exceeding the time limit,\nthen the job object remains unsignaled. \nThis is a historical artifact of the original motivation for\ncreating job objects,\nwhich was to manage batch style server applications which\nwere short-lived and usually ran to completion.\nThe original purpose of job objects was to keep those processes\nfrom getting into a runaway state and consuming excessive resources.\nTherefore, the interesting thing from a job object&#8217;s point of view\nwas whether the process being managed in the job had to be killed\nfor exceeding its resource allocation.\n<\/P>\n<P>\nOf course, nowadays, most people use job objects just to wait for\na process tree to exit,\nnot for keeping a server batch process from going runaway.\nThe original motivation for job objects has vanished into the mists\nof time.\n<\/P>\n<P>\nIn order to wait for all processes in a job object to exit,\nyou need to listen for job completion port notifications.\nLet&#8217;s try it:\n<\/P>\n<PRE>\n#define UNICODE\n#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">_UNICODE<\/A>\n#define STRICT\n#include &lt;windows.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;atlbase.h&gt;\n#include &lt;atlalloc.h&gt;\n#include &lt;shlwapi.h&gt;<\/p>\n<p>int __cdecl wmain(int argc, PWSTR argv[])\n{\n CHandle Job(CreateJobObject(nullptr, nullptr));\n if (!Job) {\n  wprintf(L&#8221;CreateJobObject, error %d\\n&#8221;, GetLastError());\n  return 0;\n }<\/p>\n<p> CHandle IOPort(CreateIoCompletionPort(INVALID_HANDLE_VALUE,\n                                       nullptr, 0, 1));\n if (!IOPort) {\n  wprintf(L&#8221;CreateIoCompletionPort, error %d\\n&#8221;,\n          GetLastError());\n  return 0;\n }<\/p>\n<p> JOBOBJECT_ASSOCIATE_COMPLETION_PORT Port;\n Port.CompletionKey = Job;\n Port.CompletionPort = IOPort;\n if (!SetInformationJobObject(Job,\n       JobObjectAssociateCompletionPortInformation,\n       &amp;Port, sizeof(Port))) {\n  wprintf(L&#8221;SetInformation, error %d\\n&#8221;, GetLastError());\n  return 0;\n }<\/p>\n<p> PROCESS_INFORMATION ProcessInformation;\n STARTUPINFO StartupInfo = { sizeof(StartupInfo) };\n PWSTR <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/08\/15\/10195600.aspx\">CommandLine<\/A> = PathGetArgs(GetCommandLine());<\/p>\n<p> if (!CreateProcess(nullptr, CommandLine, nullptr, nullptr,\n                    FALSE, CREATE_SUSPENDED, nullptr, nullptr,\n                    &amp;StartupInfo, &amp;ProcessInformation)) {\n  wprintf(L&#8221;CreateProcess, error %d\\n&#8221;, GetLastError());\n  return 0;\n }<\/p>\n<p> if (!AssignProcessToJobObject(Job,\n         ProcessInformation.hProcess)) {\n  wprintf(L&#8221;Assign, error %d\\n&#8221;, GetLastError());\n  return 0;\n }<\/p>\n<p> ResumeThread(ProcessInformation.hThread);\n CloseHandle(ProcessInformation.hThread);\n CloseHandle(ProcessInformation.hProcess);<\/p>\n<p> DWORD CompletionCode;\n ULONG_PTR CompletionKey;\n LPOVERLAPPED Overlapped;<\/p>\n<p> while (GetQueuedCompletionStatus(IOPort, &amp;CompletionCode,\n          &amp;CompletionKey, &amp;Overlapped, INFINITE) &amp;&amp;\n          !((HANDLE)CompletionKey == Job &amp;&amp;\n           CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) {\n  wprintf(L&#8221;Still waiting&#8230;\\n&#8221;);\n }<\/p>\n<p> wprintf(L&#8221;All done\\n&#8221;);<\/p>\n<p> return 0;\n}\n<\/PRE>\n<P>\nThe first few steps are to\ncreate a job object, then associate it with\na completion port.\nWe set the completion key to be the job itself,\njust in case some other I\/O gets queued to our port that\nwe aren&#8217;t expecting.\n(Not sure how that could happen, but we&#8217;ll watch out for it.)\n<\/P>\n<P>\nNext, we launch the desired process into the job.\nIt&#8217;s important that we create it suspended so that we can\nput it into the job before it exits or does something else\nthat would mess up our bookkeeping.\nAfter it is safely assigned to the job, we can resume\nthe process&#8217;s main thread,\nat which point we have no use for the thread and process handles.\n<\/P>\n<P>\nFinally, we go into a loop pulling events from the I\/O\ncompletion port.\nIf the event is not &#8220;this job has no more active processes&#8221;,\nthen we just keep waiting.\n<\/P>\n<P>\nOfficially, the last parameter to\n<CODE>Get&shy;Queued&shy;Completion&shy;Status<\/CODE>\nis\n<CODE>lpNumber&shy;Of&shy;Bytes<\/CODE>,\nbut the job notifications are posted via\n<CODE>Post&shy;Queued&shy;Completion&shy;Status<\/CODE>,\nand\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2007\/05\/25\/2854506.aspx\">\nthe parameters to\n<CODE>Post&shy;Queued&shy;Completion&shy;Status<\/CODE>\ncan mean anything you want<\/A>.\nIn particular,\nwhen the job object posts notifications, it\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/ms684141(v=vs.85).aspx\">\nputs the notification\ncode in the &#8220;number of bytes&#8221; field<\/A>.\n<\/P>\n<P>\nRun this program with, say, <CODE>cmd<\/CODE> on the command line.\nFrom the nested <CODE>cmd<\/CODE> prompt,\ntype\n<CODE>start notepad<\/CODE>.\nThen type <CODE>exit<\/CODE> to exit the nested command prompt.\nObserve that our program is still waiting,\nbecause it&#8217;s waiting for Notepad to exit.\nWhen you exit Notepad,\nour program finally prints <TT>&#8220;All done&#8221;<\/TT>.\n<P>\n<B>Exercise<\/B>:\nThe statement &#8220;Not sure how that could happen&#8221; is a lie.\nName a case where a spurious notification could arrive,\nand how the code can protect against it.\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A customer was having trouble with job objects, specifically, the customer found that a Wait&shy;For&shy;Single&shy;Object on a job object was not completing even though all the processes in the job had exited. This is probably the most frustrating part of job objects: A job object does not become signaled when all processes have exited. The [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-4743","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A customer was having trouble with job objects, specifically, the customer found that a Wait&shy;For&shy;Single&shy;Object on a job object was not completing even though all the processes in the job had exited. This is probably the most frustrating part of job objects: A job object does not become signaled when all processes have exited. The [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=4743"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4743\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=4743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=4743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=4743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}