/*****************************************************************************\
 * job_scheduler.c - manage the scheduling of pending jobs in priority order
 *	Note there is a global job list (job_list)
 *****************************************************************************
 *  Copyright (C) 2002-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Copyright (C) SchedMD LLC.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Morris Jette <jette1@llnl.gov>
 *  CODE-OCEC-09-009. All rights reserved.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "config.h"

#include <ctype.h>
#include <errno.h>
#include <poll.h>
#include <signal.h> /* for SIGKILL */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "src/common/assoc_mgr.h"
#include "src/common/cpu_frequency.h"
#include "src/common/env.h"
#include "src/common/group_cache.h"
#include "src/common/job_features.h"
#include "src/common/list.h"
#include "src/common/macros.h"
#include "src/common/node_features.h"
#include "src/common/parse_time.h"
#include "src/common/strlcpy.h"
#include "src/common/threadpool.h"
#include "src/common/timers.h"
#include "src/common/track_script.h"
#include "src/common/uid.h"
#include "src/common/xassert.h"
#include "src/common/xstring.h"

#include "src/interfaces/accounting_storage.h"
#include "src/interfaces/acct_gather.h"
#include "src/interfaces/burst_buffer.h"
#include "src/interfaces/gres.h"
#include "src/interfaces/node_features.h"
#include "src/interfaces/preempt.h"
#include "src/interfaces/prep.h"
#include "src/interfaces/select.h"
#include "src/interfaces/switch.h"

#include "src/slurmctld/acct_policy.h"
#include "src/slurmctld/agent.h"
#include "src/slurmctld/fed_mgr.h"
#include "src/slurmctld/gang.h"
#include "src/slurmctld/locks.h"
#include "src/slurmctld/job_scheduler.h"
#include "src/slurmctld/licenses.h"
#include "src/slurmctld/locks.h"
#include "src/slurmctld/node_scheduler.h"
#include "src/slurmctld/power_save.h"
#include "src/slurmctld/proc_req.h"
#include "src/slurmctld/reservation.h"
#include "src/slurmctld/slurmctld.h"
#include "src/slurmctld/state_save.h"

#include "src/stepmgr/gres_stepmgr.h"
#include "src/stepmgr/srun_comm.h"
#include "src/stepmgr/stepmgr.h"

#ifndef CORRESPOND_ARRAY_TASK_CNT
#  define CORRESPOND_ARRAY_TASK_CNT 10
#endif
#define BUILD_TIMEOUT 2000000	/* Max build_job_queue() run time in usec */

typedef enum {
	ARRAY_SPLIT_BURST_BUFFER,
	ARRAY_SPLIT_AFTER_CORR,
} array_split_type_t;

typedef struct {
	list_t *job_list;
	int pend_cnt_limit;
	char *reason_msg;
	array_split_type_t type;
} split_job_t;

typedef enum {
	SEP_DEPEND_OR,
	SEP_DEPEND_AND,
	SEP_DEPEND_ANY,
} sep_depend_t;

typedef struct {
	bool backfill;
	bool clear_start;
	int job_prio_pairs;
	job_record_t *job_ptr;
	list_t *job_queue;
	time_t *last_log_time;
	time_t now;
	int prio_inx;
	struct timeval start_tv;
	int tested_jobs;
} build_job_queue_for_part_t;

typedef struct {
	bool completing;
	bitstr_t *eff_cg_bitmap;
	time_t recent;
} job_is_comp_t;

typedef struct {
	uint32_t prio;
	bool set;
} part_prios_same_t;

typedef struct {
	char *cg_part_str;
	char *cg_part_str_pos;
	bitstr_t *eff_cg_bitmap;
} part_reduce_frag_t;

typedef struct {
	job_record_t *het_job;
	job_record_t *het_job_leader;
	job_record_t *job_ptr;
} het_job_ready_t;

typedef struct {
	job_record_t *het_job_leader;
	int het_job_offset;
	batch_job_launch_msg_t *launch_msg_ptr;
} het_job_env_t;

typedef struct {
	int expand_cnt;
	bool is_remote_cluster;
	job_record_t *job_ptr;
	list_t *unique_depend_list;
	int rc;
	int select_hetero;
} update_dependency_args_t;

typedef struct {
	job_record_t *job_ptr;
	char *sep;
	bool set_or_flag;
} depend_str_t;

typedef struct {
	bool and_failed;
	bool changed;
	bool has_local_depend;
	bool has_unfulfilled;
	job_record_t *job_ptr;
	bool or_flag;
	bool or_satisfied;
} test_job_dep_t;

typedef struct {
	uint64_t cume_space_time;
	job_record_t *job_ptr;
	uint32_t part_cpus_per_node;
} delay_start_t;

typedef struct {
	job_record_t *job_ptr;
	time_t now;
	int rc;
	will_run_response_msg_t **resp;
} job_start_data_t;

typedef struct {
	int bracket;
	bool can_reboot;
	char *debug_str;
	char *features;
	list_t *feature_list;
	bool has_xand;
	bool has_mor;
	int paren;
	int rc;
	bool skip_validation;
} valid_feature_t;

static batch_job_launch_msg_t *_build_launch_job_msg(job_record_t *job_ptr,
						     uint16_t protocol_version);
static bool	_job_runnable_test1(job_record_t *job_ptr, bool clear_start);
static bool	_job_runnable_test2(job_record_t *job_ptr, time_t now,
				    bool check_min_time);
static bool _scan_depend(list_t *dependency_list, job_record_t *job_ptr);
static void *	_sched_agent(void *args);
static void _set_schedule_exit(schedule_exit_t code);
static int	_schedule(bool full_queue);
static int	_valid_batch_features(job_record_t *job_ptr, bool can_reboot);
static int _valid_feature_list(job_record_t *job_ptr,
			       valid_feature_t *valid_feature,
			       bool is_reservation);
static int	_valid_node_feature(char *feature, bool can_reboot);
static int	build_queue_timeout = BUILD_TIMEOUT;
static int	correspond_after_task_cnt = CORRESPOND_ARRAY_TASK_CNT;

pthread_mutex_t sched_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sched_cond = PTHREAD_COND_INITIALIZER;
static bool sched_full_queue = false;
int sched_requests = 0;
bool sched_running = false;
bool sched_alive = false;
static struct timeval sched_last = {0, 0};

static uint32_t max_array_size = NO_VAL;
static bool bf_hetjob_immediate = false;
static uint16_t bf_hetjob_prio = 0;
static int sched_min_interval = 2;

static int bb_array_stage_cnt = 10;
extern diag_stats_t slurmctld_diag_stats;

static int _find_singleton_job (void *x, void *key)
{
	job_record_t *qjob_ptr = (job_record_t *) x;
	job_record_t *job_ptr = (job_record_t *) key;

	xassert (qjob_ptr->magic == JOB_MAGIC);

	/*
	 * get user jobs with the same user and name
	 */
	if (qjob_ptr->user_id != job_ptr->user_id)
		return 0;
	if (qjob_ptr->name && job_ptr->name &&
	    xstrcmp(qjob_ptr->name, job_ptr->name))
		return 0;
	/*
	 * already running/suspended job or previously
	 * submitted pending job
	 * and not a het job, or not part of the same het job
	 */
	if ((IS_JOB_RUNNING(qjob_ptr) || IS_JOB_SUSPENDED(qjob_ptr) ||
	     (IS_JOB_PENDING(qjob_ptr) &&
	      (qjob_ptr->job_id < job_ptr->job_id))) &&
	    (!job_ptr->het_job_id ||
	     (job_ptr->het_job_id != qjob_ptr->het_job_id))) {
		return 1;
	}

	return 0;
}

static int _queue_resv_list(void *x, void *key)
{
	job_queue_req_t *job_queue_req = (job_queue_req_t *) key;

	job_queue_req->resv_ptr = (slurmctld_resv_t *) x;

	if ((job_queue_req->job_ptr->bit_flags & JOB_PART_ASSIGNED) &&
	    job_queue_req->resv_ptr->part_ptr)
		job_queue_req->part_ptr = job_queue_req->resv_ptr->part_ptr;

	job_queue_append_internal(job_queue_req);

	return 0;
}

static void _job_queue_append(list_t *job_queue, job_record_t *job_ptr,
			      uint32_t prio)
{
	job_queue_req_t job_queue_req = { .job_ptr = job_ptr,
					  .job_queue = job_queue,
					  .part_ptr = job_ptr->part_ptr,
					  .prio = prio };

	/* We have multiple reservations, process and end here */
	if (job_ptr->resv_list) {
		list_for_each(job_ptr->resv_list, _queue_resv_list,
			      &job_queue_req);
		return;
	}

	job_queue_append_internal(&job_queue_req);

	/*
	 * This means we requested a specific reservation, don't do any magnetic
	 * ones
	 */
	if (job_ptr->resv_name)
		return;

	/*
	 * For het jobs, backfill makes a plan for each component; however,
	 * backfill doesn't track magnetic reservations in the plan, so backfill
	 * can't start hetjobs in a magnetic reservation unless the het job
	 * explicitly requests the magnetic reservation.
	 *
	 * Also, if there is a magnetic reservation that starts in the future,
	 * backfill will not be able to start the het job if there is a separate
	 * magnetic reservation queue record for the component. So, don't create
	 * a separate magnetic reservation queue record for het jobs.
	 */
	if (job_ptr->het_job_id)
		return;

	job_resv_append_magnetic(&job_queue_req);
}

/* Job test for ability to run now, excludes partition specific tests */
static bool _job_runnable_test1(job_record_t *job_ptr, bool sched_plugin)
{
	bool job_indepen = false;
	time_t now = time(NULL);

	xassert(job_ptr->magic == JOB_MAGIC);
	if (!IS_JOB_PENDING(job_ptr) || IS_JOB_COMPLETING(job_ptr))
		return false;

	if (IS_JOB_REVOKED(job_ptr))
		return false;

	if ((job_ptr->details && job_ptr->details->prolog_running) ||
	    (job_ptr->step_list && list_count(job_ptr->step_list))) {
		/* Job's been requeued and the
		 * previous run hasn't finished yet */
		job_ptr->state_reason = WAIT_CLEANING;
		xfree(job_ptr->state_desc);
		last_job_update = now;
		sched_debug3("%pJ. State=PENDING. Reason=Cleaning.", job_ptr);
		return false;
	}

	job_indepen = job_independent(job_ptr);
	if (sched_plugin)
		job_ptr->start_time = (time_t) 0;
	if (job_ptr->priority == 0)	{ /* held */
		if ((job_ptr->state_reason != FAIL_BAD_CONSTRAINTS) &&
		    (job_ptr->state_reason != FAIL_BURST_BUFFER_OP) &&
		    (job_ptr->state_reason != WAIT_HELD) &&
		    (job_ptr->state_reason != WAIT_HELD_USER) &&
		    (job_ptr->state_reason != WAIT_MAX_REQUEUE) &&
		    (job_ptr->state_reason != WAIT_RESV_INVALID) &&
		    (job_ptr->state_reason != WAIT_RESV_DELETED)) {
			job_ptr->state_reason = WAIT_HELD;
			xfree(job_ptr->state_desc);
			last_job_update = now;
		}
		sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u.",
			     job_ptr,
			     job_state_string(job_ptr->job_state),
			     job_state_reason_string(job_ptr->state_reason),
			     job_ptr->priority);
		return false;
	}

	if (!job_indepen &&
	    ((job_ptr->state_reason == WAIT_HELD) ||
	     (job_ptr->state_reason == WAIT_HELD_USER))) {
		/* released behind active dependency? */
		job_ptr->state_reason = WAIT_DEPENDENCY;
		xfree(job_ptr->state_desc);
		last_job_update = now;
	}

	if (!job_indepen)	/* can not run now */
		return false;

	return true;
}

/*
 * Job and partition tests for ability to run now
 * IN job_ptr - job to test
 * IN now - update time
 * IN check_min_time - If set, test job's minimum time limit
 *		otherwise test maximum time limit
 */
static bool _job_runnable_test2(job_record_t *job_ptr, time_t now,
				bool check_min_time)
{
	int reason;

	reason = job_limits_check(&job_ptr, check_min_time);
	if ((reason != job_ptr->state_reason) &&
	    ((reason != WAIT_NO_REASON) ||
	     (job_state_reason_check(job_ptr->state_reason, JSR_PART)))) {
		job_ptr->state_reason = reason;
		xfree(job_ptr->state_desc);
		last_job_update = now;
	}
	if (reason != WAIT_NO_REASON)
		return false;
	return true;
}

/*
 * Job, reservation and partition tests for ability to run now.
 * If a job is submitted to multiple partitions, don't consider partitions
 * on which the job would not fit given the current set of nodes in the
 * reservation.
 * IN job_ptr - job to test
 * IN part_ptr - partition to test
 */
static bool _job_runnable_test3(job_record_t *job_ptr, part_record_t *part_ptr)
{
	if (job_ptr->resv_ptr && job_ptr->resv_ptr->node_bitmap &&
	    !(job_ptr->resv_ptr->flags & RESERVE_FLAG_FLEX) &&
	    part_ptr && part_ptr->node_bitmap &&
	    (bit_overlap(job_ptr->resv_ptr->node_bitmap, part_ptr->node_bitmap)
	     < job_ptr->node_cnt_wag))
		return false;
	return true;
}

static int _find_depend_after_corr(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;

	if (dep_ptr->depend_type == SLURM_DEPEND_AFTER_CORRESPOND)
		return 1;

	return 0;
}

static job_record_t *_split_job_on_schedule_recurse(
	job_record_t *job_ptr, split_job_t *split_job)
{
	job_record_t *new_job_ptr;
	int array_task_id;

	if (num_pending_job_array_tasks(job_ptr->array_job_id) >=
	    split_job->pend_cnt_limit)
		return job_ptr;

	if (job_ptr->array_recs->task_cnt < 1)
		return job_ptr;

	array_task_id = bit_ffs(job_ptr->array_recs->task_id_bitmap);
	if (array_task_id < 0)
		return job_ptr;

	if (job_ptr->array_recs->task_cnt == 1) {
		job_ptr->array_task_id = array_task_id;
		new_job_ptr = job_array_post_sched(job_ptr, false);
		if (new_job_ptr != job_ptr) {
			if (!split_job->job_list)
				split_job->job_list = list_create(NULL);
			list_append(split_job->job_list, new_job_ptr);
		}
		if (job_ptr->details &&
		    job_ptr->details->dependency &&
		    job_ptr->details->depend_list)
			fed_mgr_submit_remote_dependencies(job_ptr,
							   false,
							   false);
		return new_job_ptr;
	}

	job_ptr->array_task_id = array_task_id;
	new_job_ptr = job_array_split(job_ptr, false);
	debug("%s: Split out %pJ for %s use",
	      __func__, job_ptr, split_job->reason_msg);
	job_state_set(new_job_ptr, JOB_PENDING);
	new_job_ptr->start_time = (time_t) 0;

	if (!split_job->job_list)
		split_job->job_list = list_create(NULL);
	list_append(split_job->job_list, new_job_ptr);

	/*
	 * Do NOT clear db_index here, it is handled when task_id_str
	 * is created elsewhere.
	 */

	if (split_job->type == ARRAY_SPLIT_BURST_BUFFER)
		(void) bb_g_job_validate2(new_job_ptr, NULL);

	/*
	 * See if we need to spawn off any more since the new_job_ptr now has
	 * ->array_recs.
	 */
	return _split_job_on_schedule_recurse(new_job_ptr, split_job);
}

static int _split_job_on_schedule(void *x, void *arg)
{
	job_record_t *job_ptr = x;
	split_job_t *split_job = arg;

	if (!IS_JOB_PENDING(job_ptr) ||
	    !job_ptr->array_recs ||
	    !job_ptr->array_recs->task_id_bitmap ||
	    (job_ptr->array_task_id != NO_VAL))
		return 0;
	/*
	 * Create individual job records for job arrays that need burst buffer
	 * staging
	 */
	if (job_ptr->burst_buffer) {
		split_job->pend_cnt_limit = bb_array_stage_cnt;
		split_job->reason_msg = "burst buffer";
		split_job->type = ARRAY_SPLIT_BURST_BUFFER;
		job_ptr = _split_job_on_schedule_recurse(job_ptr, split_job);
	}

	/*
	 * Create individual job records for job arrays with
	 * depend_type == SLURM_DEPEND_AFTER_CORRESPOND
	 */
	if (job_ptr->details &&
	    job_ptr->details->depend_list &&
	    list_count(job_ptr->details->depend_list) &&
	    list_find_first(job_ptr->details->depend_list,
			    _find_depend_after_corr,
			    NULL)) {
		split_job->pend_cnt_limit = correspond_after_task_cnt;
		split_job->reason_msg = "SLURM_DEPEND_AFTER_CORRESPOND";
		split_job->type = ARRAY_SPLIT_AFTER_CORR;
		/* If another thing is added after this set job_ptr as above */
		(void) _split_job_on_schedule_recurse(job_ptr, split_job);
	}

	return 0;
}

static int _transfer_job_list(void *x, void *arg)
{
	list_append(job_list, x);

	return 0;
}

static int _build_job_queue_for_qos(void *x, void *arg)
{
	build_job_queue_for_part_t *setup_job = arg;
	job_record_t *job_ptr = setup_job->job_ptr;

	job_ptr->qos_ptr = x;

	/*
	 * priority_array index matches part_ptr_list * qos_list
	 * position: increment inx
	 */
	setup_job->prio_inx++;

	if (!_job_runnable_test2(job_ptr, setup_job->now, setup_job->backfill))
		return 0;

	setup_job->job_prio_pairs++;
	if (job_ptr->prio_mult && job_ptr->prio_mult->priority_array) {
		_job_queue_append(setup_job->job_queue, job_ptr,
				  job_ptr->prio_mult->
				  priority_array[setup_job->prio_inx]);
	} else {
		_job_queue_append(setup_job->job_queue, job_ptr,
				  job_ptr->priority);
	}

	return 0;
}

static int _build_job_queue_for_part(void *x, void *arg)
{
	build_job_queue_for_part_t *setup_job = arg;
	job_record_t *job_ptr = setup_job->job_ptr;

	job_ptr->part_ptr = x;

	if (job_ptr->qos_list) {
		(void) list_for_each(job_ptr->qos_list,
				     _build_job_queue_for_qos,
				     setup_job);
	} else {
		(void) _build_job_queue_for_qos(job_ptr->qos_ptr, setup_job);
	}

	return 0;
}

static int _foreach_job_is_completing(void *x, void *arg)
{
	job_record_t *job_ptr = x;
	job_is_comp_t *job_is_comp = arg;

	if (IS_JOB_COMPLETING(job_ptr) &&
	    (job_ptr->end_time >= job_is_comp->recent)) {
		job_is_comp->completing = true;

		/*
		 * Can return after finding first completing job so long
		 * as a map of nodes in partitions affected by
		 * completing jobs is not required.
		 */
		if (!job_is_comp->eff_cg_bitmap)
			return -1;
		else if (job_ptr->part_ptr)
			bit_or(job_is_comp->eff_cg_bitmap,
			       job_ptr->part_ptr->node_bitmap);
	}

	return 0;
}

static int _foreach_part_reduce_frag(void *x, void *arg)
{
	part_record_t *part_ptr = x;
	part_reduce_frag_t *part_reduce_frag = arg;

	if (bit_overlap_any(part_reduce_frag->eff_cg_bitmap,
			    part_ptr->node_bitmap) &&
	    (part_ptr->state_up & PARTITION_SCHED)) {
		part_ptr->flags |= PART_FLAG_SCHED_FAILED;
		if (slurm_conf.slurmctld_debug >= LOG_LEVEL_DEBUG) {
			xstrfmtcatat(part_reduce_frag->cg_part_str,
				     &part_reduce_frag->cg_part_str_pos,
				     "%s%s",
				     part_reduce_frag->cg_part_str ? "," : "",
				     part_ptr->name);
		}
	}

	return 0;
}

static int _foreach_setup_part_sched(void *x, void *arg)
{
	part_record_t *part_ptr = x;

	part_ptr->num_sched_jobs = 0;
	part_ptr->flags &= ~PART_FLAG_SCHED_FAILED;
	part_ptr->flags &= ~PART_FLAG_SCHED_CLEARED;

	return 0;
}

static int _foreach_setup_resv_sched(void *x, void *arg)
{
	slurmctld_resv_t *resv_ptr = x;

	resv_ptr->flags &= ~RESERVE_FLAG_SCHED_FAILED;

	return 0;
}

static int _foreach_build_job_queue(void *x, void *arg)
{
	job_record_t *job_ptr = x;
	build_job_queue_for_part_t *setup_job = arg;

	setup_job->job_ptr = job_ptr;

	if (IS_JOB_PENDING(job_ptr)) {
		/* Remove backfill flag */
		job_ptr->bit_flags &= ~BACKFILL_SCHED;
		set_job_failed_assoc_qos_ptr(job_ptr);
		acct_policy_handle_accrue_time(job_ptr, false);
		if ((job_ptr->state_reason != WAIT_NO_REASON) &&
		    (job_ptr->state_reason != WAIT_PRIORITY) &&
		    (job_ptr->state_reason != WAIT_RESOURCES) &&
		    (job_ptr->state_reason != job_ptr->state_reason_prev_db)) {
			job_ptr->state_reason_prev_db = job_ptr->state_reason;
			last_job_update = setup_job->now;
		}
	}

	if (((setup_job->tested_jobs % 100) == 0) &&
	    (slurm_delta_tv(&setup_job->start_tv) >= build_queue_timeout)) {
		if (difftime(setup_job->now, *setup_job->last_log_time) > 600) {
			/* Log at most once every 10 minutes */
			info("%s has run for %d usec, exiting with %d of %d jobs tested, %d job-partition-qos pairs added",
			     __func__, build_queue_timeout,
			     setup_job->tested_jobs,
			     list_count(job_list),
			     setup_job->job_prio_pairs);
			*setup_job->last_log_time = setup_job->now;
		}
		return -1;
	}
	setup_job->tested_jobs++;
	job_ptr->preempt_in_progress = false; /* initialize */
	if (job_ptr->array_recs && setup_job->backfill)
		job_ptr->array_recs->pend_run_tasks = 0;
	if (job_ptr->resv_list)
		job_ptr->resv_ptr = NULL;
	if (!_job_runnable_test1(job_ptr, setup_job->clear_start))
		return 0;

	setup_job->prio_inx = -1;
	if (job_ptr->part_ptr_list) {
		(void) list_for_each(job_ptr->part_ptr_list,
				     _build_job_queue_for_part,
				     setup_job);
	} else {
		if (job_ptr->part_ptr == NULL) {
			part_record_t *part_ptr =
				find_part_record(job_ptr->partition);
			if (!part_ptr) {
				error("Could not find partition %s for %pJ",
				      job_ptr->partition, job_ptr);
				return 0;
			}
			job_ptr->part_ptr = part_ptr;
			error("partition pointer reset for %pJ, part %s",
			      job_ptr, job_ptr->partition);
			job_ptr->bit_flags |= JOB_PART_ASSIGNED;
		}
		(void) _build_job_queue_for_part(job_ptr->part_ptr, setup_job);
	}

	return 0;
}

static int _foreach_set_job_elig(void *x, void *arg)
{
	job_record_t *job_ptr = x;
	time_t now = *(time_t *) arg;
	part_record_t *part_ptr = job_ptr->part_ptr;

	if (!IS_JOB_PENDING(job_ptr))
		return 0;
	if (!part_ptr)
		return 0;
	if (!job_ptr->details ||
	    (job_ptr->details->begin_time > now))
		return 0;
	if (!(part_ptr->state_up & PARTITION_SCHED))
		return 0;
	if ((job_ptr->time_limit != NO_VAL) &&
	    (job_ptr->time_limit > part_ptr->max_time))
		return 0;
	if (job_ptr->details->max_nodes &&
	    ((job_ptr->details->max_nodes < part_ptr->min_nodes) ||
	     (job_ptr->details->min_nodes > part_ptr->max_nodes)))
		return 0;
	/* Job's eligible time is set in job_independent() */
	(void) job_independent(job_ptr);

	return 0;
}

extern void job_queue_rec_magnetic_resv(job_queue_rec_t *job_queue_rec)
{
	job_record_t *job_ptr;

	if (!job_queue_rec->resv_ptr)
		return;

	xassert(job_queue_rec->job_ptr);
	xassert(!job_queue_rec->job_ptr->resv_name);

	job_ptr = job_queue_rec->job_ptr;
	job_ptr->resv_ptr = job_queue_rec->resv_ptr;
	job_ptr->resv_name = xstrdup(job_ptr->resv_ptr->name);
	job_ptr->resv_id = job_ptr->resv_ptr->resv_id;
	job_queue_rec->job_ptr->bit_flags |= JOB_MAGNETIC;
}

extern void job_queue_rec_resv_list(job_queue_rec_t *job_queue_rec)
{
	job_record_t *job_ptr;

	if (!job_queue_rec->resv_ptr)
		return;

	xassert(job_queue_rec->job_ptr);

	job_ptr = job_queue_rec->job_ptr;
	job_ptr->resv_ptr = job_queue_rec->resv_ptr;
	/*
	 * Do not set the name since we have multiple and we don't want to
	 * overwrite it.
	 */
	job_ptr->resv_id = job_ptr->resv_ptr->resv_id;
}

/*
 * build_job_queue - build (non-priority ordered) list of pending jobs
 * IN clear_start - if set then clear the start_time for pending jobs,
 *		    true when called from sched/backfill or sched/builtin
 * IN backfill - true if running backfill scheduler, enforce min time limit
 * RET the job queue
 * NOTE: the caller must call FREE_NULL_LIST() on RET value to free memory
 */
extern list_t *build_job_queue(bool clear_start, bool backfill)
{
	static time_t last_log_time = 0;
	split_job_t split_job = { 0 };
	build_job_queue_for_part_t setup_job = {
		.backfill = backfill,
		.clear_start = clear_start,
		.last_log_time = &last_log_time,
		.now = time(NULL),
		.start_tv = { 0, 0 },
	};
	/* init the timer */
	(void) slurm_delta_tv(&setup_job.start_tv);
	setup_job.job_queue = list_create(xfree_ptr);

	(void) list_for_each(job_list, _split_job_on_schedule, &split_job);

	if (split_job.job_list) {
		/*
		 * We can't use list_transfer() because we don't have the same
		 * destroy function.
		 */
		(void) list_for_each(split_job.job_list,
				     _transfer_job_list, NULL);
		FREE_NULL_LIST(split_job.job_list);
	}

	(void) list_for_each(job_list, _foreach_build_job_queue, &setup_job);

	return setup_job.job_queue;
}

/*
 * job_is_completing - Determine if jobs are in the process of completing.
 * IN/OUT  eff_cg_bitmap - optional bitmap of all relevant completing nodes,
 *                         relevenace determined by filtering via CompleteWait
 *                         if NULL, function will terminate at first completing
 *                         job
 * RET - True of any job is in the process of completing AND
 *	 CompleteWait is configured non-zero
 * NOTE: This function can reduce resource fragmentation, which is a
 * critical issue on Elan interconnect based systems.
 */
extern bool job_is_completing(bitstr_t *eff_cg_bitmap)
{
	job_is_comp_t job_is_comp = {
		.eff_cg_bitmap = eff_cg_bitmap,
	};

	if ((job_list == NULL) || (slurm_conf.complete_wait == 0))
		return false;

	job_is_comp.recent = time(NULL) - slurm_conf.complete_wait;

	(void) list_for_each(job_list, _foreach_job_is_completing,
			     &job_is_comp);

	return job_is_comp.completing;
}

/*
 * set_job_elig_time - set the eligible time for pending jobs once their
 *      dependencies are lifted (in job->details->begin_time)
 */
extern void set_job_elig_time(void)
{
	slurmctld_lock_t job_write_lock =
		{ READ_LOCK, WRITE_LOCK, WRITE_LOCK, READ_LOCK, NO_LOCK };
	time_t now = time(NULL);

	lock_slurmctld(job_write_lock);
	(void) list_for_each(job_list, _foreach_set_job_elig, &now);
	unlock_slurmctld(job_write_lock);
}

static void _do_diag_stats(long delta_t)
{
	if (delta_t > slurmctld_diag_stats.schedule_cycle_max)
		slurmctld_diag_stats.schedule_cycle_max = delta_t;

	slurmctld_diag_stats.schedule_cycle_sum += delta_t;
	slurmctld_diag_stats.schedule_cycle_last = delta_t;
	slurmctld_diag_stats.schedule_cycle_counter++;
}

/*
 * Queue requests of job scheduler
 */
extern void schedule(bool full_queue)
{

	if (slurmctld_config.scheduling_disabled)
		return;

	slurm_mutex_lock(&sched_mutex);
	sched_full_queue |= full_queue;
	slurm_cond_broadcast(&sched_cond);
	sched_requests++;
	slurm_mutex_unlock(&sched_mutex);
}

/* detached thread periodically attempts to schedule jobs */
static void *_sched_agent(void *args)
{
	long delta_t;
	struct timeval now;
	int job_cnt;
	bool full_queue;

	while (true) {
		slurm_mutex_lock(&sched_mutex);
		while (true) {
			if (slurmctld_config.shutdown_time) {
				slurm_mutex_unlock(&sched_mutex);
				goto cleanup;
			}

			gettimeofday(&now, NULL);
			delta_t  = (now.tv_sec  - sched_last.tv_sec) *
				   USEC_IN_SEC;
			delta_t +=  now.tv_usec - sched_last.tv_usec;

			if (sched_requests && delta_t > sched_min_interval ) {
				break;
			} else if (sched_requests) {
				struct timespec ts = {0, 0};
				int64_t nsec;

				nsec = sched_min_interval + sched_last.tv_usec;
				nsec *= NSEC_IN_USEC;
				nsec += NSEC_IN_USEC;
				ts.tv_sec = sched_last.tv_sec +
					    (nsec / NSEC_IN_SEC);
				ts.tv_nsec = nsec % NSEC_IN_SEC;
				slurm_cond_timedwait(&sched_cond,
						     &sched_mutex, &ts);
			} else {
				slurm_cond_wait(&sched_cond, &sched_mutex);
			}
		}

		full_queue = sched_full_queue;
		sched_full_queue = false;
		sched_requests = 0;
		sched_running = true;
		slurm_mutex_unlock(&sched_mutex);

		job_cnt = _schedule(full_queue);
		gettimeofday(&now, NULL);
		sched_last.tv_sec  = now.tv_sec;
		sched_last.tv_usec = now.tv_usec;
		if (job_cnt) {
			/* jobs were started, save state */
			schedule_node_save();		/* Has own locking */
			schedule_job_save();		/* Has own locking */
		}

		/* Signal that scheduling work is complete */
		slurm_mutex_lock(&sched_mutex);
		sched_running = false;
		slurm_cond_broadcast(&sched_cond);
		slurm_mutex_unlock(&sched_mutex);
	}

cleanup:
	slurm_mutex_lock(&sched_mutex);
	xassert(sched_alive);
	sched_alive = false;
	slurm_cond_broadcast(&sched_cond);
	slurm_mutex_unlock(&sched_mutex);

	return NULL;
}

/* Determine if job's deadline specification is still valid, kill job if not
 * job_ptr IN - Job to test
 * func IN - function name used for logging
 * RET - true of valid, false if invalid and job cancelled
 */
extern bool deadline_ok(job_record_t *job_ptr, const char *func)
{
	time_t now;
	char time_str_deadline[256];
	bool fail_job = false;
	time_t inter;

	now = time(NULL);
	if ((job_ptr->time_min) && (job_ptr->time_min != NO_VAL)) {
		inter = now + job_ptr->time_min * 60;
		if (job_ptr->deadline < inter) {
			slurm_make_time_str(&job_ptr->deadline,
					    time_str_deadline,
					    sizeof(time_str_deadline));
			info("%s: %pJ with time_min %u exceeded deadline %s and cancelled",
			     func, job_ptr, job_ptr->time_min,
			     time_str_deadline);
			fail_job = true;
		}
	} else if ((job_ptr->time_limit != NO_VAL) &&
		   (job_ptr->time_limit != INFINITE)) {
		inter = now + job_ptr->time_limit * 60;
		if (job_ptr->deadline < inter) {
			slurm_make_time_str(&job_ptr->deadline,
					    time_str_deadline,
					    sizeof(time_str_deadline));
			info("%s: %pJ with time_limit %u exceeded deadline %s and cancelled",
			     func, job_ptr, job_ptr->time_limit,
			     time_str_deadline);
			fail_job = true;
		}
	}
	if (fail_job) {
		last_job_update = now;
		job_state_set(job_ptr, JOB_DEADLINE);
		job_ptr->exit_code = 1;
		job_ptr->state_reason = FAIL_DEADLINE;
		xfree(job_ptr->state_desc);
		job_ptr->start_time = now;
		job_ptr->end_time = now;
		srun_allocate_abort(job_ptr);
		job_completion_logger(job_ptr, false);
		return false;
	}
	return true;
}

/*
 * When an array job is rejected for some reason, the remaining array tasks will
 * get skipped by both the main scheduler and the backfill scheduler (it's an
 * optimization). Hence, their reasons should match the reason of the first job.
 * This function sets those reasons.
 *
 * job_ptr		(IN) The current job being evaluated, after it has gone
 * 			through the scheduling loop.
 * reject_array_job	(IN) A pointer to the first job (array task) in the most
 * 			recently rejected array job. If job_ptr belongs to the
 * 			same array as reject_array_job, then set job_ptr's
 * 			reason to match reject_array_job.
 */
extern void fill_array_reasons(job_record_t *job_ptr,
			       job_record_t *reject_array_job)
{
	if (!reject_array_job || !reject_array_job->array_job_id)
		return;

	if (job_ptr == reject_array_job)
		return;

	/*
	 * If the current job is part of the rejected job array...
	 * And if the reason isn't properly set yet...
	 */
	if ((job_ptr->array_job_id == reject_array_job->array_job_id) &&
	    (job_ptr->state_reason != reject_array_job->state_reason)) {
		/* Set the reason for the subsequent array task */
		xfree(job_ptr->state_desc);
		job_ptr->state_reason = reject_array_job->state_reason;
		last_job_update = time(NULL);
		debug3("%s: Setting reason of array task %pJ to %s",
		       __func__, job_ptr,
		       job_state_reason_string(job_ptr->state_reason));
	}
}

static job_queue_rec_t *_create_job_queue_rec(job_queue_req_t *job_queue_req)
{
	job_queue_rec_t *job_queue_rec = xmalloc(sizeof(*job_queue_rec));
	job_queue_rec->array_task_id = job_queue_req->job_ptr->array_task_id;
	job_queue_rec->job_id   = job_queue_req->job_ptr->job_id;
	job_queue_rec->job_ptr  = job_queue_req->job_ptr;
	job_queue_rec->part_ptr = job_queue_req->part_ptr;
	job_queue_rec->priority = job_queue_req->prio;
	job_queue_rec->qos_ptr = job_queue_req->job_ptr->qos_ptr;
	job_queue_rec->resv_ptr = job_queue_req->resv_ptr;

	return job_queue_rec;
}

extern void job_queue_append_internal(job_queue_req_t *job_queue_req)
{
	job_queue_rec_t *job_queue_rec;

	xassert(job_queue_req);
	xassert(job_queue_req->job_ptr);
	xassert(job_queue_req->job_queue);
	xassert(job_queue_req->part_ptr);

	if (job_queue_req->job_ptr->details &&
	    job_queue_req->job_ptr->details->prefer) {
		job_queue_rec = _create_job_queue_rec(job_queue_req);
		job_queue_rec->use_prefer = true;
		list_append(job_queue_req->job_queue, job_queue_rec);
	}

	job_queue_rec = _create_job_queue_rec(job_queue_req);

	list_append(job_queue_req->job_queue, job_queue_rec);
}

static void _set_features(job_record_t *job_ptr, bool use_prefer)
{
	/*
	 * feature_list_use is a temporary variable and should
	 * be reset before each use. Do this after the check for
	 * pending because the job could have started with
	 * "preferred" job_queue_rec.
	 */
	if (use_prefer) {
		job_ptr->details->features_use =
			job_ptr->details->prefer;
		job_ptr->details->feature_list_use =
			job_ptr->details->prefer_list;
	} else {
		job_ptr->details->features_use =
			job_ptr->details->features;
		job_ptr->details->feature_list_use =
			job_ptr->details->feature_list;
	}
}

static void _set_schedule_exit(schedule_exit_t code)
{
	xassert(code < SCHEDULE_EXIT_COUNT);

	slurmctld_diag_stats.schedule_exit[code]++;
}

static int _get_nodes_in_reservations(void *x, void *arg)
{
	slurmctld_resv_t *resv_ptr = x;
	bitstr_t *node_bitmap = arg;

	xassert(resv_ptr);
	xassert(node_bitmap);

	if (resv_ptr->node_bitmap)
		bit_or(node_bitmap, resv_ptr->node_bitmap);
	return SLURM_SUCCESS;
}

static int _schedule(bool full_queue)
{
	list_t *job_queue = NULL;
	int job_cnt = 0;
	int error_code, i, time_limit, pend_time;
	uint32_t job_depth = 0, array_task_id;
	job_queue_rec_t *job_queue_rec;
	job_record_t *job_ptr = NULL;
	part_record_t *part_ptr, *skip_part_ptr = NULL;
	bitstr_t *save_avail_node_bitmap;
	int bb_wait_cnt = 0;
	/* Locks: Read config, write job, write node, read partition */
	slurmctld_lock_t job_write_lock =
		{ READ_LOCK, WRITE_LOCK, WRITE_LOCK, READ_LOCK, READ_LOCK };
	bool is_job_array_head;
	static time_t sched_update = 0;
	static bool assoc_limit_stop = false;
	static int sched_timeout = 0;
	static int sched_max_job_start = 0;
	static int bf_min_age_reserve = 0;
	static uint32_t bf_min_prio_reserve = 0;
	static bool bf_licenses = false;
	static int def_job_limit = 100;
	static int max_jobs_per_part = 0;
	static int defer_rpc_cnt = 0;
	static bool reduce_completing_frag = false;
	time_t now, last_job_sched_start, sched_start;
	job_record_t *reject_array_job = NULL;
	part_record_t *reject_array_part = NULL;
	slurmctld_resv_t *reject_array_resv = NULL;
	bool reject_array_use_prefer = false;
	bool use_prefer;
	bool fail_by_part, wait_on_resv, fail_by_part_non_reserve;
	uint32_t deadline_time_limit, save_time_limit = 0;
	uint32_t prio_reserve;
	DEF_TIMERS;
	job_node_select_t job_node_select = { 0 };
	static bool ignore_prefer_val = false;

	if (slurmctld_config.shutdown_time)
		return 0;

	if (sched_update != slurm_conf.last_update) {
		char *tmp_ptr;

		if (xstrcasestr(slurm_conf.sched_params, "assoc_limit_stop"))
			assoc_limit_stop = true;
		else
			assoc_limit_stop = false;

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "batch_sched_delay="))) {
			batch_sched_delay = atoi(tmp_ptr + 18);
			if (batch_sched_delay < 0) {
				error("Invalid batch_sched_delay: %d",
				      batch_sched_delay);
				batch_sched_delay = 3;
			}
		} else {
			batch_sched_delay = 3;
		}

		bb_array_stage_cnt = 10;
		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "bb_array_stage_cnt="))) {
			int task_cnt = atoi(tmp_ptr + 19);
			if (task_cnt > 0)
				bb_array_stage_cnt = task_cnt;
		}

		bf_min_age_reserve = 0;
		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "bf_min_age_reserve="))) {
			int min_age = atoi(tmp_ptr + 19);
			if (min_age > 0)
				bf_min_age_reserve = min_age;
		}

		bf_min_prio_reserve = 0;
		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "bf_min_prio_reserve="))) {
			int64_t min_prio = (int64_t) atoll(tmp_ptr + 20);
			if (min_prio > 0)
				bf_min_prio_reserve = (uint32_t) min_prio;
		}

		bf_licenses = false;
		if (xstrcasestr(slurm_conf.sched_params, "bf_licenses")) {
			if (!xstrcmp(slurm_conf.schedtype, "sched/builtin"))
				error("Ignoring SchedulerParameters=bf_licenses, this option is incompatible with sched/builtin.");
			else
				bf_licenses = true;
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "build_queue_timeout="))) {
			build_queue_timeout = atoi(tmp_ptr + 20);
			if (build_queue_timeout < 100) {
				error("Invalid build_queue_time: %d",
				      build_queue_timeout);
				build_queue_timeout = BUILD_TIMEOUT;
			}
		} else {
			build_queue_timeout = BUILD_TIMEOUT;
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "correspond_after_task_cnt="))) {
			correspond_after_task_cnt = atoi(tmp_ptr + 26);
			if (correspond_after_task_cnt <
			    CORRESPOND_ARRAY_TASK_CNT) {
				error("Invalid correspond_after_task_cnt: %d, the value can't be lower than %d",
				      correspond_after_task_cnt,
				      CORRESPOND_ARRAY_TASK_CNT);
				correspond_after_task_cnt =
					CORRESPOND_ARRAY_TASK_CNT;
			}
		} else {
			correspond_after_task_cnt = CORRESPOND_ARRAY_TASK_CNT;
		}


		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "default_queue_depth="))) {
			def_job_limit = atoi(tmp_ptr + 20);
			if (def_job_limit < 0) {
				error("ignoring SchedulerParameters: "
				      "default_queue_depth value of %d",
				      def_job_limit);
				def_job_limit = 100;
			}
		} else {
			def_job_limit = 100;
		}

		bf_hetjob_prio = 0;
		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
		                           "bf_hetjob_prio="))) {
			tmp_ptr += 15;
			if (!xstrncasecmp(tmp_ptr, "min", 3))
				bf_hetjob_prio |= HETJOB_PRIO_MIN;
			else if (!xstrncasecmp(tmp_ptr, "max", 3))
				bf_hetjob_prio |= HETJOB_PRIO_MAX;
			else if (!xstrncasecmp(tmp_ptr, "avg", 3))
				bf_hetjob_prio |= HETJOB_PRIO_AVG;
			else
				error("Invalid SchedulerParameters bf_hetjob_prio: %s",
				      tmp_ptr);
		}

		bf_hetjob_immediate = false;
		if (xstrcasestr(slurm_conf.sched_params, "bf_hetjob_immediate"))
			bf_hetjob_immediate = true;

		if (bf_hetjob_immediate && !bf_hetjob_prio) {
			bf_hetjob_prio |= HETJOB_PRIO_MIN;
			info("bf_hetjob_immediate automatically sets bf_hetjob_prio=min");
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "partition_job_depth="))) {
			max_jobs_per_part = atoi(tmp_ptr + 20);
			if (max_jobs_per_part < 0) {
				error("ignoring SchedulerParameters: "
				      "partition_job_depth value of %d",
				      max_jobs_per_part);
				max_jobs_per_part = 0;
			}
		} else {
			max_jobs_per_part = 0;
		}

		if (xstrcasestr(slurm_conf.sched_params,
		                "reduce_completing_frag"))
			reduce_completing_frag = true;
		else
			reduce_completing_frag = false;

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
		                           "max_rpc_cnt=")))
			defer_rpc_cnt = atoi(tmp_ptr + 12);
		else if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
						"max_rpc_count=")))
			defer_rpc_cnt = atoi(tmp_ptr + 14);
		else
			defer_rpc_cnt = 0;
		if (defer_rpc_cnt < 0) {
			error("Invalid max_rpc_cnt: %d", defer_rpc_cnt);
			defer_rpc_cnt = 0;
		}

		time_limit = slurm_conf.msg_timeout / 2;
		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
		                           "max_sched_time="))) {
			sched_timeout = atoi(tmp_ptr + 15);
			if ((sched_timeout <= 0) ||
			    (sched_timeout > time_limit)) {
				error("Invalid max_sched_time: %d",
				      sched_timeout);
				sched_timeout = 0;
			}
		} else {
			sched_timeout = 0;
		}
		if (sched_timeout == 0) {
			sched_timeout = MAX(time_limit, 1);
			sched_timeout = MIN(sched_timeout, 2);
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
		                           "sched_interval="))) {
			sched_interval = atoi(tmp_ptr + 15);
			if (sched_interval == -1) {
				sched_debug("schedule() returning, sched_interval=-1");
				/*
				 * Exit without setting sched_update.  This gets
				 * verbose, but makes this setting easy to
				 * happen.
				 *
				 * No memory is allocated above this.
				 */
				return 0;
			} else if (sched_interval < 0) {
				error("Invalid sched_interval: %d",
				      sched_interval);
				sched_interval = 60;
			}
		} else {
			sched_interval = 60;
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "sched_min_interval="))) {
			i = atoi(tmp_ptr + 19);
			if (i < 0)
				error("Invalid sched_min_interval: %d", i);
			else
				sched_min_interval = i;
		} else {
			sched_min_interval = 2;
		}

		if ((tmp_ptr = xstrcasestr(slurm_conf.sched_params,
					   "sched_max_job_start="))) {
			sched_max_job_start = atoi(tmp_ptr + 20);
			if (sched_max_job_start < 0) {
				error("Invalid sched_max_job_start: %d",
				      sched_max_job_start);
				sched_max_job_start = 0;
			}
		} else {
			sched_max_job_start = 0;
		}

		if (xstrcasestr(slurm_conf.sched_params,
				"ignore_prefer_validation"))
			ignore_prefer_val = true;
		else
			ignore_prefer_val = false;

		sched_update = slurm_conf.last_update;
		if (slurm_conf.sched_params && strlen(slurm_conf.sched_params))
			info("SchedulerParameters=%s", slurm_conf.sched_params);
	}

	slurm_mutex_lock(&slurmctld_config.thread_count_lock);
	if ((defer_rpc_cnt > 0) &&
	    (slurmctld_config.server_thread_count >= defer_rpc_cnt)) {
		sched_debug("schedule() returning, too many RPCs");
		slurm_mutex_unlock(&slurmctld_config.thread_count_lock);
		goto out;
	}
	slurm_mutex_unlock(&slurmctld_config.thread_count_lock);

	if (!fed_mgr_sibs_synced()) {
		sched_info("schedule() returning, federation siblings not synced yet");
		goto out;
	}

	lock_slurmctld(job_write_lock);
	now = time(NULL);
	sched_start = now;
	last_job_sched_start = now;
	START_TIMER;
	if (!reduce_completing_frag && job_is_completing(NULL)) {
		unlock_slurmctld(job_write_lock);
		sched_debug("schedule() returning, some job is still completing");
		goto out;
	}

	(void) list_for_each(part_list, _foreach_setup_part_sched, NULL);
	(void) list_for_each(resv_list, _foreach_setup_resv_sched, NULL);

	save_avail_node_bitmap = bit_copy(avail_node_bitmap);

	/* Avoid resource fragmentation if important */
	if (reduce_completing_frag) {
		bitstr_t *eff_cg_bitmap = bit_alloc(node_record_count);
		if (job_is_completing(eff_cg_bitmap)) {
			part_reduce_frag_t part_reduce_frag = {
				.eff_cg_bitmap = eff_cg_bitmap,
			};
			(void) list_for_each(part_list,
					     _foreach_part_reduce_frag,
					     &part_reduce_frag);
			if (part_reduce_frag.cg_part_str) {
				sched_debug("some job is still completing, skipping partitions '%s'",
					    part_reduce_frag.cg_part_str);
				xfree(part_reduce_frag.cg_part_str);
			}
		}
		FREE_NULL_BITMAP(eff_cg_bitmap);
	}

	sched_debug("Running job scheduler %s.", full_queue ? "for full queue":"for default depth");
	job_queue = build_job_queue(false, false);
	slurmctld_diag_stats.schedule_queue_len = list_count(job_queue);
	sort_job_queue(job_queue);

	job_ptr = NULL;
	wait_on_resv = false;
	while (1) {
		/* Run some final guaranteed logic after each job iteration */
		if (job_ptr) {
			job_resv_clear_magnetic_flag(job_ptr);
			fill_array_reasons(job_ptr, reject_array_job);
		}

		job_queue_rec = list_pop(job_queue);
		if (!job_queue_rec) {
			_set_schedule_exit(SCHEDULE_EXIT_END);
			break;
		}
		array_task_id = job_queue_rec->array_task_id;
		job_ptr = job_queue_rec->job_ptr;
		part_ptr = job_queue_rec->part_ptr;

		if ((job_ptr->array_task_id != array_task_id) &&
		    (array_task_id == NO_VAL)) {
			/* Job array element started in other partition,
			 * reset pointer to "master" job array record */
			job_ptr = find_job_record(job_ptr->array_job_id);
			job_queue_rec->job_ptr = job_ptr;
		}
		if (!job_ptr ||
		    !IS_JOB_PENDING(job_ptr) || /* started in other part/qos */
		    !job_ptr->priority) { /* held from fail in other part/qos */
			xfree(job_queue_rec);
			continue;
		}

		use_prefer = job_queue_rec->use_prefer;
		_set_features(job_ptr, use_prefer);

		if (job_ptr->resv_list)
			job_queue_rec_resv_list(job_queue_rec);
		else
			job_queue_rec_magnetic_resv(job_queue_rec);

		if (!_job_runnable_test3(job_ptr, part_ptr)) {
			xfree(job_queue_rec);
			continue;
		}

		job_ptr->qos_ptr = job_queue_rec->qos_ptr;
		job_ptr->part_ptr = part_ptr;
		job_ptr->priority = job_queue_rec->priority;

		xfree(job_queue_rec);

		job_ptr->last_sched_eval = time(NULL);

		if (job_ptr->preempt_in_progress)
			continue;	/* scheduled in another partition */

		if (job_ptr->het_job_id) {
			fail_by_part = true;
			fail_by_part_non_reserve = false;
			goto fail_this_part;
		}

		if (job_ptr->array_recs && (job_ptr->array_task_id == NO_VAL))
			is_job_array_head = true;
		else
			is_job_array_head = false;

next_task:
		if ((time(NULL) - sched_start) >= sched_timeout) {
			sched_debug("loop taking too long, breaking out");
			_set_schedule_exit(SCHEDULE_EXIT_TIMEOUT);
			break;
		}
		if (sched_max_job_start && (job_cnt >= sched_max_job_start)) {
			sched_debug("sched_max_job_start reached, breaking out");
			_set_schedule_exit(SCHEDULE_EXIT_MAX_JOB_START);
			break;
		}

		if ((job_ptr->array_task_id != NO_VAL) || job_ptr->array_recs) {
			if (reject_array_job &&
			    (reject_array_job->array_job_id ==
				job_ptr->array_job_id) &&
			    (reject_array_part == part_ptr) &&
			    (reject_array_resv == job_ptr->resv_ptr) &&
			    (reject_array_use_prefer == use_prefer))
				continue;  /* already rejected array element */


			/* assume reject whole array for now, clear if OK */
			reject_array_job = job_ptr;
			reject_array_part = part_ptr;
			reject_array_resv = job_ptr->resv_ptr;
			reject_array_use_prefer = use_prefer;

			if (!job_array_start_test(job_ptr))
				continue;
		}
		if (max_jobs_per_part &&
		    (max_jobs_per_part < ++job_ptr->part_ptr->num_sched_jobs)) {
			if (job_ptr->state_reason == WAIT_NO_REASON) {
				xfree(job_ptr->state_desc);
				job_ptr->state_reason = WAIT_PRIORITY;
				last_job_update = now;
			}
			if (job_ptr->part_ptr == skip_part_ptr)
				continue;
			sched_debug2("reached partition %s job limit",
				     job_ptr->part_ptr->name);
			skip_part_ptr = job_ptr->part_ptr;
			continue;
		}
		if (!full_queue && (job_depth++ > def_job_limit)) {
			sched_debug("already tested %u jobs, breaking out",
				    job_depth);
			_set_schedule_exit(SCHEDULE_EXIT_MAX_DEPTH);
			break;
		}

		slurm_mutex_lock(&slurmctld_config.thread_count_lock);
		if ((defer_rpc_cnt > 0) &&
		    (slurmctld_config.server_thread_count >= defer_rpc_cnt)) {
			sched_debug("schedule() returning, too many RPCs");
			slurm_mutex_unlock(&slurmctld_config.thread_count_lock);
			_set_schedule_exit(SCHEDULE_EXIT_RPC_CNT);
			break;
		}
		slurm_mutex_unlock(&slurmctld_config.thread_count_lock);

		if (job_limits_check(&job_ptr, false) != WAIT_NO_REASON) {
			/* should never happen */
			continue;
		}

		slurmctld_diag_stats.schedule_cycle_depth++;

		if (job_ptr->resv_name) {
			/*
			 * If we have a MaxStartDelay we need to make sure we
			 * don't schedule any jobs that could potentially run to
			 * avoid starvation of this job.
			 */
			if (job_ptr->resv_ptr &&
			    job_ptr->resv_ptr->max_start_delay)
				wait_on_resv = true;

			if (job_ptr->resv_ptr->flags &
			    RESERVE_FLAG_SCHED_FAILED) {
				job_ptr->state_reason = WAIT_PRIORITY;
				xfree(job_ptr->state_desc);
				last_job_update = now;
				sched_debug3("%pJ. State=PENDING. Reason=Priority. Priority=%u. Resv=%s.",
					     job_ptr,
					     job_ptr->priority,
					     job_ptr->resv_name);
				continue;
			}
		} else if (job_ptr->part_ptr->flags & PART_FLAG_SCHED_FAILED) {
			if (!(job_ptr->part_ptr->flags &
			      PART_FLAG_SCHED_CLEARED)) {
				bit_and_not(avail_node_bitmap,
					    part_ptr->node_bitmap);
				job_ptr->part_ptr->flags |=
					PART_FLAG_SCHED_CLEARED;
			}

			if ((job_ptr->state_reason == WAIT_NO_REASON) ||
			    (job_ptr->state_reason == WAIT_RESOURCES)) {
				sched_debug("%pJ unable to schedule in Partition=%s (per PART_FLAG_SCHED_FAILED). State=PENDING. Previous-Reason=%s. Previous-Desc=%s. New-Reason=Priority. Priority=%u.",
					    job_ptr,
					    job_ptr->part_ptr->name,
					    job_state_reason_string(
						    job_ptr->state_reason),
					    job_ptr->state_desc,
					    job_ptr->priority);
				job_ptr->state_reason = WAIT_PRIORITY;
				xfree(job_ptr->state_desc);
				last_job_update = now;
			} else {
				/*
				 * Log job can not run even though we are not
				 * overriding the reason */
				sched_debug2("%pJ. unable to schedule in Partition=%s (per PART_FLAG_SCHED_FAILED). Retaining previous scheduling Reason=%s. Desc=%s. Priority=%u.",
					     job_ptr,
					     job_ptr->part_ptr->name,
					     job_state_reason_string(
						     job_ptr->state_reason),
					     job_ptr->state_desc,
					     job_ptr->priority);
			}
			last_job_update = now;

			continue;
		} else if (wait_on_resv &&
			   (job_ptr->warn_flags & KILL_JOB_RESV)) {
			sched_debug("%pJ. State=PENDING. Reason=Priority, Priority=%u. May be able to backfill on MaxStartDelay reservations.",
				    job_ptr, job_ptr->priority);
			continue;

		}

		/* Test for valid QOS and required nodes on each pass */
		if (job_ptr->qos_ptr) {
			assoc_mgr_lock_t locks =
				{ .assoc = READ_LOCK, .qos = READ_LOCK };

			assoc_mgr_lock(&locks);
			if (job_ptr->assoc_ptr
			    && (accounting_enforce & ACCOUNTING_ENFORCE_QOS)
			    && ((job_ptr->qos_ptr->id >= g_qos_count) ||
				!job_ptr->assoc_ptr->usage ||
				!job_ptr->assoc_ptr->usage->valid_qos ||
				!bit_test(job_ptr->assoc_ptr->usage->valid_qos,
					  job_ptr->qos_ptr->id))
			    && !job_ptr->limit_set.qos) {
				assoc_mgr_unlock(&locks);
				sched_debug("%pJ has invalid QOS", job_ptr);
				job_fail_qos(job_ptr, __func__, false);
				last_job_update = now;
				continue;
			} else if (job_ptr->state_reason == FAIL_QOS) {
				xfree(job_ptr->state_desc);
				job_ptr->state_reason = WAIT_NO_REASON;
				last_job_update = now;
			}
			assoc_mgr_unlock(&locks);
		}

		deadline_time_limit = 0;
		if ((job_ptr->deadline) && (job_ptr->deadline != NO_VAL)) {
			if (!deadline_ok(job_ptr, __func__))
				continue;

			deadline_time_limit = job_ptr->deadline - now;
			deadline_time_limit /= 60;
			if ((job_ptr->time_limit != NO_VAL) &&
			    (job_ptr->time_limit != INFINITE)) {
				deadline_time_limit = MIN(job_ptr->time_limit,
							  deadline_time_limit);
			} else {
				if ((job_ptr->part_ptr->default_time != NO_VAL) &&
				    (job_ptr->part_ptr->default_time != INFINITE)){
					deadline_time_limit = MIN(
						job_ptr->part_ptr->default_time,
						deadline_time_limit);
				} else if ((job_ptr->part_ptr->max_time != NO_VAL) &&
					   (job_ptr->part_ptr->max_time != INFINITE)){
					deadline_time_limit = MIN(
						job_ptr->part_ptr->max_time,
						deadline_time_limit);
				}
			}
		}

		if (job_state_reason_check(job_ptr->state_reason,
					   JSR_QOS_ASSOC) &&
		    !acct_policy_job_runnable_pre_select(job_ptr, false))
			continue;

		if ((job_ptr->state_reason == WAIT_NODE_NOT_AVAIL) &&
		    job_ptr->details && job_ptr->details->req_node_bitmap &&
		    !bit_super_set(job_ptr->details->req_node_bitmap,
				   avail_node_bitmap)) {
			continue;
		}

		if (!job_ptr->part_ptr)
			continue;
		i = bit_overlap(avail_node_bitmap,
				job_ptr->part_ptr->node_bitmap);
		if ((job_ptr->details &&
		     (job_ptr->details->min_nodes != NO_VAL) &&
		     (job_ptr->details->min_nodes >  i)) ||
		    (!job_ptr->details && (i == 0))) {
			/*
			 * Too many nodes DRAIN, DOWN, or
			 * reserved for jobs in higher priority partition
			 */
			job_ptr->state_reason = WAIT_RESOURCES;
			xfree(job_ptr->state_desc);
			job_ptr->state_desc =
				xstrdup_printf("Nodes required for job are DOWN, DRAINED%s or reserved for jobs in higher priority partitions",
					       bit_overlap(rs_node_bitmap,
							   job_ptr->part_ptr->
							   node_bitmap) ? ", REBOOTING" : "");
			last_job_update = now;
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u. Partition=%s.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority, job_ptr->partition);
			fail_by_part = true;
			fail_by_part_non_reserve = false;
			goto fail_this_part;
		}

		if (assoc_mgr_validate_assoc_id(acct_db_conn,
						job_ptr->assoc_id,
						accounting_enforce)) {
			/* NOTE: This only happens if a user's account is
			 * disabled between when the job was submitted and
			 * the time we consider running it. It should be
			 * very rare. */
			sched_info("%pJ has invalid account", job_ptr);
			last_job_update = now;
			job_ptr->state_reason = FAIL_ACCOUNT;
			xfree(job_ptr->state_desc);
			continue;
		}

		last_job_sched_start = MAX(last_job_sched_start,
					   job_ptr->start_time);
		if (deadline_time_limit) {
			save_time_limit = job_ptr->time_limit;
			job_ptr->time_limit = deadline_time_limit;
		}

		/* get fed job lock from origin cluster */
		if (fed_mgr_job_lock(job_ptr)) {
			error_code = ESLURM_FED_JOB_LOCK;
			goto skip_start;
		}

		job_node_select.job_ptr = job_ptr;
		error_code = select_nodes(&job_node_select,
					  false, false,
					  SLURMDB_JOB_FLAG_SCHED);

		if (error_code == SLURM_SUCCESS) {
			/*
			 * If the following fails because of network
			 * connectivity, the origin cluster should ask
			 * when it comes back up if the cluster_lock
			 * cluster actually started the job
			 */
			fed_mgr_job_start(job_ptr, job_ptr->start_time);
		} else {
			/*
			 * Node config unavailable plus state_reason
			 * FAIL_BAD_CONSTRAINTS causes the job to be held
			 * later. If job specs were unsatisfied due to
			 * --prefer, give the opportunity to test the record
			 * without it in a second attempt by resetting
			 * state_reason to FAIL_CONSTRAINTS.
			 */
			if (ignore_prefer_val && job_ptr->details->prefer &&
			    job_ptr->details->prefer_list &&
			    (job_ptr->details->prefer_list ==
			     job_ptr->details->feature_list_use) &&
			    (error_code ==
			     ESLURM_REQUESTED_NODE_CONFIG_UNAVAILABLE) &&
			    (job_ptr->state_reason == FAIL_BAD_CONSTRAINTS)) {
				sched_debug2("StateReason='%s' set after evaluating %pJ in partition %s (maybe unsatisfied due to --prefer while ignore_prefer_validation configured). Re-testing without --prefer if needed.",
					     job_state_reason_string(job_ptr->state_reason), job_ptr, job_ptr->part_ptr->name);
				job_ptr->state_reason = FAIL_CONSTRAINTS;
			}

			fed_mgr_job_unlock(job_ptr);
		}

skip_start:

		fail_by_part = false;
		fail_by_part_non_reserve = false;
		if ((error_code != SLURM_SUCCESS) && deadline_time_limit)
			job_ptr->time_limit = save_time_limit;
		if (error_code == ESLURM_NODES_BUSY) {
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u. Partition=%s.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority, job_ptr->partition);
			if (!use_prefer)
				fail_by_part = true;
		} else if (error_code == ESLURM_LICENSES_UNAVAILABLE) {
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority);
			if (bf_licenses) {
				sched_debug("%pJ is blocked on licenses. Stopping scheduling so license backfill can handle this",
					    job_ptr);
				_set_schedule_exit(SCHEDULE_EXIT_LIC);
				break;
			}
		} else if (error_code == ESLURM_BURST_BUFFER_WAIT) {
			if (job_ptr->start_time == 0) {
				job_ptr->start_time = last_job_sched_start;
				bb_wait_cnt++;
				/*
				 * Since start time wasn't set yet until this
				 * point, this means that the job hasn't had a
				 * chance to start stage-in yet. Clear
				 * reject_array_job so that other jobs in this
				 * array (if it was an array) may also have
				 * a chance to have a start time set and
				 * therefore have a chance to start stage-in.
				 */
				reject_array_job = NULL;
				reject_array_part = NULL;
				reject_array_resv = NULL;
			}
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority);
			continue;
		} else if ((error_code == ESLURM_RESERVATION_BUSY) ||
			   (error_code == ESLURM_RESERVATION_NOT_USABLE)) {
			if (job_ptr->resv_ptr &&
			    job_ptr->resv_ptr->node_bitmap) {
				sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u.",
					     job_ptr,
					     job_state_string(job_ptr->job_state),
					     job_state_reason_string(
						     job_ptr->state_reason),
					     job_ptr->priority);
				bit_and_not(avail_node_bitmap,
					    job_ptr->resv_ptr->node_bitmap);
			} else {
				/*
				 * The job has no reservation but requires
				 * nodes that are currently in some reservation
				 * so just skip over this job and try running
				 * the next lower priority job
				 */
				sched_debug3("%pJ. State=%s. Reason=Required nodes are reserved. Priority=%u",
					     job_ptr,
					     job_state_string(job_ptr->job_state),
					     job_ptr->priority);
			}
		} else if (error_code == ESLURM_FED_JOB_LOCK) {
			job_ptr->state_reason = WAIT_FED_JOB_LOCK;
			xfree(job_ptr->state_desc);
			last_job_update = now;
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u. Partition=%s. Couldn't get federation job lock.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority, job_ptr->partition);
			fail_by_part = true;
		} else if (error_code == SLURM_SUCCESS) {
			/* job initiated */
			sched_debug3("%pJ initiated", job_ptr);
			last_job_update = now;

			/* Clear assumed rejected array status */
			reject_array_job = NULL;
			reject_array_part = NULL;
			reject_array_resv = NULL;

			sched_info("Allocate %pJ NodeList=%s #CPUs=%u Partition=%s",
				   job_ptr, job_ptr->nodes,
				   job_ptr->total_cpus,
				   job_ptr->part_ptr->name);
			if (job_ptr->batch_flag == 0)
				srun_allocate(job_ptr);
			else if (!IS_JOB_CONFIGURING(job_ptr))
				launch_job(job_ptr);
			rebuild_job_part_list(job_ptr);
			job_cnt++;
			if (is_job_array_head &&
			    (job_ptr->array_task_id != NO_VAL)) {
				/* Try starting another task of the job array */
				job_record_t *tmp = job_ptr;
				job_ptr = find_job_record(job_ptr->array_job_id);
				if (job_ptr && (job_ptr != tmp) &&
				    IS_JOB_PENDING(job_ptr) &&
				    (bb_g_job_test_stage_in(job_ptr, false) ==
				     1)) {
					_set_features(job_ptr, use_prefer);
					goto next_task;
				}
			}
			continue;
		} else if ((error_code ==
			    ESLURM_REQUESTED_NODE_CONFIG_UNAVAILABLE) &&
			   (job_ptr->resv_ptr)) {
			debug("%pJ non-runnable in reservation %s: %s",
			      job_ptr, job_ptr->resv_ptr->name,
			      slurm_strerror(error_code));
		} else if (((error_code ==
			     ESLURM_REQUESTED_NODE_CONFIG_UNAVAILABLE) ||
			    (error_code ==
			     ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE)) &&
			   job_ptr->part_ptr_list) {
			debug("%pJ non-runnable in partition %s: %s",
			      job_ptr, job_ptr->part_ptr->name,
			      slurm_strerror(error_code));
		} else if ((error_code ==
			    ESLURM_REQUESTED_NODE_CONFIG_UNAVAILABLE) &&
			   (job_ptr->state_reason == FAIL_CONSTRAINTS)) {
			sched_info("%pJ current node constraints not satisfied",
				   job_ptr);
			/*
			 * Future node updates may satisfy the constraints, so
			 * do not hold the job.
			 */
		} else if (error_code == ESLURM_ACCOUNTING_POLICY) {
			sched_debug3("%pJ delayed for accounting policy",
				     job_ptr);
			/* potentially starve this job */
			if (assoc_limit_stop)
				fail_by_part = true;
		} else if (error_code == ESLURM_MAX_POWERED_NODES) {
			sched_debug2("%pJ cannot start: %s",
				   job_ptr, slurm_strerror(error_code));
			job_ptr->state_reason = WAIT_MAX_POWERED_NODES;
			xfree(job_ptr->state_desc);
		} else if (error_code == ESLURM_PORTS_BUSY) {
			/*
			 * This can only happen if using stepd step manager.
			 * The nodes selected for the job ran out of ports.
			 */
			fail_by_part = true;
			job_ptr->state_reason = WAIT_MPI_PORTS_BUSY;
			xfree(job_ptr->state_desc);
			sched_debug3("%pJ. State=%s. Reason=%s. Priority=%u. Partition=%s.",
				     job_ptr,
				     job_state_string(job_ptr->job_state),
				     job_state_reason_string(
					     job_ptr->state_reason),
				     job_ptr->priority, job_ptr->partition);
		} else if (IS_TOPO_ERROR(error_code)) {
			sched_debug2("%pJ cannot start: %s. Partition=%s. state_desc=%s",
				   job_ptr, slurm_strerror(error_code),
				   job_ptr->partition,
				   job_ptr->state_desc);
			if (!use_prefer)
				fail_by_part = true;
		} else if ((error_code !=
			    ESLURM_REQUESTED_PART_CONFIG_UNAVAILABLE) &&
			   (error_code != ESLURM_NODE_NOT_AVAIL) &&
			   (error_code !=
			    ESLURM_INVALID_BURST_BUFFER_REQUEST)) {
			sched_info("schedule: %pJ non-runnable: %s",
				   job_ptr, slurm_strerror(error_code));
			last_job_update = now;
			job_state_set(job_ptr, JOB_PENDING);
			job_ptr->state_reason = FAIL_BAD_CONSTRAINTS;
			xfree(job_ptr->state_desc);
			job_ptr->start_time = job_ptr->end_time = now;
			job_ptr->priority = 0;
			debug2("%s: setting %pJ to \"%s\" (%s)",
			       __func__, job_ptr,
			       job_state_reason_string(job_ptr->state_reason),
			       slurm_strerror(error_code));
		}

		if (job_ptr->details && job_ptr->details->req_node_bitmap &&
		    (bit_set_count(job_ptr->details->req_node_bitmap) >=
		     job_ptr->details->min_nodes)) {
			fail_by_part = false;
			/* Do not schedule more jobs on nodes required by this
			 * job, but don't block the entire queue/partition. */
			bit_and_not(avail_node_bitmap,
				    job_ptr->details->req_node_bitmap);
		}
		if (fail_by_part && job_ptr->resv_name) {
			/*
			 * If the reservation is not FLEX or ANY_NODES, other
			 * jobs in this partition can be scheduled.
			 *
			 * Jobs submitted to FLEX or ANY_NODES reservations can
			 * use nodes outside of the reservation. If the
			 * reservation is FLEX or ANY_NODES, other jobs in
			 * this partition submitted to other reservations can
			 * be scheduled.
			 *
			 * In both cases, do not schedule more jobs in this
			 * reservation.
			 */
			if ((job_ptr->resv_ptr->flags & RESERVE_FLAG_FLEX) ||
			    (job_ptr->resv_ptr->flags & RESERVE_FLAG_ANY_NODES))
				fail_by_part_non_reserve = true;
			else
				fail_by_part = false;

			job_ptr->resv_ptr->flags |= RESERVE_FLAG_SCHED_FAILED;
		}
		if (fail_by_part && bf_min_age_reserve) {
			/* Consider other jobs in this partition if job has been
			 * waiting for less than bf_min_age_reserve time */
			if (job_ptr->details->begin_time == 0) {
				fail_by_part = false;
			} else {
				pend_time = difftime(
					now, job_ptr->details->begin_time);
				if (pend_time < bf_min_age_reserve)
					fail_by_part = false;
			}
		}

		if (!(prio_reserve = acct_policy_get_prio_thresh(
			      job_ptr, false)))
			prio_reserve = bf_min_prio_reserve;

		if (fail_by_part && prio_reserve &&
		    (job_ptr->priority < prio_reserve))
			fail_by_part = false;

fail_this_part:	if (fail_by_part) {
			/* Search for duplicates */
			if (job_ptr->part_ptr->flags & PART_FLAG_SCHED_FAILED) {
				fail_by_part = false;
				break;
			}
		}
		if (fail_by_part) {
			/*
			 * Do not schedule more jobs in this partition or on
			 * nodes in this partition
			 */
			job_ptr->part_ptr->flags |= PART_FLAG_SCHED_FAILED;

			if (fail_by_part_non_reserve) {
				/*
				 * If a FLEX or ANY_NODES reservation job fails
				 * by part, remove all nodes that are not in
				 * reservations from avail_node_bitmap.
				 *
				 * Jobs submitted to FLEX or ANY_NODES
				 * reservations can be scheduled on nodes
				 * outside of the reservation. If we allowed
				 * lower priority jobs to be scheduled on nodes
				 * not in this reservation, they could delay
				 * the higher priority job submitted to this
				 * reservation.
				 *
				 * We only remove nodes not in reservations,
				 * so lower priority jobs submitted to other
				 * reservations can still be scheduled.
				 *
				 * We don't mark the partition as being
				 * cleared. Once the first non-reservation job
				 * in the partition gets evaluated, which cannot
				 * be scheduled since non-reserved nodes have
				 * been removed, the partition's reserved nodes
				 * will be removed and it will be marked as
				 * cleared.
				 */
				bitstr_t *remove_nodes =
					bit_alloc(node_record_count);

				list_for_each(resv_list,
					      _get_nodes_in_reservations,
					      remove_nodes);
				bit_not(remove_nodes);
				bit_and(remove_nodes,
					job_ptr->part_ptr->node_bitmap);
				bit_and_not(avail_node_bitmap, remove_nodes);
				FREE_NULL_BITMAP(remove_nodes);
			} else {
				job_ptr->part_ptr->flags |=
					PART_FLAG_SCHED_CLEARED;
				bit_and_not(avail_node_bitmap,
					    job_ptr->part_ptr->node_bitmap);
			}
		}
	}

	if (bb_wait_cnt)
		(void) bb_g_job_try_stage_in();

	if (job_ptr)
		job_resv_clear_magnetic_flag(job_ptr);
	FREE_NULL_BITMAP(avail_node_bitmap);
	avail_node_bitmap = save_avail_node_bitmap;
	FREE_NULL_LIST(job_queue);

	slurm_mutex_lock(&slurmctld_config.thread_count_lock);
	if ((slurmctld_config.server_thread_count >= 150) &&
	    (defer_rpc_cnt == 0)) {
		sched_info("%d pending RPCs at cycle end, consider configuring max_rpc_cnt",
			   slurmctld_config.server_thread_count);
	}
	slurm_mutex_unlock(&slurmctld_config.thread_count_lock);
	unlock_slurmctld(job_write_lock);
	END_TIMER2(__func__);

	_do_diag_stats(TIMER_DURATION_USEC());

out:
	return job_cnt;
}

/*
 * sort_job_queue - sort job_queue in descending priority order
 * IN/OUT job_queue - sorted job queue
 */
extern void sort_job_queue(list_t *job_queue)
{
	list_sort(job_queue, sort_job_queue2);
}

/* Note this differs from the ListCmpF typedef since we want jobs sorted
 * in order of decreasing priority then submit time and the by increasing
 * job id */
extern int sort_job_queue2(void *x, void *y)
{
	job_queue_rec_t *job_rec1 = *(job_queue_rec_t **) x;
	job_queue_rec_t *job_rec2 = *(job_queue_rec_t **) y;
	bool expedited1 = false, expedited2 = false;
	het_job_details_t *details = NULL;
	bool has_resv1, has_resv2;
	static time_t config_update = 0;
	static bool preemption_enabled = true;
	uint32_t job_id1, job_id2;
	uint32_t p1, p2;

	/* The following block of code is designed to minimize run time in
	 * typical configurations for this frequently executed function. */
	if (config_update != slurm_conf.last_update) {
		preemption_enabled = slurm_preemption_enabled();
		config_update = slurm_conf.last_update;
	}

	/*
	 * Expedited Requeue jobs are defined as "infinite" priority.
	 * If, somehow, both have that state, then fall back to "normal"
	 * sorting between the jobs.
	 */
	if (IS_JOB_EXPEDITING(job_rec1->job_ptr))
		expedited1 = true;
	if (IS_JOB_EXPEDITING(job_rec2->job_ptr))
		expedited2 = true;
	if (expedited1 != expedited2)
		return (expedited1 ? -1 : 1);

	if (preemption_enabled) {
		if (preempt_g_job_preempt_check(job_rec1, job_rec2))
			return -1;
		if (preempt_g_job_preempt_check(job_rec2, job_rec1))
			return 1;
	}

	if (bf_hetjob_prio && job_rec1->job_ptr->het_job_id &&
	    (job_rec1->job_ptr->het_job_id !=
	     job_rec2->job_ptr->het_job_id)) {
		if ((details = job_rec1->job_ptr->het_details))
			has_resv1 = details->any_resv;
		else
			has_resv1 = (job_rec1->job_ptr->resv_id != 0) ||
				job_rec1->resv_ptr;
	} else
		has_resv1 = (job_rec1->job_ptr->resv_id != 0) ||
			job_rec1->resv_ptr;

	if (bf_hetjob_prio && job_rec2->job_ptr->het_job_id &&
	    (job_rec2->job_ptr->het_job_id !=
	     job_rec1->job_ptr->het_job_id)) {
		if ((details = job_rec2->job_ptr->het_details))
			has_resv2 = details->any_resv;
		else
			has_resv2 = (job_rec2->job_ptr->resv_id != 0) ||
				job_rec2->resv_ptr;
	} else
		has_resv2 = (job_rec2->job_ptr->resv_id != 0) ||
			job_rec2->resv_ptr;

	if (has_resv1 && !has_resv2)
		return -1;
	if (!has_resv1 && has_resv2)
		return 1;

	if (job_rec1->part_ptr && job_rec2->part_ptr) {
		if (bf_hetjob_prio && job_rec1->job_ptr->het_job_id &&
		    (job_rec1->job_ptr->het_job_id !=
		     job_rec2->job_ptr->het_job_id)) {
			if ((details = job_rec1->job_ptr->het_details))
				p1 = details->priority_tier;
			else
				p1 = job_rec1->part_ptr->priority_tier;
		} else
			p1 = job_rec1->part_ptr->priority_tier;

		if (bf_hetjob_prio && job_rec2->job_ptr->het_job_id &&
		    (job_rec2->job_ptr->het_job_id !=
		     job_rec1->job_ptr->het_job_id)) {
			if ((details = job_rec2->job_ptr->het_details))
				p2 = details->priority_tier;
			else
				p2 = job_rec2->part_ptr->priority_tier;
		} else
			p2 = job_rec2->part_ptr->priority_tier;

		if (p1 < p2)
			return 1;
		if (p1 > p2)
			return -1;
	}

	if (bf_hetjob_prio && job_rec1->job_ptr->het_job_id &&
	    (job_rec1->job_ptr->het_job_id !=
	     job_rec2->job_ptr->het_job_id)) {
		if ((details = job_rec1->job_ptr->het_details))
			p1 = details->priority;
		else {
			if (job_rec1->job_ptr->part_ptr_list &&
			    job_rec1->job_ptr->prio_mult &&
			    job_rec1->job_ptr->prio_mult->priority_array)
				p1 = job_rec1->priority;
			else
				p1 = job_rec1->job_ptr->priority;
		}
	} else {
		if (job_rec1->job_ptr->part_ptr_list &&
		    job_rec1->job_ptr->prio_mult &&
		    job_rec1->job_ptr->prio_mult->priority_array)
			p1 = job_rec1->priority;
		else
			p1 = job_rec1->job_ptr->priority;
	}

	if (bf_hetjob_prio && job_rec2->job_ptr->het_job_id &&
	    (job_rec2->job_ptr->het_job_id !=
	     job_rec1->job_ptr->het_job_id)) {
		if ((details = job_rec2->job_ptr->het_details))
			p2 = details->priority;
		else {
			if (job_rec2->job_ptr->part_ptr_list &&
			    job_rec2->job_ptr->prio_mult &&
			    job_rec2->job_ptr->prio_mult->priority_array)
				p2 = job_rec2->priority;
			else
				p2 = job_rec2->job_ptr->priority;
		}
	} else {
		if (job_rec2->job_ptr->part_ptr_list &&
		    job_rec2->job_ptr->prio_mult &&
		    job_rec2->job_ptr->prio_mult->priority_array)
			p2 = job_rec2->priority;
		else
			p2 = job_rec2->job_ptr->priority;
	}

	if (p1 < p2)
		return 1;
	if (p1 > p2)
		return -1;

	/* If the priorities are the same sort by submission time */
	if (job_rec1->job_ptr->details && job_rec2->job_ptr->details) {
		if (job_rec1->job_ptr->details->submit_time >
		    job_rec2->job_ptr->details->submit_time)
			return 1;
		if (job_rec2->job_ptr->details->submit_time >
		    job_rec1->job_ptr->details->submit_time)
			return -1;
	}

	/* If the submission times are the same sort by increasing job id's */
	if (job_rec1->array_task_id == NO_VAL)
		job_id1 = job_rec1->job_id;
	else
		job_id1 = job_rec1->job_ptr->array_job_id;
	if (job_rec2->array_task_id == NO_VAL)
		job_id2 = job_rec2->job_id;
	else
		job_id2 = job_rec2->job_ptr->array_job_id;
	if (job_id1 > job_id2)
		return 1;
	else if (job_id1 < job_id2)
		return -1;

	/* If job IDs match compare task IDs */
	if (job_rec1->array_task_id > job_rec2->array_task_id)
		return 1;
	if (job_rec1->array_task_id < job_rec2->array_task_id)
		return -1;

	/* Magnetic or multi-reservation. */
	if (job_rec1->resv_ptr && job_rec2->resv_ptr) {
		if (job_rec1->resv_ptr->start_time >
		    job_rec2->resv_ptr->start_time)
			return 1;
		if (job_rec1->resv_ptr->start_time <
		    job_rec2->resv_ptr->start_time)
			return -1;
	}

	if (job_rec1->use_prefer && !job_rec2->use_prefer)
		return -1;
	else if (!job_rec1->use_prefer && job_rec2->use_prefer)
		return 1;

	return 0;
}

/* The environment" variable is points to one big xmalloc. In order to
 * manipulate the array for a hetjob, we need to split it into an array
 * containing multiple xmalloc variables */
static void _split_env(batch_job_launch_msg_t *launch_msg_ptr)
{
	int i;

	for (i = 1; i < launch_msg_ptr->envc; i++) {
		launch_msg_ptr->environment[i] =
			xstrdup(launch_msg_ptr->environment[i]);
	}
}

/* Given a scheduled job, return a pointer to it batch_job_launch_msg_t data */
static batch_job_launch_msg_t *_build_launch_job_msg(job_record_t *job_ptr,
						     uint16_t protocol_version)
{
	char *fail_why = NULL;
	batch_job_launch_msg_t *launch_msg_ptr;
	slurm_step_id_t step_id = STEP_ID_FROM_JOB_RECORD(job_ptr);

	/* Initialization of data structures */
	launch_msg_ptr = (batch_job_launch_msg_t *)
		xmalloc(sizeof(batch_job_launch_msg_t));
	launch_msg_ptr->deprecated.job_id = job_ptr->job_id;
	launch_msg_ptr->het_job_id = job_ptr->het_job_id;
	launch_msg_ptr->array_job_id = job_ptr->array_job_id;
	launch_msg_ptr->array_task_id = job_ptr->array_task_id;

	/* not packed, but set for use routing this message within slurmctld */
	launch_msg_ptr->step_id = STEP_ID_FROM_JOB_RECORD(job_ptr);
	launch_msg_ptr->step_id.step_id = SLURM_BATCH_SCRIPT;

	if (!(launch_msg_ptr->script_buf = get_job_script(job_ptr))) {
		fail_why = "Unable to load job batch script";
		goto job_failed;
	}

	/*
	 * We only want send the number of tasks if we explicitly requested
	 * them: num_tasks could be set (job_mgr.c
	 * _figure_out_num_tasks()). Otherwise a step requesting less than the
	 * allocation will be polluted with this calculated task count
	 * erroneously.
	 */
	if (job_ptr->bit_flags & JOB_NTASKS_SET)
		launch_msg_ptr->ntasks = job_ptr->details->num_tasks;
	launch_msg_ptr->container = xstrdup(job_ptr->container);
	launch_msg_ptr->cpu_freq_min = job_ptr->details->cpu_freq_min;
	launch_msg_ptr->cpu_freq_max = job_ptr->details->cpu_freq_max;
	launch_msg_ptr->cpu_freq_gov = job_ptr->details->cpu_freq_gov;
	launch_msg_ptr->nodes = xstrdup(job_ptr->nodes);
	launch_msg_ptr->overcommit = job_ptr->details->overcommit;
	launch_msg_ptr->open_mode  = job_ptr->details->open_mode;
	launch_msg_ptr->cpus_per_task = job_ptr->details->cpus_per_task;
	launch_msg_ptr->pn_min_memory = job_ptr->details->pn_min_memory;
	launch_msg_ptr->restart_cnt   = job_ptr->restart_cnt;
	launch_msg_ptr->profile       = job_ptr->profile;

	if (make_batch_job_cred(launch_msg_ptr, job_ptr, protocol_version)) {
		error("%s: slurm_cred_create failure for %pJ, holding job",
		      __func__, job_ptr);
		slurm_free_job_launch_msg(launch_msg_ptr);
		job_mgr_handle_cred_failure(job_ptr);
		return NULL;
	}

	launch_msg_ptr->acctg_freq = xstrdup(job_ptr->details->acctg_freq);
	if (job_ptr->part_ptr)
		launch_msg_ptr->partition = xstrdup(job_ptr->part_ptr->name);
	else
		launch_msg_ptr->partition = xstrdup(job_ptr->partition);
	launch_msg_ptr->std_err = xstrdup(job_ptr->details->std_err);
	launch_msg_ptr->std_in = xstrdup(job_ptr->details->std_in);
	launch_msg_ptr->std_out = xstrdup(job_ptr->details->std_out);
	launch_msg_ptr->work_dir = xstrdup(job_ptr->details->work_dir);

	/*
	 * Upon job submission, slurmctld will fill in the default stdout path
	 * in the job record if the job did not specify a path (e.g. no --output
	 * for sbatch). For jobs that were submitted to an older slurmctld
	 * (25.05 and older), and are using the using the default stdout path,
	 * they will not store the stdout path in the job record. The following
	 * logic fills in the default stdout path in the batch launch message so
	 * that slurmstepd can handle the paths correctly as they now expect
	 * stdout path to be set.
	 */
	if ((job_ptr->start_protocol_ver <= SLURM_25_05_PROTOCOL_VERSION) &&
	    !launch_msg_ptr->std_out) {
		if (!job_ptr->array_job_id) {
			launch_msg_ptr->std_out =
				xstrdup(DEFAULT_BATCH_STDOUT_PATH);
		} else {
			launch_msg_ptr->std_out =
				xstrdup(DEFAULT_BATCH_ARRAY_STDOUT_PATH);
		}
	}

	launch_msg_ptr->argc = job_ptr->details->argc;
	launch_msg_ptr->argv = xduparray(job_ptr->details->argc,
					 job_ptr->details->argv);
	launch_msg_ptr->spank_job_env_size = job_ptr->spank_job_env_size;
	launch_msg_ptr->spank_job_env = xduparray(job_ptr->spank_job_env_size,
						  job_ptr->spank_job_env);
	launch_msg_ptr->environment = get_job_env(job_ptr,
						  &launch_msg_ptr->envc);
	if (!launch_msg_ptr->container && !launch_msg_ptr->environment) {
		fail_why = "Unable to load job environment";
		goto job_failed;
	}

	_split_env(launch_msg_ptr);

	if (job_ptr->bit_flags & STEPMGR_ENABLED) {
		env_array_overwrite(&launch_msg_ptr->environment,
				    "SLURM_STEPMGR", job_ptr->batch_host);
		/* Update envc if env was added to */
		launch_msg_ptr->envc =
			PTR_ARRAY_SIZE(launch_msg_ptr->environment) - 1;
	}

	launch_msg_ptr->job_mem = job_ptr->details->pn_min_memory;
	launch_msg_ptr->num_cpu_groups = job_ptr->job_resrcs->cpu_array_cnt;
	launch_msg_ptr->cpus_per_node  = xmalloc(
		sizeof(uint16_t) * job_ptr->job_resrcs->cpu_array_cnt);
	memcpy(launch_msg_ptr->cpus_per_node,
	       job_ptr->job_resrcs->cpu_array_value,
	       (sizeof(uint16_t) * job_ptr->job_resrcs->cpu_array_cnt));
	launch_msg_ptr->cpu_count_reps  = xmalloc(
		sizeof(uint32_t) * job_ptr->job_resrcs->cpu_array_cnt);
	memcpy(launch_msg_ptr->cpu_count_reps,
	       job_ptr->job_resrcs->cpu_array_reps,
	       (sizeof(uint32_t) * job_ptr->job_resrcs->cpu_array_cnt));

	launch_msg_ptr->account = xstrdup(job_ptr->account);
	if (job_ptr->qos_ptr)
		launch_msg_ptr->qos = xstrdup(job_ptr->qos_ptr->name);

	if (job_ptr->details->oom_kill_step != NO_VAL16)
		launch_msg_ptr->oom_kill_step = job_ptr->details->oom_kill_step;
	else
		launch_msg_ptr->oom_kill_step =
			slurm_conf.task_plugin_param & OOM_KILL_STEP;
	/*
	 * Use resv_ptr->name instead of job_ptr->resv_name as the job
	 * could contain multiple reservation names.
	 */
	if (job_ptr->resv_ptr)
		launch_msg_ptr->resv_name = xstrdup(job_ptr->resv_ptr->name);

	launch_msg_ptr->tres_per_task = xstrdup(job_ptr->tres_per_task);

	xassert(!fail_why);
	return launch_msg_ptr;

job_failed:
	/* fatal or kill the job as it can never be recovered */
	if (!ignore_state_errors)
		fatal("%s: %s for %pJ. Check file system serving StateSaveLocation as that directory may be missing or corrupted. Start with '-i' to ignore this error and kill the afflicted jobs.",
		      __func__, fail_why, job_ptr);

	error("%s: %s for %pJ. %pJ will be killed due to system error.",
	      __func__, fail_why, job_ptr, job_ptr);
	xfree(job_ptr->state_desc);
	job_ptr->state_desc = xstrdup(fail_why);
	job_ptr->state_reason = FAIL_SYSTEM;
	last_job_update = time(NULL);
	slurm_free_job_launch_msg(launch_msg_ptr);
	/* ignore the return as job is in an unknown state anyway */
	job_complete(&step_id, slurm_conf.slurm_user_id, false, false, 1);
	return NULL;
}

static int _foreach_het_job_ready(void *x, void *arg)
{
	job_record_t *het_job = x;
	het_job_ready_t *ready_struct = arg;
	bool prolog = false;

	if (ready_struct->het_job_leader->het_job_id != het_job->het_job_id) {
		error("%s: Bad het_job_list for %pJ",
		      __func__, ready_struct->het_job_leader);
		return 0;
	}

	ready_struct->het_job = het_job;

	if (het_job->details)
		prolog = het_job->details->prolog_running;
	if (prolog || IS_JOB_CONFIGURING(het_job) ||
	    !test_job_nodes_ready(het_job)) {
		ready_struct->het_job_leader = NULL;
		return -1;
	}
	if (!ready_struct->job_ptr->batch_flag ||
	    (!IS_JOB_RUNNING(ready_struct->job_ptr) &&
	     !IS_JOB_SUSPENDED(ready_struct->job_ptr))) {
		ready_struct->het_job_leader = NULL;
		return -1;
	}

	ready_struct->het_job = NULL;
	return 0;
}

/* Validate the job is ready for launch
 * RET pointer to batch job to launch or NULL if not ready yet */
static job_record_t *_het_job_ready(job_record_t *job_ptr)
{
	het_job_ready_t ready_struct = { 0 };

	if (job_ptr->het_job_id == 0)	/* Not a hetjob */
		return job_ptr;
	ready_struct.het_job_leader = find_job_record(job_ptr->het_job_id);
	if (!ready_struct.het_job_leader) {
		error("Hetjob leader %pJ not found", job_ptr);
		return NULL;
	}
	if (!ready_struct.het_job_leader->het_job_list) {
		error("Hetjob leader %pJ lacks het_job_list", job_ptr);
		return NULL;
	}

	ready_struct.job_ptr = job_ptr;
	(void) list_for_each(ready_struct.het_job_leader->het_job_list,
			     _foreach_het_job_ready, &ready_struct);

	if (ready_struct.het_job_leader)
		log_flag(HETJOB, "Batch hetjob %pJ being launched",
			 ready_struct.het_job_leader);
	else if (ready_struct.het_job)
		log_flag(HETJOB, "Batch hetjob %pJ waiting for job to be ready",
			 ready_struct.het_job);

	return ready_struct.het_job_leader;
}

extern char *get_tasks_per_node(job_record_t *job_ptr)
{
	/* Should always be set for active jobs */
	struct job_resources *resrcs_ptr = job_ptr->job_resrcs;
	slurm_step_layout_t *step_layout = NULL;
	uint16_t cpus_per_task = 1;
	char *task_count = NULL;
	uint16_t *cpus_per_node = NULL;
	uint16_t *cpus_per_task_array = NULL;
	int node_inx = 0;

	slurm_step_layout_req_t step_layout_req = {
		.plane_size = NO_VAL16,
	};

	xassert(job_ptr->details);
	xassert(resrcs_ptr);

	step_layout_req.num_hosts = resrcs_ptr->nhosts;

	if ((job_ptr->details->cpus_per_task > 0) &&
	    (job_ptr->details->cpus_per_task != NO_VAL16))
		cpus_per_task = job_ptr->details->cpus_per_task;

	/* Expand RLE cpu_array into flat per-node arrays */
	cpus_per_node = xcalloc(resrcs_ptr->nhosts, sizeof(*cpus_per_node));
	cpus_per_task_array =
		xcalloc(resrcs_ptr->nhosts, sizeof(*cpus_per_task_array));
	for (int i = 0; i < resrcs_ptr->cpu_array_cnt; i++) {
		for (int j = 0; j < resrcs_ptr->cpu_array_reps[i]; j++) {
			if (node_inx >= resrcs_ptr->nhosts)
				break;
			cpus_per_node[node_inx] =
				resrcs_ptr->cpu_array_value[i];
			cpus_per_task_array[node_inx] = cpus_per_task;
			node_inx++;
		}
	}

	step_layout_req.cpus_per_node = cpus_per_node;
	step_layout_req.cpus_per_task = cpus_per_task_array;

	if (job_ptr->bit_flags & JOB_NTASKS_SET &&
	    (job_ptr->details->num_tasks)) {
		step_layout_req.num_tasks = job_ptr->details->num_tasks;
	} else if (job_ptr->details->ntasks_per_node) {
		step_layout_req.num_tasks = job_ptr->details->ntasks_per_node *
					    step_layout_req.num_hosts;
	} else {
		step_layout_req.num_tasks = 0;
		for (int i = 0; i < resrcs_ptr->nhosts; i++) {
			step_layout_req.num_tasks +=
				cpus_per_node[i] / cpus_per_task;
		}
	}

	step_layout_req.task_dist = job_ptr->details->task_dist;

	if ((job_ptr->details->task_dist & SLURM_DIST_STATE_BASE) ==
	    SLURM_DIST_ARBITRARY) {
		step_layout_req.node_list = job_ptr->details->req_nodes;
	} else {
		step_layout_req.node_list = job_ptr->nodes;
	}

	if (job_ptr->details->mc_ptr->plane_size)
		step_layout_req.plane_size =
			job_ptr->details->mc_ptr->plane_size;

	step_layout = slurm_step_layout_create(&step_layout_req);

	if (step_layout) {
		task_count = uint16_array_to_str(step_layout->node_cnt,
						 step_layout->tasks);
		slurm_step_layout_destroy(step_layout);
	}

	xfree(cpus_per_node);
	xfree(cpus_per_task_array);
	return task_count;
}

static void _set_job_env(job_record_t *job, batch_job_launch_msg_t *launch)
{
	char *task_count;

	if (job->name)
		env_array_overwrite(&launch->environment, "SLURM_JOB_NAME",
				    job->name);

	/*
	 * The oversubscribe/exclusive helpers handle job->details == NULL
	 * internally; the rest of this function dereferences job->details
	 * unconditionally, so no guard is needed here. These return static
	 * strings; do not xfree.
	 */
	env_array_overwrite(&launch->environment,
			    "SLURM_JOB_OVERSUBSCRIBE",
			    job_oversubscribe_string(
				    get_job_oversubscribe_value(job)));
	env_array_overwrite(&launch->environment,
			    "SLURM_JOB_EXCLUSIVE",
			    job_exclusive_display_string(
				    get_job_exclusive_display_value(job)));

	if (job->details->open_mode) {
		/* Propagate mode to spawned job using environment variable */
		if (job->details->open_mode == OPEN_MODE_APPEND)
			env_array_overwrite(&launch->environment,
					    "SLURM_OPEN_MODE", "a");
		else
			env_array_overwrite(&launch->environment,
					    "SLURM_OPEN_MODE", "t");
	}

	if (job->details->dependency)
		env_array_overwrite(&launch->environment,
				    "SLURM_JOB_DEPENDENCY",
				    job->details->dependency);

	/* intentionally skipping SLURM_EXPORT_ENV */

	if (job->profile) {
		char tmp[128] = {0};
		acct_gather_profile_to_string_r(job->profile, tmp);
		env_array_overwrite(&launch->environment, "SLURM_PROFILE", tmp);
	}

	if (job->details->acctg_freq)
		env_array_overwrite(&launch->environment, "SLURM_ACCTG_FREQ",
				    job->details->acctg_freq);

	if (job->network)
		env_array_overwrite(&launch->environment, "SLURM_NETWORK",
				    job->network);

	if (job->details->cpu_freq_min || job->details->cpu_freq_max ||
	    job->details->cpu_freq_gov) {
		char *tmp = cpu_freq_to_cmdline(job->details->cpu_freq_min,
						job->details->cpu_freq_max,
						job->details->cpu_freq_gov);

		if (tmp)
			env_array_overwrite(&launch->environment,
					    "SLURM_CPU_FREQ_REQ", tmp);

		xfree(tmp);
	}

	if (job->details->segment_size)
		env_array_overwrite_fmt(&launch->environment,
					"SLURM_JOB_SEGMENT_SIZE", "%u",
					job->details->segment_size);

	task_count = get_tasks_per_node(job);
	if (task_count)
		env_array_overwrite(&launch->environment,
				    "SLURM_TASKS_PER_NODE", task_count);
	xfree(task_count);

	/* update size of env in case it changed */
	if (launch->environment)
		launch->envc = PTR_ARRAY_SIZE(launch->environment) - 1;
}

static int _foreach_set_het_job_env(void *x, void *arg)
{
	job_record_t *het_job = x;
	het_job_env_t *het_job_env = arg;
	job_record_t *het_job_leader = het_job_env->het_job_leader;
	int het_job_offset = het_job_env->het_job_offset;
	batch_job_launch_msg_t *launch_msg_ptr = het_job_env->launch_msg_ptr;
	uint64_t tmp_mem = 0;
	char *tmp_str = NULL;

	if (het_job_leader->het_job_id != het_job->het_job_id) {
		error("%s: Bad het_job_list for %pJ",
		      __func__, het_job_leader);
		return 0;
	}
	if (het_job->account) {
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_JOB_ACCOUNT",
			het_job_offset, "%s", het_job->account);
	}

	if (het_job->job_resrcs) {
		tmp_str = uint32_compressed_to_str(
			het_job->job_resrcs->cpu_array_cnt,
			het_job->job_resrcs->cpu_array_value,
			het_job->job_resrcs->cpu_array_reps);
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_JOB_CPUS_PER_NODE",
			het_job_offset, "%s", tmp_str);
		xfree(tmp_str);
	}
	(void) env_array_overwrite_het_fmt(
		&launch_msg_ptr->environment,
		"SLURM_JOB_ID",
		het_job_offset, "%u", het_job->job_id);
	(void) env_array_overwrite_het_fmt(
		&launch_msg_ptr->environment,
		"SLURM_JOB_NAME",
		het_job_offset, "%s", het_job->name);
	(void) env_array_overwrite_het_fmt(
		&launch_msg_ptr->environment,
		"SLURM_JOB_NODELIST",
		het_job_offset, "%s", het_job->nodes);
	(void) env_array_overwrite_het_fmt(
		&launch_msg_ptr->environment,
		"SLURM_JOB_NUM_NODES",
		het_job_offset, "%u", het_job->node_cnt);
	if (het_job->partition) {
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_JOB_PARTITION",
			het_job_offset, "%s", het_job->partition);
	}
	if (het_job->qos_ptr) {
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_JOB_QOS",
			het_job_offset, "%s", het_job->qos_ptr->name);
	}
	if (het_job->resv_ptr) {
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_JOB_RESERVATION",
			het_job_offset, "%s", het_job->resv_ptr->name);
	}
	if (het_job->details)
		tmp_mem = het_job->details->pn_min_memory;
	if (tmp_mem & MEM_PER_CPU) {
		tmp_mem &= (~MEM_PER_CPU);
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_MEM_PER_CPU",
			het_job_offset, "%"PRIu64"", tmp_mem);
	} else if (tmp_mem) {
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment,
			"SLURM_MEM_PER_NODE",
			het_job_offset, "%"PRIu64"", tmp_mem);
	}

	if (het_job->details && het_job->details->segment_size)
		(void) env_array_overwrite_het_fmt(
			&launch_msg_ptr->environment, "SLURM_JOB_SEGMENT_SIZE",
			het_job_offset, "%u", het_job->details->segment_size);

	if (het_job->details && het_job->job_resrcs) {
		/* Both should always be set for active jobs */
		tmp_str = get_tasks_per_node(het_job);
		if (tmp_str) {
			(void) env_array_overwrite_het_fmt(
				&launch_msg_ptr->environment,
				"SLURM_TASKS_PER_NODE",
				het_job_offset,"%s", tmp_str);
			xfree(tmp_str);
		}
	} else if (IS_JOB_RUNNING(het_job)) {
		if (!het_job->details)
			error("%s: %pJ has null details member",
			      __func__, het_job);
		if (!het_job->job_resrcs)
			error("%s: %pJ has null job_resrcs member",
			      __func__, het_job);
	}
	het_job_env->het_job_offset++;

	return 0;
}

/*
 * Set some hetjob environment variables. This will include information
 * about multiple job components (i.e. different slurmctld job records).
 */
static void _set_het_job_env(job_record_t *het_job_leader,
			     batch_job_launch_msg_t *launch_msg_ptr)
{
	int i;
	het_job_env_t het_job_env = {
		.het_job_leader = het_job_leader,
		.het_job_offset = 0,
		.launch_msg_ptr = launch_msg_ptr,
	};

	if (het_job_leader->het_job_id == 0)
		return;
	if (!launch_msg_ptr->environment) {
		error("%pJ lacks environment", het_job_leader);
		return;
	}
	if (!het_job_leader->het_job_list) {
		error("Hetjob leader %pJ lacks het_job_list",
		      het_job_leader);
		return;
	}

	(void) list_for_each(het_job_leader->het_job_list,
			     _foreach_set_het_job_env,
			     &het_job_env);

	/* Continue support for old hetjob terminology. */
	(void) env_array_overwrite_fmt(&launch_msg_ptr->environment,
				       "SLURM_PACK_SIZE", "%d",
				       het_job_env.het_job_offset);
	(void) env_array_overwrite_fmt(&launch_msg_ptr->environment,
				       "SLURM_HET_SIZE", "%d",
				       het_job_env.het_job_offset);

	for (i = 0; launch_msg_ptr->environment[i]; i++)
		;
	launch_msg_ptr->envc = i;
}

/*
 * launch_job - send an RPC to a slurmd to initiate a batch job
 * IN job_ptr - pointer to job that will be initiated
 */
extern void launch_job(job_record_t *job_ptr)
{
	batch_job_launch_msg_t *launch_msg_ptr;
	uint16_t protocol_version = NO_VAL16;
	agent_arg_t *agent_arg_ptr;
	job_record_t *launch_job_ptr;
	node_record_t *node_ptr;

	xassert(job_ptr);
	xassert(job_ptr->batch_flag);

	if (job_ptr->total_cpus == 0)
		return;

	launch_job_ptr = _het_job_ready(job_ptr);
	if (!launch_job_ptr)
		return;

	if (pick_batch_host(launch_job_ptr) != SLURM_SUCCESS)
		return;

	node_ptr = find_node_record(job_ptr->batch_host);
	if (node_ptr)
		protocol_version = node_ptr->protocol_version;

	(void)build_batch_step(job_ptr);

	launch_msg_ptr = _build_launch_job_msg(launch_job_ptr,protocol_version);
	if (launch_msg_ptr == NULL)
		return;
	if (launch_job_ptr->het_job_id)
		_set_het_job_env(launch_job_ptr, launch_msg_ptr);

	_set_job_env(launch_job_ptr, launch_msg_ptr);

	agent_arg_ptr = xmalloc(sizeof(agent_arg_t));
	agent_arg_ptr->protocol_version = protocol_version;
	agent_arg_ptr->node_count = 1;
	agent_arg_ptr->retry = 0;
	xassert(job_ptr->batch_host);
	agent_arg_ptr->hostlist = hostlist_create(launch_job_ptr->batch_host);
	agent_arg_ptr->msg_type = REQUEST_BATCH_JOB_LAUNCH;
	agent_arg_ptr->msg_args = (void *) launch_msg_ptr;
	set_agent_arg_r_uid(agent_arg_ptr, SLURM_AUTH_UID_ANY);

	/* Launch the RPC via agent */
	agent_queue_request(agent_arg_ptr);

	/* Reset expedited requeue flags now that the job is running again */
	if (IS_JOB_EXPEDITING(job_ptr)) {
		job_ptr->epilog_failed = false;
		job_state_unset_flag(job_ptr, JOB_EXPEDITING);
	}
}

/*
 * make_batch_job_cred - add a job credential to the batch_job_launch_msg
 * IN/OUT launch_msg_ptr - batch_job_launch_msg in which job_id, step_id,
 *                         uid and nodes have already been set
 * IN job_ptr - pointer to job record
 * RET 0 or error code
 */
extern int make_batch_job_cred(batch_job_launch_msg_t *launch_msg_ptr,
			       job_record_t *job_ptr,
			       uint16_t protocol_version)
{
	slurm_cred_arg_t cred_arg;
	job_resources_t *job_resrcs_ptr;

	xassert(job_ptr->job_resrcs);
	job_resrcs_ptr = job_ptr->job_resrcs;

	if (job_ptr->job_resrcs == NULL) {
		error("%s: %pJ is missing job_resrcs info",
		      __func__, job_ptr);
		return SLURM_ERROR;
	}

	setup_cred_arg(&cred_arg, job_ptr);

	cred_arg.step_id = launch_msg_ptr->step_id;

	if (job_resrcs_ptr->memory_allocated) {
		int batch_inx = job_get_node_inx(
			job_ptr->batch_host, job_ptr->node_bitmap);

		if (batch_inx == -1) {
			error("%s: Invalid batch host %s for %pJ; this should never happen",
			      __func__, job_ptr->batch_host, job_ptr);
			batch_inx = 0;
		}
		cred_arg.job_mem_alloc = xmalloc(sizeof(uint64_t));
		cred_arg.job_mem_alloc[0] =
			job_resrcs_ptr->memory_allocated[batch_inx];
		cred_arg.job_mem_alloc_rep_count = xmalloc(sizeof(uint64_t));
		cred_arg.job_mem_alloc_rep_count[0] = 1;
		cred_arg.job_mem_alloc_size = 1;
	}
/*	cred_arg.step_gres_list      = NULL; */

	xassert(job_ptr->batch_host);
	cred_arg.step_hostlist       = job_ptr->batch_host;
	cred_arg.step_core_bitmap    = job_resrcs_ptr->core_bitmap;

	if (!switch_g_setup_special_steps() ||
	    (switch_g_stepinfo_build(
		    (dynamic_plugin_data_t **) &cred_arg.switch_step,
		    job_ptr->switch_jobinfo,
		    NULL) == SLURM_SUCCESS)) {
		launch_msg_ptr->cred = slurm_cred_create(
			&cred_arg, false,
			protocol_version);
	}

	switch_g_stepinfo_free(cred_arg.switch_step);
	xfree(cred_arg.job_mem_alloc);
	xfree(cred_arg.job_mem_alloc_rep_count);

	if (launch_msg_ptr->cred)
		return SLURM_SUCCESS;
	error("slurm_cred_create failure for batch job %u",
	      cred_arg.step_id.job_id);
	return SLURM_ERROR;
}

static int _foreach_depend_list_copy(void *x, void *arg)
{
	depend_spec_t *dep_src = x;
	list_t **depend_list_dest = arg;
	depend_spec_t *dep_dest = xmalloc(sizeof(depend_spec_t));

	memcpy(dep_dest, dep_src, sizeof(depend_spec_t));
	list_append(*depend_list_dest, dep_dest);

	return 0;
}

/*
 * Copy a job's dependency list
 * IN depend_list_src - a job's depend_lst
 * RET copy of depend_list_src, must bee freed by caller
 */
extern list_t *depended_list_copy(list_t *depend_list_src)
{
	list_t *depend_list_dest = NULL;

	if (!depend_list_src)
		return depend_list_dest;

	depend_list_dest = list_create(xfree_ptr);
	(void) list_for_each(depend_list_src, _foreach_depend_list_copy,
			     &depend_list_dest);
	return depend_list_dest;
}

static char *_depend_type2str(depend_spec_t *dep_ptr)
{
	xassert(dep_ptr);

	switch (dep_ptr->depend_type) {
	case SLURM_DEPEND_AFTER:
		return "after";
	case SLURM_DEPEND_AFTER_ANY:
		return "afterany";
	case SLURM_DEPEND_AFTER_NOT_OK:
		return "afternotok";
	case SLURM_DEPEND_AFTER_OK:
		return "afterok";
	case SLURM_DEPEND_AFTER_CORRESPOND:
		return "aftercorr";
	case SLURM_DEPEND_EXPAND:
		return "expand";
	case SLURM_DEPEND_BURST_BUFFER:
		return "afterburstbuffer";
	case SLURM_DEPEND_SINGLETON:
		return "singleton";
	default:
		return "unknown";
	}
}

static uint32_t _depend_state_str2state(char *state_str)
{
	if (!xstrcasecmp(state_str, "fulfilled"))
		return DEPEND_FULFILLED;
	if (!xstrcasecmp(state_str, "failed"))
		return DEPEND_FAILED;
	/* Default to not fulfilled */
	return DEPEND_NOT_FULFILLED;
}

static char *_depend_state2str(depend_spec_t *dep_ptr)
{
	xassert(dep_ptr);

	switch(dep_ptr->depend_state) {
	case DEPEND_NOT_FULFILLED:
		return "unfulfilled";
	case DEPEND_FULFILLED:
		return "fulfilled";
	case DEPEND_FAILED:
		return "failed";
	default:
		return "unknown";
	}
}

static int _foreach_depend_list2str(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;
	depend_str_t *depend_str = arg;
	job_record_t *job_ptr = depend_str->job_ptr;

	/*
	 * Show non-fulfilled (including failed) dependencies, but don't
	 * show fulfilled dependencies.
	 */
	if (dep_ptr->depend_state == DEPEND_FULFILLED)
		return 0;
	if (dep_ptr->depend_type == SLURM_DEPEND_SINGLETON) {
		xstrfmtcat(job_ptr->details->dependency,
			   "%ssingleton(%s)",
			   depend_str->sep, _depend_state2str(dep_ptr));
	} else {
		char *dep_str = _depend_type2str(dep_ptr);

		if (dep_ptr->array_task_id == INFINITE)
			xstrfmtcat(job_ptr->details->dependency, "%s%s:%u_*",
				   depend_str->sep, dep_str, dep_ptr->job_id);
		else if (dep_ptr->array_task_id == NO_VAL)
			xstrfmtcat(job_ptr->details->dependency, "%s%s:%u",
				   depend_str->sep, dep_str, dep_ptr->job_id);
		else
			xstrfmtcat(job_ptr->details->dependency, "%s%s:%u_%u",
				   depend_str->sep, dep_str, dep_ptr->job_id,
				   dep_ptr->array_task_id);

		if (dep_ptr->depend_time)
			xstrfmtcat(job_ptr->details->dependency,
				   "+%u", dep_ptr->depend_time / 60);
		xstrfmtcat(job_ptr->details->dependency, "(%s)",
			   _depend_state2str(dep_ptr));
	}
	if (depend_str->set_or_flag)
		dep_ptr->depend_flags |= SLURM_FLAGS_OR;
	if (dep_ptr->depend_flags & SLURM_FLAGS_OR)
		depend_str->sep = "?";
	else
		depend_str->sep = ",";

	return 0;
}

static void _depend_list2str(job_record_t *job_ptr, bool set_or_flag)
{
	depend_str_t depend_str = {
		.job_ptr = job_ptr,
		.sep = "",
		.set_or_flag = set_or_flag,
	};

	if (job_ptr->details == NULL)
		return;

	xfree(job_ptr->details->dependency);

	if (job_ptr->details->depend_list == NULL
	    || list_count(job_ptr->details->depend_list) == 0)
		return;

	(void) list_for_each(job_ptr->details->depend_list,
			     _foreach_depend_list2str,
			     &depend_str);
}

/* Print a job's dependency information based upon job_ptr->depend_list */
extern void print_job_dependency(job_record_t *job_ptr, const char *func)
{
	if ((job_ptr->details == NULL) ||
	    (job_ptr->details->depend_list == NULL)) {
		info("%s: %pJ has no dependency.", func, job_ptr);
		return;
	}
	_depend_list2str(job_ptr, false);
	info("%s: Dependency information for %pJ:\n  %s",
	     func, job_ptr, job_ptr->details->dependency);
}

static int _test_job_dependency_common(
	bool is_complete, bool is_completed, bool is_pending,
	bool *clear_dep, bool *failure,
	job_record_t *job_ptr, struct depend_spec *dep_ptr)
{
	int rc = 0;
	job_record_t *djob_ptr = dep_ptr->job_ptr;
	time_t now = time(NULL);

	xassert(clear_dep);
	xassert(failure);

	if (dep_ptr->depend_type == SLURM_DEPEND_AFTER) {
		if (!is_pending) {
			if (!dep_ptr->depend_time ||
			    (djob_ptr->start_time &&
			     ((now - djob_ptr->start_time) >=
			      dep_ptr->depend_time)) ||
			    fed_mgr_job_started_on_sib(djob_ptr)) {
				*clear_dep = true;
			} /* else still depends */
		} /* else still depends */
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_AFTER_ANY) {
		if (is_completed)
			*clear_dep = true;
		/* else still depends */
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_AFTER_NOT_OK) {
		if (djob_ptr->job_state & JOB_SPECIAL_EXIT)
			*clear_dep = true;
		else if (!is_completed) { /* Still depends */
		} else if (!is_complete)
			*clear_dep = true;
		else
			*failure = true;
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_AFTER_OK) {
		if (!is_completed) { /* Still depends */
		} else if (is_complete)
			*clear_dep = true;
		else
			*failure = true;
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_AFTER_CORRESPOND) {
		job_record_t *dcjob_ptr = NULL;
		if ((job_ptr->array_task_id == NO_VAL) ||
		    (job_ptr->array_task_id == INFINITE))
			dcjob_ptr = NULL;
		else
			dcjob_ptr = find_job_array_rec(dep_ptr->job_id,
						       job_ptr->array_task_id);

		if (dcjob_ptr) {
			if (!IS_JOB_COMPLETED(dcjob_ptr)) { /* Still depends */
			} else if (IS_JOB_COMPLETE(dcjob_ptr))
				*clear_dep = true;
			else
				*failure = true;
		} else {
			if (!is_completed) { /* Still depends */
			} else if (is_complete)
				*clear_dep = true;
			else if (job_ptr->array_recs &&
				 (job_ptr->array_task_id == NO_VAL)) {
				/* Still depends */
			} else
				*failure = true;
		}
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_BURST_BUFFER) {
		if (is_completed &&
		    (bb_g_job_test_stage_out(djob_ptr) == 1))
			*clear_dep = true;
		/* else still depends */
		rc = 1;
	} else if (dep_ptr->depend_type == SLURM_DEPEND_EXPAND) {
		time_t now = time(NULL);
		if (is_pending) { /* Still depends */
		} else if (is_completed)
			*failure = true;
		else if ((djob_ptr->end_time != 0) &&
			 (djob_ptr->end_time > now)) {
			job_ptr->time_limit = djob_ptr->end_time - now;
			job_ptr->time_limit /= 60;  /* sec to min */
			*clear_dep = true;
		}
		if (!*failure && job_ptr->details && djob_ptr->details) {
			job_ptr->details->share_res =
				djob_ptr->details->share_res;
			job_ptr->details->whole_node =
				djob_ptr->details->whole_node;
		}
		rc = 1;
	}

	return rc;
}

static void _test_dependency_state(depend_spec_t *dep_ptr,
				   test_job_dep_t *test_job_dep)
{
	xassert(test_job_dep);

	test_job_dep->or_flag =
		(dep_ptr->depend_flags & SLURM_FLAGS_OR) ? true : false;

	if (test_job_dep->or_flag) {
		if (dep_ptr->depend_state == DEPEND_FULFILLED)
			test_job_dep->or_satisfied = true;
		else if (dep_ptr->depend_state == DEPEND_NOT_FULFILLED)
			test_job_dep->has_unfulfilled = true;
	} else { /* AND'd dependencies */
		if (dep_ptr->depend_state == DEPEND_FAILED)
			test_job_dep->and_failed = true;
		else if (dep_ptr->depend_state == DEPEND_NOT_FULFILLED)
			test_job_dep->has_unfulfilled = true;
	}
}

static int _foreach_test_job_dependency(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;
	test_job_dep_t *test_job_dep = arg;
	job_record_t *job_ptr = test_job_dep->job_ptr;
	job_record_t *djob_ptr;
	bool clear_dep = false, failure = false;
	bool remote = (dep_ptr->depend_flags & SLURM_FLAGS_REMOTE) ?
		true : false;
	/*
	 * If the job id is for a cluster that's not in the federation
	 * (it's likely the cluster left the federation), then set
	 * this dependency's state to failed.
	 */
	if (remote) {
		if (fed_mgr_is_origin_job(job_ptr) &&
		    (dep_ptr->depend_state == DEPEND_NOT_FULFILLED) &&
		    (dep_ptr->depend_type != SLURM_DEPEND_SINGLETON) &&
		    (!fed_mgr_is_job_id_in_fed(dep_ptr->job_id))) {
			log_flag(DEPENDENCY, "%s: %pJ dependency %s:%u failed due to job_id not in federation.",
				 __func__, job_ptr,
				 _depend_type2str(dep_ptr),
				 dep_ptr->job_id);
			test_job_dep->changed = true;
			dep_ptr->depend_state = DEPEND_FAILED;
		}
	}
	if ((dep_ptr->depend_state != DEPEND_NOT_FULFILLED) || remote) {
		_test_dependency_state(dep_ptr, test_job_dep);
		return 0;
	}

	/* Test local, unfulfilled dependency: */
	test_job_dep->has_local_depend = true;
	dep_ptr->job_ptr = find_job_array_rec(dep_ptr->job_id,
					      dep_ptr->array_task_id);
	djob_ptr = dep_ptr->job_ptr;
	if ((dep_ptr->depend_type == SLURM_DEPEND_SINGLETON) &&
	    job_ptr->name) {
		if (list_find_first(job_list, _find_singleton_job,
				    job_ptr) ||
		    !fed_mgr_is_singleton_satisfied(job_ptr,
						    dep_ptr, true)) {
			/* Still depends */
		} else
			clear_dep = true;
	} else if (!djob_ptr || (djob_ptr->magic != JOB_MAGIC) ||
		   ((djob_ptr->job_id != dep_ptr->job_id) &&
		    (djob_ptr->array_job_id != dep_ptr->job_id))) {
		/* job is gone, dependency lifted */
		clear_dep = true;
	} else {
		bool is_complete, is_completed, is_pending;

		/* Special case, apply test to job array as a whole */
		if (dep_ptr->array_task_id == INFINITE) {
			is_complete = test_job_array_complete(
				dep_ptr->job_id);
			is_completed = test_job_array_completed(
				dep_ptr->job_id);
			is_pending = test_job_array_pending(
				dep_ptr->job_id);
		} else {
			/* Normal job */
			is_complete = IS_JOB_COMPLETE(djob_ptr);
			is_completed = IS_JOB_COMPLETED(djob_ptr);
			is_pending = IS_JOB_PENDING(djob_ptr) ||
				     IS_JOB_CONFIGURING(djob_ptr);
		}

		if (!_test_job_dependency_common(
			    is_complete, is_completed, is_pending,
			    &clear_dep, &failure,
			    job_ptr, dep_ptr))
			failure = true;
	}

	if (failure) {
		dep_ptr->depend_state = DEPEND_FAILED;
		test_job_dep->changed = true;
		log_flag(DEPENDENCY, "%s: %pJ dependency %s:%u failed.",
			 __func__, job_ptr, _depend_type2str(dep_ptr),
			 dep_ptr->job_id);
	} else if (clear_dep) {
		dep_ptr->depend_state = DEPEND_FULFILLED;
		test_job_dep->changed = true;
		log_flag(DEPENDENCY, "%s: %pJ dependency %s:%u fulfilled.",
			 __func__, job_ptr, _depend_type2str(dep_ptr),
			 dep_ptr->job_id);
	}

	_test_dependency_state(dep_ptr, test_job_dep);

	return 0;
}

static int _handle_failed_dependency(job_record_t *job_ptr,
				     test_job_dep_t *test_job_dep)
{
	int results;

	if (test_job_dep->changed) {
		_depend_list2str(job_ptr, false);
		if (slurm_conf.debug_flags & DEBUG_FLAG_DEPENDENCY)
			print_job_dependency(job_ptr, __func__);
	}
	job_ptr->bit_flags |= JOB_DEPENDENT;
	acct_policy_remove_accrue_time(job_ptr, false);
	if (test_job_dep->and_failed ||
	    (test_job_dep->or_flag && !test_job_dep->has_unfulfilled))
		/* Dependency failed */
		results = FAIL_DEPEND;
	else
		/* Still dependent */
		results = test_job_dep->has_local_depend ? LOCAL_DEPEND :
			REMOTE_DEPEND;
	return results;
}

/*
 * Determine if a job's dependencies are met
 * Inputs: job_ptr
 * Outputs: was_changed (optional) -
 *          If it exists, set it to true if at least 1 dependency changed
 *          state, otherwise false.
 * RET: NO_DEPEND = no dependencies
 *      LOCAL_DEPEND = local dependencies remain
 *      FAIL_DEPEND = failure (job completion code not per dependency),
 *                    delete the job
 *      REMOTE_DEPEND = only remote dependencies remain
 */
extern int test_job_dependency(job_record_t *job_ptr, bool *was_changed)
{
	test_job_dep_t test_job_dep = {
		.job_ptr = job_ptr,
	};
	int results = NO_DEPEND;

	if ((job_ptr->details == NULL) ||
	    (job_ptr->details->depend_list == NULL) ||
	    (list_count(job_ptr->details->depend_list) == 0)) {
		job_ptr->bit_flags &= ~JOB_DEPENDENT;
		if (was_changed)
			*was_changed = false;
		return NO_DEPEND;
	}

	(void) list_for_each(job_ptr->details->depend_list,
			     _foreach_test_job_dependency,
			     &test_job_dep);

	if (test_job_dep.or_satisfied &&
	    (job_ptr->state_reason == WAIT_DEP_INVALID)) {
		job_ptr->state_reason = WAIT_NO_REASON;
		xfree(job_ptr->state_desc);
		last_job_update = time(NULL);
	}

	if (test_job_dep.or_satisfied ||
	    (!test_job_dep.or_flag &&
	     !test_job_dep.and_failed &&
	     !test_job_dep.has_unfulfilled)) {
		/* Dependency fulfilled */
		fed_mgr_remove_remote_dependencies(job_ptr);
		job_ptr->bit_flags &= ~JOB_DEPENDENT;
		/*
		 * Don't flush the list if this job isn't on the origin - that
		 * means that we were called from
		 * fed_mgr_test_remote_dependencies() and need to send back the
		 * dependency list to the origin.
		 */
		if (fed_mgr_is_origin_job(job_ptr))
			list_flush(job_ptr->details->depend_list);
		_depend_list2str(job_ptr, false);
		results = NO_DEPEND;
		log_flag(DEPENDENCY, "%s: %pJ dependency fulfilled",
			 __func__, job_ptr);
	} else {
		results = _handle_failed_dependency(job_ptr, &test_job_dep);
	}

	if (was_changed)
		*was_changed = test_job_dep.changed;
	return results;
}

/* Given a new job dependency specification, expand job array specifications
 * into a collection of task IDs that update_job_dependency can parse.
 * (e.g. "after:123_[4-5]" to "after:123_4:123_5")
 * Returns NULL if not valid job array specification.
 * Returned value must be xfreed. */
static char *_xlate_array_dep(char *new_depend)
{
	char *new_array_dep = NULL, *array_tmp, *jobid_ptr = NULL, *sep;
	bitstr_t *array_bitmap;
	int i;
	uint32_t job_id;
	int32_t t, t_first, t_last;

	if (strstr(new_depend, "_[") == NULL)
		return NULL;	/* No job array expressions */

	if (max_array_size == NO_VAL) {
		max_array_size = slurm_conf.max_array_sz;
	}

	for (i = 0; new_depend[i]; i++) {
		xstrfmtcat(new_array_dep, "%c", new_depend[i]);
		if ((new_depend[i] >= '0') && (new_depend[i] <= '9')) {
			if (jobid_ptr == NULL)
				jobid_ptr = new_depend + i;
		} else if ((new_depend[i] == '_') && (new_depend[i+1] == '[') &&
			   (jobid_ptr != NULL)) {
			job_id = (uint32_t) atol(jobid_ptr);
			i += 2;	/* Skip over "_[" */
			array_tmp = xstrdup(new_depend + i);
			sep = strchr(array_tmp, ']');
			if (sep)
				sep[0] = '\0';
			array_bitmap = bit_alloc(max_array_size);
			if ((sep == NULL) ||
			    (bit_unfmt(array_bitmap, array_tmp) != 0) ||
			    ((t_first = bit_ffs(array_bitmap)) == -1)) {
				/* Invalid format */
				xfree(array_tmp);
				FREE_NULL_BITMAP(array_bitmap);
				xfree(new_array_dep);
				return NULL;
			}
			i += (sep - array_tmp);	/* Move to location of ']' */
			xfree(array_tmp);
			t_last = bit_fls(array_bitmap);
			for (t = t_first; t <= t_last; t++) {
				if (!bit_test(array_bitmap, t))
					continue;
				if (t == t_first) {
					xstrfmtcat(new_array_dep, "%d", t);
				} else {
					xstrfmtcat(new_array_dep, ":%u_%d",
						   job_id, t);
				}
			}
			FREE_NULL_BITMAP(array_bitmap);
			jobid_ptr = NULL;
		} else {
			jobid_ptr = NULL;
		}
	}

	return new_array_dep;
}

/* Copy dependent job's TRES options into another job's options  */
static void _copy_tres_opts(job_record_t *job_ptr, job_record_t *dep_job_ptr)
{
	xfree(job_ptr->cpus_per_tres);
	job_ptr->cpus_per_tres = xstrdup(dep_job_ptr->cpus_per_tres);
	xfree(job_ptr->tres_per_job);
	job_ptr->tres_per_job = xstrdup(dep_job_ptr->tres_per_job);
	xfree(job_ptr->tres_per_node);
	job_ptr->tres_per_node = xstrdup(dep_job_ptr->tres_per_node);
	xfree(job_ptr->tres_per_socket);
	job_ptr->tres_per_socket = xstrdup(dep_job_ptr->tres_per_socket);
	xfree(job_ptr->tres_per_task);
	job_ptr->tres_per_task = xstrdup(dep_job_ptr->tres_per_task);
	xfree(job_ptr->mem_per_tres);
	job_ptr->mem_per_tres = xstrdup(dep_job_ptr->mem_per_tres);
}

static int _find_dependency(void *arg, void *key)
{
	/* Does arg (dependency in the list) match key (new dependency)? */
	depend_spec_t *dep_ptr = (depend_spec_t *)arg;
	depend_spec_t *new_dep = (depend_spec_t *)key;
	return (dep_ptr->job_id == new_dep->job_id) &&
	       (dep_ptr->array_task_id == new_dep->array_task_id) &&
	       (dep_ptr->depend_type == new_dep->depend_type);
}

extern depend_spec_t *find_dependency(job_record_t *job_ptr,
				      depend_spec_t *dep_ptr)
{
	if (!job_ptr->details || !job_ptr->details->depend_list)
		return NULL;
	return list_find_first(job_ptr->details->depend_list,
			       _find_dependency, dep_ptr);
}

static int _parse_depend_state(char **str_ptr, uint32_t *depend_state)
{
	char *sep_ptr;

	if ((sep_ptr = strchr(*str_ptr, '('))) {
		/* Get the whole string before ")", convert to state */
		char *paren = strchr(*str_ptr, ')');
		if (!paren)
			return SLURM_ERROR;
		else
			*paren = '\0';
		sep_ptr++; /* skip over "(" */
		*depend_state = _depend_state_str2state(sep_ptr);
		/* Don't allow depend_fulfilled as a string. */
		if (*depend_state != DEPEND_FAILED)
			*depend_state = DEPEND_NOT_FULFILLED;
		*str_ptr = paren + 1; /* skip over ")" */
	} else
		*depend_state = DEPEND_NOT_FULFILLED;

	return SLURM_SUCCESS;
}

static job_record_t *_find_dependent_job_ptr(uint32_t job_id,
					     uint32_t *array_task_id)
{
	job_record_t *dep_job_ptr;

	if (*array_task_id == NO_VAL) {
		dep_job_ptr = find_job_record(job_id);
		if (!dep_job_ptr)
			dep_job_ptr = find_job_array_rec(job_id, INFINITE);
		if (dep_job_ptr &&
		    (dep_job_ptr->array_job_id == job_id) &&
		    ((dep_job_ptr->array_task_id != NO_VAL) ||
		     (dep_job_ptr->array_recs != NULL)))
			*array_task_id = INFINITE;
	} else
		dep_job_ptr = find_job_array_rec(job_id, *array_task_id);

	return dep_job_ptr;
}

/*
 * job_ptr - job that is getting a new dependency
 * dep_job_ptr - pointer to the job that job_ptr wants to depend on
 *   - This can be NULL, for example if it's a remote dependency. That's okay.
 * job_id - job_id of the dependency string
 * array_task_id - array_task_id of the dependency string
 *   - Equals NO_VAL if the dependency isn't a job array.
 *   - Equals INFINITE if the dependency is the whole job array.
 *   - Otherwise this equals a specific task of the job array (0, 1, 2, etc.)
 *
 * RET true if job_ptr is the same job as the new dependency, false otherwise.
 *
 * Example:
 *   scontrol update jobid=123 dependency=afterok:456_5
 *
 * job_ptr points to the job record for jobid=123.
 * dep_job_ptr points to the job record for 456_5.
 * job_id == 456. (This is probably different from dep_job_ptr->job_id.)
 * array_task_id == 5.
 */
static bool _depends_on_same_job(job_record_t *job_ptr,
				 job_record_t *dep_job_ptr,
				 uint32_t job_id, uint32_t array_task_id)
{
	if (array_task_id == INFINITE) {
		/* job_ptr wants to set a dependency on a whole job array */
		if ((job_ptr->array_task_id != NO_VAL) ||
		    (job_ptr->array_recs)) {
			/*
			 * job_ptr is a specific task in a job array, or is
			 * the meta job of a job array.
			 * Test if job_ptr belongs to the array indicated by
			 * the dependency string's "job_id"
			 */
			return (job_ptr->array_job_id == job_id);
		} else {
			/* job_ptr is a normal job */
			return (job_ptr == dep_job_ptr);
		}
	} else {
		/* Doesn't depend on a whole job array; test normally */
		return (job_ptr == dep_job_ptr);
	}
}

static void _update_job_expand_dep(job_record_t *job_ptr,
				   job_record_t *dep_job_ptr, uint32_t job_id,
				   int select_hetero)
{
	assoc_mgr_lock_t locks = { .tres = READ_LOCK };
	job_details_t *detail_ptr = job_ptr->details;
	multi_core_data_t *mc_ptr = detail_ptr->mc_ptr;
	gres_job_state_validate_t gres_js_val = {
		.cpus_per_task = &detail_ptr->orig_cpus_per_task,
		.max_nodes = &detail_ptr->max_nodes,
		.min_cpus = &detail_ptr->min_cpus,
		.min_nodes = &detail_ptr->min_nodes,
		.ntasks_per_node = &detail_ptr->ntasks_per_node,
		.ntasks_per_socket = &mc_ptr->ntasks_per_socket,
		.ntasks_per_tres = &detail_ptr->ntasks_per_tres,
		.num_tasks = &detail_ptr->num_tasks,
		.sockets_per_node = &mc_ptr->sockets_per_node,

		.gres_list = &job_ptr->gres_list_req,
	};

	job_ptr->details->expanding_jobid = job_id;
	if (select_hetero == 0) {
		/*
		 * GRES per node of this job must match
		 * the job being expanded. Other options
		 * are ignored.
		 */
		_copy_tres_opts(job_ptr, dep_job_ptr);
	}

	gres_js_val.cpus_per_tres = job_ptr->cpus_per_tres;
	gres_js_val.mem_per_tres = job_ptr->mem_per_tres;
	gres_js_val.tres_freq = job_ptr->tres_freq;
	gres_js_val.tres_per_job = job_ptr->tres_per_job;
	gres_js_val.tres_per_node = job_ptr->tres_per_node;
	gres_js_val.tres_per_socket = job_ptr->tres_per_socket;
	gres_js_val.tres_per_task = job_ptr->tres_per_task;

	FREE_NULL_LIST(job_ptr->gres_list_req);
	(void) gres_job_state_validate(&gres_js_val);
	assoc_mgr_lock(&locks);
	gres_stepmgr_set_job_tres_cnt(job_ptr->gres_list_req,
				      job_ptr->details->min_nodes,
				      job_ptr->tres_req_cnt, true);
	xfree(job_ptr->tres_req_str);
	job_ptr->tres_req_str =
		assoc_mgr_make_tres_str_from_array(job_ptr->tres_req_cnt,
						   TRES_STR_FLAG_SIMPLE, true);
	assoc_mgr_unlock(&locks);
}

/*
 * Parse a single job id with optional array task id.
 * Format is: <job_id>[_(*|<array_task_id>)]
 * Returns one char past the end of the job array or NULL for invalid syntax.
 */
static char *_parse_dependency_job_array(char *tok, uint32_t *job_id,
					 uint32_t *array_task_id)
{
	char *tmp = NULL;
	*job_id = strtol(tok, &tmp, 10);
	if (tmp && (tmp[0] == '_')) {
		char *tmp2 = tmp + 1; /* Past '_' */
		if (tmp2[0] == '*') {
			tmp = tmp2 + 1; /* Past '*' */
			*array_task_id = INFINITE;
		} else {
			*array_task_id = strtol(tmp2, &tmp, 10);
			if (tmp2 == tmp)
				return NULL;
		}
	} else {
		*array_task_id = NO_VAL;
	}
	return tmp;
}

/*
 * Parse one or more ':'-separated job dependency specs of the same depend type.
 * The new job dependency spec includes optional depend_time and job_state:
 *
 * <job_id>[+<depend_time>][(<job_state>)]
 *
 * One char past the end of the dependency specs is returned in (*sep_ptr).
 * Set (*rc) to ESLURM_DEPENDENCY for invalid syntax.
 */
static void _parse_dependency_jobid_new(list_t *new_depend_list, char **sep_ptr,
					char *tok, uint16_t depend_type,
					int *rc)
{
	depend_spec_t *dep_ptr;
	uint32_t job_id, array_task_id, depend_state;
	char *tmp = NULL;
	int depend_time = 0;

	while ((*rc) == SLURM_SUCCESS) {
		tmp = _parse_dependency_job_array(tok, &job_id, &array_task_id);
		if (!tmp || (job_id == 0)) {
			*rc = ESLURM_DEPENDENCY;
			break;
		}

		if (tmp[0] == '+') {
			tok = tmp + 1; /* skip over "+" */
			depend_time = strtol(tok, &tmp, 10);

			if (depend_time <= 0) {
				*rc = ESLURM_DEPENDENCY;
				break;
			}
			depend_time *= 60;
		}

		if (_parse_depend_state(&tmp, &depend_state)) {
			*rc = ESLURM_DEPENDENCY;
			break;
		}

		dep_ptr = xmalloc(sizeof(depend_spec_t));
		dep_ptr->job_id = job_id;
		dep_ptr->array_task_id = array_task_id;
		dep_ptr->depend_type = depend_type;
		dep_ptr->depend_time = depend_time;
		dep_ptr->depend_state = depend_state;
		dep_ptr->parsed_array_task_id = array_task_id;
		list_append(new_depend_list, dep_ptr);
		if (tmp[0] != ':')
			break;
		tok = tmp + 1; /* skip over ":" */
	}
	*sep_ptr = tmp;
}

/*
 * The old dependency format is a comma-separated list of job id's.
 * Parse a single jobid.
 * One char past the end of a valid job id will be returned in (*sep_ptr).
 * For an invalid job id, (*rc) will be set to ESLURM_DEPENDENCY.
 */
static void _parse_dependency_jobid_old(list_t *new_depend_list, char **sep_ptr,
					char *tok, int *rc)
{
	depend_spec_t *dep_ptr;
	uint32_t job_id, array_task_id;
	char *tmp = NULL;

	tmp = _parse_dependency_job_array(tok, &job_id, &array_task_id);
	if (!tmp || (job_id == 0)) {
		*rc = ESLURM_DEPENDENCY;
		return;
	}

	dep_ptr = xmalloc(sizeof(depend_spec_t));
	dep_ptr->job_id = job_id;
	dep_ptr->array_task_id = array_task_id;
	dep_ptr->depend_type = SLURM_DEPEND_AFTER_ANY;
	dep_ptr->parsed_array_task_id = array_task_id;

	*sep_ptr = tmp;
	list_append(new_depend_list, dep_ptr);
}

static int _foreach_update_job_depenency_list(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x, *job_depend;
	test_job_dep_t *test_job_dep = arg;
	job_record_t *job_ptr = test_job_dep->job_ptr;

	/*
	 * If the dependency is marked as remote, then it wasn't updated
	 * by the sibling cluster. Skip it.
	 */
	if (dep_ptr->depend_flags & SLURM_FLAGS_REMOTE)
		return 0;

	/*
	 * Find the dependency in job_ptr that matches this one.
	 * Then update job_ptr's dependency state (not fulfilled,
	 * fulfilled, or failed) to match this one.
	 */
	job_depend = list_find_first(job_ptr->details->depend_list,
				     _find_dependency,
				     dep_ptr);
	if (!job_depend) {
		/*
		 * This can happen if the job's dependency is updated
		 * and the update doesn't get to the sibling before
		 * the sibling sends back an update to the origin (us).
		 */
		log_flag(DEPENDENCY, "%s: Cannot find dependency %s:%u for %pJ, it may have been cleared before we got here.",
			 __func__, _depend_type2str(dep_ptr),
			 dep_ptr->job_id, job_ptr);
		return 0;
	}

	/*
	 * If the dependency is already fulfilled, don't update it.
	 * Otherwise update the dependency state.
	 */
	if ((job_depend->depend_state == DEPEND_FULFILLED) ||
	    (job_depend->depend_state == dep_ptr->depend_state))
		return 0;
	if (job_depend->depend_type == SLURM_DEPEND_SINGLETON) {
		/*
		 * We need to update the singleton dependency with
		 * the cluster bit, but test_job_dependency() will test
		 * if it is fulfilled, so don't change the depend_state
		 * here.
		 */
		job_depend->singleton_bits |= dep_ptr->singleton_bits;
		if (!fed_mgr_is_singleton_satisfied(job_ptr, job_depend,
						    false))
			return 0;
	}
	job_depend->depend_state = dep_ptr->depend_state;
	test_job_dep->changed = true;

	return 0;
}

extern bool update_job_dependency_list(job_record_t *job_ptr,
				       list_t *new_depend_list)
{
	test_job_dep_t test_job_dep = {
		.job_ptr = job_ptr,
	};

	xassert(job_ptr);
	xassert(job_ptr->details);
	xassert(job_ptr->details->depend_list);

	(void) list_for_each(new_depend_list,
			     _foreach_update_job_depenency_list,
			     &test_job_dep);

	return test_job_dep.changed;
}

static int _foreach_handle_job_dependency_updates(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;
	test_job_dep_t *test_job_dep = arg;

	_test_dependency_state(dep_ptr, test_job_dep);

	return 0;
}

extern int handle_job_dependency_updates(void *object, void *arg)
{
	job_record_t *job_ptr = (job_record_t *) object;
	time_t now = time(NULL);
	test_job_dep_t test_job_dep = {
		.job_ptr = job_ptr,
	};
	xassert(job_ptr->details);
	xassert(job_ptr->details->depend_list);

	/*
	 * Check the depend_state of each dependency.
	 * All dependencies are OR'd or AND'd - we don't allow a mix.
	 * OR'd dependencies:
	 *   - If one dependency succeeded, the whole thing passes.
	 *   - If there is at least one unfulfilled dependency,
	 *     the job is still dependent.
	 *   - All dependencies failed == dependency never satisfied.
	 * AND'd dependencies:
	 *   - One failure == dependency never satisfied
	 *   - One+ not fulfilled == still dependent
	 *   - All succeeded == dependency fulfilled
	 */
	(void) list_for_each(job_ptr->details->depend_list,
			     _foreach_handle_job_dependency_updates,
			     &test_job_dep);

	if (test_job_dep.or_satisfied ||
	    (!test_job_dep.or_flag &&
	     !test_job_dep.and_failed &&
	     !test_job_dep.has_unfulfilled)) {
		/* Dependency fulfilled */
		fed_mgr_remove_remote_dependencies(job_ptr);
		job_ptr->bit_flags &= ~JOB_DEPENDENT;
		list_flush(job_ptr->details->depend_list);
		if ((job_ptr->state_reason == WAIT_DEP_INVALID) ||
		    (job_ptr->state_reason == WAIT_DEPENDENCY)) {
			job_ptr->state_reason = WAIT_NO_REASON;
			xfree(job_ptr->state_desc);
			last_job_update = now;
		}
		_depend_list2str(job_ptr, false);
		fed_mgr_job_requeue(job_ptr);
	} else {
		_depend_list2str(job_ptr, false);
		job_ptr->bit_flags |= JOB_DEPENDENT;
		acct_policy_remove_accrue_time(job_ptr, false);
		if (test_job_dep.and_failed ||
		    (test_job_dep.or_flag && !test_job_dep.has_unfulfilled)) {
			/* Dependency failed */
			handle_invalid_dependency(job_ptr);
		} else {
			/* Still dependent */
			job_ptr->state_reason = WAIT_DEPENDENCY;
			xfree(job_ptr->state_desc);
			last_job_update = now;
		}
	}
	if (slurm_conf.debug_flags & DEBUG_FLAG_DEPENDENCY)
		print_job_dependency(job_ptr, __func__);

	return SLURM_SUCCESS;
}

/*
 * _parse_dependency_sep - parse separator between dependencies
 * IN tok - String to parse
 * IN expected_sep - Which separator(s) will succeed if encountered
 * OUT sep_ptr - Set to one char past the separator if it exists, otherwise tok
 * OUT sep_type - Set to SEP_DEPEND_OR or SEP_DEPEND_AND if parsing succeeds
 * OUT rc - Set if an unexpected separator was encountered
 * Returns true when a separator was successfully parsed
 */
static bool _parse_dependency_sep(char *tok, sep_depend_t expected_sep,
				  char **sep_ptr, sep_depend_t *sep_type,
				  int *rc)
{
	sep_depend_t parsed_sep = SEP_DEPEND_ANY;
	*sep_ptr = tok;
	if (tok) {
		if (tok[0] == ',') {
			parsed_sep = SEP_DEPEND_AND;
		} else if (tok[0] == '?') {
			parsed_sep = SEP_DEPEND_OR;
		} else {
			return false;
		}
	}

	if ((expected_sep != SEP_DEPEND_ANY) && (expected_sep != parsed_sep)) {
		*rc = ESLURM_DEPENDENCY;
		return false;
	}
	*sep_ptr = tok + 1;
	*sep_type = parsed_sep;
	return true;
}

/*
 * Parse a dependency string and populate specified fields:
 * job_id, array_task_id, depend_type, depend_time, and depend_state
 *
 * IN  dependency - Dependency string to parse.
 * OUT rc - Set to an error code if the string could not be parsed.
 * OUT or_flag - Set to true when separated by ORs, otherwise false.
 * Returns a list of depend_spec_t or NULL to indicate empty input
 * when rc is SLURM_SUCCESS. Caller must free.
 */
static list_t *_parse_dependency_str(char *new_depend, int *rc, bool *or_flag)
{
	uint16_t depend_type = 0;
	char *tok, *new_array_dep, *sep_ptr;
	list_t *new_depend_list = NULL;
	depend_spec_t *dep_ptr;
	sep_depend_t sep_type = SEP_DEPEND_ANY;

	/* Empty dependency input */
	if (!new_depend || (new_depend[0] == '\0') ||
	    ((new_depend[0] == '0') && (new_depend[1] == '\0'))) {
		return NULL;
	}

	new_depend_list = list_create(xfree_ptr);
	if ((new_array_dep = _xlate_array_dep(new_depend)))
		tok = new_array_dep;
	else
		tok = new_depend;

	while ((*rc) == SLURM_SUCCESS) {
		/* test singleton dependency flag */
		if (xstrncasecmp(tok, "singleton", 9) == 0) {
			uint32_t state;
			tok += 9; /* skip past "singleton" */
			depend_type = SLURM_DEPEND_SINGLETON;
			if (_parse_depend_state(&tok, &state)) {
				*rc = ESLURM_DEPENDENCY;
				break;
			}

			dep_ptr = xmalloc(sizeof(depend_spec_t));
			dep_ptr->depend_state = state;
			dep_ptr->depend_type = depend_type;
			/* dep_ptr->job_id = 0;	set by xmalloc */
			/* dep_ptr->job_ptr = NULL; set by xmalloc */
			/* dep_ptr->singleton_bits = 0;set by xmalloc */
			list_append(new_depend_list, dep_ptr);

			if (_parse_dependency_sep(tok, sep_type, &tok,
						  &sep_type, rc))
				continue;
			break;
		}

		/* Test for old format, just a job ID */
		sep_ptr = strchr(tok, ':');
		if (!sep_ptr && (tok[0] >= '0') && (tok[0] <= '9')) {
			_parse_dependency_jobid_old(new_depend_list, &sep_ptr,
						    tok, rc);
			if (*rc)
				break;
			if (_parse_dependency_sep(sep_ptr, SEP_DEPEND_AND, &tok,
						  &sep_type, rc))
				continue;
			break;
		} else if (!sep_ptr) {
			*rc = ESLURM_DEPENDENCY;
			break;
		}

		/* New format, <test>:job_ID */
		if (!xstrncasecmp(tok, "afternotok:", 11))
			depend_type = SLURM_DEPEND_AFTER_NOT_OK;
		else if (!xstrncasecmp(tok, "aftercorr:", 10))
			depend_type = SLURM_DEPEND_AFTER_CORRESPOND;
		else if (!xstrncasecmp(tok, "afterany:", 9))
			depend_type = SLURM_DEPEND_AFTER_ANY;
		else if (!xstrncasecmp(tok, "afterok:", 8))
			depend_type = SLURM_DEPEND_AFTER_OK;
		else if (!xstrncasecmp(tok, "afterburstbuffer:", 11))
			depend_type = SLURM_DEPEND_BURST_BUFFER;
		else if (!xstrncasecmp(tok, "after:", 6))
			depend_type = SLURM_DEPEND_AFTER;
		else if (!xstrncasecmp(tok, "expand:", 7)) {
			depend_type = SLURM_DEPEND_EXPAND;
		} else {
			*rc = ESLURM_DEPENDENCY;
			break;
		}
		tok = sep_ptr + 1; /* skip over ":" */
		_parse_dependency_jobid_new(new_depend_list, &sep_ptr, tok,
					    depend_type, rc);
		if (*rc)
			break;
		if (!_parse_dependency_sep(sep_ptr, sep_type, &tok, &sep_type,
					   rc))
			break;
	}

	if (!tok || tok[0] != '\0')
		*rc = ESLURM_DEPENDENCY;

	xfree(new_array_dep);
	*or_flag = (sep_type == SEP_DEPEND_OR);
	return new_depend_list;
}

/*
 * Used by list_delete_all().
 */
static int _update_job_dependency(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;
	update_dependency_args_t *update_args = arg;
	slurm_depend_types_t dep_type;
	job_record_t *dep_job_ptr, *job_ptr = update_args->job_ptr;
	uint32_t job_id;
	bool dep_is_remote;

	/*
	 * Skip checks if we already have an error and are on the origin
	 * cluster.
	 */
	if (update_args->rc && !update_args->is_remote_cluster)
		return 1;

	job_id = dep_ptr->job_id;
	dep_type = dep_ptr->depend_type;

	dep_is_remote = update_args->job_ptr->fed_details &&
			!fed_mgr_is_origin_job_id(job_id);

	/* Finish populating fields */
	dep_job_ptr = _find_dependent_job_ptr(job_id, &dep_ptr->array_task_id);

	if (!dep_job_ptr && fed_mgr_is_origin_job_id(job_id) &&
	    ((dep_type == SLURM_DEPEND_AFTER_OK) ||
	     (dep_type == SLURM_DEPEND_AFTER_NOT_OK))) {
		/*
		 * Reject the job since we won't be able to check if
		 * job dependency was fulfilled or not.
		 */
		update_args->rc = ESLURM_DEPENDENCY;
		dep_ptr->depend_state = DEPEND_FAILED;
		goto add_dependency;
	}

	/*
	 * _find_dependent_job_ptr() may modify array_task_id, so check
	 * if the job is the same after that.
	 */
	if (_depends_on_same_job(job_ptr, dep_job_ptr, job_id,
				 dep_ptr->array_task_id)) {
		update_args->rc = ESLURM_DEPENDENCY;
		dep_ptr->depend_state = DEPEND_FAILED;
		goto add_dependency;
	}

	if (dep_is_remote) {
		dep_ptr->depend_flags |= SLURM_FLAGS_REMOTE;
		dep_job_ptr = NULL;
	}
	dep_ptr->job_ptr = dep_job_ptr; /* Can be NULL */

	/* Ignore duplicates */
	if (!update_args->is_remote_cluster &&
	    list_find_first(update_args->unique_depend_list, _find_dependency,
			    dep_ptr))
		return 1;

	/* Enforce policy */
	if (dep_type == SLURM_DEPEND_SINGLETON) {
		if (disable_remote_singleton &&
		    !fed_mgr_is_origin_job(job_ptr)) {
			/* Singleton disabled for non-origin cluster */
			return 1;
		}
	} else if (dep_type == SLURM_DEPEND_EXPAND) {
		if (!permit_job_expansion()) {
			update_args->rc = ESLURM_NOT_SUPPORTED;
			dep_ptr->depend_state = DEPEND_FAILED;
			goto add_dependency;
		}
		if (dep_is_remote) {
			error("%s: Job expansion not permitted for remote jobs",
			      __func__);
			update_args->rc = ESLURM_DEPENDENCY;
			dep_ptr->depend_state = DEPEND_FAILED;
			goto add_dependency;
		}
		if ((update_args->expand_cnt++ > 0) || (!dep_job_ptr) ||
		    (!IS_JOB_RUNNING(dep_job_ptr)) ||
		    (dep_job_ptr->qos_id != job_ptr->qos_id) ||
		    (!dep_job_ptr->part_ptr) || (!job_ptr->part_ptr) ||
		    (dep_job_ptr->part_ptr != job_ptr->part_ptr)) {
			/*
			 * Expand only jobs in the same QOS and partition
			 */
			update_args->rc = ESLURM_DEPENDENCY;
			dep_ptr->depend_state = DEPEND_FAILED;
			goto add_dependency;
		}

		_update_job_expand_dep(job_ptr, dep_job_ptr, job_id,
				       update_args->select_hetero);
	}

add_dependency:
	_foreach_depend_list_copy(dep_ptr, &update_args->unique_depend_list);
	return 1;
}

/*
 * Parse a job dependency string and use it to establish a "depend_spec"
 * list of dependencies. We accept both old format (a single job ID) and
 * new format (e.g. "afterok:123:124,after:128").
 * IN job_ptr - job record to have dependency and depend_list updated
 * IN new_depend - new dependency description
 * IN is_remote_cluster = if true, then this is a remote cluster. Do not
 *    discard invalid dependencies. Instead, update the dependency like normal,
 *    but return an error code.
 * RET returns an error code from slurm_errno.h
 */
extern int update_job_dependency(job_record_t *job_ptr, char *new_depend,
				 bool is_remote_cluster)
{
	static int select_hetero = -1;
	int rc = SLURM_SUCCESS;
	update_dependency_args_t update_args = {
		.is_remote_cluster = is_remote_cluster,
		.job_ptr = job_ptr,
	};
	list_t *new_depend_list, *unique_depend_list;
	bool or_flag = false;

	if (!job_ptr->details)
		return EINVAL;
	job_ptr->details->expanding_jobid = 0;

	if (select_hetero == -1) {
		/*
		 * Determine if the select plugin supports heterogeneous
		 * GRES allocations (count differ by node): 1=yes, 0=no
		 */
		if (xstrstr(slurm_conf.select_type, "cons_tres"))
			select_hetero = 1;
		else
			select_hetero = 0;
	}

	new_depend_list = _parse_dependency_str(new_depend, &rc, &or_flag);

	/* Invalid syntax */
	if (rc != SLURM_SUCCESS)
		return rc;

	/* Clear dependencies on empty dependency input */
	if (!new_depend_list) {
		xfree(job_ptr->details->dependency);
		FREE_NULL_LIST(job_ptr->details->depend_list);
		return rc;
	}

	/*
	 * Enforce dependency policies and populate state-dependent fields.
	 * Copy unique entries to unique_depend_list.
	 */
	update_args.select_hetero = select_hetero;
	unique_depend_list = list_create(xfree_ptr);
	update_args.unique_depend_list = unique_depend_list;
	list_delete_all(new_depend_list, _update_job_dependency, &update_args);
	FREE_NULL_LIST(new_depend_list);
	rc = update_args.rc;

	if (rc == SLURM_SUCCESS) {
		/* test for circular dependencies (e.g. A -> B -> A) */
		(void) _scan_depend(NULL, job_ptr);
		if (_scan_depend(unique_depend_list, job_ptr))
			rc = ESLURM_CIRCULAR_DEPENDENCY;
	}

	/* Always update the list if we're on a remote cluster. */
	if ((rc == SLURM_SUCCESS) || is_remote_cluster) {
		FREE_NULL_LIST(job_ptr->details->depend_list);
		job_ptr->details->depend_list = unique_depend_list;
		_depend_list2str(job_ptr, or_flag);
		if (slurm_conf.debug_flags & DEBUG_FLAG_DEPENDENCY)
			print_job_dependency(job_ptr, __func__);
	} else {
		FREE_NULL_LIST(unique_depend_list);
	}

	return rc;
}

static int _foreach_scan_depend(void *x, void *arg)
{
	depend_spec_t *dep_ptr = x;
	test_job_dep_t *test_job_dep = arg;
	job_record_t *job_ptr = test_job_dep->job_ptr;

	if (dep_ptr->job_id == 0)	/* Singleton */
		return 0;
	/*
	 * We can't test for circular dependencies if the job_ptr
	 * wasn't found - the job may not be on this cluster, or the
	 * job was already purged when the dependency submitted,
	 * or the job just didn't exist.
	 */
	if (!dep_ptr->job_ptr)
		return 0;
	if ((test_job_dep->changed = _depends_on_same_job(
		     job_ptr, dep_ptr->job_ptr,
		     dep_ptr->job_id,
		     dep_ptr->array_task_id)))
		return -1;
	else if (dep_ptr->job_ptr->magic != JOB_MAGIC)
		return 0;	/* purged job, ptr not yet cleared */
	else if (!IS_JOB_FINISHED(dep_ptr->job_ptr) &&
		 dep_ptr->job_ptr->details &&
		 dep_ptr->job_ptr->details->depend_list) {
		test_job_dep->changed = _scan_depend(
			dep_ptr->job_ptr->details->depend_list,
			job_ptr);
		if (test_job_dep->changed) {
			info("circular dependency: %pJ is dependent upon %pJ",
			     dep_ptr->job_ptr, job_ptr);
			return -1;
		}
	}
	return 0;
}

/* Return true if the job job_ptr is found in dependency_list.
 * Pass NULL dependency list to clear the counter.
 * Execute recursively for each dependent job */
static bool _scan_depend(list_t *dependency_list, job_record_t *job_ptr)
{
	static int job_counter = 0;
	test_job_dep_t test_job_dep = {
		.job_ptr = job_ptr,
	};

	if (dependency_list == NULL) {
		job_counter = 0;
		return false;
	} else if (job_counter++ >= max_depend_depth) {
		return false;
	}

	xassert(job_ptr);

	(void) list_for_each(dependency_list,
			     _foreach_scan_depend,
			     &test_job_dep);

	return test_job_dep.changed;
}

static int _foreach_delayed_job_start_time(void *x, void *arg)
{
	job_record_t *job_q_ptr = x;
	delay_start_t *delay_start = arg;
	job_record_t *job_ptr = delay_start->job_ptr;
	uint32_t job_size_cpus, job_size_nodes, job_time;

	if (!IS_JOB_PENDING(job_q_ptr) || !job_q_ptr->details ||
	    (job_q_ptr->part_ptr != job_ptr->part_ptr) ||
	    (job_q_ptr->priority < job_ptr->priority) ||
	    (job_q_ptr->job_id == job_ptr->job_id) ||
	    (IS_JOB_REVOKED(job_q_ptr)))
		return 0;

	if (job_q_ptr->details->min_nodes == NO_VAL)
		job_size_nodes = 1;
	else
		job_size_nodes = job_q_ptr->details->min_nodes;
	if (job_q_ptr->details->min_cpus == NO_VAL)
		job_size_cpus = 1;
	else
		job_size_cpus = job_q_ptr->details->min_cpus;
	job_size_cpus = MAX(job_size_cpus,
			    (job_size_nodes * delay_start->part_cpus_per_node));
	if (job_q_ptr->time_limit == NO_VAL)
		job_time = job_q_ptr->part_ptr->max_time;
	else
		job_time = job_q_ptr->time_limit;
	delay_start->cume_space_time += job_size_cpus * job_time;

	return 0;
}

/* If there are higher priority queued jobs in this job's partition, then
 * delay the job's expected initiation time as needed to run those jobs.
 * NOTE: This is only a rough estimate of the job's start time as it ignores
 * job dependencies, feature requirements, specific node requirements, etc. */
static void _delayed_job_start_time(job_record_t *job_ptr)
{
	uint32_t part_node_cnt, part_cpu_cnt;
	delay_start_t delay_start = {
		.job_ptr = job_ptr,
		.part_cpus_per_node = 1,
	};

	if (job_ptr->part_ptr == NULL)
		return;
	part_node_cnt = job_ptr->part_ptr->total_nodes;
	part_cpu_cnt  = job_ptr->part_ptr->total_cpus;
	if (part_cpu_cnt > part_node_cnt)
		delay_start.part_cpus_per_node = part_cpu_cnt / part_node_cnt;

	(void) list_for_each(job_list,
			     _foreach_delayed_job_start_time,
			     &delay_start);
	delay_start.cume_space_time /= part_cpu_cnt;/* Factor out size */
	delay_start.cume_space_time *= 60;		/* Minutes to seconds */
	debug2("Increasing estimated start of %pJ by %"PRIu64" secs",
	       job_ptr, delay_start.cume_space_time);
	job_ptr->start_time += delay_start.cume_space_time;
}

static int _foreach_add_to_preemptee_job_id(void *x, void *arg)
{
	job_record_t *job_ptr = x;
	will_run_response_msg_t *resp_data = arg;
	uint32_t *preemptee_jid = xmalloc(sizeof(uint32_t));

	(*preemptee_jid) = job_ptr->job_id;

	if (!resp_data->preemptee_job_id)
		resp_data->preemptee_job_id = list_create(xfree_ptr);

	list_append(resp_data->preemptee_job_id, preemptee_jid);

	return 0;
}

static int _foreach_job_start_data_part(void *x, void *arg)
{
	part_record_t *part_ptr = x;
	job_start_data_t *job_start_data = arg;
	job_record_t *job_ptr = job_start_data->job_ptr;

	bitstr_t *active_bitmap = NULL, *avail_bitmap = NULL;
	bitstr_t *resv_bitmap = NULL;
	uint32_t min_nodes, max_nodes, req_nodes;
	int rc2 = SLURM_SUCCESS;
	time_t start_res, orig_start_time = (time_t) 0;
	list_t *preemptee_candidates = NULL, *preemptee_job_list = NULL;
	bool resv_overlap = false;
	resv_exc_t resv_exc = { 0 };

	job_start_data->rc = SLURM_SUCCESS;
	if (!part_ptr) {
		job_start_data->rc = ESLURM_INVALID_PARTITION_NAME;
		return -1;
	}

	if (job_ptr->details->req_nodes && job_ptr->details->req_nodes[0]) {
		if (node_name2bitmap(job_ptr->details->req_nodes, false,
				     &avail_bitmap, NULL)) {
			job_start_data->rc = ESLURM_INVALID_NODE_NAME;
			return -1;
		}
	} else {
		/* assume all nodes available to job for testing */
		avail_bitmap = node_conf_get_active_bitmap();
	}

	/* Consider only nodes in this job's partition */
	if (part_ptr->node_bitmap)
		bit_and(avail_bitmap, part_ptr->node_bitmap);
	else
		job_start_data->rc = ESLURM_REQUESTED_PART_CONFIG_UNAVAILABLE;
	if (job_req_node_filter(job_ptr, avail_bitmap, true))
		job_start_data->rc = ESLURM_REQUESTED_PART_CONFIG_UNAVAILABLE;
	if (job_ptr->details->exc_node_bitmap) {
		bit_and_not(avail_bitmap, job_ptr->details->exc_node_bitmap);
	}
	if (job_ptr->details->req_node_bitmap) {
		if (!bit_super_set(job_ptr->details->req_node_bitmap,
				   avail_bitmap)) {
			job_start_data->rc =
				ESLURM_REQUESTED_PART_CONFIG_UNAVAILABLE;
		}
	}

	/* Enforce reservation: access control, time and nodes */
	if (job_ptr->details->begin_time &&
	    (job_ptr->details->begin_time > job_start_data->now))
		start_res = job_ptr->details->begin_time;
	else
		start_res = job_start_data->now;

	rc2 = job_test_resv(job_ptr, &start_res, true, &resv_bitmap,
			    &resv_exc, &resv_overlap, false);
	if (rc2 != SLURM_SUCCESS) {
		FREE_NULL_BITMAP(avail_bitmap);
		reservation_delete_resv_exc_parts(&resv_exc);
		job_start_data->rc = rc2;
		if ((rc2 == ESLURM_INVALID_QOS) ||
		    (rc2 == ESLURM_INVALID_PARTITION_NAME))
			return 0;
		return -1;
	}

	bit_and(avail_bitmap, resv_bitmap);
	FREE_NULL_BITMAP(resv_bitmap);

	/* Only consider nodes that are not DOWN or DRAINED */
	bit_and(avail_bitmap, avail_node_bitmap);

	if (job_start_data->rc == SLURM_SUCCESS) {
		int test_fini = -1;
		uint8_t save_share_res, save_whole_node;
		min_nodes = MAX(job_ptr->details->min_nodes,
				part_ptr->min_nodes);
		if (job_ptr->details->max_nodes == 0)
			max_nodes = part_ptr->max_nodes;
		else
			max_nodes = MIN(job_ptr->details->max_nodes,
					part_ptr->max_nodes);
		max_nodes = MIN(max_nodes, 500000);	/* prevent overflows */
		if (!job_ptr->limit_set.tres[TRES_ARRAY_NODE] &&
		    job_ptr->details->max_nodes)
			req_nodes = max_nodes;
		else
			req_nodes = min_nodes;
		preemptee_candidates = slurm_find_preemptable_jobs(job_ptr);

		/* The orig_start is based upon the backfill scheduler data
		 * and considers all higher priority jobs. The logic below
		 * only considers currently running jobs, so the expected
		 * start time will almost certainly be earlier and not as
		 * accurate, but this algorithm is much faster. */
		orig_start_time = job_ptr->start_time;
		build_active_feature_bitmap(job_ptr, avail_bitmap,
					    &active_bitmap);
		if (active_bitmap) {
			job_start_data->rc = select_g_job_test(
				job_ptr, active_bitmap,
				min_nodes, max_nodes, req_nodes,
				SELECT_MODE_WILL_RUN,
				preemptee_candidates,
				&preemptee_job_list,
				&resv_exc,
				NULL);
			if (job_start_data->rc == SLURM_SUCCESS) {
				FREE_NULL_BITMAP(avail_bitmap);
				avail_bitmap = active_bitmap;
				active_bitmap = NULL;
				test_fini = 1;
			} else {
				FREE_NULL_BITMAP(active_bitmap);
				save_share_res  = job_ptr->details->share_res;
				save_whole_node = job_ptr->details->whole_node;
				job_ptr->details->share_res = 0;
				job_ptr->details->whole_node |=
					WHOLE_NODE_REQUIRED;
				test_fini = 0;
			}
		}
		if (test_fini != 1) {
			job_start_data->rc = select_g_job_test(
				job_ptr, avail_bitmap,
				min_nodes, max_nodes, req_nodes,
				SELECT_MODE_WILL_RUN,
				preemptee_candidates,
				&preemptee_job_list,
				&resv_exc,
				NULL);
			if (test_fini == 0) {
				job_ptr->details->share_res = save_share_res;
				job_ptr->details->whole_node = save_whole_node;
			}
		}
	}

	if (job_start_data->rc == SLURM_SUCCESS) {
		will_run_response_msg_t *resp_data;
		resp_data = xmalloc(sizeof(will_run_response_msg_t));
		resp_data->step_id = STEP_ID_FROM_JOB_RECORD(job_ptr);
		resp_data->proc_cnt = job_ptr->total_cpus;
		_delayed_job_start_time(job_ptr);
		resp_data->start_time = MAX(job_ptr->start_time,
					    orig_start_time);
		resp_data->start_time = MAX(resp_data->start_time, start_res);
		/* restore pending job start time to what backfill set */
		job_ptr->start_time = orig_start_time;
		resp_data->node_list  = bitmap2node_name(avail_bitmap);
		resp_data->part_name  = xstrdup(part_ptr->name);

		if (preemptee_job_list)
			(void) list_for_each(preemptee_job_list,
					     _foreach_add_to_preemptee_job_id,
					     resp_data);

		*job_start_data->resp = resp_data;
	} else {
		job_start_data->rc = ESLURM_REQUESTED_NODE_CONFIG_UNAVAILABLE;
	}

	FREE_NULL_LIST(preemptee_candidates);
	FREE_NULL_LIST(preemptee_job_list);
	FREE_NULL_BITMAP(avail_bitmap);
	reservation_delete_resv_exc_parts(&resv_exc);

	if (job_start_data->rc)
		return 0;

	return -1;
}

/*
 * Determine if a pending job will run using only the specified nodes, build
 * response message and return SLURM_SUCCESS on success. Otherwise return an
 * error code. Caller must free response message.
 */
extern int job_start_data(job_record_t *job_ptr,
			  will_run_response_msg_t **resp)
{
	job_start_data_t job_start_data = {
		.job_ptr = job_ptr,
		.now = time(NULL),
		.resp = resp,
	};

	if (job_ptr == NULL)
		return ESLURM_INVALID_JOB_ID;

	/*
	 * NOTE: Do not use IS_JOB_PENDING since that doesn't take
	 * into account the COMPLETING FLAG which we need to since we don't want
	 * to schedule a requeued job until it is actually done completing
	 * the first time.
	 */
	if ((job_ptr->details == NULL) || (job_ptr->job_state != JOB_PENDING))
		return ESLURM_DISABLED;

	if (job_ptr->part_ptr_list)
		(void) list_for_each(job_ptr->part_ptr_list,
				     _foreach_job_start_data_part,
				     &job_start_data);
	else
		(void) _foreach_job_start_data_part(job_ptr->part_ptr,
						    &job_start_data);

	return job_start_data.rc;
}

/*
 * epilog_slurmctld - execute the epilog_slurmctld for a job that has just
 *	terminated.
 * IN job_ptr - pointer to job that has been terminated
 */
extern void epilog_slurmctld(job_record_t *job_ptr)
{
	xassert(verify_lock(JOB_LOCK, WRITE_LOCK));

	prep_g_epilog_slurmctld(job_ptr);
}

/*
 * Determine which nodes must be rebooted for a job
 * IN job_ptr - pointer to job that will be initiated
 * IN/OUT reboot_features - features that should be applied to the node on
 *                          reboot. Caller must xfree().
 * RET bitmap of nodes requiring a reboot for NodeFeaturesPlugin or NULL if none
 */
extern bitstr_t *node_features_reboot(job_record_t *job_ptr,
				      char **reboot_features)
{
	bitstr_t *active_bitmap = NULL, *boot_node_bitmap = NULL;
	bitstr_t *feature_node_bitmap, *tmp_bitmap;

	xassert(reboot_features);
	xassert(!(*reboot_features)); /* It needs to start out NULL */

	if ((node_features_g_count() == 0) ||
	    !node_features_g_user_update(job_ptr->user_id))
		return NULL;

	/*
	 * Check if all features supported with AND/OR combinations
	 */
	build_active_feature_bitmap(job_ptr, job_ptr->node_bitmap,
				    &active_bitmap);
	if (active_bitmap == NULL)	/* All nodes have desired features */
		return NULL;
	FREE_NULL_BITMAP(active_bitmap);

	/*
	 * If some MOR/XAND option, filter out only first set of features
	 * for NodeFeaturesPlugin
	 */
	feature_node_bitmap = node_features_g_get_node_bitmap();
	if (feature_node_bitmap == NULL) /* No nodes under NodeFeaturesPlugin */
		return NULL;

	*reboot_features = node_features_g_job_xlate(
		job_ptr->details->features_use,
		job_ptr->details->feature_list_use,
		job_ptr->node_bitmap);
	tmp_bitmap = build_active_feature_bitmap2(*reboot_features);
	boot_node_bitmap = bit_copy(job_ptr->node_bitmap);
	bit_and(boot_node_bitmap, feature_node_bitmap);
	FREE_NULL_BITMAP(feature_node_bitmap);
	if (tmp_bitmap) {
		bit_and_not(boot_node_bitmap, tmp_bitmap);
		FREE_NULL_BITMAP(tmp_bitmap);
	}
	if (bit_ffs(boot_node_bitmap) == -1)
		FREE_NULL_BITMAP(boot_node_bitmap);

	return boot_node_bitmap;
}

static void _do_reboot(bitstr_t *node_bitmap, job_record_t *job_ptr,
		       char *reboot_features, uint16_t protocol_version)
{
	xassert(node_bitmap);

	if (bit_ffs(node_bitmap) == -1)
		return;

	power_action_reboot(node_bitmap, reboot_features);
	if (get_log_level() >= LOG_LEVEL_DEBUG) {
		char *nodes = bitmap2node_name(node_bitmap);
		if (nodes) {
			debug("%s: reboot nodes %s features %s",
			      __func__, nodes,
			      reboot_features ? "reboot_features" : "N/A");
		} else {
			error("%s: bitmap2nodename", __func__);
		}
		xfree(nodes);
	}
}

static void _set_reboot_features_active(bitstr_t *node_bitmap,
					char *reboot_features)
{
	node_record_t *node_ptr;

	for (int i = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); i++) {
		char *tmp_feature;

		tmp_feature = node_features_g_node_xlate(reboot_features,
							 node_ptr->features_act,
							 node_ptr->features, i);
		xfree(node_ptr->features_act);
		node_ptr->features_act = tmp_feature;
		(void) update_node_active_features(node_ptr->name,
						   node_ptr->features_act,
						   FEATURE_MODE_IND);
	}
}

extern void reboot_job_nodes(job_record_t *job_ptr)
{
	node_record_t *node_ptr;
	time_t now = time(NULL);
	bitstr_t *boot_node_bitmap = NULL, *feature_node_bitmap = NULL;
	bitstr_t *non_feature_node_bitmap = NULL;
	char *reboot_features = NULL;
	uint16_t protocol_version = SLURM_PROTOCOL_VERSION;

	if ((job_ptr->details == NULL) || (job_ptr->node_bitmap == NULL))
		return;

	if (job_ptr->reboot)
		boot_node_bitmap = bit_copy(job_ptr->node_bitmap);
	else
		boot_node_bitmap = node_features_reboot(job_ptr,
							&reboot_features);

	if (boot_node_bitmap &&
	    job_ptr->details->features_use &&
	    node_features_g_user_update(job_ptr->user_id)) {
		non_feature_node_bitmap = bit_copy(boot_node_bitmap);
		/*
		 * node_features_g_job_xlate is called from
		 * node_features_reboot, which we may have already called.
		 * Avoid calling node_features_g_job_xlate twice.
		 */
		if (!reboot_features) {
			reboot_features = node_features_g_job_xlate(
				job_ptr->details->features_use,
				job_ptr->details->feature_list_use,
				job_ptr->node_bitmap);
		}
		if (reboot_features)
			feature_node_bitmap = node_features_g_get_node_bitmap();
		if (feature_node_bitmap)
			bit_and(feature_node_bitmap, non_feature_node_bitmap);
		if (!feature_node_bitmap ||
		    (bit_ffs(feature_node_bitmap) == -1)) {
			/* No KNL nodes to reboot */
			FREE_NULL_BITMAP(feature_node_bitmap);
		} else {
			bit_and_not(non_feature_node_bitmap,
				    feature_node_bitmap);
			if (bit_ffs(non_feature_node_bitmap) == -1) {
				/* No non-KNL nodes to reboot */
				FREE_NULL_BITMAP(non_feature_node_bitmap);
			}
		}
	}

	if (feature_node_bitmap) {
		/*
		 * Update node features now to avoid a race where a
		 * second job may request that this node gets rebooted
		 * (in order to get a new active feature) *after* the
		 * first reboot request but *before* slurmd actually
		 * starts up. If that would happen then the second job
		 * would stay configuring forever, waiting for the node
		 * to reboot even though the node already rebooted.
		 *
		 * By setting the node's active features right now, any
		 * other job that wants that active feature can be
		 * scheduled onto this node, which will also already be
		 * rebooting, so those other jobs won't send additional
		 * reboot requests to change the feature.
		 */
		_set_reboot_features_active(feature_node_bitmap,
					    reboot_features);
	}

	/*
	 * Assume the power save thread will handle the boot if any of the nodes
	 * are cloud nodes. In KNL/features, the node is being rebooted and not
	 * brought up from being powered down.
	 */
	if ((boot_node_bitmap == NULL) ||
	    bit_overlap_any(cloud_node_bitmap, job_ptr->node_bitmap)) {
		/* launch_job() when all nodes have booted */
		if (bit_overlap_any(power_down_node_bitmap,
		                    job_ptr->node_bitmap) ||
		    bit_overlap_any(booting_node_bitmap,
				    job_ptr->node_bitmap)) {
			/* Reset job start time when nodes are booted */
			job_state_set_flag(job_ptr, (JOB_CONFIGURING |
						     JOB_POWER_UP_NODE));
			job_ptr->wait_all_nodes = 1;
		}

		goto cleanup;
	}

	/* Reset job start time when nodes are booted */
	job_state_set_flag(job_ptr, (JOB_CONFIGURING | JOB_POWER_UP_NODE));
	/* launch_job() when all nodes have booted */
	job_ptr->wait_all_nodes = 1;

	/* Modify state information for all nodes, KNL and others */
	for (int i = 0; (node_ptr = next_node_bitmap(boot_node_bitmap, &i));
	     i++) {
		if (protocol_version > node_ptr->protocol_version)
			protocol_version = node_ptr->protocol_version;

		if (IS_NODE_POWERED_DOWN(node_ptr)) {
			node_ptr->node_state &= (~NODE_STATE_POWERED_DOWN);
			clusteracct_storage_g_node_up(acct_db_conn, node_ptr,
						      now);
		}
		node_ptr->node_state |= NODE_STATE_NO_RESPOND;
		node_ptr->node_state |= NODE_STATE_POWERING_UP;
		if (reboot_features &&
		    !node_features_g_job_features_need_reboot(reboot_features)) {
			/* Don't need to put REBOOT_ISSUSED flag on. */
		} else {
			/*
			 * Use REBOOT_ISSUED flag to be able to detect when a
			 * node was rebooted through this code and to determine
			 * if the health check should be run when the node
			 * finished rebooting.
			 */
			node_ptr->node_state |= NODE_STATE_REBOOT_ISSUED;
		}

		bit_clear(avail_node_bitmap, i);
		bit_clear(power_down_node_bitmap, i);
		bit_set(booting_node_bitmap, i);
		node_ptr->boot_req_time = now;
	}

	if (feature_node_bitmap) {
		/* Reboot nodes to change KNL NUMA and/or MCDRAM mode */
		_do_reboot(feature_node_bitmap, job_ptr, reboot_features,
			   protocol_version);
		bit_and_not(boot_node_bitmap, feature_node_bitmap);
	}

	if (non_feature_node_bitmap) {
		/* Reboot nodes with no feature changes */
		_do_reboot(non_feature_node_bitmap, job_ptr, NULL,
			   protocol_version);
		bit_and_not(boot_node_bitmap, non_feature_node_bitmap);
	}

	if (job_ptr->reboot) {
		/* Reboot the remaining nodes blindly as per direct request */
		_do_reboot(boot_node_bitmap, job_ptr, NULL, protocol_version);
	}

cleanup:
	xfree(reboot_features);
	FREE_NULL_BITMAP(boot_node_bitmap);
	FREE_NULL_BITMAP(non_feature_node_bitmap);
	FREE_NULL_BITMAP(feature_node_bitmap);
}

/*
 * Deferring this setup ensures that all calling paths into select_nodes()
 * have had a chance to update all appropriate job records.
 * This works since select_nodes() will always be holding the job_write lock,
 * and thus this new thread will be blocked waiting to acquire job_write
 * until that has completed.
 * For HetJobs in particular, this is critical to ensure that all components
 * have been setup properly before prolog_slurmctld actually runs.
 */
static void *_start_prolog_slurmctld_thread(void *x)
{
	slurmctld_lock_t node_write_lock = {
		.conf = READ_LOCK, .job = WRITE_LOCK,
		.node = WRITE_LOCK, .fed = READ_LOCK };
	uint32_t *job_id = (uint32_t *) x;
	job_record_t *job_ptr;

	lock_slurmctld(node_write_lock);
	if (!(job_ptr = find_job_record(*job_id))) {
		error("%s: missing JobId=%u", __func__, *job_id);
		unlock_slurmctld(node_write_lock);
		return NULL;
	}
	prep_g_prolog_slurmctld(job_ptr);

	/*
	 * No async prolog_slurmctld threads running, so decrement now to move
	 * on with the job launch.
	 */
	if (!job_ptr->prep_prolog_cnt) {
		debug2("%s: no async prolog_slurmctld running", __func__);
		prolog_running_decr(job_ptr);
	}

	unlock_slurmctld(node_write_lock);
	xfree(job_id);

	return NULL;
}

/*
 * prolog_slurmctld - execute the prolog_slurmctld for a job that has just
 *	been allocated resources.
 * IN job_ptr - pointer to job that will be initiated
 */
extern void prolog_slurmctld(job_record_t *job_ptr)
{
	uint32_t *job_id;
	xassert(verify_lock(JOB_LOCK, WRITE_LOCK));

	if (!prep_g_required(PREP_PROLOG_SLURMCTLD))
		return;
	job_ptr->details->prolog_running++;
	job_state_set_flag(job_ptr, JOB_CONFIGURING);

	job_id = xmalloc(sizeof(*job_id));
	*job_id = job_ptr->job_id;
	slurm_thread_create_detached(NULL, _start_prolog_slurmctld_thread,
				     job_id);
}

/* Decrement a job's prolog_running counter and launch the job if zero */
extern void prolog_running_decr(job_record_t *job_ptr)
{
	xassert(verify_lock(JOB_LOCK, WRITE_LOCK));
	xassert(verify_lock(FED_LOCK, READ_LOCK));

	if (!job_ptr)
		return;

	if (job_ptr->details && job_ptr->details->prolog_running &&
	    (--job_ptr->details->prolog_running > 0))
		return;

	/* Federated job notified the origin that the job is to be requeued,
	 * need to wait for this job to be cancelled. */
	if (job_ptr->job_state & JOB_REQUEUE_FED)
		return;

	if (IS_JOB_CONFIGURING(job_ptr) && !pick_batch_host(job_ptr) &&
	    test_job_nodes_ready(job_ptr)) {
		info("%s: Configuration for %pJ is complete",
		     __func__, job_ptr);
		job_config_fini(job_ptr);
		if (job_ptr->batch_flag &&
		    (IS_JOB_RUNNING(job_ptr) || IS_JOB_SUSPENDED(job_ptr))) {
			launch_job(job_ptr);
		}
	}
}

static int _foreach_feature_list_copy(void *x, void *arg)
{
	job_feature_t *feat_src = x, *feat_dest;
	list_t **feature_list_dest = arg;

	feat_dest = xmalloc(sizeof(job_feature_t));
	memcpy(feat_dest, feat_src, sizeof(job_feature_t));
	if (feat_src->node_bitmap_active)
		feat_dest->node_bitmap_active =
			bit_copy(feat_src->node_bitmap_active);
	if (feat_src->node_bitmap_avail)
		feat_dest->node_bitmap_avail =
			bit_copy(feat_src->node_bitmap_avail);
	feat_dest->name = xstrdup(feat_src->name);
	list_append(*feature_list_dest, feat_dest);

	return 0;
}

/*
 * Copy a job's feature list
 * IN feature_list_src - a job's depend_lst
 * RET copy of feature_list_src, must be freed by caller
 */
extern list_t *feature_list_copy(list_t *feature_list_src)
{
	list_t *feature_list_dest = NULL;

	if (!feature_list_src)
		return feature_list_dest;

	feature_list_dest = list_create(feature_list_delete);
	(void) list_for_each(feature_list_src,
			     _foreach_feature_list_copy,
			     &feature_list_dest);

	return feature_list_dest;
}

/*
 * IN/OUT convert_to_matching_or -
 * If at least one changeable feature is requested, then all the nodes
 * in the job allocation need to match the same feature set.
 *
 * As an input: if true, then mark all '|' operators as matching OR, and also
 * imply that it is surrounded by brackets by setting bracket=1 for all the
 * features except the last one. The AND operators are still treated as normal
 * AND (not XAND), as if they were surrounded by parentheses within the
 * brackets.
 *
 * As an output: if multiple changeable features are requested,
 * and bar (OR) was requested, then set this to true.
 *
 * This is needed for the scheduling logic with parentheses and matching OR.
 */
static int _feature_string2list(char *features, char *debug_str,
				list_t **feature_list,
				bool *convert_to_matching_or)
{
	int rc = SLURM_SUCCESS;
	int bracket = 0, count = 0, i, paren = 0;
	int brack_set_count = 0;
	char *tmp_requested;
	char *str_ptr, *feature = NULL;
	bool has_changeable = false;
	bool has_or = false;
	bool has_asterisk = false;

	xassert(feature_list);

	/* Use of commas separator is a common error. Replace them with '&' */
	while ((str_ptr = strstr(features, ",")))
		str_ptr[0] = '&';

	tmp_requested = xstrdup(features);
	*feature_list = list_create(feature_list_delete);

	for (i = 0; ; i++) {
		job_feature_t *feat;

		if (tmp_requested[i] == '*') {
			tmp_requested[i] = '\0';
			count = strtol(&tmp_requested[i+1], &str_ptr, 10);
			if (!bracket)
				has_asterisk = true;
			if ((feature == NULL) || (count <= 0) || (paren != 0)) {
				verbose("%s constraint invalid, '*' must be requested with a positive integer, and after a feature or parentheses: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			i = str_ptr - tmp_requested - 1;
		} else if (tmp_requested[i] == '&') {
			tmp_requested[i] = '\0';
			if (feature == NULL) {
				verbose("%s constraint requested '&' without a feature: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			feat = xmalloc(sizeof(job_feature_t));
			feat->bracket = *convert_to_matching_or ? 1 : bracket;
			feat->name = xstrdup(feature);
			feat->changeable = node_features_g_changeable_feature(
				feature);
			feat->count = count;
			feat->paren = paren;

			has_changeable |= feat->changeable;

			if (paren || *convert_to_matching_or)
				feat->op_code = FEATURE_OP_AND;
			else if (bracket)
				feat->op_code = FEATURE_OP_XAND;
			else
				feat->op_code = FEATURE_OP_AND;
			list_append(*feature_list, feat);
			feature = NULL;
			count = 0;
		} else if (tmp_requested[i] == '|') {
			bool changeable;

			tmp_requested[i] = '\0';
			if (feature == NULL) {
				verbose("%s constraint requested '|' without a feature: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			changeable = node_features_g_changeable_feature(
				feature);
			feat = xmalloc(sizeof(job_feature_t));
			feat->bracket = *convert_to_matching_or ? 1 : bracket;
			feat->name = xstrdup(feature);
			feat->changeable = changeable;
			feat->count = count;
			feat->paren = paren;

			has_changeable |= changeable;
			has_or = true;

			/*
			 * The if-else-if is like this for priority:
			 * - paren is highest priority
			 * - then bracket
			 * - then outside of paren/bracket
			 */
			if (paren && !(*convert_to_matching_or))
				feat->op_code = FEATURE_OP_OR;
			else if (bracket || changeable ||
				 (*convert_to_matching_or))
				feat->op_code = FEATURE_OP_MOR;
			else
				feat->op_code = FEATURE_OP_OR;
			list_append(*feature_list, feat);
			feature = NULL;
			count = 0;
		} else if (tmp_requested[i] == '[') {
			tmp_requested[i] = '\0';
			if ((feature != NULL) || bracket || paren) {
				verbose("%s constraint has imbalanced brackets: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			bracket++;
			brack_set_count++;
			if (brack_set_count > 1) {
				verbose("%s constraint has more than one set of brackets: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
		} else if (tmp_requested[i] == ']') {
			tmp_requested[i] = '\0';
			if ((feature == NULL) || (bracket == 0) || paren) {
				verbose("%s constraint has imbalanced brackets: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			bracket--;
		} else if (tmp_requested[i] == '(') {
			tmp_requested[i] = '\0';
			if ((feature != NULL) || paren) {
				verbose("%s constraint has imbalanced parentheses: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			paren++;
		} else if (tmp_requested[i] == ')') {
			tmp_requested[i] = '\0';
			if ((feature == NULL) || (paren == 0)) {
				verbose("%s constraint has imbalanced parentheses: %s",
					debug_str, features);
				rc = ESLURM_INVALID_FEATURE;
				goto fini;
			}
			paren--;
		} else if (tmp_requested[i] == '\0') {
			if (feature) {
				feat = xmalloc(sizeof(job_feature_t));
				feat->bracket = bracket;
				feat->name = xstrdup(feature);
				feat->changeable = node_features_g_changeable_feature(
					feature);
				feat->count = count;
				feat->paren = paren;
				feat->op_code = FEATURE_OP_END;
				list_append(*feature_list, feat);

				has_changeable |= feat->changeable;
			}
			break;
		} else if (feature == NULL) {
			feature = &tmp_requested[i];
		} else if (i && (tmp_requested[i - 1] == '\0')) {
			/* ')' and ']' should be followed by a token. */
			verbose("%s constraint has an unexpected character: %s",
				debug_str, features);
			rc = ESLURM_INVALID_FEATURE;
			goto fini;
		}
	}

	if (bracket != 0) {
		verbose("%s constraint has unbalanced brackets: %s",
			debug_str, features);
		rc = ESLURM_INVALID_FEATURE;
		goto fini;
	}
	if (paren != 0) {
		verbose("%s constraint has unbalanced parenthesis: %s",
			debug_str, features);
		rc = ESLURM_INVALID_FEATURE;
		goto fini;
	}
	if (has_asterisk && (list_count(*feature_list) > 1)) {
		verbose("%s constraint has '*' outside of brackets with more than one feature: %s",
			debug_str, features);
		rc = ESLURM_INVALID_FEATURE;
		goto fini;
	}

	*convert_to_matching_or = (has_changeable && has_or);

fini:
	if (rc != SLURM_SUCCESS) {
		FREE_NULL_LIST(*feature_list);
		info("%s invalid constraint: %s",
		     debug_str, features);
	}
	xfree(tmp_requested);

	return rc;
}

/*
 * build_feature_list - Translate a job's feature string into a feature_list
 * NOTE: This function is also used for reservations if is_reservation is true
 * and for job_desc_msg_t if job_id == 0
 * IN  details->features
 * OUT details->feature_list
 * RET error code
 */
extern int build_feature_list(job_record_t *job_ptr, bool prefer,
			      bool is_reservation)
{
	job_details_t *detail_ptr = job_ptr->details;
	list_t **feature_list;
	int rc;
	int feature_err;
	bool convert_to_matching_or = false;
	valid_feature_t valid_feature = {
		.rc = SLURM_SUCCESS,
	};

	/* no hard constraints */
	if (!detail_ptr || (!detail_ptr->features && !detail_ptr->prefer)) {
		if (job_ptr->batch_features)
			return ESLURM_BATCH_CONSTRAINT;
		return SLURM_SUCCESS;
	}

	if (prefer) {
		valid_feature.features = detail_ptr->prefer;
		feature_list = &detail_ptr->prefer_list;
		feature_err = ESLURM_INVALID_PREFER;
	} else {
		valid_feature.features = detail_ptr->features;
		feature_list = &detail_ptr->feature_list;
		feature_err = ESLURM_INVALID_FEATURE;
	}

	if (!valid_feature.features) /* The other constraint is non NULL. */
		return SLURM_SUCCESS;

	if (*feature_list)		/* already processed */
		return SLURM_SUCCESS;

	if (is_reservation)
		valid_feature.debug_str = xstrdup("Reservation");
	else if (!job_ptr->job_id)
		valid_feature.debug_str = xstrdup("Job specs");
	else
		valid_feature.debug_str =
			xstrdup_printf("JobId=%u", job_ptr->job_id);

	valid_feature.can_reboot =
		node_features_g_user_update(job_ptr->user_id);
	rc = _feature_string2list(valid_feature.features,
				  valid_feature.debug_str,
				  feature_list, &convert_to_matching_or);
	if (rc != SLURM_SUCCESS) {
		rc = feature_err;
		goto fini;
	}

	if (convert_to_matching_or) {
		char *str = NULL;
		list_t *feature_sets;

		/*
		 * Restructure the list into a format of AND'ing features in
		 * parentheses and matching OR each parentheses together. The
		 * current scheduling logic does not know how to handle matching
		 * OR inside of parentheses; however, it does know how to handle
		 * matching OR outside of parentheses, so we restructure the
		 * feature list to a format the scheduling logic understands.
		 * This is needed for changeable features which need all nodes
		 * in the job allocation to match the same feature set, so they
		 * cannot have any boolean OR in the feature list.
		 *
		 * For example, "(a|b)&c" becomes "(a&c)|(b&c)"
		 *
		 * Restructure only the feature list; leave the original
		 * constraint expression intact.
		 */
		feature_sets = job_features_list2feature_sets(
			valid_feature.features,
			*feature_list,
			false);
		list_for_each(feature_sets, job_features_set2str, &str);
		FREE_NULL_LIST(feature_sets);
		FREE_NULL_LIST(*feature_list);
		rc = _feature_string2list(str, valid_feature.debug_str,
					  feature_list,
					  &convert_to_matching_or);
		if (rc != SLURM_SUCCESS) {
			/*
			 * Something went wrong - we should have caught this
			 * error the first time we called _feature_string2list.
			 */
			error("%s: Problem converting feature string %s to matching OR list",
			      __func__, str);
			rc = feature_err;
			xfree(str);
			goto fini;
		}
		log_flag(NODE_FEATURES, "%s: Converted %sfeature list:'%s' to matching OR:'%s'",
			 __func__, prefer ? "prefer " : "",
			 valid_feature.features, str);
		xfree(str);
	}

	if (job_ptr->batch_features) {
		detail_ptr->feature_list_use = *feature_list;
		detail_ptr->features_use = valid_feature.features;
		rc = _valid_batch_features(job_ptr, valid_feature.can_reboot);
		detail_ptr->feature_list_use = NULL;
		detail_ptr->features_use = NULL;
		if (rc != SLURM_SUCCESS)
			goto fini;
	}

	valid_feature.feature_list = *feature_list;
	rc = _valid_feature_list(job_ptr, &valid_feature, is_reservation);
	if (rc != SLURM_SUCCESS) {
		rc = feature_err;
		goto fini;
	}

fini:
	xfree(valid_feature.debug_str);
	return rc;
}

/*
 * Delete a record from a job's feature_list
 */
extern void feature_list_delete(void *x)
{
	job_feature_t *feature_ptr = (job_feature_t *)x;
	xfree(feature_ptr->name);
	FREE_NULL_BITMAP(feature_ptr->node_bitmap_active);
	FREE_NULL_BITMAP(feature_ptr->node_bitmap_avail);
	xfree(feature_ptr);
}

static int _match_job_feature(void *x, void *key)
{
	job_feature_t *feat = (job_feature_t *) x;
	char *tok = (char *) key;

	if (!xstrcmp(feat->name, tok))	/* Found matching feature name */
		return 1;
	return 0;
}

static int _valid_batch_features(job_record_t *job_ptr, bool can_reboot)
{
	char *tmp, *tok, *save_ptr = NULL;
	int rc = SLURM_SUCCESS;
	bool have_or = false, success_or = false;

	if (!job_ptr->batch_features)
		return SLURM_SUCCESS;
	if (!job_ptr->details || !job_ptr->details->feature_list_use)
		return ESLURM_BATCH_CONSTRAINT;

	if (strchr(job_ptr->batch_features, '|'))
		have_or = true;
	tmp = xstrdup(job_ptr->batch_features);
	tok = strtok_r(tmp, "&|", &save_ptr);
	while (tok) {
		if (!list_find_first(job_ptr->details->feature_list_use,
				     _match_job_feature, tok)) {
			rc = ESLURM_BATCH_CONSTRAINT;
			break;
		}
		rc = _valid_node_feature(tok, can_reboot);
		if (have_or) {
			if (rc == SLURM_SUCCESS)
				success_or = true;
			/* Ignore failure on some OR components */
		} else if (rc != SLURM_SUCCESS) {
			rc = ESLURM_BATCH_CONSTRAINT;
			break;
		}
		tok = strtok_r(NULL, "&|", &save_ptr);
	}
	xfree(tmp);

	if (have_or && success_or)
		return SLURM_SUCCESS;
	return rc;
}

static int _foreach_valid_feature_list(void *x, void *arg)
{
	job_feature_t *feat_ptr = x;
	valid_feature_t *valid_feature = arg;

	if ((feat_ptr->op_code == FEATURE_OP_MOR) ||
	    (feat_ptr->op_code == FEATURE_OP_XAND)) {
		valid_feature->bracket = feat_ptr->paren + 1;
	}
	if (feat_ptr->paren > valid_feature->paren) {
		valid_feature->paren = feat_ptr->paren;
	}
	if (feat_ptr->paren < valid_feature->paren) {
		valid_feature->paren = feat_ptr->paren;
	}
	if ((valid_feature->rc == SLURM_SUCCESS) &&
	    !valid_feature->skip_validation) {
		valid_feature->rc =
			_valid_node_feature(feat_ptr->name,
					    valid_feature->can_reboot);
		if (valid_feature->rc != SLURM_SUCCESS)
			verbose("%s feature %s is not usable on any node: %s",
				valid_feature->debug_str, feat_ptr->name,
				valid_feature->features);
	}
	if ((feat_ptr->op_code == FEATURE_OP_XAND) && !feat_ptr->count) {
		verbose("%s feature %s invalid, count must be used with XAND: %s",
			valid_feature->debug_str, feat_ptr->name,
			valid_feature->features);
		valid_feature->rc = ESLURM_INVALID_FEATURE;
	}
	if ((feat_ptr->op_code == FEATURE_OP_MOR) && feat_ptr->count) {
		verbose("%s feature %s invalid, count must not be used with MOR: %s",
			valid_feature->debug_str, feat_ptr->name,
			valid_feature->features);
		valid_feature->rc = ESLURM_INVALID_FEATURE;
	}

	/* In brackets, outside of paren */
	if ((valid_feature->bracket > valid_feature->paren) &&
	    ((feat_ptr->op_code != FEATURE_OP_MOR) &&
	     (feat_ptr->op_code != FEATURE_OP_XAND))) {
		if (valid_feature->has_xand && !feat_ptr->count) {
			valid_feature->rc = ESLURM_INVALID_FEATURE;
			verbose("%s feature %s invalid, count must be used with XAND: %s",
				valid_feature->debug_str, feat_ptr->name,
				valid_feature->features);
		}
		if (valid_feature->has_mor && feat_ptr->count) {
			valid_feature->rc = ESLURM_INVALID_FEATURE;
			verbose("%s feature %s invalid, count must not be used with MOR: %s",
				valid_feature->debug_str, feat_ptr->name,
				valid_feature->features);
		}
		valid_feature->bracket = 0;
		valid_feature->has_xand = false;
		valid_feature->has_mor = false;
	}
	if (feat_ptr->op_code == FEATURE_OP_XAND)
		valid_feature->has_xand = true;
	if (feat_ptr->op_code == FEATURE_OP_MOR)
		valid_feature->has_mor = true;

	return 0;
}

static int _valid_feature_list(job_record_t *job_ptr,
			       valid_feature_t *valid_feature,
			       bool is_reservation)
{
	static time_t sched_update = 0;
	static bool ignore_prefer_val = false, ignore_constraint_val = false;
	bool is_prefer_list, skip_validation;

	if (!valid_feature->feature_list) {
		debug2("%s feature list is empty",
		       valid_feature->debug_str);
		return valid_feature->rc;
	}

	if (sched_update != slurm_conf.last_update) {
		sched_update = slurm_conf.last_update;
		if (xstrcasestr(slurm_conf.sched_params,
				"ignore_prefer_validation"))
			ignore_prefer_val = true;
		else
			ignore_prefer_val = false;
		if (xstrcasestr(slurm_conf.sched_params,
				"ignore_constraint_validation"))
			ignore_constraint_val = true;
		else
			ignore_constraint_val = false;

	}

	is_prefer_list = (valid_feature->feature_list ==
			  job_ptr->details->prefer_list);
	skip_validation = (is_prefer_list && ignore_prefer_val) ||
			  (!is_prefer_list && ignore_constraint_val);

	valid_feature->skip_validation = skip_validation;

	(void) list_for_each(valid_feature->feature_list,
			     _foreach_valid_feature_list,
			     valid_feature);

	if (valid_feature->rc == SLURM_SUCCESS) {
		debug("%s feature list: %s",
		      valid_feature->debug_str, valid_feature->features);
	} else {
		if (is_reservation) {
			info("Reservation has invalid feature list: %s",
			     valid_feature->features);
		} else {
			if (valid_feature->can_reboot)
				info("%s has invalid feature list: %s",
				     valid_feature->debug_str,
				     valid_feature->features);
			else
				info("%s has invalid feature list (%s) or the features are not active and this user cannot reboot to update node features",
				     valid_feature->debug_str,
				     valid_feature->features);
		}
	}

	return valid_feature->rc;
}

static int _find_feature_in_list(void *x, void *arg)
{
	node_feature_t *feature_ptr = x;
	char *feature = arg;

	if (!xstrcmp(feature_ptr->name, feature))
		return 1;

	return 0;
}

/* Validate that job's feature is available on some node(s) */
static int _valid_node_feature(char *feature, bool can_reboot)
{
	int rc = ESLURM_INVALID_FEATURE;
	list_t *use_list =
		can_reboot ? avail_feature_list : active_feature_list;

	if (list_find_first(use_list, _find_feature_in_list, feature))
		rc = SLURM_SUCCESS;

	return rc;
}

#define REBUILD_PENDING SLURM_BIT(0)
#define REBUILD_ACTIVE SLURM_BIT(1)

typedef struct {
	uint16_t flags;
	job_record_t *job_ptr;
} rebuild_args_t;

static int _build_partition_string(void *object, void *arg) {
	part_record_t *part_ptr = object;
	rebuild_args_t *args = arg;
	uint16_t flags = args->flags;
	job_record_t *job_ptr = args->job_ptr;

	if (flags & REBUILD_PENDING) {
		job_ptr->part_ptr = part_ptr;
		flags &= ~(REBUILD_PENDING);
	}
	if ((flags & REBUILD_ACTIVE) && (part_ptr == job_ptr->part_ptr))
		return SLURM_SUCCESS;       /* already added */
	if (job_ptr->partition)
		xstrcat(job_ptr->partition, ",");
	xstrcat(job_ptr->partition, part_ptr->name);
	return SLURM_SUCCESS;
}

/* If a job can run in multiple partitions, when it is started we want to
 * put the name of the partition used _first_ in that list. When slurmctld
 * restarts, that will be used to set the job's part_ptr and that will be
 * reported to squeue. We leave all of the partitions in the list though,
 * so the job can be requeued and have access to them all. */
extern void rebuild_job_part_list(job_record_t *job_ptr)
{
	rebuild_args_t arg = {
		.job_ptr = job_ptr,
	};

	xfree(job_ptr->partition);

	if (!job_ptr->part_ptr_list) {
		job_ptr->partition = xstrdup(job_ptr->part_ptr->name);
		last_job_update = time(NULL);
		return;
	}

	if (IS_JOB_RUNNING(job_ptr) || IS_JOB_SUSPENDED(job_ptr)) {
		arg.flags |= REBUILD_ACTIVE;
		job_ptr->partition = xstrdup(job_ptr->part_ptr->name);
	} else if (IS_JOB_PENDING(job_ptr))
		arg.flags |= REBUILD_PENDING;
	list_for_each(job_ptr->part_ptr_list, _build_partition_string, &arg);
	last_job_update = time(NULL);
}

/* cleanup_completing()
 *
 * Clean up the JOB_COMPLETING flag and eventually
 * requeue the job if there is a pending request
 * for it. This function assumes the caller has the
 * appropriate locks on the job_record.
 */
void cleanup_completing(job_record_t *job_ptr, bool requeue)
{
	time_t delay;
	if (job_ptr->epilog_running || job_ptr->node_cnt)
		return;
	log_flag(TRACE_JOBS, "%s: %pJ", __func__, job_ptr);

	delay = last_job_update - job_ptr->end_time;
	if (delay > 60) {
		info("%s: %pJ completion process took %ld seconds",
		     __func__, job_ptr, (long) delay);
	}

	license_job_return(job_ptr);
	gs_job_fini(job_ptr);

	delete_step_records(job_ptr);
	job_state_unset_flag(job_ptr, JOB_COMPLETING);
	job_hold_requeue(job_ptr);

	/*
	 * Clear alloc tres fields after a requeue. job_set_alloc_tres will
	 * clear the fields when the job is pending and not completing.
	 */
	if (IS_JOB_PENDING(job_ptr))
		job_set_alloc_tres(job_ptr, false);

	/* Job could be pending if the job was requeued due to a node failure */
	if (IS_JOB_COMPLETED(job_ptr))
		fed_mgr_job_complete(job_ptr, job_ptr->exit_code,
				     job_ptr->start_time);
	if (requeue)
		batch_requeue_fini(job_ptr);
}

void main_sched_init(void)
{
	bool was_alive;

	slurm_mutex_lock(&sched_mutex);
	was_alive = sched_alive;
	sched_alive = true;
	slurm_mutex_unlock(&sched_mutex);

	if (!was_alive)
		slurm_thread_create_detached("sched_agent", _sched_agent, NULL);
}

void main_sched_fini(void)
{
	slurm_mutex_lock(&sched_mutex);

	while (sched_alive) {
		/*
		 * Wake up _sched_agent() if it is sleeping
		 * before waiting for it to change state
		 */
		slurm_cond_broadcast(&sched_cond);
		slurm_cond_wait(&sched_cond, &sched_mutex);
	}

	slurm_mutex_unlock(&sched_mutex);
}
