Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
libpkg: implement new job scheduler
Isaac Freund committed 1 year ago
commit d71e17778788cbc41eedf4bcde92a777d3181f24
parent 2e1ff21
7 files changed +448 -254
modified libpkg/Makefile.autosetup
@@ -43,6 +43,7 @@ SRCS= backup_lib.c \
	pkg_attributes.c \
	pkg_delete.c \
	pkg_jobs.c \
+
	pkg_jobs_schedule.c \
	pkg_repo_create.c \
	pkg_version.c \
	rcscripts.c \
modified libpkg/pkg_cudf.c
@@ -150,7 +150,7 @@ cudf_emit_pkg(struct pkg *pkg, int version, FILE *f,
	column = 0;
	if (pkghash_count(pkg->conflictshash) > 0 ||
			(conflicts_chain->next != NULL &&
-
			conflicts_chain->next->priority != INT_MIN)) {
+
			!conflicts_chain->next->cudf_emit_skip)) {
		if (fprintf(f, "conflicts: ") < 0)
			return (EPKG_FATAL);
		LL_FOREACH(pkg->conflicts, conflict) {
@@ -161,7 +161,7 @@ cudf_emit_pkg(struct pkg *pkg, int version, FILE *f,
		}
		ver = 1;
		LL_FOREACH(conflicts_chain, u) {
-
			if (u->pkg != pkg && u->priority != INT_MIN) {
+
			if (u->pkg != pkg && !u->cudf_emit_skip) {
				if (cudf_print_conflict(f, pkg->uid, ver,
				   (u->next != NULL && u->next->pkg != pkg), &column) < 0) {
					return (EPKG_FATAL);
@@ -240,9 +240,9 @@ pkg_cudf_version_cmp(struct pkg_job_universe_item *a, struct pkg_job_universe_it
	if (ret == 0) {
		/* Ignore remote packages whose versions are equal to ours */
		if (a->pkg->type != PKG_INSTALLED)
-
			a->priority = INT_MIN;
+
			a->cudf_emit_skip = true;
		else if (b->pkg->type != PKG_INSTALLED)
-
			b->priority = INT_MIN;
+
			b->cudf_emit_skip = true;
	}


@@ -281,7 +281,7 @@ pkg_jobs_cudf_emit_file(struct pkg_jobs *j, pkg_jobs_t t, FILE *f)

		version = 1;
		LL_FOREACH(it, icur) {
-
			if (icur->priority != INT_MIN) {
+
			if (!icur->cudf_emit_skip) {
				pkg = icur->pkg;

				if (cudf_emit_pkg(pkg, version ++, f, it) != EPKG_OK)
@@ -336,7 +336,7 @@ cudf_strdup(const char *in)
}

static void
-
pkg_jobs_cudf_insert_res_job (pkg_solved *target,
+
pkg_jobs_cudf_insert_res_job (pkg_solved_list *target,
		struct pkg_job_universe_item *it_new,
		struct pkg_job_universe_item *it_old,
		int type)
modified libpkg/pkg_jobs.c
@@ -572,90 +572,6 @@ pkg_jobs_process_delete_request(struct pkg_jobs *j)
	return (rc);
}

-
static int
-
pkg_jobs_set_execute_priority(struct pkg_jobs *j, struct pkg_solved *solved)
-
{
-
	struct pkg_solved *ts;
-

-
	if (solved->type == PKG_SOLVED_UPGRADE && solved->items[1]->pkg->conflicts != NULL) {
-
		/*
-
		 * We have an upgrade request that has some conflicting packages, therefore
-
		 * update priorities of local packages and try to update priorities of remote ones
-
		 */
-
		if (solved->items[0]->priority == 0)
-
			pkg_jobs_update_conflict_priority(j->universe, solved);
-

-
		if (solved->items[1]->priority > solved->items[0]->priority) {
-
			/*
-
			 * Split conflicting upgrade request into delete -> upgrade request
-
			 */
-
			ts = xcalloc(1, sizeof(struct pkg_solved));
-
			ts->type = PKG_SOLVED_UPGRADE_REMOVE;
-
			ts->items[0] = solved->items[1];
-
			ts->xlink = solved;
-
			solved->items[1] = NULL;
-
			solved->type = PKG_SOLVED_UPGRADE_INSTALL;
-
			solved->xlink = ts;
-
			tll_push_back(j->jobs, ts);
-
			dbg(2, "split upgrade request for %s",
-
			   ts->items[0]->pkg->uid);
-
			return (EPKG_CONFLICT);
-
		}
-
	}
-
	else if (solved->type == PKG_SOLVED_DELETE) {
-
		if (solved->items[0]->priority == 0)
-
			pkg_jobs_update_universe_priority(j->universe, solved->items[0],
-
					PKG_PRIORITY_UPDATE_DELETE);
-
	}
-
	else if (solved->items[0]->priority == 0) {
-
		pkg_jobs_update_universe_priority(j->universe, solved->items[0],
-
				PKG_PRIORITY_UPDATE_REQUEST);
-
	}
-

-
	return (EPKG_OK);
-
}
-

-
static bool
-
pkg_jobs_is_delete(struct pkg_solved *req)
-
{
-
	return (req->type == PKG_SOLVED_DELETE ||
-
	    req->type == PKG_SOLVED_UPGRADE_REMOVE);
-
}
-

-
static int
-
pkg_jobs_sort_priority(struct pkg_solved *r1, struct pkg_solved *r2)
-
{
-
	if (r1->items[0]->priority == r2->items[0]->priority) {
-
		if (pkg_jobs_is_delete(r1) && !pkg_jobs_is_delete(r2))
-
			return (-1);
-
		if (pkg_jobs_is_delete(r2) && !pkg_jobs_is_delete(r1))
-
			return (1);
-

-
		return (0);
-
	}
-
	return (r2->items[0]->priority - r1->items[0]->priority);
-
}
-

-
static void
-
pkg_jobs_set_priorities(struct pkg_jobs *j)
-
{
-
	struct pkg_solved *req;
-

-
iter_again:
-
	tll_foreach(j->jobs, r) {
-
		req = r->item;
-
		req->items[0]->priority = 0;
-
		if (req->items[1] != NULL)
-
			req->items[1]->priority = 0;
-
	}
-
	tll_foreach(j->jobs, r) {
-
		if (pkg_jobs_set_execute_priority(j, r->item) == EPKG_CONFLICT)
-
			goto iter_again;
-
	}
-

-
	tll_sort(j->jobs, pkg_jobs_sort_priority);
-
}
-

static bool pkg_jobs_test_automatic(struct pkg_jobs *j, struct pkg *p);

static bool
@@ -2156,7 +2072,9 @@ pkg_jobs_execute(struct pkg_jobs *j)

	pkg_plugins_hook_run(pre, j, j->db);

-
	pkg_jobs_set_priorities(j);
+
	retcode = pkg_jobs_schedule(j);
+
	if (retcode != EPKG_OK)
+
		return (retcode);

	tll_foreach(j->jobs, _p) {
		struct pkg_solved *ps = _p->item;
@@ -2246,7 +2164,6 @@ pkg_jobs_apply(struct pkg_jobs *j)
				bool found_conflicts = false;
				rc = pkg_jobs_check_and_solve_conflicts(j, &found_conflicts);
				if (found_conflicts) {
-
					pkg_jobs_set_priorities(j);
					rc = EPKG_CONFLICT;
				} else if (rc == EPKG_OK) {
					rc = pkg_jobs_execute(j);
added libpkg/pkg_jobs_schedule.c
@@ -0,0 +1,420 @@
+
/*-
+
 * Copyright (c) 2024 The FreeBSD Foundation
+
 *
+
 * This software was developed by Isaac Freund <ifreund@freebsdfoundation.org>
+
 * under sponsorship from the FreeBSD Foundation.
+
 *
+
 * Redistribution and use in source and binary forms, with or without
+
 * modification, are permitted provided that the following conditions
+
 * are met:
+
 * 1. Redistributions of source code must retain the above copyright
+
 *    notice, this list of conditions and the following disclaimer
+
 *    in this position and unchanged.
+
 * 2. Redistributions in binary form must reproduce the above copyright
+
 *    notice, this list of conditions and the following disclaimer in the
+
 *    documentation and/or other materials provided with the distribution.
+
 *
+
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 */
+
#include <assert.h>
+
#include <tllist.h>
+

+
#include "pkg.h"
+
#include "private/event.h"
+
#include "private/pkg.h"
+
#include "private/pkg_jobs.h"
+

+
#define dbg(x, ...) pkg_dbg(PKG_DBG_SCHEDULER, x, __VA_ARGS__)
+

+
extern struct pkg_ctx ctx;
+

+
static const char *
+
pkg_jobs_schedule_job_type_string(struct pkg_solved *job)
+
{
+
	switch (job->type) {
+
	case PKG_SOLVED_INSTALL:
+
		return "install";
+
	case PKG_SOLVED_DELETE:
+
		return "delete";
+
	case PKG_SOLVED_UPGRADE:
+
		return "upgrade";
+
	case PKG_SOLVED_UPGRADE_INSTALL:
+
		return "split upgrade install";
+
	case PKG_SOLVED_UPGRADE_REMOVE:
+
		return "split upgrade delete";
+
	default:
+
		assert(false);
+
	}
+
}
+

+
/*
+
 * Returns true if pkg a directly depends on pkg b.
+
 *
+
 * Checking only direct dependencies is sufficient to define the edges in a
+
 * graph that models indirect dependencies as well as long as all of the
+
 * intermediate dependencies are also nodes in the graph.
+
 */
+
static bool pkg_jobs_schedule_direct_depends(struct pkg *a, struct pkg *b)
+
{
+
	struct pkg_dep *dep = NULL;
+
	while (pkg_deps(a, &dep) == EPKG_OK) {
+
		if (STREQ(b->uid, dep->uid)) {
+
			return (true);
+
		}
+
	}
+
	return (false);
+
}
+

+
/* Enable debug logging in pkg_jobs_schedule_graph_edge() */
+
static bool debug_edges = false;
+

+
/*
+
 * Jobs are nodes in a directed graph. Edges represent job scheduling order
+
 * requirements. The existence of an edge from node A to node B indicates
+
 * that job A must be executed before job B.
+
 *
+
 * There is a directed edge from node A to node B if and only if
+
 * one of the following conditions holds:
+
 *
+
 * 1. B's new package depends on A's new package
+
 * 2. A's old package depends on B's old package
+
 * 3. A's old package conflicts with B's new package
+
 * 4. A and B are the two halves of a split upgrade job
+
 *    and A is the delete half.
+
 */
+
static bool
+
pkg_jobs_schedule_graph_edge(struct pkg_solved *a, struct pkg_solved *b)
+
{
+
	if (a == b) {
+
		return (false);
+
	}
+

+
	if (a->xlink == b || b->xlink == a) {
+
		assert(a->xlink == b && b->xlink == a);
+
		assert(a->type == PKG_SOLVED_UPGRADE_INSTALL ||
+
		       a->type == PKG_SOLVED_UPGRADE_REMOVE);
+
		assert(b->type == PKG_SOLVED_UPGRADE_INSTALL ||
+
		       b->type == PKG_SOLVED_UPGRADE_REMOVE);
+
		assert(a->type != b->type);
+

+
		bool edge = a->type == PKG_SOLVED_UPGRADE_REMOVE;
+
		if (edge && debug_edges) {
+
			dbg(4, "  edge to %s %s, split upgrade",
+
			    pkg_jobs_schedule_job_type_string(b),
+
			    b->items[0]->pkg->uid);
+
		}
+
		return (edge);
+
	}
+

+
	/* TODO: These switches would be unnecessary if delete jobs used
+
	 * items[1] rather than items[0]. I suspect other cleanups could
+
	 * be made as well. */
+
	struct pkg *a_new = NULL;
+
	struct pkg *a_old = NULL;
+
	switch (a->type) {
+
	case PKG_SOLVED_INSTALL:
+
	case PKG_SOLVED_UPGRADE_INSTALL:
+
		a_new = a->items[0]->pkg;
+
		break;
+
	case PKG_SOLVED_DELETE:
+
	case PKG_SOLVED_UPGRADE_REMOVE:
+
		a_old = a->items[0]->pkg;
+
		break;
+
	case PKG_SOLVED_UPGRADE:
+
		a_new = a->items[0]->pkg;
+
		a_old = a->items[1]->pkg;
+
		break;
+
	default:
+
		assert(false);
+
	}
+

+
	struct pkg *b_new = NULL;
+
	struct pkg *b_old = NULL;
+
	switch (b->type) {
+
	case PKG_SOLVED_INSTALL:
+
	case PKG_SOLVED_UPGRADE_INSTALL:
+
		b_new = b->items[0]->pkg;
+
		break;
+
	case PKG_SOLVED_DELETE:
+
	case PKG_SOLVED_UPGRADE_REMOVE:
+
		b_old = b->items[0]->pkg;
+
		break;
+
	case PKG_SOLVED_UPGRADE:
+
		b_new = b->items[0]->pkg;
+
		b_old = b->items[1]->pkg;
+
		break;
+
	default:
+
		assert(false);
+
	}
+

+
	if (a_new != NULL && b_new != NULL &&
+
	    pkg_jobs_schedule_direct_depends(b_new, a_new)) {
+
		if (debug_edges) {
+
			dbg(4, "  edge to %s %s, new depends on new",
+
			    pkg_jobs_schedule_job_type_string(b),
+
			    b->items[0]->pkg->uid);
+
		}
+
		return (true);
+
	} else if (a_old != NULL && b_old != NULL &&
+
		   pkg_jobs_schedule_direct_depends(a_old, b_old)) {
+
		if (debug_edges) {
+
			dbg(4, "  edge to %s %s, old depends on old",
+
			    pkg_jobs_schedule_job_type_string(b),
+
			    b->items[0]->pkg->uid);
+
		}
+
		return (true);
+
	} else if (a_old != NULL && b_new != NULL) {
+
		struct pkg_conflict *conflict = NULL;
+
		while (pkg_conflicts(a_old, &conflict) == EPKG_OK) {
+
			if (STREQ(b_new->uid, conflict->uid)) {
+
				if (debug_edges) {
+
					dbg(4, "  edge to %s %s, old conflicts with new",
+
					    pkg_jobs_schedule_job_type_string(b),
+
					    b->items[0]->pkg->uid);
+
				}
+
				return (true);
+
			}
+
		}
+
	}
+

+
	return (false);
+
}
+

+
static void
+
pkg_jobs_schedule_dbg_job(pkg_solved_list *jobs, struct pkg_solved *job)
+
{
+
	if (ctx.debug_level < 4) {
+
		return;
+
	}
+

+
	dbg(4, "job: %s %s", pkg_jobs_schedule_job_type_string(job),
+
	    job->items[0]->pkg->uid);
+

+
	debug_edges = true;
+
	tll_foreach(*jobs, it) {
+
		pkg_jobs_schedule_graph_edge(job, it->item);
+
	}
+
	debug_edges = false;
+
}
+

+
static bool
+
pkg_jobs_schedule_has_incoming_edge(pkg_solved_list *nodes,
+
    struct pkg_solved *node)
+
{
+
	tll_foreach(*nodes, it) {
+
		if (pkg_jobs_schedule_graph_edge(it->item, node)) {
+
			return (true);
+
		}
+
	}
+
	return (false);
+
}
+

+
/*
+
 * Prioritizing the install jobs and deprioritizing the delete jobs of split
+
 * upgrades reduces the distance between the two halves of the split job in the
+
 * final execution order.
+
 */
+
static int
+
pkg_jobs_schedule_priority(struct pkg_solved *node)
+
{
+
	switch (node->type) {
+
	case PKG_SOLVED_UPGRADE_INSTALL:
+
		return 1;
+
	case PKG_SOLVED_UPGRADE_REMOVE:
+
		return -1;
+
	default:
+
		return 0;
+
	}
+
}
+

+
/* This comparison function is used as a tiebreaker in the topological sort. */
+
static int
+
pkg_jobs_schedule_cmp_available(struct pkg_solved *a, struct pkg_solved *b)
+
{
+
	int ret = pkg_jobs_schedule_priority(b) - pkg_jobs_schedule_priority(a);
+
	if (ret == 0) {
+
		/* Falling back to lexicographical ordering ensures that job execution
+
		 * order is always consistent and makes testing easier. */
+
		return strcmp(a->items[0]->pkg->uid, b->items[0]->pkg->uid);
+
	} else {
+
		return ret;
+
	}
+
}
+

+
/* Topological sort based on Kahn's algorithm with a tiebreaker */
+
static void
+
pkg_jobs_schedule_topological_sort(pkg_solved_list *jobs)
+
{
+
	pkg_solved_list sorted = tll_init();
+
	pkg_solved_list available = tll_init();
+

+
	/* Place all job nodes with no incoming edges in available */
+
	tll_foreach(*jobs, it) {
+
		if (!pkg_jobs_schedule_has_incoming_edge(jobs, it->item) &&
+
		    !pkg_jobs_schedule_has_incoming_edge(&available, it->item)) {
+
			tll_push_front(available, it->item);
+
			tll_remove(*jobs, it);
+
		}
+
	}
+

+
	while (tll_length(available) > 0) {
+
		/* Add the highest priority job from the set of available jobs
+
		 * to the sorted list */
+
		tll_sort(available, pkg_jobs_schedule_cmp_available);
+
		struct pkg_solved *node = tll_pop_front(available);
+
		tll_push_back(sorted, node);
+

+
		/* Again, place all job nodes with no incoming edges in the set
+
		 * of available jobs, ignoring any incoming edges from job nodes
+
		 * already added to the sorted list */
+
		tll_foreach(*jobs, it) {
+
			if (pkg_jobs_schedule_graph_edge(node, it->item)) {
+
				if (!pkg_jobs_schedule_has_incoming_edge(jobs, it->item) &&
+
				    !pkg_jobs_schedule_has_incoming_edge(&available, it->item)) {
+
					tll_push_front(available, it->item);
+
					tll_remove(*jobs, it);
+
				}
+
			}
+
		}
+
	}
+

+
	/* The jobs list will only be non-empty at this point if there is a
+
	 * cycle in the graph and all cycles must be eliminated by splitting
+
	 * upgrade jobs before calling this function. */
+
	assert(tll_length(*jobs) == 0);
+

+
	*jobs = sorted;
+
}
+

+
/*
+
 * This is a depth-first search that keeps track of the path taken to the
+
 * current node in the graph. If a node on this path is encountered a
+
 * second time a cycle has been found.
+
 */
+
static struct pkg_solved *
+
pkg_jobs_schedule_find_cycle(pkg_solved_list *jobs,
+
    struct pkg_solved **path, struct pkg_solved *node)
+
{
+
	/* Push node to path */
+
	assert(node->mark == PKG_SOLVED_CYCLE_MARK_NONE);
+
	node->mark = PKG_SOLVED_CYCLE_MARK_PATH;
+
	assert(node->path_next == NULL);
+
	node->path_next = *path;
+
	*path = node;
+

+
	tll_foreach(*jobs, it) {
+
		if (pkg_jobs_schedule_graph_edge(node, it->item)) {
+
			switch (it->item->mark){
+
			case PKG_SOLVED_CYCLE_MARK_NONE:;
+
				struct pkg_solved *cycle =
+
				    pkg_jobs_schedule_find_cycle(jobs, path, it->item);
+
				if (cycle != NULL) {
+
					return (cycle);
+
				}
+
				break;
+
			case PKG_SOLVED_CYCLE_MARK_DONE:
+
				break;
+
			case PKG_SOLVED_CYCLE_MARK_PATH:
+
				return (it->item); /* Found a cycle */
+
			default:
+
				assert(false);
+
			}
+
		}
+
	}
+

+
	/* Pop node from path */
+
	assert(node->mark == PKG_SOLVED_CYCLE_MARK_PATH);
+
	node->mark = PKG_SOLVED_CYCLE_MARK_DONE;
+
	*path = node->path_next;
+
	node->path_next = NULL;
+

+
	return (NULL);
+
}
+

+
int pkg_jobs_schedule(struct pkg_jobs *j)
+
{
+
	while (true) {
+
		dbg(3, "checking job scheduling graph for cycles...");
+

+
		tll_foreach(j->jobs, it) {
+
			it->item->mark = PKG_SOLVED_CYCLE_MARK_NONE;
+
			it->item->path_next = NULL;
+

+
			pkg_jobs_schedule_dbg_job(&j->jobs, it->item);
+
		}
+

+
		/* The graph may not be connected, in which case it is necessary to
+
		 * run multiple searches for cycles from different start nodes. */
+
		struct pkg_solved *path = NULL;
+
		struct pkg_solved *cycle = NULL;
+
		tll_foreach(j->jobs, it) {
+
			switch (it->item->mark) {
+
			case PKG_SOLVED_CYCLE_MARK_NONE:
+
				cycle = pkg_jobs_schedule_find_cycle(&j->jobs, &path, it->item);
+
				break;
+
			case PKG_SOLVED_CYCLE_MARK_DONE:
+
				break;
+
			case PKG_SOLVED_CYCLE_MARK_PATH:
+
			default:
+
				assert(false);
+
			}
+
			if (cycle != NULL) {
+
				break;
+
			}
+
		}
+

+
		if (cycle == NULL) {
+
			dbg(3, "no job scheduling graph cycles found");
+
			assert(path == NULL);
+
			break;
+
		}
+

+
		dbg(3, "job scheduling graph cycle found");
+
		assert(path != NULL);
+
		assert(path != cycle);
+

+
		/* Choose an arbitrary upgrade job in the cycle to split in order
+
		 * to break the cycle.
+
		 *
+
		 * TODO: Does it truly not matter which upgrade job in the cycle we
+
		 * choose to split? I'm relatively confident that splitting any upgrade job
+
		 * will break the given cycle but is it possible that one of the choices
+
		 * would break additional cycles as well?
+
		 */
+
		while (path->type != PKG_SOLVED_UPGRADE) {
+
			if (path == cycle) {
+
				pkg_emit_error("found job scheduling cycle without upgrade job");
+
			 	return (EPKG_FATAL);
+
			}
+
			path = path->path_next;
+
			assert(path != NULL);
+
		}
+

+
		/* path is now the upgrade job chosen to be split */
+
		dbg(2, "splitting upgrade %s job", path->items[0]->pkg->uid);
+

+
		struct pkg_solved *new = xcalloc(1, sizeof(struct pkg_solved));
+
		new->type = PKG_SOLVED_UPGRADE_REMOVE;
+
		new->items[0] = path->items[1];
+
		new->xlink = path;
+
		path->type = PKG_SOLVED_UPGRADE_INSTALL;
+
		path->items[1] = NULL;
+
		path->xlink = new;
+
		tll_push_back(j->jobs, new);
+
	}
+

+
	pkg_jobs_schedule_topological_sort(&j->jobs);
+

+
	dbg(3, "finished job scheduling");
+

+
	return (EPKG_OK);
+
}
modified libpkg/pkg_jobs_universe.c
@@ -658,165 +658,6 @@ pkg_jobs_universe_process(struct pkg_jobs_universe *universe,
	return (pkg_jobs_universe_process_item(universe, pkg, NULL));
}

-
#define RECURSION_LIMIT 1024
-

-
static void
-
pkg_jobs_update_universe_item_priority(struct pkg_jobs_universe *universe,
-
		struct pkg_job_universe_item *item, int priority,
-
		enum pkg_priority_update_type type)
-
{
-
	struct pkg_dep *d = NULL;
-
	struct pkg_conflict *c = NULL;
-
	struct pkg_job_universe_item *found, *cur, *it;
-
	const char *is_local;
-
	int maxpri;
-

-
	int (*deps_func)(const struct pkg *pkg, struct pkg_dep **d);
-
	int (*rdeps_func)(const struct pkg *pkg, struct pkg_dep **d);
-

-
	if (priority > RECURSION_LIMIT) {
-
		dbg(1, "recursion limit has been reached, something is bad"
-
					" with dependencies/conflicts graph");
-
		return;
-
	}
-
	else if (priority + 10 > RECURSION_LIMIT) {
-
		dbg(2, "approaching recursion limit at %d, while processing of"
-
		    " package %s", priority, item->pkg->uid);
-
	}
-

-
	LL_FOREACH(item, it) {
-
		bool is_group = false;
-
		if ((item->next != NULL || item->prev != NULL) &&
-
		    it->pkg->type != PKG_INSTALLED &&
-
		    (type == PKG_PRIORITY_UPDATE_CONFLICT ||
-
		     type == PKG_PRIORITY_UPDATE_DELETE)) {
-
			/*
-
			 * We do not update priority of a remote part of conflict, as we know
-
			 * that remote packages should not contain conflicts (they should be
-
			 * resolved in request prior to calling of this function)
-
			 */
-
			dbg(4, "skip update priority for %s-%s",
-
			    it->pkg->uid, it->pkg->digest);
-
			continue;
-
		}
-
		if (it->priority > priority)
-
			continue;
-

-
		if (it->pkg->type == PKG_GROUP_INSTALLED || it->pkg->type == PKG_GROUP_REMOTE)
-
			is_group = true;
-

-
		if (it->pkg->type == PKG_INSTALLED || it->pkg->type == PKG_GROUP_INSTALLED)
-
			is_local = "local";
-
		else
-
			is_local = "remote";
-
		if (is_group)
-
			dbg(2, "update %s priority of %s: %d -> %d, reason: %d",
-
			    is_local, it->pkg->uid, it->priority, priority, type);
-
		else
-
			dbg(2, "update %s priority of %s(%s): %d -> %d, reason: %d",
-
			    is_local, it->pkg->uid, it->pkg->version, it->priority, priority, type);
-
		it->priority = priority;
-

-
		if (type == PKG_PRIORITY_UPDATE_DELETE) {
-
			/*
-
			 * For delete requests we inverse deps and rdeps logic
-
			 */
-
			deps_func = pkg_rdeps;
-
			rdeps_func = pkg_deps;
-
		}
-
		else {
-
			deps_func = pkg_deps;
-
			rdeps_func = pkg_rdeps;
-
		}
-

-
		while (deps_func(it->pkg, &d) == EPKG_OK) {
-
			found = pkghash_get_value(universe->items, d->uid);
-
			if (found == NULL)
-
				continue;
-
			LL_FOREACH(found, cur) {
-
				if (cur->priority < priority + 1)
-
					pkg_jobs_update_universe_item_priority(universe, cur,
-
					    priority + 1, type);
-
			}
-
		}
-

-
		d = NULL;
-
		maxpri = priority;
-
		while (rdeps_func(it->pkg, &d) == EPKG_OK) {
-
			found = pkghash_get_value(universe->items, d->uid);
-
			if (found == NULL)
-
				continue;
-
			LL_FOREACH(found, cur) {
-
				if (cur->priority >= maxpri) {
-
					maxpri = cur->priority + 1;
-
				}
-
			}
-
		}
-
		if (maxpri != priority) {
-
			pkg_jobs_update_universe_item_priority(universe, it,
-
					maxpri, type);
-
			return;
-
		}
-
		if (it->pkg->type == PKG_INSTALLED)
-
			continue;
-

-
		while (pkg_conflicts(it->pkg, &c) == EPKG_OK) {
-
			found = pkghash_get_value(universe->items, c->uid);
-
			LL_FOREACH(found, cur) {
-
				if (cur->pkg->type != PKG_INSTALLED)
-
					continue;
-
				/*
-
				 * Move delete requests to be done before installing
-
				 */
-
				if (cur->priority <= it->priority)
-
					pkg_jobs_update_universe_item_priority(universe, cur,
-
					    it->priority + 1, PKG_PRIORITY_UPDATE_CONFLICT);
-
			}
-
		}
-
	}
-
}
-

-
void
-
pkg_jobs_update_conflict_priority(struct pkg_jobs_universe *universe,
-
	struct pkg_solved *req)
-
{
-
	struct pkg_conflict *c = NULL;
-
	struct pkg *lp = req->items[1]->pkg;
-
	struct pkg_job_universe_item *found, *cur, *rit = NULL;
-

-
	while (pkg_conflicts(lp, &c) == EPKG_OK) {
-
		rit = NULL;
-
		found = pkghash_get_value(universe->items, c->uid);
-
		assert(found != NULL);
-

-
		LL_FOREACH(found, cur) {
-
			if (cur->pkg->type != PKG_INSTALLED) {
-
				rit = cur;
-
				break;
-
			}
-
		}
-

-
		assert(rit != NULL);
-
		if (rit->priority >= req->items[1]->priority) {
-
			pkg_jobs_update_universe_item_priority(universe, req->items[1],
-
					rit->priority + 1, PKG_PRIORITY_UPDATE_CONFLICT);
-
			/*
-
			 * Update priorities for a remote part as well
-
			 */
-
			pkg_jobs_update_universe_item_priority(universe, req->items[0],
-
					req->items[0]->priority, PKG_PRIORITY_UPDATE_REQUEST);
-
		}
-
	}
-
}
-

-

-
void
-
pkg_jobs_update_universe_priority(struct pkg_jobs_universe *universe,
-
	struct pkg_job_universe_item *it, enum pkg_priority_update_type type)
-
{
-
	pkg_jobs_update_universe_item_priority(universe, it, 0, type);
-
}
-

static void
pkg_jobs_universe_provide_free(struct pkg_job_provide *pr)
{
modified libpkg/private/event.h
@@ -64,6 +64,7 @@ typedef enum {
	PKG_DBG_UNIVERSE = (1UL << 8),
	PKG_DBG_PACKAGE = (1UL << 9),
	PKG_DBG_DATABASE = (1UL << 10),
+
	PKG_DBG_SCHEDULER = (1UL << 11),
	PKG_DBG_ALL = (1UL << 63),
} pkg_debug_flags;

@@ -84,6 +85,7 @@ static const struct pkg_dbg_flags debug_flags[] = {
	{ PKG_DBG_UNIVERSE, "universe" },
	{ PKG_DBG_PACKAGE, "package" },
	{ PKG_DBG_DATABASE, "db" },
+
	{ PKG_DBG_SCHEDULER, "scheduler" },
	{ PKG_DBG_ALL, "all" },
};

modified libpkg/private/pkg_jobs.h
@@ -46,9 +46,9 @@ struct job_pattern;
 */
struct pkg_job_universe_item {
	struct pkg *pkg;
-
	int priority;
	bool processed;
	bool inhash;
+
	bool cudf_emit_skip;
	struct pkg_job_universe_item *next, *prev;
};

@@ -66,12 +66,20 @@ struct pkg_job_request {
	bool automatic;
};

+
enum pkg_solved_cycle_mark {
+
	PKG_SOLVED_CYCLE_MARK_NONE,	/* Not yet checked */
+
	PKG_SOLVED_CYCLE_MARK_DONE,	/* Finished checking */
+
	PKG_SOLVED_CYCLE_MARK_PATH,	/* In the path currently being checked */
+
};
+

struct pkg_solved {
	struct pkg_job_universe_item *items[2]; /* to-add/to-delete */
	struct pkg_solved *xlink;	/* link split jobs together */
	pkg_solved_t type;
+
	enum pkg_solved_cycle_mark mark;/* scheduling cycle detection */
+
	struct pkg_solved *path_next;	/* scheduling cycle detection */
};
-
typedef tll(struct pkg_solved *) pkg_solved;
+
typedef tll(struct pkg_solved *) pkg_solved_list;

struct pkg_job_provide {
	struct pkg_job_universe_item *un;
@@ -98,7 +106,7 @@ struct pkg_jobs {
	struct pkg_jobs_universe *universe;
	pkghash	*request_add;
	pkghash	*request_delete;
-
	pkg_solved	 jobs;
+
	pkg_solved_list	 jobs;
	struct pkgdb	*db;
	pkg_jobs_t	 type;
	pkg_flags	 flags;
@@ -233,6 +241,11 @@ pkg_jobs_universe_select_candidate(struct pkg_job_universe_item *chain,
	const char *reponame, bool pinning);

/*
+
 * Determine execution order and sort the pkg_jobs->jobs list.
+
 */
+
int pkg_jobs_schedule(struct pkg_jobs *j);
+

+
/*
 * Free job request (with all candidates)
 */
void pkg_jobs_request_free(struct pkg_job_request *req);