From b079d40cb96b390eeacc47950bfc571feb18a8e7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 16 Oct 2007 23:02:13 +0000 Subject: [PATCH] - API, Unix: call getrusage() from the timer thread, not the worker signal handler. There's no reason to call it from the signal handler - it returns the CPU for the entire process, not the calling thread. And it may be asynch-signal-handler-unsafe. - API: comment out checks for bad CPU times. I don't think this is needed now, and in some cases it's wrong (multi-threaded apps can accumulate CPU faster than real time) - API, Unix: in boinc_calling_thread_cpu_time(), don't retry getrusage(). - Bossa: switch to better class structure (suggested by Nicolas Alvarez). Haven't switched to mysqli yet, but will later. Also various other Bossa fixes svn path=/trunk/boinc/; revision=13855 --- api/boinc_api.C | 26 ++++++--- checkin_notes | 31 ++++++++++ db/bossa_constraints.sql | 8 ++- db/bossa_schema.sql | 2 +- html/inc/bossa_db.inc | 84 +++++++++++++++++++++------- html/ops/bossa_make_jobs_example.php | 16 ++++-- html/ops/bossa_setup_example.php | 11 ++-- html/user/bossa_example.php | 62 ++++++++++++++++++++ html/user/bossa_get_job.php | 12 ++-- lib/util.C | 22 ++------ 10 files changed, 208 insertions(+), 66 deletions(-) create mode 100644 html/user/bossa_example.php diff --git a/api/boinc_api.C b/api/boinc_api.C index d8317c4a86..95b6281d62 100644 --- a/api/boinc_api.C +++ b/api/boinc_api.C @@ -191,15 +191,10 @@ static int setup_shared_mem() { } // Return CPU time of worker thread (and optionally others) -// This may be called from other threads +// This may be called from any thread // double boinc_worker_thread_cpu_time() { - static double last_cpu=0; - // last value returned by this func - static time_t last_time=0; - // when it was returned - time_t now = time(0); - double cpu, time_diff = (double)(now - last_time); + double cpu; #ifdef _WIN32 int retval; if (options.all_threads_cpu_time) { @@ -216,6 +211,18 @@ double boinc_worker_thread_cpu_time() { cpu += (double)worker_thread_ru.ru_stime.tv_sec + (((double)worker_thread_ru.ru_stime.tv_usec)/1000000.0); #endif + +#if 0 + // The following paranoia is (I hope) not needed anymore. + // In any case, the check for CPU incrementing faster than real time + // is misguided - it assumes no multi-threading. + // + static double last_cpu=0; + // last value returned by this func + static time_t last_time=0; + // when it was returned + time_t now = time(0); + double time_diff = (double)(now - last_time); if (!finite(cpu)) { fprintf(stderr, "CPU time infinite or NaN\n"); last_time = now; @@ -228,11 +235,12 @@ double boinc_worker_thread_cpu_time() { return last_cpu; } if (cpu_diff>(time_diff + 1)) { -// fprintf(stderr, "CPU time incrementing faster than real time. Correcting.\n"); + fprintf(stderr, "CPU time incrementing faster than real time. Correcting.\n"); cpu = last_cpu + time_diff + 1; // allow catch-up } last_cpu = cpu; last_time = now; +#endif return cpu; } @@ -923,6 +931,7 @@ void* timer_thread(void*) { block_sigalrm(); while(1) { boinc_sleep(TIMER_PERIOD); + getrusage(RUSAGE_SELF, &worker_thread_ru); timer_handler(); } return 0; @@ -933,7 +942,6 @@ void* timer_thread(void*) { // It must call only signal-safe functions, and must not do FP math // void worker_signal_handler(int) { - getrusage(RUSAGE_SELF, &worker_thread_ru); if (options.direct_process_action) { while (boinc_status.suspended && !in_critical_section) { sleep(1); // don't use boinc_sleep() because it does FP math diff --git a/checkin_notes b/checkin_notes index 41f04d6769..b1a40f76c7 100644 --- a/checkin_notes +++ b/checkin_notes @@ -9556,3 +9556,34 @@ Rom 16 Oct 2007 boinc_tray.h (Added) boinc_tray.rc (Added) tray_win.cpp, .h (Added) + +David 16 Oct 2007 + - API, Unix: call getrusage() from the timer thread, + not the worker signal handler. + There's no reason to call it from the signal handler - + it returns the CPU for the entire process, not the calling thread. + And it may be asynch-signal-handler-unsafe. + - API: comment out checks for bad CPU times. + I don't think this is needed now, and in some cases it's wrong + (multi-threaded apps can accumulate CPU faster than real time) + - API, Unix: in boinc_calling_thread_cpu_time(), don't retry getrusage(). + - Bossa: switch to better class structure (suggested by Nicolas Alvarez). + Haven't switched to mysqli yet, but will later. + Also various other Bossa fixes + + api/ + boinc_api.C + db/ + bossa_constraints.sal + bossa_schema.sql + html/ + inc/ + bossa_db.inc + ops/ + bossa_make_jobs_example.php + bossa_setup_example.php + user/ + bossa_example.php (new) + bossa_get_job.php + lib/ + util.C diff --git a/db/bossa_constraints.sql b/db/bossa_constraints.sql index 1c05f25b31..94530740ce 100644 --- a/db/bossa_constraints.sql +++ b/db/bossa_constraints.sql @@ -2,5 +2,9 @@ alter table bossa_app add unique(name); alter table bossa_job - add unique(name) - add index bj_more_needed(more_needed); + add unique(name), + add index bj_more_needed(app, more_needed); + +alter table bossa_job_inst + add index bji_job(job_id), + add index bji_user(user_id); diff --git a/db/bossa_schema.sql b/db/bossa_schema.sql index da3dd8d99c..8c7faf42d1 100644 --- a/db/bossa_schema.sql +++ b/db/bossa_schema.sql @@ -5,7 +5,6 @@ create table bossa_app ( user_friendly_name varchar(255) not null, long_jobs tinyint not null, start_url varchar(255) not null, - finish_url varchar(255) not null, deprecated tinyint not null, beta tinyint not null, primary key(id) @@ -33,5 +32,6 @@ create table bossa_job_inst ( job_id integer not null, user_id integer not null, finish_time integer not null, + info varchar(255) not null, primary key(id) ); diff --git a/html/inc/bossa_db.inc b/html/inc/bossa_db.inc index e700aa7cab..a5e483c1ab 100644 --- a/html/inc/bossa_db.inc +++ b/html/inc/bossa_db.inc @@ -1,18 +1,25 @@ long_jobs) $app->long_jobs = 0; +function bossa_lookup($id, $table) { + $result = mysql_query("select * from $table where id='$id'"); + if (!$result) return null; + $obj = mysql_fetch_object($result); + mysql_free_result($result); + return $obj; +} + +class BossaApp { + function insert() { + if (!$this->long_jobs) $this->long_jobs = 0; $now = time(); - $query = "insert into bossa_app (create_time, name, user_friendly_name, long_jobs, start_url, finish_url) values ($now, '$app->name', '$app->user_friendly_name', $app->long_jobs, '$app->start_url', '$app->finish_url')"; - echo $query; + $query = "insert into bossa_app (create_time, name, user_friendly_name, long_jobs, start_url) values ($now, '$this->name', '$this->user_friendly_name', $this->long_jobs, '$this->start_url')"; $result = mysql_query($query); if (!$result) return false; - $app->id = mysql_insert_id(); + $this->id = mysql_insert_id(); return true; } - function app_lookup_name($name) { + static function lookup_name($name) { $result = mysql_query("select * from bossa_app where name='$name'"); if (!$result) return null; $app = mysql_fetch_object($result); @@ -20,42 +27,56 @@ class Bossa { return $app; } - function app_lookup_id($id) { - $result = mysql_query("select * from bossa_app where id='$id'"); - if (!$result) return null; - $app = mysql_fetch_object($result); - mysql_free_result($result); - return $app; + static function lookup_id($id) { + return bossa_lookup($id, 'bossa_app'); } +} - function insert_job(&$job) { +class BossaJob { + function insert() { $now = time(); - $query = "insert into bossa_job (create_time, name, app_id, info, batch, time_estimate, time_limit, more_needed, npending, nsuccess, nsuccess_needed) values ($now, '$job->name', $job->app_id, '$job->info', $job->batch, $job->time_estimate, $job->time_limit, 1, 0, 0, $job->nsuccess_needed)"; + $query = "insert into bossa_job (create_time, name, app_id, info, batch, time_estimate, time_limit, more_needed, npending, nsuccess, nsuccess_needed) values ($now, '$this->name', $this->app_id, '$this->info', $this->batch, $this->time_estimate, $this->time_limit, 1, 0, 0, $this->nsuccess_needed)"; $result = mysql_query($query); if (!$result) { echo "$query\n"; return false; } - $job->id = mysql_insert_id(); + $this->id = mysql_insert_id(); return true; } + function update($clause) { + return mysql_query("update bossa_job set $clause where id=$this->id"); + } + static function lookup_id($id) { + return bossa_lookup($id, 'bossa_job'); + } +} - function insert_job_inst(&$ji) { +class BossaJobInst { + function insert() { $now = time(); - $query = "insert into bossa_job_inst (create_time, job_id, user_id) values ($now, $ji->job_id, $ji->user_id)"; + $query = "insert into bossa_job_inst (create_time, job_id, user_id) values ($now, $this->job_id, $this->user_id)"; $result = mysql_query($query); if (!$result) { echo "$query\n"; return false; } - $ji->id = mysql_insert_id(); + $this->id = mysql_insert_id(); return true; } + static function lookup_id($id) { + return bossa_lookup($id, 'bossa_job_inst'); + } + + function update($clause) { + return mysql_query("update bossa_job_inst set $clause where id=$this->id"); + } + // Assign a job from the given app to the given user. // Returns the job instance or NULL. // - function assign_job($app, $user) { + static function assign($app, $user) { // this query skips jobs for which this user // has already been assigned an instance // @@ -69,15 +90,36 @@ class Bossa { echo "
"; print_r($job); echo "
"; + $ji = new BossaJobInst(); $ji->user_id = $user->id; $ji->job_id = $job->id; - if (!Bossa::insert_job_inst($ji)) { + if (!$ji->insert()) { echo mysql_error(); return null; } return $ji; } + + // The given job instance has completed + // + function completed($job) { + $this->finish_time = time(); + $this->update("finish_time=$this->finish_time, info='$this->info'"); + + $job->npending--; + $job->nsuccess++; + $job->more_needed = ($job->npending+$job->nsuccess < $job->nsuccess_needed); + return BossaJob::update("npending=$job->npending, nsuccess=$job->nsuccess, more_needed=$job->more_needed"); + } + + // The given job instance has timed out. + // + function timed_out($job) { + $job->npending--; + $job->more_needed = ($job->npending+$job->nsuccess < $job->nsuccess_needed); + return BossaJob::update("npending=$job->npending, more_needed=$job->more_needed"); + } } ?> diff --git a/html/ops/bossa_make_jobs_example.php b/html/ops/bossa_make_jobs_example.php index 50abbbf086..0984c07736 100644 --- a/html/ops/bossa_make_jobs_example.php +++ b/html/ops/bossa_make_jobs_example.php @@ -1,31 +1,35 @@ app_id = $app->id; $job->batch = 0; $job->time_estimate = 30; $job->time_limit = 600; $job->nsuccess_needed = 3; for ($i=0; $i<10; $i++) { + $j = $i % 2; $job->name = "job_$i"; - $job->job_info = "$i"; - if (!Bossa::insert_job($job)) { - echo "failed: ", mysql_error(); + $job->info = "$j"; + if (!$job->insert()) { + echo "BossaJob::insert failed: ", mysql_error(), "\n"; exit(1); } } } make_jobs(); +echo "All done.\n"; ?> diff --git a/html/ops/bossa_setup_example.php b/html/ops/bossa_setup_example.php index eb1a1217c4..2e60a488dc 100644 --- a/html/ops/bossa_setup_example.php +++ b/html/ops/bossa_setup_example.php @@ -1,6 +1,6 @@ name = 'bossa_test'; $ba->user_friendly_name = 'Simple pattern recognition'; -$ba->start_url = 'test_start.php'; +$ba->start_url = 'bossa_example.php'; -if (Bossa::insert_app($ba)) { - echo "success\n"; +if ($ba->insert($ba)) { + echo "Added application '$ba->name'\n"; } else { - echo "failed ", mysql_error(); + echo "Couldn't add '$ba->name': ", mysql_error(), "\n"; } ?> diff --git a/html/user/bossa_example.php b/html/user/bossa_example.php new file mode 100644 index 0000000000..d5ad879fc6 --- /dev/null +++ b/html/user/bossa_example.php @@ -0,0 +1,62 @@ +finish_time) { + error_page("You already finished this job"); + } + $i = $bj->info; + $img_url = "http://boinc.berkeley.edu/images/number_$i.jpg"; + echo " +
+ id> + +
+ The picture shows a +
zero +
one +
not sure +

+
+ "; +} + +function handle_job_completion($bj, $bji) { + $response = get_int('response'); + print_r($bji); + $bji->info = "response=$response"; + $bji->completed($bj); + + // show another job immediately + // + $url = "bossa_get_job.php?bossa_app_id=$bj->app_id"; + Header("Location: $url"); +} + +$user = get_logged_in_user(); +$bji = BossaJobInst::lookup_id(get_int('bji')); +if (!$bji) { + error_page("No such job instance"); +} +if ($bji->user_id != $user->id) { + error_page("Bad user ID"); +} +$bj = BossaJob::lookup_id($bji->job_id); +if (!$bj) { + error_page("No such job"); +} + +if ($_GET['submit']) { + handle_job_completion($bj, $bji); +} else { + show_job($bj, $bji); +} + +?> diff --git a/html/user/bossa_get_job.php b/html/user/bossa_get_job.php index 9ed1e8c8c0..7f2bc68fb7 100644 --- a/html/user/bossa_get_job.php +++ b/html/user/bossa_get_job.php @@ -2,27 +2,27 @@ require_once("../inc/util.inc"); require_once("../inc/db.inc"); -require_once("../bossa_inc/bossa_db.inc"); +require_once("../inc/bossa_db.inc"); db_init(); $user = get_logged_in_user(); -$bossa_app_id = get_int('bossa_app_id'); -$app = Bossa::app_lookup_id($bossa_app_id); +$app = BossaApp::lookup_id(get_int('bossa_app_id')); if (!$app) { error_page("no such app: $bossa_app_id"); } -$ji = Bossa::assign_job($app, $user); +$ji = BossaJobInst::assign($app, $user); if ($ji) { - $url = $app->start_url."&job_info=".$job->job_info; + $url = $app->start_url."?bji=$ji->id"; Header("Location: $url"); } else { page_head("No jobs available"); echo " - Sorry, no jobs are available right not. + Sorry, no more jobs are available right now. +

Please try again later. "; page_tail(); diff --git a/lib/util.C b/lib/util.C index c6e0afe4a0..0ec4b33faa 100644 --- a/lib/util.C +++ b/lib/util.C @@ -191,26 +191,16 @@ int boinc_calling_thread_cpu_time(double& cpu) { #else -// Unix: pthreads doesn't seem to provide an API for getting -// per-thread CPU time. So just get the process's CPU time +// Unix: pthreads doesn't provide an API for getting per-thread CPU time, +// so just get the process's CPU time // int boinc_calling_thread_cpu_time(double &cpu_t) { - int retval=1; struct rusage ru; - // getrusage can return an error, so try a few times if it returns an error. - // - for (int i=0; i<10; i++) { - retval = getrusage(RUSAGE_SELF, &ru); - if (!retval) break; - } - if (retval) { - return ERR_GETRUSAGE; - } - // Sum the user and system time - // - cpu_t = (double)ru.ru_utime.tv_sec + (((double)ru.ru_utime.tv_usec) / ((double)1000000.0)); - cpu_t += (double)ru.ru_stime.tv_sec + (((double)ru.ru_stime.tv_usec) / ((double)1000000.0)); + int retval = getrusage(RUSAGE_SELF, &ru); + if (retval) return ERR_GETRUSAGE; + cpu_t = (double)ru.ru_utime.tv_sec + ((double)ru.ru_utime.tv_usec) / 1e6; + cpu_t += (double)ru.ru_stime.tv_sec + ((double)ru.ru_stime.tv_usec) / 1e6; return 0; }