This is my initial definition of a new internal job control api. I am
working on this as a part of the google summer of code. These patches
contain the core job control api and deal only with managing individual
jobs. I am currently working on writing code using this api to manage
jobs in domains, in such a way that I will be able to replace the
current job control code in qemu and libxl. Ultimately I will use this
to implement job control in the storage driver which is my ultimate
goal for the summer of code.
---
src/Makefile.am | 1 +
src/util/virjobcontrol.c | 574 +++++++++++++++++++++++++++++++++++++++++++++++
src/util/virjobcontrol.h | 342 ++++++++++++++++++++++++++++
3 files changed, 917 insertions(+)
create mode 100644 src/util/virjobcontrol.c
create mode 100644 src/util/virjobcontrol.h
diff --git a/src/Makefile.am b/src/Makefile.am
index 2b9ac61..77de0e7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -118,6 +118,7 @@ UTIL_SOURCES = \
util/virinitctl.c util/virinitctl.h \
util/viriptables.c util/viriptables.h \
util/viriscsi.c util/viriscsi.h \
+ util/virjobcontrol.h util/virjobcontrol.c \
util/virjson.c util/virjson.h \
util/virkeycode.c util/virkeycode.h \
util/virkeyfile.c util/virkeyfile.h \
diff --git a/src/util/virjobcontrol.c b/src/util/virjobcontrol.c
new file mode 100644
index 0000000..04a5246
--- /dev/null
+++ b/src/util/virjobcontrol.c
@@ -0,0 +1,574 @@
+/*
+ * virjobcontrol.c Core implementation of job control
+ *
+ * Copyright (C) 2014 Tucker DiNapoli
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Author: Tucker DiNapoli
+ */
+
+#include <config.h>
+
+#include "virjobcontrol.h"
+#include "viralloc.h"
+#include "virtime.h"
+#include "virlog.h"
+VIR_LOG_INIT("virjobcontrol");
+
+VIR_ENUM_IMPL(virJob, 4,
+ "none",
+ "query",
+ "modify",
+ "destroy",
+);
+/*
+ No files other then this and virjobcontrol.c should need to
+ have access to the core implmentation of jobs. The code in these
+ files is intended to serve as a base for job control independent of
+ drivers.
+*/
+
+#define LOCK_JOB(job) \
+ virMutexLock(&job->lock)
+#define UNLOCK_JOB(job) \
+ virMutexUnlock(&job->lock)
+#define LOCK_JOB_INFO(job) \
+ virMutexLock(&job->info->lock)
+#define UNLOCK_JOB_INFO(job) \
+ virMutexUnlock(&job->info->lock)
+#define GET_CURRENT_TIME(time) \
+ if (virTimeMillisNow(&time) < 0) { \
+ return -1; \
+ }
+
+
+#define CHECK_FLAG_ATOMIC(job, flag) (virAtomicIntGet(&job->flags) &
VIR_JOB_FLAG_##flag)
+#define CHECK_FLAG(job, flag) (job->flags & VIR_JOB_FLAG_##flag)
+#define SET_FLAG_ATOMIC(job, flag) (virAtomicIntOr(&job->flags,
VIR_JOB_FLAG_##flag))
+#define SET_FLAG(job, flag) (job->flags |= VIR_JOB_FLAG_##flag)
+#define UNSET_FLAG_ATOMIC(job, flag) (virAtomicIntAnd(&job->flags,
(~VIR_JOB_FLAG_##flag)))
+#define UNSET_FLAG(job, flag) (job->flags &= (~VIR_JOB_FLAG_##flag))
+#define CLEAR_FLAGS_ATOMIC(job) (virAtomicIntSet(&job->flags, VIR_JOB_FLAG_NONE))
+#define CLEAR_FLAGS(job) (job->flags = VIR_JOB_FLAG_NONE)
+
+typedef struct _jobHashEntry {
+ virJobID id;
+ virJobObjPtr job;
+ struct _jobHashEntry *next;
+} jobHashEntry;
+
+typedef struct _jobHash {
+ jobHashEntry **table;
+ size_t size;
+ size_t num_entries;
+ virRWLock lock;
+} jobHash;
+
+/* Using this incures a cost on every call to a job control function
+ that uses the job control hash table, but it means that no one using
+ job control needs to call an initialization function to use it.
+
+ The other option would be to have a function:
+ virJobControlInit(void)
+ {
+ return virOnce(job_once, jobControlInit);
+ }
+ and require anyone using job control to call it.
+ */
+static struct _jobHash *job_hash; /* Hash table that contians all current jobs */
+static int init_err = 0;
+static virOnceControl job_once = VIR_ONCE_CONTROL_INITIALIZER;
+static void
+jobControlInit(void)
+{
+ if (VIR_ALLOC_QUIET(job_hash) < 0) {
+ init_err = 1;
+ }
+ if (virRWLockInit(&job_hash->lock) < 0) {
+ init_err = 1;
+ }
+ if (VIR_ALLOC_N_QUIET(job_hash->table, 16) <0) {
+ init_err = 1;
+ }
+}
+
+/* global job id
+
+ Because it gets incremented before it's first use 0 is
+ never a valid job id
+
+ Only ever touched with atomic instructions.
+*/
+static unsigned int global_id = 0;
+
+
+
+/* Simple hash table implementation for jobs.
+ It is keyed by job id's which are just integers so there isn't acutally
+ a hash function. Using the existing hash table in libvirt would be overkill.
+*/
+
+#ifndef VIR_JOB_REHASH_THRESHOLD
+#define VIR_JOB_REHASH_THRESHOLD 0.8
+#endif
+
+#ifndef VIR_JOB_BUCKET_LEN
+#define VIR_JOB_BUCKET_LEN 5
+#endif
+
+#ifndef VIR_JOB_GROWTH_FACTOR
+#define VIR_JOB_GROWTH_FACTOR 2
+#endif
+
+#define READ_LOCK_TABLE(ht) virRWLockRead((virRWLockPtr)&ht->lock)
+#define WRITE_LOCK_TABLE(ht) virRWLockWrite((virRWLockPtr)&ht->lock)
+#define UNLOCK_TABLE(ht) virRWLockUnlock((virRWLockPtr)&ht->lock)
+
+static int jobHashRehash(jobHash *ht);
+#define maybe_rehash(ht) \
+ if (((double)ht->size*VIR_JOB_BUCKET_LEN)/(ht->num_entries) >= \
+ VIR_JOB_REHASH_THRESHOLD){ \
+ jobHashRehash(ht); \
+ }
+
+
+/* Doesn't lock ht, so should only be called with a write lock held on ht*/
+static int jobHashRehash(jobHash *ht);
+
+/* look for id in the hash table ht and return the entry associated with it
+ if id isn't it the table return null*/
+static jobHashEntry*
+jobLookup(const jobHash *ht, virJobID id)
+{
+ if (virOnce(&job_once, jobControlInit) < 0) {
+ return NULL;
+ }
+ READ_LOCK_TABLE(ht);
+ int bucket = id % ht->size;
+ jobHashEntry *retval = NULL, *job_entry;
+ if (!ht->table[bucket]) {
+ goto end;
+ }
+ job_entry = ht->table[bucket];
+ do {
+ if (job_entry->id == id) {
+ retval = job_entry;
+ goto end;
+ }
+ } while ((job_entry = job_entry->next));
+
+ end:
+ UNLOCK_TABLE(ht);
+ return retval;
+}
+
+/* add job the hashtable of currently existing jobs, job should
+ have already been initialized via virJobObjInit.
+ returns 0 if job is already in the hash,
+ returns the id of the job if it was successfully added
+ returns -1 on error.
+ */
+static int
+jobHashAdd(jobHash *ht, virJobObjPtr job)
+{
+ if (virOnce(&job_once, jobControlInit) < 0) {
+ return -1;
+ }
+ virJobID id = job->id;
+ if (jobLookup(ht, id)) { /* check if job is in the table*/
+ return 0;
+ }
+ int bucket = id % ht->size;
+ jobHashEntry *new_entry;
+ if (VIR_ALLOC_QUIET(new_entry) < 0) {
+ return -1;
+ }
+ jobHashEntry *last_entry = ht->table[bucket];
+ *new_entry = (jobHashEntry) {.id = id, .job = job};
+
+ WRITE_LOCK_TABLE(ht);
+ if (!last_entry) {
+ ht->table[bucket] = new_entry;
+ } else {
+ while (last_entry->next) {
+ last_entry = last_entry->next;
+ }
+ last_entry->next = new_entry;
+ }
+ ht->num_entries++;
+ maybe_rehash(ht);
+ UNLOCK_TABLE(ht);
+ return id;
+}
+
+/* Remove the job with the given id from the list of currently existing jobs,
+ this doesn't free/cleanup the actual job object, it just removes
+ the hash entry, this is called by virJobObjFree, so there shouldn't
+ be any reason to call it directly.
+
+ return values are the same as for jobAlist add, 0 if no job found,
+ job id on success, -1 on some other error;
+*/
+static int
+jobHashRemove(jobHash *ht, virJobID id)
+{
+ int bucket = id % ht->size;
+ /*we can't just call jobLookup because we need the entry before the one
+ we want to free*/
+ WRITE_LOCK_TABLE(ht);
+ jobHashEntry *entry = ht->table[bucket], *old_entry;
+ if (!entry) {
+ goto error;
+ }
+ if (entry->id != id) {
+ while (entry && entry->next->id != id) {
+ entry = entry->next;
+ }
+ }
+ if (!entry) {
+ goto error;
+ }
+ old_entry = entry->next;
+ entry->next = old_entry->next;
+ VIR_FREE(old_entry);
+ UNLOCK_TABLE(ht);
+ return id;
+ error:
+ UNLOCK_TABLE(ht);
+ return 0;
+}
+static int
+jobHashRehash(jobHash *ht)
+{
+ size_t new_size = ht->size*VIR_JOB_GROWTH_FACTOR;
+ jobHashEntry **new_table;
+ if (VIR_ALLOC_N_QUIET(new_table, new_size)) {
+ return -1;
+ }
+ jobHashEntry **old_table = ht->table;
+ int index;
+ for (index = 0; index<ht->size; index++) {
+ jobHashEntry *old_entry = old_table[index];
+ while (old_entry) {
+ int bucket = old_entry->id % new_size;
+ jobHashEntry *new_entry = new_table[bucket];
+ if (!new_entry) {
+ new_table[bucket] = old_entry;
+ } else {
+ while (new_entry->next) {
+ new_entry = new_entry->next;
+ }
+ new_entry->next = old_entry;
+ }
+ old_entry = old_entry->next;
+ }
+ }
+ ht->size = new_size;
+ ht->table = new_table;
+ VIR_FREE(old_table);
+ return 1;
+}
+
+#define jobIDFromJobInternal(job) (job->id)
+
+static inline virJobObjPtr
+jobFromIDInternal(virJobID id)
+{
+ jobHashEntry *entry = jobLookup(job_hash, id);
+ if (entry) {
+ return entry->job;
+ } else {
+ return NULL;
+ }
+}
+
+virJobObjPtr
+virJobFromID(virJobID id)
+{
+ return jobFromIDInternal(id);
+
+}
+virJobID
+virJobIDFromJob(virJobObjPtr job)
+{
+ return jobIDFromJobInternal(job);
+}
+
+int
+virJobObjInit(virJobObjPtr job)
+{
+ /* This code checks to see if job was already initialized,
+ I don't know if this is needed or not.*/
+ if (job->id != 0) {
+ VIR_DEBUG("job %d has already been initialized", job->id);
+ return 0;
+ }
+ job->id = virAtomicIntInc(&global_id);
+ job->maxJobsWaiting = INT_MAX;
+
+ if (virCondInit(&job->cond) < 0) {
+ return -1;
+ }
+
+ if (virMutexInit(&job->lock) < 0) {
+ virCondDestroy(&job->cond);
+ return -1;
+ }
+
+ jobHashAdd(job_hash, job);
+
+ return job->id;
+}
+
+void
+virJobObjFree(virJobObjPtr job)
+{
+ virCondDestroy(&job->cond);
+ jobHashRemove(job_hash, job->id);
+ VIR_FREE(job);
+}
+
+void
+virJobObjCleanup(virJobObjPtr job)
+{
+
+ virCondDestroy(&job->cond);
+ jobHashRemove(job_hash, job->id);
+ memset(job, '\0', sizeof(virJobObj));
+ return;
+}
+
+int
+virJobObjBegin(virJobObjPtr job,
+ virJobType type)
+{
+ LOCK_JOB(job);
+ if (CHECK_FLAG(job, ACTIVE)) {
+ VIR_DEBUG("Job %d is already running", job->id);
+ UNLOCK_JOB(job);
+ return 0;
+ }
+ VIR_DEBUG("Starting job %d with type %s",
+ job->id, virJobTypeToString(type));
+ unsigned long long now;
+ if (job->id <= 0) {
+ goto error; /* job wasn't initialiazed*/
+ }
+ if (virTimeMillisNow(&now) < 0) {
+ goto error;
+ }
+ job->type = type;
+ SET_FLAG(job, ACTIVE);
+ job->start = now;
+ job->owner = virThreadSelfID();
+ return job->id;
+ error:
+ UNLOCK_JOB(job);
+ return -1;
+}
+
+int
+virJobObjEnd(virJobObjPtr job)
+{
+ if (job->type == VIR_JOB_NONE) {
+ return -1;
+ }
+ virJobObjReset(job);
+ virCondSignal(&job->cond);
+
+ return job->id;
+}
+
+/* There shouldn't be any threads waiting on job when this is called*/
+int
+virJobObjReset(virJobObjPtr job)
+{
+ LOCK_JOB(job);
+ job->type = VIR_JOB_NONE;
+ job->flags = VIR_JOB_FLAG_NONE;
+ job->owner = 0;
+ job->jobsWaiting = 0;
+ UNLOCK_JOB(job);
+
+ return job->id;
+}
+
+int
+virJobObjAbort(virJobObjPtr job)
+{
+ int retval;
+ LOCK_JOB(job);
+ if (CHECK_FLAG(job, ACTIVE) || CHECK_FLAG(job, SUSPENDED)) {
+ SET_FLAG(job, ABORTED);
+ retval = job->id;
+ } else {
+ retval = -1;
+ }
+ UNLOCK_JOB(job);
+ return retval;
+}
+
+int
+virJobObjAbortAndSignal(virJobObjPtr job)
+{
+ int retval;
+ LOCK_JOB(job);
+ if (CHECK_FLAG(job, ACTIVE) || CHECK_FLAG(job, SUSPENDED)) {
+ SET_FLAG(job, ABORTED);
+ retval = job->id;
+ virCondBroadcast(&job->cond);
+ } else {
+ retval = -1;
+ }
+ UNLOCK_JOB(job);
+ return retval;
+}
+
+/* lock should be held by the calling function*/
+int
+virJobObjWait(virJobObjPtr job,
+ virMutexPtr lock,
+ unsigned long long limit)
+{
+
+ bool job_lock = false;
+ int retval;
+ if (CHECK_FLAG_ATOMIC(job, ACTIVE)) { /* if the job isn't active we're
fine*/
+ if (virAtomicIntInc(&job->jobsWaiting) > job->maxJobsWaiting) {
+ errno = 0;
+ goto error;
+ }
+ if (!lock) {
+ LOCK_JOB(job);
+ lock = &job->lock;
+ job_lock = true;
+ }
+ while (CHECK_FLAG_ATOMIC(job, ACTIVE)) {
+ if (limit) {
+ retval = virCondWaitUntil(&job->cond, lock, limit);
+ } else {
+ retval = virCondWait(&job->cond, lock);
+ }
+ if (retval < 0) {
+ goto error;
+ }
+ }
+ }
+ virAtomicIntDec(&job->jobsWaiting);
+ if (job_lock) {
+ UNLOCK_JOB(job);
+ }
+ return job->id;
+
+ error:
+ virAtomicIntDec(&job->jobsWaiting);
+ if (job_lock) {
+ UNLOCK_JOB(job);
+ }
+ if (!errno) {
+ return -3;
+ } else if (errno == ETIMEDOUT) {
+ return -2;
+ } else {
+ return -1;
+ }
+}
+
+int
+virJobObjSuspend(virJobObjPtr job)
+{
+ if (!CHECK_FLAG_ATOMIC(job, ACTIVE)) {
+ return -1;
+ }
+ LOCK_JOB(job);
+ SET_FLAG(job, SUSPENDED);
+ UNSET_FLAG(job, ACTIVE);
+ UNLOCK_JOB(job);
+ return job->id;
+}
+int
+virJobObjResume(virJobObjPtr job)
+{
+ if (!CHECK_FLAG_ATOMIC(job, SUSPENDED)) {
+ return -1;
+ }
+ LOCK_JOB(job);
+ UNSET_FLAG(job, SUSPENDED);
+ SET_FLAG(job, ACTIVE);
+ UNLOCK_JOB(job);
+ return job->id;
+}
+int
+virJobObjResumeIfNotAborted(virJobObjPtr job)
+{
+ int retval;
+ LOCK_JOB(job);
+ if (!CHECK_FLAG(job, SUSPENDED)) {
+ retval = -1;
+ } else if (CHECK_FLAG(job, ABORTED)) {
+ retval = 0;
+ } else {
+ UNSET_FLAG(job, SUSPENDED);
+ SET_FLAG(job, ACTIVE);
+ retval = job->id;
+ }
+ UNLOCK_JOB(job);
+ return retval;
+}
+
+void
+virJobObjSetMaxWaiters(virJobObjPtr job, int max)
+{
+ virAtomicIntSet(&job->maxJobsWaiting, max);
+}
+
+bool
+virJobObjCheckAbort(virJobObjPtr job)
+{
+ return CHECK_FLAG_ATOMIC(job, ABORTED);
+}
+bool
+virJobObjActive(virJobObjPtr job)
+{
+ return CHECK_FLAG_ATOMIC(job, ACTIVE);
+}
+
+/* since we need to be able to return a negitive answer on error
+ the time difference we can return is only half of the maximum
+ possible time. This shouldn't pose any real issue.
+*/
+long long
+virJobObjCheckTime(virJobObjPtr job)
+{
+ if (!CHECK_FLAG(job, ACTIVE)) {
+ return 0;
+ }
+ unsigned long long now;
+ if (virTimeMillisNow(&now) < 0) {
+ return -1;
+ }
+ return now - job->start;
+}
+
+void
+virJobObjSignal(virJobObjPtr job, bool all)
+{
+ if (all) {
+ virCondBroadcast(&job->cond);
+ } else {
+ virCondSignal(&job->cond);
+ }
+}
diff --git a/src/util/virjobcontrol.h b/src/util/virjobcontrol.h
new file mode 100644
index 0000000..235cc06
--- /dev/null
+++ b/src/util/virjobcontrol.h
@@ -0,0 +1,342 @@
+/*
+ * virjobcontrol.h Core implementation of job control
+ *
+ * Copyright (C) 2014 Tucker DiNapoli
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Author: Tucker DiNapoli
+ */
+
+
+#ifndef __JOB_CONTROL_H__
+#define __JOB_CONTROL_H__
+
+#include <stdlib.h>
+
+#include "viratomic.h"
+#include "virthread.h"
+#include "virutil.h"
+
+
+/* The general type of the job, specifically if it will modify
+ the object it is acting on.*/
+typedef enum {
+ VIR_JOB_NONE = 0, /*no job running*/
+ VIR_JOB_QUERY = 0x1, /* job will not change object state (read-only)*/
+ VIR_JOB_MODIFY = 0x2, /* job may change object state (read-write)*/
+ VIR_JOB_DESTROY = 0x4, /* job will destroy the object it is acting on */
+ VIR_JOB_LAST
+} virJobType;
+VIR_ENUM_DECL(virJob);
+
+
+/* General metadata/flags for jobs
+
+ more specific flags can be added for speciic drivers,
+ to do this the first value of the enum for the extra flags
+ should be set to (VIR_JOB_FLAG_LAST << 1) and each additional
+ flag should be set to the last flag shifted to the left by 1.
+
+*/
+typedef enum {
+ VIR_JOB_FLAG_NONE = 0x000, /* No job is active */
+ VIR_JOB_FLAG_ACTIVE = 0x001, /* Job is active */
+ /* These next flags are used to indicate how progress should be measured
+ and are currently unused
+ */
+ VIR_JOB_FLAG_TIME_BOUND = 0x002, /* Job is bound by a specific ammount of time */
+ VIR_JOB_FLAG_MEM_BOUND = 0x004, /* Job is bound by a specific ammount of memory
*/
+ VIR_JOB_FLAG_FILE_BOUND = 0x008, /* Job is bound by a specific number of files */
+ VIR_JOB_FLAG_DATA_BOUND = 0x010, /* Job is bound by a specific ammount of data */
+ VIR_JOB_FLAG_UNBOUNDED = 0x020, /* Job has no specific bound */
+ /* These flags indicate a job is no longer active but still exists
+ */
+ VIR_JOB_FLAG_SUSPENDED = 0x040, /* Job is Suspended and can be resumed */
+ VIR_JOB_FLAG_COMPLETED = 0x080, /* Job has finished, but isn't cleaned up */
+ VIR_JOB_FLAG_FAILED = 0x100, /* Job hit error, but isn't cleaned up */
+ VIR_JOB_FLAG_ABORTED = 0x200, /* Job was aborted, but isn't cleaned up */
+ VIR_JOB_FLAG_LAST = 0x400
+} virJobFlag;
+
+
+typedef int virJobID; /*signed so negitive values can be returned on error*/
+typedef struct _virJobObj virJobObj;
+typedef virJobObj *virJobObjPtr;
+typedef struct _virJobInfo virJobInfo;
+typedef virJobInfo *virJobInfoPtr;
+
+struct _virJobObj {
+ /* this should only be locked when changing the job object, not while running a job
+ it's use is mostly optional, but it is needed for waiting on a job*/
+ virMutex lock;/*should have a compile time option to enable/disable locking */
+ virCond cond; /* Use to coordinate jobs (is this necessary?) */
+ virJobType type; /* The type of the job */
+ unsigned long long owner; /* Thread id which set current job */
+ unsigned long long start; /* when the job started*/
+ int jobsWaiting; /* number jobs waiting on cond */
+ int maxJobsWaiting; /* max number of jobs that can wait on cond */
+ /* information about the job, this is a private struct, access to information
+ about the job should be obtained by functions */
+ virJobInfoPtr info;
+ virJobID id; /* the job id, constant, and unique */
+ virJobFlag flags; /* The current state of the job */
+ void *privateData;
+};
+
+/* Getting information on a currently running job is currently unimplmeneted and
+ any suggestions on what should go into the api for querying/setting job
+ progress information would be appreciated
+*/
+typedef enum {
+ VIR_JOB_INFO_NONE = 0,
+ VIR_JOB_INFO_TIME,
+ VIR_JOB_INFO_MEM,
+ VIR_JOB_INFO_FILE,
+ VIR_JOB_INFO_DATA,
+ VIR_JOB_INFO_LAST
+} virJobInfoType;
+
+struct _virJobInfo {
+ /* these fields are for compatability with existing job
+ information structs.
+ */
+ virDomainBlockJobInfoPtr blockJobInfoPtr;
+ virDomainJobInfoPtr domainJobInfoPtr;
+
+ unsigned long long progressTotal;
+ unsigned long long dataTotal;
+ unsigned long long memTotal;
+ unsigned long long filesTotal;
+ unsigned long long timeTotal;
+
+ unsigned long long progressProcessed;
+ unsigned long long timeProcessed;
+ unsigned long long memProcessed;
+ unsigned long long filesProcessed;
+ unsigned long long dataProcessed;
+
+ unsigned long long progressRemaining;
+ unsigned long long dataRemaining;
+ unsigned long long memRemaining;
+ unsigned long long filesRemaining;
+ unsigned long long timeRemaining;
+};
+
+/**
+ * virJobObjInit:
+ * @job: Pointer to the job object to initialize
+ *
+ * Initialize the job object given, should be called before any other
+ * job function.
+ *
+ * Like all job functions it returns the job id on success and -1 on error
+ */
+int virJobObjInit(virJobObjPtr job);
+/**
+ * virJobObjFree:
+ * @job: Pointer to the job object to free
+ *
+ * Cleanup/free all resources/memory assoicated with job
+ */
+void virJobObjFree(virJobObjPtr job);
+/**
+ * virJobObjCleanup:
+ * @job: Pointer to the job object to cleanup
+ *
+ * Cleanup all resources assoicated with job, and zero out the
+ * corrsponding memory, but do not free it.
+ */
+
+void virJobObjCleanup(virJobObjPtr job);
+/**
+ * virJobObjBegin:
+ * @job: The job to begin
+ * @type: The type of job that is being started
+ *
+ * Marks job as active, sets the calling thread as the owner of the job,
+ * sets the job's start time to the current time and sets it's type to type.
+ *
+ * End job should be called after this, once the job is done
+ *
+ * Returns the job id of job on success, 0 if job is already active
+ * and -1 on error.
+ */
+int virJobObjBegin(virJobObjPtr job,
+ virJobType type);
+/**
+ * virJobObjEnd:
+ *
+ * @job: The job to end
+ *
+ * Ends job, calls virJobObjReset and signals all threads waiting on job.
+ *
+ * Ending a job does not invalidate the job object, and a new job can be
+ * started using the same job object, call virJobObjFree or virJobObjCleanup
+ * in order to destroy a job object.
+ *
+ * returns the job's id on success and -1 if the job was not active.
+ */
+int virJobObjEnd(virJobObjPtr job);
+/**
+ * virJobObjReset:
+ *
+ * @job: The job to reset
+ *
+ * Clears all fields of job related to running a job. This does not
+ * clear the job id, any configurable parameters (currently just the
+ * maximum number of waiting threads), or the mutex/condition variable
+ * assoicated with the job. This is called internally by virJobObjEnd
+ * and there should be few reasons to call this explicitly.
+ */
+int virJobObjReset(virJobObjPtr job);
+/**
+ * virJobObjAbort:
+ * @job: The job to abort
+ *
+ * Marks job as aborted, since jobs are asyncronous this doesn't actually
+ * stop the job. The abort status of a job can be checked by
+ * virJobObjCheckAbort. A suspended job can be aborted.
+ *
+ * returns the job id on success and -1 if
+ * job is not currently running/suspended.
+ */
+int virJobObjAbort(virJobObjPtr job);
+/**
+ * virJobObjAbort:
+ * @job: The job to abort/signal waiters
+ *
+ * Behaves identically to virJobObjAbort except all threads waiting
+ * on job are signaled after the abort status is set.
+ */
+int virJobObjAbortAndSignal(virJobObjPtr job);
+/**
+ * virJobObjSuspend:
+ * @job: The job to suspend
+ *
+ * Marks job as suspended, it is up to the caller of the function
+ * to actually save any state assoicated with the job
+ *
+ * This function returns the job's id on success and -1 if the job
+ * was not active.
+ */
+int virJobObjSuspend(virJobObjPtr job);
+/**
+ * virJobObjResume:
+ * @job The job to resume
+ *
+ * Resume job, as with virJobObjSuspend it is up to the caller to
+ * insure that the work being done by job is actually restarted.
+ *
+ * Since a job can be aborted while it is suspended the caller should
+ * check to see if job has been aborted, a convenience function
+ * virJobObjResumeIfNotAborted is provided.
+ *
+ * returns the job id if job was resumed and -1 if the job was not suspended.
+ */
+int virJobObjResume(virJobObjPtr job);
+
+/**
+ * virJobObjResumeIfNotAborted:
+ * @job The job to resume
+ *
+ * Behaves the same as virJobObjResume except it returns 0 and does not
+ * resume the job if the job was aborted while suspended.
+ */
+int virJobObjResumeIfNotAborted(virJobObjPtr job);
+
+/* returns -3 if max waiters is exceeded, -2 on timeout, -1 on other error*/
+/**
+ * virJobObjWait:
+ * @job: The job to wait on
+ * @lock: The lock to use in the call to virCondWait
+ * @limit: If not 0 the maximum ammount of time to wait (in milliseconds)
+ *
+ * This function waits for job to be completed, or to otherwise signal on it's
+ * condition variable.
+ *
+ * If lock is NULL the internal job lock will be used, otherwise lock should
+ * be held by the calling thread.
+ * (NOTE: I'm not sure if it's a good idea or not to use the internal lock)
+ *
+ * If limit is > 0 virCondWaitUntil is called instead of virCondWait with limit
+ * being used as the time parameter.
+ *
+ * If job is not currently active return successfully.
+ *
+ * Like all job functions returns the job's id on success.
+ *
+ * On Failure returns a negitive number to indicate the cause of failure
+ * -3 indicates the maximum number of threads were alread waiting on job
+ * -2 indicates that virCondWaitUntil timed out
+ * -1 indicates some other error in virCondWait/virCondWaitUntil
+ */
+int virJobObjWait(virJobObjPtr job,
+ virMutexPtr lock,
+ unsigned long long limit);
+/* Should I provide a function to wait for a suspended job to resume? */
+
+/**
+ * virJobObjSignal:
+ * @job: The job to signal from
+ * @all: If true signal all waiting threads, otherwise just signal one
+ *
+ * Signal a thread/threads waiting on job. In most cases waiting threads
+ * are signaled when needed internally, but this is provided if for
+ * some reason waiting threads need to be manually signaled.
+ */
+
+void virJobObjSignal(virJobObjPtr job, bool all);
+
+/* accessor functions*/
+/**
+ * virJobObjCheckAbort:
+ * @job: The job whoes status should be checked
+ *
+ * Returns true if job has been aborted, false otherwise
+ */
+bool virJobObjCheckAbort(virJobObjPtr job);
+/**
+ * virJobObjCheckTime:
+ * @job: The job whoes time should be checked
+ *
+ * Returns the time in milliseconds that job has been running for.
+ * returns 0 if job is not active and -1 if there is an error in
+ * getting the current time.
+ */
+long long virJobObjCheckTime(virJobObjPtr job);
+/**
+ * virJobObjActive:
+ * @job: The job whoes status should be checked
+ *
+ * Returns true if job is currently active, false otherwise
+ */
+bool virJobObjActive(virJobObjPtr job);
+/**
+ * virJobObjSetMaxWaiters:
+ * @job: The job to modify
+ * @max: The maximum number of threads to allow to wait on job at once
+ *
+ * Sets the maximum number of threads that can simultaneously wait on job.
+ * By default there is essentially no limit (in reality the limit is the
+ * maximum value that can be held by an int)
+ */
+void virJobObjSetMaxWaiters(virJobObjPtr job, int max);
+
+/* These convert between a job object and a job id.
+*/
+virJobObjPtr virJobFromID(virJobID id);
+virJobID virJobIDFromJob(virJobObjPtr job);
+#endif
--
2.0.0