Commit 13400c95 authored by David Anderson's avatar David Anderson

Changes for multithread app support:

- update_versions: use __ (not :) as separator for plan class
- client: add plan_class to APP_VERSION;
    an app version is now identified by platform/version/plan_class
- client CPU scheduler: don't assume apps use 1 CPU
- client: add avg_ncpus, max_cpus, flops, cmdline to RESULT
- scheduler: implement app planning scheme

Other changes:

- client: if symlink() fails, make a XML soft link instead
    (for Unix running off a FAT32 FS)
- client: don't accept nonpositive resource share from AMS
- daemons and DB: check for error returns from enumerations,
    and exit if so.  Thus, if the MySQL server goes down,
    all the daemons will soon exit.
    The cron script will restart them every 5 min,
    so when the DB server comes back up so will the project.
- web: show empty max CPU % as ---
- API: get rid of all_threads_cpu_time option (always the case now)


svn path=/trunk/boinc/; revision=14966
parent 0a180fdf
......@@ -202,18 +202,13 @@ static int setup_shared_mem() {
return 0;
}
// Return CPU time of worker thread (and optionally others)
// This may be called from any thread
// Return CPU time of process.
//
double boinc_worker_thread_cpu_time() {
double cpu;
#ifdef _WIN32
int retval;
if (options.all_threads_cpu_time) {
retval = boinc_process_cpu_time(cpu);
} else {
retval = boinc_thread_cpu_time(worker_thread_handle, cpu);
}
retval = boinc_process_cpu_time(cpu);
if (retval) {
cpu = nrunning_ticks * TIMER_PERIOD; // for Win9x
}
......
......@@ -50,9 +50,6 @@ typedef struct BOINC_OPTIONS {
// if heartbeat fail, or get process control msg, take
// direction action (exit, suspend, resume).
// Otherwise just set flag in BOINC status
int all_threads_cpu_time;
// count the CPU time of all threads
// (for apps that have multiple worker threads)
int worker_thread_stack_size;
// if nonzero, the worker thread stack size limit
int backwards_compatible_graphics;
......@@ -148,7 +145,6 @@ inline void boinc_options_defaults(BOINC_OPTIONS& b) {
b.handle_process_control = 1;
b.send_status_msgs = 1;
b.direct_process_action = 1;
b.all_threads_cpu_time = 0;
b.worker_thread_stack_size = 0;
b.backwards_compatible_graphics = 1;
}
......
......@@ -2556,13 +2556,15 @@ David Mar 19 2008
sched_send.C
Charlie Mar 20 2008
- Client: fix a compiler warning which indicated a real logic error (variable
used uninitialized).
- Mac: More work on backtrace code: run atos utility via a bidirectional pipe
instead of backtrace_symbols_fd() API to get better symbols in backtrace.
Set visibility of all variables in Client and Manager back to hidden (as
before) to reduce size of executables, since atos utility can use hidden
symbols. This also involves return to previous wxWidgets build script.
- Client: fix a compiler warning which indicated a real logic error
(variable used uninitialized).
- Mac: More work on backtrace code: run atos utility via a bidirectional
pipe instead of backtrace_symbols_fd() API to get better symbols
in backtrace.
Set visibility of all variables in Client and Manager back to hidden
(as before) to reduce size of executables,
since atos utility can use hidden symbols.
This also involves return to previous wxWidgets build script.
client/
app_start.C
......@@ -2641,3 +2643,65 @@ Rom Mar 27 2008
boinccas.dll
boinccas95.dll
David Mar 27 2008
Changes for multithread app support:
- update_versions: use __ (not :) as separator for plan class
- client: add plan_class to APP_VERSION;
an app version is now identified by platform/version/plan_class
- client CPU scheduler: don't assume apps use 1 CPU
- client: add avg_ncpus, max_cpus, flops, cmdline to RESULT
- scheduler: implement app planning scheme
Other changes:
- client: if symlink() fails, make a XML soft link instead
(for Unix running off a FAT32 FS)
- client: don't accept nonpositive resource share from AMS
- daemons and DB: check for error returns from enumerations,
and exit if so. Thus, if the MySQL server goes down,
all the daemons will soon exit.
The cron script will restart them every 5 min,
so when the DB server comes back up so will the project.
- web: show empty max CPU % as ---
- API: get rid of all_threads_cpu_time option (always the case now)
api/
boinc_api.C,h
client/
acct_mgr.C
app.C,h
app_start.C
client_state.C,h
client_types.C,h
cpu_sched.C
cs_scheduler.C
cs_statefile.C
configure.ac
db/
boinc_db.C,h
html/inc/
countries.inc
prefs.inc
lib/
app_ipc.C,h
error_numbers.h
shmem.C
sched/
assimilator.C
db_purge.C
feeder.C
file_deleter.C
make_work.C
message_handler.C
sched_array.C
sched_assign.C
sched_locality.C
sched_plan.C,h
sched_resend.C
sched_send.C,h
server_types.C,h
validator.C
tools/
update_versions
version.h
......@@ -234,7 +234,13 @@ int AM_ACCOUNT::parse(XML_PARSER& xp) {
continue;
}
if (xp.parse_double(tag, "resource_share", dtemp)) {
resource_share.set(dtemp);
if (dtemp > 0) {
resource_share.set(dtemp);
} else {
msg_printf(NULL, MSG_INFO,
"Resource share out of range: %f", dtemp
);
}
continue;
}
if (log_flags.unparsed_xml) {
......
......@@ -112,7 +112,6 @@ ACTIVE_TASK::ACTIVE_TASK() {
too_large = false;
needs_shmem = false;
want_network = 0;
nthreads = 1;
memset(&procinfo, 0, sizeof(procinfo));
#ifdef _WIN32
pid_handle = 0;
......@@ -554,7 +553,8 @@ int ACTIVE_TASK::parse(MIOFILE& fin) {
wup = result->wup;
app_version = gstate.lookup_app_version(
result->app, result->platform, result->version_num
result->app, result->platform, result->version_num,
result->plan_class
);
if (!app_version) {
msg_printf(
......
......@@ -109,8 +109,6 @@ public:
double abort_time;
// when we sent an abort message to this app
// kill it 5 seconds later if it doesn't exit
int nthreads;
// current # of threads in app (assumed to be 1 by default)
APP_CLIENT_SHM app_client_shm; // core/app shared mem
MSG_QUEUE graphics_request_queue;
MSG_QUEUE process_control_queue;
......
......@@ -211,6 +211,19 @@ int ACTIVE_TASK::write_app_init_file() {
return retval;
}
static int make_soft_link(PROJECT* project, char* link_path, char* rel_file_path) {
FILE *fp = boinc_fopen(link_path, "w");
if (!fp) {
msg_printf(project, MSG_INTERNAL_ERROR,
"Can't create link file %s", link_path
);
return ERR_FOPEN;
}
fprintf(fp, "<soft_link>%s</soft_link>\n", rel_file_path);
fclose(fp);
return 0;
}
// set up a file reference, given a slot dir and project dir.
// This means:
// 1) copy the file to slot dir, if reference is by copy
......@@ -251,23 +264,17 @@ static int setup_file(
}
#ifdef _WIN32
FILE *fp = boinc_fopen(link_path, "w");
if (!fp) {
msg_printf(project, MSG_INTERNAL_ERROR,
"Can't open link file %s", link_path
);
return ERR_FOPEN;
}
fprintf(fp, "<soft_link>%s</soft_link>\n", rel_file_path);
fclose(fp);
retval = make_soft_link(project, link_path, rel_file_path);
if (retval) return retval;
#else
retval = symlink(rel_file_path, link_path);
if (retval) {
msg_printf(project, MSG_INTERNAL_ERROR,
"Can't symlink %s to %s: %d", rel_file_path, link_path, retval
);
perror("symlink");
return ERR_SYMLINK;
// A Unix system can't make symlinks if the filesystem if FAT32
// (e.g. external USB disk).
// Try making a soft link instead.
//
retval = make_soft_link(project, link_path, rel_file_path);
if (retval) return retval;
}
#endif
#ifdef SANDBOX
......@@ -615,6 +622,10 @@ int ACTIVE_TASK::start(bool first_time) {
argv[0] = exec_name;
char cmdline[8192];
strcpy(cmdline, wup->command_line.c_str());
if (strlen(result->cmdline)) {
strcat(cmdline, " ");
strcat(cmdline, result->cmdline);
}
parse_command_line(cmdline, argv+1);
if (log_flags.task_debug) {
debug_print_argv(argv);
......@@ -749,6 +760,10 @@ int ACTIVE_TASK::start(bool first_time) {
#endif
char cmdline[8192];
strcpy(cmdline, wup->command_line.c_str());
if (strlen(result->cmdline)) {
strcat(cmdline, " ");
strcat(cmdline, result->cmdline);
}
sprintf(buf, "../../%s", exec_path );
if (g_use_sandbox) {
char switcher_path[100];
......
......@@ -665,7 +665,7 @@ WORKUNIT* CLIENT_STATE::lookup_workunit(PROJECT* p, const char* name) {
}
APP_VERSION* CLIENT_STATE::lookup_app_version(
APP* app, char* platform, int version_num
APP* app, char* platform, int version_num, char* plan_class
) {
for (unsigned int i=0; i<app_versions.size(); i++) {
APP_VERSION* avp = app_versions[i];
......@@ -675,6 +675,7 @@ APP_VERSION* CLIENT_STATE::lookup_app_version(
return avp;
}
if (strcmp(avp->platform, platform)) continue;
if (strcmp(avp->plan_class, plan_class)) continue;
return avp;
}
return 0;
......@@ -722,10 +723,10 @@ int CLIENT_STATE::link_app_version(PROJECT* p, APP_VERSION* avp) {
}
avp->app = app;
if (lookup_app_version(app, avp->platform, avp->version_num)) {
if (lookup_app_version(app, avp->platform, avp->version_num, avp->plan_class)) {
msg_printf(p, MSG_INTERNAL_ERROR,
"State file error: duplicate app version: %s %s %d",
avp->app_name, avp->platform, avp->version_num
"State file error: duplicate app version: %s %s %d %s",
avp->app_name, avp->platform, avp->version_num, avp->plan_class
);
return ERR_NOT_UNIQUE;
}
......
......@@ -222,7 +222,9 @@ public:
FILE_INFO* lookup_file_info(PROJECT*, const char* name);
RESULT* lookup_result(PROJECT*, const char*);
WORKUNIT* lookup_workunit(PROJECT*, const char*);
APP_VERSION* lookup_app_version(APP*, char* platform, int ver);
APP_VERSION* lookup_app_version(
APP*, char* platform, int ver, char* plan_class
);
int detach_project(PROJECT*);
int report_result_error(RESULT&, const char *format, ...);
int reset_project(PROJECT*, bool detaching);
......@@ -262,7 +264,7 @@ private:
bool enforce_schedule();
bool no_work_for_a_cpu();
void rr_simulation();
void make_running_task_heap(vector<ACTIVE_TASK*>&);
void make_running_task_heap(vector<ACTIVE_TASK*>&, double&);
void print_deadline_misses();
public:
double retry_shmem_time;
......
......@@ -1079,6 +1079,7 @@ int APP_VERSION::parse(MIOFILE& in) {
strcpy(api_version, "");
version_num = 0;
strcpy(platform, "");
strcpy(plan_class, "");
app = NULL;
project = NULL;
while (in.fgets(buf, 256)) {
......@@ -1092,6 +1093,7 @@ int APP_VERSION::parse(MIOFILE& in) {
if (parse_int(buf, "<version_num>", version_num)) continue;
if (parse_str(buf, "<api_version>", api_version, sizeof(api_version))) continue;
if (parse_str(buf, "<platform>", platform, sizeof(platform))) continue;
if (parse_str(buf, "<plan_class>", plan_class, sizeof(plan_class))) continue;
if (log_flags.unparsed_xml) {
msg_printf(0, MSG_INFO,
"[unparsed_xml] APP_VERSION::parse(): unrecognized: %s\n", buf
......@@ -1114,6 +1116,9 @@ int APP_VERSION::write(MIOFILE& out) {
version_num,
platform
);
if (strlen(plan_class)) {
out.printf(" <plan_class>%s</plan_class>\n", plan_class);
}
if (strlen(api_version)) {
out.printf(" <api_version>%s</api_version>\n", api_version);
}
......@@ -1404,6 +1409,11 @@ void RESULT::clear() {
project = NULL;
version_num = 0;
strcpy(platform, "");
strcpy(plan_class, "");
strcpy(cmdline, "");
avg_ncpus = 1;
max_ncpus = 1;
flops = gstate.host_info.p_fpops;
}
// parse a <result> element from scheduling server.
......@@ -1419,7 +1429,12 @@ int RESULT::parse_server(MIOFILE& in) {
if (parse_str(buf, "<wu_name>", wu_name, sizeof(wu_name))) continue;
if (parse_double(buf, "<report_deadline>", report_deadline)) continue;
if (parse_str(buf, "<platform>", platform, sizeof(platform))) continue;
if (parse_str(buf, "<plan_class>", plan_class, sizeof(plan_class))) continue;
if (parse_int(buf, "<version_num>", version_num)) continue;
if (parse_double(buf, "<avg_ncpus>", avg_ncpus)) continue;
if (parse_double(buf, "<max_ncpus>", max_ncpus)) continue;
if (parse_double(buf, "<flops>", flops)) continue;
if (parse_str(buf, "<cmdline>", cmdline, sizeof(cmdline))) continue;
if (match_tag(buf, "<file_ref>")) {
file_ref.parse(in);
output_files.push_back(file_ref);
......@@ -1481,7 +1496,12 @@ int RESULT::parse_state(MIOFILE& in) {
if (parse_double(buf, "<intops_per_cpu_sec>", intops_per_cpu_sec)) continue;
if (parse_double(buf, "<intops_cumulative>", intops_cumulative)) continue;
if (parse_str(buf, "<platform>", platform, sizeof(platform))) continue;
if (parse_str(buf, "<plan_class>", plan_class, sizeof(plan_class))) continue;
if (parse_int(buf, "<version_num>", version_num)) continue;
if (parse_double(buf, "<avg_ncpus>", avg_ncpus)) continue;
if (parse_double(buf, "<max_ncpus>", max_ncpus)) continue;
if (parse_double(buf, "<flops>", flops)) continue;
if (parse_str(buf, "<cmdline>", cmdline, sizeof(cmdline))) continue;
if (log_flags.unparsed_xml) {
msg_printf(0, MSG_INFO,
"[unparsed_xml] RESULT::parse(): unrecognized: %s\n", buf
......@@ -1503,14 +1523,26 @@ int RESULT::write(MIOFILE& out, bool to_server) {
" <exit_status>%d</exit_status>\n"
" <state>%d</state>\n"
" <platform>%s</platform>\n"
" <version_num>%d</version_num>\n",
" <version_num>%d</version_num>\n"
" <avg_ncpus>%f</avg_ncpus>\n"
" <max_ncpus>%f</max_ncpus>\n"
" <flops>%f</flops>\n",
name,
final_cpu_time,
exit_status,
state(),
platform,
version_num
version_num,
avg_ncpus,
max_ncpus,
flops
);
if (strlen(plan_class)) {
out.printf(" <plan_class>%s</plan_class>\n", plan_class);
}
if (strlen(cmdline)) {
out.printf(" <cmdline>%s</cmdline>\n", cmdline);
}
if (fpops_per_cpu_sec) {
out.printf(" <fpops_per_cpu_sec>%f</fpops_per_cpu_sec>\n", fpops_per_cpu_sec);
}
......
......@@ -34,6 +34,7 @@
#include "md5_file.h"
#include "hostinfo.h"
#include "coproc.h"
#include "miofile.h"
#define P_LOW 1
......@@ -404,6 +405,7 @@ struct APP_VERSION {
char app_name[256];
int version_num;
char platform[256];
char plan_class[64];
char api_version[16];
APP* app;
PROJECT* project;
......@@ -452,8 +454,14 @@ struct RESULT {
char wu_name[256];
double report_deadline;
int version_num; // identifies the app used
char plan_class[64];
char platform[256];
char cmdline[256]; // additional cmdline args
APP_VERSION* avp;
double avg_ncpus;
double max_ncpus;
double flops;
COPROCS coprocs;
std::vector<FILE_REF> output_files;
bool ready_to_report;
// we're ready to report this result to the server;
......
......@@ -513,9 +513,8 @@ void CLIENT_STATE::schedule_cpus() {
#ifdef SIM
if (!cpu_sched_rr_only) {
#endif
int ncpus_used = 0;
//while (ncpus_used < ncpus) {
while ((int)ordered_scheduled_results.size() < ncpus) {
double ncpus_used = 0;
while (ncpus_used < ncpus) {
rp = earliest_deadline_result();
if (!rp) break;
rp->already_selected = true;
......@@ -545,12 +544,8 @@ void CLIENT_STATE::schedule_cpus() {
atp->needs_shmem = false;
}
ram_left -= atp->procinfo.working_set_size_smoothed;
ncpus_used += atp->nthreads;
} else {
// if we haven't run the app yet, assume it has one thread
//
ncpus_used++;
}
ncpus_used += rp->avg_ncpus;
rp->project->anticipated_debt -= (rp->project->resource_share / rrs) * expected_pay_off;
rp->project->deadlines_missed--;
......@@ -569,7 +564,7 @@ void CLIENT_STATE::schedule_cpus() {
// Next, choose results from projects with large debt
//
while ((int)ordered_scheduled_results.size() < ncpus) {
while (ncpus_used < ncpus) {
assign_results_to_projects();
rp = largest_debt_project_best_result();
if (!rp) break;
......@@ -599,6 +594,7 @@ void CLIENT_STATE::schedule_cpus() {
}
ram_left -= atp->procinfo.working_set_size_smoothed;
}
ncpus_used += rp->avg_ncpus;
double xx = (rp->project->resource_share / rrs) * expected_pay_off;
rp->project->anticipated_debt -= xx;
if (log_flags.cpu_sched_debug) {
......@@ -614,17 +610,19 @@ void CLIENT_STATE::schedule_cpus() {
// make a list of preemptable tasks, ordered by their preemptability.
//
void CLIENT_STATE::make_running_task_heap(
vector<ACTIVE_TASK*> &running_tasks
vector<ACTIVE_TASK*> &running_tasks, double& ncpus_used
) {
unsigned int i;
ACTIVE_TASK* atp;
ncpus_used = 0;
for (i=0; i<active_tasks.active_tasks.size(); i++) {
atp = active_tasks.active_tasks[i];
if (atp->result->project->non_cpu_intensive) continue;
if (!atp->result->runnable()) continue;
if (atp->scheduler_state != CPU_SCHED_SCHEDULED) continue;
running_tasks.push_back(atp);
ncpus_used += atp->result->avg_ncpus;
}
std::make_heap(
......@@ -655,6 +653,7 @@ bool CLIENT_STATE::enforce_schedule() {
vector<ACTIVE_TASK*> running_tasks;
static double last_time = 0;
int retval;
double ncpus_used;
// Do this when requested, and once a minute as a safety net
//
......@@ -694,13 +693,15 @@ bool CLIENT_STATE::enforce_schedule() {
// make heap of currently running tasks, ordered by preemptibility
//
make_running_task_heap(running_tasks);
make_running_task_heap(running_tasks, ncpus_used);
// if there are more running tasks than ncpus,
// then mark the extras for preemption
//
while (running_tasks.size() > (unsigned int)ncpus) {
running_tasks[0]->next_scheduler_state = CPU_SCHED_PREEMPTED;
while (ncpus_used > ncpus) {
atp = running_tasks[0];
atp->next_scheduler_state = CPU_SCHED_PREEMPTED;
ncpus_used -= atp->result->avg_ncpus;
std::pop_heap(
running_tasks.begin(),
running_tasks.end(),
......@@ -718,11 +719,6 @@ bool CLIENT_STATE::enforce_schedule() {
);
}
// keep track of how many tasks we plan on running
// (i.e. have next_scheduler_state = SCHEDULED)
//
int nrunning = (int)running_tasks.size();
// Loop through the scheduled results
//
for (i=0; i<ordered_scheduled_results.size(); i++) {
......@@ -765,7 +761,7 @@ bool CLIENT_STATE::enforce_schedule() {
if (atp->procinfo.working_set_size_smoothed > ram_left) {
atp->next_scheduler_state = CPU_SCHED_PREEMPTED;
atp->too_large = true;
nrunning--;
ncpus_used -= atp->result->avg_ncpus;
if (log_flags.mem_usage_debug) {
msg_printf(rp->project, MSG_INFO,
"[mem_usage_debug] enforce: result %s can't continue, too big %.2fMB > %.2fMB",
......@@ -801,7 +797,7 @@ bool CLIENT_STATE::enforce_schedule() {
// Preempt something if needed (and possible).
//
bool run_task = false;
bool need_to_preempt = (nrunning==ncpus) && running_tasks.size();
bool need_to_preempt = (ncpus_used >= ncpus) && running_tasks.size();
// the 2nd half of the above is redundant
if (need_to_preempt) {
// examine the most preemptable task.
......@@ -821,7 +817,7 @@ bool CLIENT_STATE::enforce_schedule() {
rp->project->deadlines_missed--;
}
atp->next_scheduler_state = CPU_SCHED_PREEMPTED;
nrunning--;
ncpus_used -= atp->result->avg_ncpus;
std::pop_heap(
running_tasks.begin(),
running_tasks.end(),
......@@ -849,14 +845,14 @@ bool CLIENT_STATE::enforce_schedule() {
if (run_task) {
atp = get_task(rp);
atp->next_scheduler_state = CPU_SCHED_SCHEDULED;
nrunning++;
ncpus_used += rp->avg_ncpus;
ram_left -= atp->procinfo.working_set_size_smoothed;
}
}
if (log_flags.cpu_sched_debug) {
msg_printf(0, MSG_INFO,
"[cpu_sched_debug] finished preempt loop, nrunning %d",
nrunning
"[cpu_sched_debug] finished preempt loop, ncpus_used %f",
ncpus_used
);
}
......@@ -879,16 +875,13 @@ bool CLIENT_STATE::enforce_schedule() {
}
}
if (log_flags.cpu_sched_debug && nrunning < ncpus) {
msg_printf(0, MSG_INFO, "[cpu_sched_debug] Some CPUs idle (%d<%d)",
nrunning, ncpus
);
request_work_fetch("CPUs idle");
}
if (log_flags.cpu_sched_debug && nrunning > ncpus) {
msg_printf(0, MSG_INFO, "[cpu_sched_debug] Too many tasks started (%d>%d)",
nrunning, ncpus
if (log_flags.cpu_sched_debug && ncpus_used < ncpus) {
msg_printf(0, MSG_INFO, "[cpu_sched_debug] using %f out of %d CPUs",
ncpus_used, ncpus
);
if (ncpus_used < ncpus) {
request_work_fetch("CPUs idle");
}
}
// schedule new non CPU intensive tasks
......
......@@ -660,7 +660,9 @@ int CLIENT_STATE::handle_scheduler_reply(
}
}
APP* app = lookup_app(project, avpp.app_name);
APP_VERSION* avp = lookup_app_version(app, avpp.platform, avpp.version_num);
APP_VERSION* avp = lookup_app_version(
app, avpp.platform, avpp.version_num, avpp.plan_class
);
if (avp) {
// if we had download failures, clear them
//
......@@ -713,7 +715,9 @@ int CLIENT_STATE::handle_scheduler_reply(
strcpy(rp->platform, get_primary_platform());
rp->version_num = latest_version(rp->wup->app, rp->platform);
}
rp->avp = lookup_app_version(rp->wup->app, rp->platform, rp->version_num);
rp->avp = lookup_app_version(
rp->wup->app, rp->platform, rp->version_num, rp->plan_class
);
if (!rp->avp) {
msg_printf(project, MSG_INTERNAL_ERROR,
"No app version for result: %s %d",
......
......@@ -323,11 +323,13 @@ int CLIENT_STATE::parse_state_file() {
strcpy(rp->platform, get_primary_platform());
rp->version_num = latest_version(rp->wup->app, rp->platform);
}
rp->avp = lookup_app_version(rp->wup->app, rp->platform, rp->version_num);
rp->avp = lookup_app_version(
rp->wup->app, rp->platform, rp->version_num, rp->plan_class
);
if (!rp->avp) {
msg_printf(project, MSG_INTERNAL_ERROR,
"No app version for result: %s %d",
rp->platform, rp->version_num
"No app version for result: %s %d %s",
rp->platform, rp->version_num, rp->plan_class
);
delete rp;
continue;
......
......@@ -9,7 +9,7 @@ dnl not sure exactly what the minimum version is (but 2.13 wont work)
AC_PREREQ(2.57)
dnl Set the BOINC version here. You can also use the set-version script.
AC_INIT(BOINC, 6.1.10)
AC_INIT(BOINC, 6.1.11)
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
......
......@@ -1024,7 +1024,7 @@ int DB_TRANSITIONER_ITEM_SET::enumerate(
mysql_free_result(cursor.rp);
cursor.active = false;
retval = mysql_errno(db->mysql);