Changeset 3357


Ignore:
Timestamp:
Jun 5, 2020, 4:49:14 PM (5 years ago)
Author:
bird
Message:

kmk,winchildren.c: Some adjustments to the job object thing, adding a 'login' mode (default) to allow two independent kmk instances to share an mkpdbsrv.exe instance. Split the 'nokill' out of the mode and into a separate option.

Location:
trunk/src/kmk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/kmk/main.c

    r3353 r3357  
    424424/* --job-object-name=name */
    425425char *win_job_object_name = NULL;
     426
     427/** --job-object-no-kill.   */
     428int win_job_object_no_kill = 0;
    426429#endif
    427430
     
    563566    N_("\
    564567  --job-object=mode           Windows job object mode:\n\
    565                                 root-kill   = Root make instance only, kill all\n\
    566                                               process when root exits. (def)\n\
    567                                 root-nokill = Root make instance only.\n\
    568                                               No killing.\n\
    569                                 each-kill   = Each make instances, kill all\n\
    570                                               children when an instance exits.\n\
    571                                 each-nokill = Each make instances, no killing.\n\
    572                                 none        = No job objects.\n"),
     568                                login = Per login session (default).\n\
     569                                root  = Root make instance only.\n\
     570                                each  = Each make instances.\n\
     571                                none  = No job objects.\n"),
    573572    N_("\
    574573  --job-object-name=name      Name of windows job object to open or create.\n\
    575                               The default name is 'kmk-job-obj-<date>Z<pid>'.\n"),
     574                              The default name depends on the level.\n"),
     575    N_("\
     576  --job-object-no-kill        Do not kill orphaned child processes when done.\n"),
    576577#endif
    577578    NULL
     
    664665#if defined (WINDOWS32) && defined (CONFIG_NEW_WIN_CHILDREN)
    665666    { CHAR_MAX+58, string, &win_job_object_mode, 1, 1, 1, 0, 0, "job-object" },
    666     { CHAR_MAX+59, string, &win_job_object_name,  1, 1, 1, 0, 0,
     667    { CHAR_MAX+59, string, &win_job_object_name, 1, 1, 1, 0, 0,
    667668      "job-object-name" },
     669    { CHAR_MAX+60, flag, &win_job_object_no_kill, 1, 1, 1, 0, 0,
     670      "job-object-no-kill" },
    668671#endif
    669672    { 0, 0, 0, 0, 0, 0, 0, 0, 0 }
     
    37603763  /* validate the job object mode value . */
    37613764  if (win_job_object_mode == NULL)
    3762     win_job_object_mode = xstrdup("root-kill");
     3765    win_job_object_mode = xstrdup ("login");
    37633766  else if (   strcmp (win_job_object_mode, "none") != 0
    3764            && strcmp (win_job_object_mode, "root-kill") != 0
    3765            && strcmp (win_job_object_mode, "root-nokill") != 0
    3766            && strcmp (win_job_object_mode, "each-kill") != 0
    3767            && strcmp (win_job_object_mode, "each-nokill") != 0)
     3767           && strcmp (win_job_object_mode, "login") != 0
     3768           && strcmp (win_job_object_mode, "root") != 0
     3769           && strcmp (win_job_object_mode, "each") != 0)
    37683770    OS (fatal, NILF, _("unknown job object mode '%s'"), win_job_object_mode);
    37693771#endif
  • trunk/src/kmk/w32/winchildren.c

    r3355 r3357  
    111111extern const char *win_job_object_mode;
    112112extern const char *win_job_object_name;
     113extern int         win_job_object_no_kill;
    113114
    114115
     
    378379#endif
    379380
     381/** The job object for this make instance, if we created/opened one. */
     382static HANDLE               g_hJob = NULL;
     383
     384
     385/*********************************************************************************************************************************
     386*   Internal Functions                                                                                                           *
     387*********************************************************************************************************************************/
     388static void mkWinChildInitJobObjectAssociation(void);
    380389
    381390#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer)
     
    487496
    488497    /*
    489      * Depending on the --job-object=mode value, we typically create a job
    490      * object here if we're the root make instance.  The job object is then
    491      * typically configured to kill all remaining processes when the root make
    492      * terminates, so that there aren't any stuck processes around messing up
    493      * subsequent builds.  This is very handy on build servers, provided of
    494      * course that, there aren't parallel kmk instance that wants to share
    495      * mspdbsrv.exe or something like that.
    496      *
    497      * If we're not in a -kill mode, the job object is pretty pointless for
    498      * manual cleanup as the job object becomes invisible (or something) when
    499      * the last handle to it closes, i.e. hJob below.  On windows 8 and later
    500      * it looks like any orphaned children are immediately assigned to the
    501      * parent job object. Too bad for kmk_kill and such.
    502      *
    503      * win_job_object_mode values: none, root-kill, root-nokill, all-kill, all-nokill
    504      */
    505     if (   strcmp(win_job_object_mode, "none") != 0
    506         && (   makelevel == 0
    507             || strstr(win_job_object_mode, "root") == NULL))
    508     {
    509         HANDLE      hJob = NULL;
    510         BOOL        fCreate = TRUE;
    511         const char *pszJobName = win_job_object_name;
    512         if (pszJobName)
    513         {
    514             /* Try open it first, in case it already exists. If it doesn't we'll try create it. */
    515             fCreate = FALSE;
    516             hJob = OpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS, FALSE /*bInheritHandle*/, pszJobName);
    517             if (hJob)
    518             {
    519                 DWORD dwErr = GetLastError();
    520                 if (dwErr == ERROR_PATH_NOT_FOUND || dwErr == ERROR_FILE_NOT_FOUND)
    521                     fCreate = TRUE;
    522                 else
    523                     OSN(message, 0, _("OpenJobObjectA(,,%s) failed: %u"), pszJobName, GetLastError());
    524             }
    525         }
    526 
    527         if (fCreate)
    528         {
    529             char       szJobName[128];
    530             SYSTEMTIME Now = {0};
    531             GetSystemTime(&Now);
    532             snprintf(szJobName, sizeof(szJobName) / 2, "kmk-job-obj-%04u-%02u-%02uT%02u-%02u-%02uZ%u",
    533                      Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond, getpid());
    534             if (!pszJobName)
    535                 pszJobName = szJobName;
    536 
    537             hJob = CreateJobObjectA(NULL, pszJobName);
    538             if (hJob)
    539             {
    540                 /* We need to set the BREAKAWAY_OK flag, as we don't want make CreateProcess fail if
    541                    someone tries to break way.  Also set KILL_ON_JOB_CLOSE unless 'nokill' is given. */
    542                 JOBOBJECT_EXTENDED_LIMIT_INFORMATION Info;
    543                 DWORD cbActual = 0;
    544                 memset(&Info, 0, sizeof(Info));
    545                 if (QueryInformationJobObject(hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info), &cbActual))
    546                 {
    547                     Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
    548                     if (strstr(win_job_object_mode, "nokill") == NULL)
    549                         Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    550                     else
    551                         Info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    552                     if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info)))
    553                         OSSN(message, 0, _("SetInformationJobObject(%s,JobObjectExtendedLimitInformation,{%s},) failed: %u"),
    554                              pszJobName, win_job_object_mode, GetLastError());
    555                 }
    556                 else
    557                     OSN(message, 0, _("QueryInformationJobObject(%s,JobObjectExtendedLimitInformation,,,) failed: %u"),
    558                         pszJobName, GetLastError());
    559             }
    560             else
    561                 OSN(message, 0, _("CreateJobObjectA(NULL,%s) failed: %u"), pszJobName, GetLastError());
    562         }
    563 
    564         if (hJob)
    565         {
    566             if (!(AssignProcessToJobObject(hJob, GetCurrentProcess())))
    567                 OSN(message, 0, _("AssignProcessToJobObject(%s, me) failed: %u"), pszJobName, GetLastError());
    568         }
    569     }
     498     * Associate with a job object.
     499     */
     500    mkWinChildInitJobObjectAssociation();
    570501
    571502    /*
     
    638569
    639570/**
     571 * Create or open a job object for this make instance and its children.
     572 *
     573 * Depending on the --job-object=mode value, we typically create/open a job
     574 * object here if we're the root make instance.  The job object is then
     575 * typically configured to kill all remaining processes when the root make
     576 * terminates, so that there aren't any stuck processes around messing up
     577 * subsequent builds.  This is very handy on build servers.
     578 *
     579 * If we're it no-kill mode, the job object is pretty pointless for manual
     580 * cleanup as the job object becomes invisible (or something) when the last
     581 * handle to it closes, i.e. g_hJob.  On windows 8 and later it looks
     582 * like any orphaned children are immediately assigned to the parent job
     583 * object.  Too bad for kmk_kill and such.
     584 *
     585 * win_job_object_mode values: login, root, each, none
     586 */
     587static void mkWinChildInitJobObjectAssociation(void)
     588{
     589    BOOL        fCreate   = TRUE;
     590    char        szJobName[128];
     591    const char *pszJobName = win_job_object_name;
     592
     593    /* Skip if disabled. */
     594    if (strcmp(win_job_object_mode, "none") == 0)
     595        return;
     596
     597    /* Skip if not root make instance, unless we're having one job object
     598       per make instance. */
     599    if (   makelevel != 0
     600        && strcmp(win_job_object_mode, "each") != 0)
     601        return;
     602
     603    /* Format the the default job object name if --job-object-name
     604       wasn't given. */
     605    if (!pszJobName || *pszJobName == '\0')
     606    {
     607        pszJobName = szJobName;
     608        if (strcmp(win_job_object_mode, "login") == 0)
     609        {
     610            /* Use the AuthenticationId like mspdbsrv.exe does. */
     611            HANDLE hToken;
     612            if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
     613            {
     614                TOKEN_STATISTICS TokenStats;
     615                DWORD            cbRet = 0;
     616                memset(&TokenStats, 0, sizeof(TokenStats));
     617                if (GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbRet))
     618                    snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-login-%08x.%08x",
     619                             (unsigned)TokenStats.AuthenticationId.HighPart, (unsigned)TokenStats.AuthenticationId.LowPart);
     620                else
     621                {
     622                    ONN(message, 0, _("GetTokenInformation failed: %u (cbRet=%u)"), GetLastError(), cbRet);
     623                    return;
     624                }
     625                CloseHandle(hToken);
     626            }
     627            else
     628            {
     629                ON(message, 0, _("OpenProcessToken failed: %u"), GetLastError());
     630                return;
     631            }
     632        }
     633        else
     634        {
     635            SYSTEMTIME Now = {0};
     636            GetSystemTime(&Now);
     637            snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-%04u-%02u-%02uT%02u-%02u-%02uZ%u",
     638                     Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond, getpid());
     639        }
     640    }
     641
     642    /* In login mode and when given a job object name, we try open it first. */
     643    if (   win_job_object_name
     644        || strcmp(win_job_object_mode, "login") == 0)
     645    {
     646        g_hJob = OpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS, win_job_object_no_kill /*bInheritHandle*/, pszJobName);
     647        if (g_hJob)
     648            fCreate = FALSE;
     649        else
     650        {
     651            DWORD dwErr = GetLastError();
     652            if (dwErr != ERROR_PATH_NOT_FOUND && dwErr != ERROR_FILE_NOT_FOUND)
     653            {
     654                OSN(message, 0, _("OpenJobObjectA(,,%s) failed: %u"), pszJobName, GetLastError());
     655                return;
     656            }
     657        }
     658    }
     659
     660    if (fCreate)
     661    {
     662        SECURITY_ATTRIBUTES SecAttr = { sizeof(SecAttr), NULL, TRUE /*bInheritHandle*/ };
     663        g_hJob = CreateJobObjectA(win_job_object_no_kill ? &SecAttr : NULL, pszJobName);
     664        if (g_hJob)
     665        {
     666            /* We need to set the BREAKAWAY_OK flag, as we don't want make CreateProcess
     667               fail if someone tries to break way.  Also set KILL_ON_JOB_CLOSE unless
     668               --job-object-no-kill is given. */
     669            JOBOBJECT_EXTENDED_LIMIT_INFORMATION Info;
     670            DWORD cbActual = 0;
     671            memset(&Info, 0, sizeof(Info));
     672            if (QueryInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info), &cbActual))
     673            {
     674                Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
     675                if (!win_job_object_no_kill)
     676                    Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
     677                else
     678                    Info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
     679                if (!SetInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info)))
     680                    OSSN(message, 0, _("SetInformationJobObject(%s,JobObjectExtendedLimitInformation,{%s},) failed: %u"),
     681                         pszJobName, win_job_object_mode, GetLastError());
     682            }
     683            else
     684                OSN(message, 0, _("QueryInformationJobObject(%s,JobObjectExtendedLimitInformation,,,) failed: %u"),
     685                    pszJobName, GetLastError());
     686        }
     687        else
     688        {
     689            OSN(message, 0, _("CreateJobObjectA(NULL,%s) failed: %u"), pszJobName, GetLastError());
     690            return;
     691        }
     692    }
     693
     694    /* Make it our job object. */
     695    if (!(AssignProcessToJobObject(g_hJob, GetCurrentProcess())))
     696        OSN(message, 0, _("AssignProcessToJobObject(%s, me) failed: %u"), pszJobName, GetLastError());
     697}
     698
     699/**
    640700 * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
    641701 * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
     
    11551215
    11561216/**
    1157  * Does the actual process creation given.
     1217 * Does the actual process creation.
    11581218 *
    11591219 * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
     
    13041364        assert(fRet);
    13051365#endif
     1366
     1367        /*
     1368         * Inject the job object if we're in a non-killing mode, to postpone
     1369         * the closing of the job object and maybe make it more useful.
     1370         */
     1371        if (win_job_object_no_kill && g_hJob)
     1372        {
     1373            HANDLE hWhatever = INVALID_HANDLE_VALUE;
     1374            DuplicateHandle(GetCurrentProcess(), g_hJob, ProcInfo.hProcess, &hWhatever, GENERIC_ALL,
     1375                            TRUE /*bInheritHandle*/, DUPLICATE_SAME_ACCESS);
     1376        }
    13061377
    13071378        /*
     
    35363607
    35373608/**
    3538  * Emulate execv() for restarting kmk after one ore more makefiles has been
    3539  * made.
     3609 * Emulate execv() for restarting kmk after one or more makefiles has been made.
    35403610 *
    35413611 * Does not return.
Note: See TracChangeset for help on using the changeset viewer.