From d362a835ee0fab41df216ae5bdff22f6b89efb13 Mon Sep 17 00:00:00 2001 From: Eric Heien Date: Mon, 17 Mar 2003 19:24:38 +0000 Subject: [PATCH] shared memory communication svn path=/trunk/boinc/; revision=1063 --- api/boinc_api.C | 92 +++++++++++++++++------------- api/boinc_api.h | 18 ++++-- apps/Makefile.in | 2 +- checkin_notes | 17 ++++++ client/app.C | 144 +++++++++++++++++++++++++++++++---------------- client/app.h | 8 ++- client/cs_apps.C | 12 ++-- configure.in | 2 +- lib/msg_queue.h | 8 +++ lib/shmem.C | 31 ++++++++-- lib/shmem.h | 2 + todo | 5 +- 12 files changed, 230 insertions(+), 111 deletions(-) diff --git a/api/boinc_api.C b/api/boinc_api.C index 4d889655bf..0a8db1b23b 100644 --- a/api/boinc_api.C +++ b/api/boinc_api.C @@ -66,6 +66,7 @@ extern BOOL win_loop_done; #endif #include "parse.h" +#include "shmem.h" #include "util.h" #include "error_numbers.h" #include "graphics_api.h" @@ -97,6 +98,7 @@ static bool write_frac_done = false; static bool this_process_active; static bool time_to_quit = false; bool using_opengl = false; +static APP_CLIENT_SHM *app_client_shm; // read the INIT_DATA and FD_INIT files // @@ -165,6 +167,7 @@ int boinc_init() { boinc_install_signal_handlers(); set_timer(timer_period); + setup_shared_mem(); return 0; } @@ -263,7 +266,7 @@ void boinc_quit(int sig) { int boinc_finish(int status) { last_checkpoint_cpu_time = boinc_cpu_time(); - write_fraction_done_file(fraction_done,last_checkpoint_cpu_time,last_checkpoint_cpu_time); + update_app_progress(fraction_done, last_checkpoint_cpu_time, last_checkpoint_cpu_time); #ifdef _WIN32 // Stop the timer timeKillEvent(timer_id); @@ -277,6 +280,7 @@ int boinc_finish(int status) { } #endif #endif + cleanup_shared_mem(); exit(status); return 0; } @@ -317,13 +321,13 @@ bool boinc_time_to_checkpoint() { switch (eventState) { case WAIT_OBJECT_0: case WAIT_ABANDONED: - time_to_quit = true; - break; + time_to_quit = true; + break; } #endif if (write_frac_done) { - write_fraction_done_file(fraction_done, boinc_cpu_time(), last_checkpoint_cpu_time); + update_app_progress(fraction_done, boinc_cpu_time(), last_checkpoint_cpu_time); time_until_fraction_done_update = aid.fraction_done_update_period; write_frac_done = false; } @@ -339,7 +343,7 @@ bool boinc_time_to_checkpoint() { int boinc_checkpoint_completed() { last_checkpoint_cpu_time = boinc_cpu_time(); - write_fraction_done_file(fraction_done,last_checkpoint_cpu_time,last_checkpoint_cpu_time); + update_app_progress(fraction_done, last_checkpoint_cpu_time, last_checkpoint_cpu_time); ready_to_checkpoint = false; time_until_checkpoint = aid.checkpoint_period; // If it's time to quit, call boinc_finish which will @@ -403,7 +407,7 @@ double boinc_cpu_time() { totTime = tKernel.QuadPart + tUser.QuadPart; // Runtimes in 100-nanosecond units - cpu_secs += totTime / 10000000.0; + cpu_secs += totTime / 1.e7; // Convert to seconds and return return cpu_secs; @@ -503,6 +507,45 @@ int set_timer(double period) { return retval; } +void setup_shared_mem(void) { +#ifdef API_IGNORE_CLIENT + fprintf( stderr, "Ignoring client, so not attaching to shared memory.\n" ); + return; +#endif + +#ifdef HAVE_SYS_SHM_H +#ifdef HAVE_SYS_IPC_H + if (attach_shmem(aid.shm_key, (void**)&app_client_shm)) { + app_client_shm = NULL; + } +#endif +#endif +} + +void cleanup_shared_mem(void) { +#ifdef HAVE_SYS_SHM_H +#ifdef HAVE_SYS_IPC_H + if (app_client_shm != NULL) + detach_shmem(app_client_shm); +#endif +#endif +} + +int update_app_progress(double frac_done, double cpu_t, double cp_cpu_t) { + if (app_client_shm == NULL || (app_client_shm->access != APP_ACCESS_OK)) + return -1; + + sprintf( app_client_shm->message_buf, + "%f\n" + "%f\n" + "%f\n", + frac_done, cpu_t, cp_cpu_t ); + + app_client_shm->access = CLIENT_ACCESS_OK; + + return 0; +} + int write_init_data_file(FILE* f, APP_INIT_DATA& ai) { if (strlen(ai.app_preferences)) { fprintf(f, "\n%s\n", ai.app_preferences); @@ -522,6 +565,7 @@ int write_init_data_file(FILE* f, APP_INIT_DATA& ai) { "%f\n" "%f\n" "%f\n" + "%d\n" "%f\n" "%f\n", ai.wu_cpu_time, @@ -529,6 +573,7 @@ int write_init_data_file(FILE* f, APP_INIT_DATA& ai) { ai.user_expavg_credit, ai.host_total_credit, ai.host_expavg_credit, + ai.shm_key, ai.checkpoint_period, ai.fraction_done_update_period ); @@ -555,6 +600,7 @@ int parse_init_data_file(FILE* f, APP_INIT_DATA& ai) { else if (parse_double(buf, "", ai.host_total_credit)) continue; else if (parse_double(buf, "", ai.host_expavg_credit)) continue; else if (parse_double(buf, "", ai.wu_cpu_time)) continue; + else if (parse_int(buf, "", ai.shm_key)) continue; else if (parse_double(buf, "", ai.checkpoint_period)) continue; else if (parse_double(buf, "", ai.fraction_done_update_period)) continue; else fprintf(stderr, "parse_init_data_file: unrecognized %s", buf); @@ -562,40 +608,6 @@ int parse_init_data_file(FILE* f, APP_INIT_DATA& ai) { return 0; } -int write_fraction_done_file(double pct, double cpu, double checkpoint_cpu) { - FILE* f = fopen(FRACTION_DONE_TEMP_FILE, "w"); - - if (!f) return -1; - - fprintf(f, - "%f\n" - "%f\n" - "%f\n", - pct, - cpu, - checkpoint_cpu - ); - - fclose(f); -#ifdef _WIN32 - unlink(FRACTION_DONE_FILE); -#endif - rename(FRACTION_DONE_TEMP_FILE, FRACTION_DONE_FILE); - - return 0; -} - -int parse_fraction_done_file(FILE* f, double& pct, double& cpu, double& checkpoint_cpu) { - char buf[256]; - while (fgets(buf, 256, f)) { - if (parse_double(buf, "", pct)) continue; - else if (parse_double(buf, "", cpu)) continue; - else if (parse_double(buf, "", checkpoint_cpu)) continue; - else fprintf(stderr, "parse_fraction_done_file: unrecognized %s", buf); - } - return 0; -} - // TODO: this should handle arbitrarily many fd/filename pairs. // Also, give the tags better names int write_fd_init_file(FILE* f, char *file_name, int fdesc, int input_file ) { diff --git a/api/boinc_api.h b/api/boinc_api.h index 9168cf2b59..4e894b08f0 100755 --- a/api/boinc_api.h +++ b/api/boinc_api.h @@ -28,7 +28,7 @@ #ifndef _BOINC_API #define _BOINC_API -#define DEFAULT_FRACTION_DONE_UPDATE_PERIOD 10 +#define DEFAULT_FRACTION_DONE_UPDATE_PERIOD 1 #define DEFAULT_CHECKPOINT_PERIOD 300 // MFILE supports a primitive form of checkpointing. @@ -62,9 +62,18 @@ struct APP_INIT_DATA { double host_total_credit; double host_expavg_credit; double checkpoint_period; // recommended checkpoint period + int shm_key; double fraction_done_update_period; }; +#define APP_ACCESS_OK 0 +#define CLIENT_ACCESS_OK 1 + +struct APP_CLIENT_SHM { + int access; + char message_buf[4096]; +}; + extern int boinc_init(); extern int boinc_get_init_data(APP_INIT_DATA&); extern int boinc_finish(int); @@ -79,20 +88,19 @@ extern int boinc_install_signal_handlers(); /////////// API ENDS HERE - IMPLEMENTATION STUFF FOLLOWS +int update_app_progress(double, double, double); int write_init_data_file(FILE* f, APP_INIT_DATA&); int parse_init_data_file(FILE* f, APP_INIT_DATA&); int write_fd_init_file(FILE*, char*, int, int); int parse_fd_init_file(FILE*); -int write_fraction_done_file(double, double, double); -int parse_fraction_done_file(FILE*, double&, double&, double&); #define INIT_DATA_FILE "init_data.xml" #define GRAPHICS_DATA_FILE "graphics.xml" #define FD_INIT_FILE "fd_init.xml" -#define FRACTION_DONE_FILE "fraction_done.xml" -#define FRACTION_DONE_TEMP_FILE "fraction_done.tmp" #define STDERR_FILE "stderr.txt" int set_timer(double period); +void setup_shared_mem(); +void cleanup_shared_mem(); #endif diff --git a/apps/Makefile.in b/apps/Makefile.in index c167586577..388a096733 100644 --- a/apps/Makefile.in +++ b/apps/Makefile.in @@ -16,7 +16,7 @@ CC = @CC@ $(CFLAGS) -I @top_srcdir@/api -I@top_srcdir@/lib APIOBJS = ../api/boinc_api.o ../api/graphics_api.o X11APIOBJS = ../api/boinc_api.x11.o ../api/graphics_api.x11.o ../api/x_opengl.x11.o -LIBS = ../api/mfile.o ../lib/parse.o ../lib/filesys.o ../lib/util.o +LIBS = ../api/mfile.o ../lib/parse.o ../lib/filesys.o ../lib/shmem.o ../lib/util.o CLIBS = @LIBS@ diff --git a/checkin_notes b/checkin_notes index c284e89079..5852a9b36d 100755 --- a/checkin_notes +++ b/checkin_notes @@ -3819,3 +3819,20 @@ David Mar 15 2003 sched/ feeder.C +Eric March 17, 2003 + - Changed app->client communication to use shared memory rather + than files. The client sets up a shared memory segment when + starting the app. The app attaches to it and writes XML tags + into a 4K text buffer every second. + + configure.in + api/ + boinc_api.C,h + apps/ + Makefile.in + client/ + app.C,h + cs_apps.C + lib/ + shmem.C,h + diff --git a/client/app.C b/client/app.C index 9c37207b2b..30c107c086 100644 --- a/client/app.C +++ b/client/app.C @@ -64,6 +64,7 @@ #include "file_names.h" #include "log_flags.h" #include "parse.h" +#include "shmem.h" #include "util.h" #include "app.h" @@ -86,6 +87,7 @@ ACTIVE_TASK::ACTIVE_TASK() { result = NULL; wup = NULL; app_version = NULL; + app_client_shm = NULL; pid = 0; slot = 0; state = PROCESS_UNINITIALIZED; @@ -151,6 +153,7 @@ int ACTIVE_TASK::start(bool first_time) { aid.host_expavg_credit = wup->project->host_expavg_credit; aid.checkpoint_period = DEFAULT_CHECKPOINT_PERIOD; aid.fraction_done_update_period = DEFAULT_FRACTION_DONE_UPDATE_PERIOD; + aid.shm_key = 0; aid.wu_cpu_time = checkpoint_cpu_time; sprintf(init_data_path, "%s%s%s", slot_dir, PATH_SEPARATOR, INIT_DATA_FILE); @@ -163,6 +166,9 @@ int ACTIVE_TASK::start(bool first_time) { show_message(wup->project, buf, MSG_ERROR); return ERR_FOPEN; } +#ifdef HAVE_SYS_IPC_H + aid.shm_key = ftok( init_data_path, slot ); +#endif retval = write_init_data_file(f, aid); if (retval) return retval; @@ -321,9 +327,17 @@ int ACTIVE_TASK::start(bool first_time) { thread_handle = process_info.hThread; #else char* argv[100]; + + // Setup shared memory to communicate between processes + // The app must attach to this shmem segment by calling boinc_init() + // + shm_key = aid.shm_key; + + // Destroy any shared memory still hanging around from previous runs + //destroy_shmem(shm_key); + pid = fork(); if (pid == 0) { - // from here on we're running in a new process. // If an error happens, exit nonzero so that the core client // knows there was a problem. @@ -349,6 +363,13 @@ int ACTIVE_TASK::start(bool first_time) { perror("execv"); exit(1); } + + // Create shared memory after forking, to prevent problems with the + // child inheriting bad information about the segment + if (!create_shmem(shm_key, sizeof(APP_CLIENT_SHM), (void**)&app_client_shm)) { + app_client_shm->access = APP_ACCESS_OK; + } + if (log_flags.task_debug) printf("forked process: pid %d\n", pid); #endif state = PROCESS_RUNNING; @@ -415,28 +436,13 @@ bool ACTIVE_TASK_SET::poll() { #ifdef _WIN32 unsigned long exit_code; - FILETIME creation_time, exit_time, kernel_time, user_time; - ULARGE_INTEGER tKernel, tUser; - LONGLONG totTime; bool found = false; for (int i=0; ipid_handle, &exit_code)) { - // - // Get the elapsed CPU time - if (GetProcessTimes( - atp->pid_handle, &creation_time, &exit_time, - &kernel_time, &user_time - )) { - tKernel.LowPart = kernel_time.dwLowDateTime; - tKernel.HighPart = kernel_time.dwHighDateTime; - tUser.LowPart = user_time.dwLowDateTime; - tUser.HighPart = user_time.dwHighDateTime; - - // Runtimes in 100-nanosecond units - totTime = tKernel.QuadPart + tUser.QuadPart; - } + // Get the elapsed CPU time, checkpoint CPU time + atp->check_app_status(); atp->result->final_cpu_time = atp->checkpoint_cpu_time; if (exit_code != STILL_ACTIVE) { found = true; @@ -469,11 +475,10 @@ bool ACTIVE_TASK_SET::poll() { } if (found) return true; #else - struct rusage rs; int pid; int stat; - pid = wait3(&stat, WNOHANG, &rs); + pid = wait3(&stat, WNOHANG, 0); if (pid > 0) { if (log_flags.task_debug) printf("process %d is done\n", pid); atp = lookup_pid(pid); @@ -481,11 +486,11 @@ bool ACTIVE_TASK_SET::poll() { fprintf(stderr, "ACTIVE_TASK_SET::poll(): pid %d not found\n", pid); return true; } - double x = rs.ru_utime.tv_sec + rs.ru_utime.tv_usec/1.e6; - atp->result->final_cpu_time = atp->starting_cpu_time + x; + atp->check_app_status(); + atp->result->final_cpu_time = atp->checkpoint_cpu_time; if (atp->state == PROCESS_ABORT_PENDING) { atp->state = PROCESS_ABORTED; - atp->result->active_task_state = PROCESS_ABORTED; + atp->result->active_task_state = PROCESS_ABORTED; gstate.report_result_error( *(atp->result), 0, "process was aborted\n" ); @@ -525,6 +530,7 @@ bool ACTIVE_TASK_SET::poll() { } } + destroy_shmem(atp->shm_key); atp->read_stderr_file(); clean_out_dir(atp->slot_dir); @@ -573,8 +579,8 @@ bool ACTIVE_TASK::read_stderr_file() { // int ACTIVE_TASK_SET::wait_for_exit(double wait_time) { bool all_exited; - unsigned int i,n; - ACTIVE_TASK *atp; + unsigned int i,n; + ACTIVE_TASK *atp; for (i=0; i<10; i++) { boinc_sleep(wait_time/10.0); @@ -588,9 +594,7 @@ int ACTIVE_TASK_SET::wait_for_exit(double wait_time) { } } - if (all_exited) { - return 0; - } + if (all_exited) return 0; } return -1; @@ -745,31 +749,71 @@ int ACTIVE_TASK_SET::restart_tasks() { return 0; } -// See if the app has generated a new fraction-done file. +int ACTIVE_TASK::get_cpu_time() { +#ifdef _WIN32 + FILETIME creation_time, exit_time, kernel_time, user_time; + ULARGE_INTEGER tKernel, tUser; + LONGLONG totTime; + + // Get the elapsed CPU time + if (GetProcessTimes( pid_handle, &creation_time, &exit_time, &kernel_time, &user_time )) { + tKernel.LowPart = kernel_time.dwLowDateTime; + tKernel.HighPart = kernel_time.dwHighDateTime; + tUser.LowPart = user_time.dwLowDateTime; + tUser.HighPart = user_time.dwHighDateTime; + + // Runtimes in 100 nanosecond units + totTime = tKernel.QuadPart + tUser.QuadPart; + current_cpu_time = checkpoint_cpu_time = starting_cpu_time + totTime/1.e7; + return 0; + } +#else + struct rusage rs; + pid_t ret_pid; + int stat; + ret_pid = wait4(pid, &stat, WNOHANG, &rs); + if (ret_pid > 0) { + double x = rs.ru_utime.tv_sec + rs.ru_utime.tv_usec/1.e6; + current_cpu_time = checkpoint_cpu_time = starting_cpu_time + x; + return 0; + } +#endif + return -1; +} + +// See if the app has generated a new fraction-done buffer. // If so read it and return true. // -bool ACTIVE_TASK::check_app_status_files() { - FILE* f; - char path[256]; - bool found = false; - int retval; - - sprintf(path, "%s%s%s", slot_dir, PATH_SEPARATOR, FRACTION_DONE_FILE); - f = fopen(path, "r"); - if (f) { - found = true; - retval = parse_fraction_done_file(f, fraction_done, current_cpu_time, checkpoint_cpu_time); - fclose(f); - if (retval) return false; - retval = file_delete(path); - if (retval) { - fprintf(stderr, - "ACTIVE_TASK.check_app_status_files: could not delete %s: %d\n", - path, retval - ); +bool ACTIVE_TASK::check_app_status() { + if (app_client_shm == NULL) { + fraction_done = 0; + get_cpu_time(); + } else { + if (app_client_shm->access == CLIENT_ACCESS_OK) { + fraction_done = current_cpu_time = checkpoint_cpu_time = 0.0; + + parse_double(app_client_shm->message_buf, "", fraction_done); + parse_double(app_client_shm->message_buf, "", current_cpu_time); + parse_double(app_client_shm->message_buf, "", checkpoint_cpu_time); + + app_client_shm->access = APP_ACCESS_OK; + + return false; } } - return found; + + return false; +} + +// Check status of all active tasks +// +void ACTIVE_TASK_SET::check_apps() { + unsigned int i; + ACTIVE_TASK *atp; + for (i=0; icheck_app_status(); + } } // Returns the estimated time to completion (in seconds) of this task, @@ -811,7 +855,7 @@ bool ACTIVE_TASK_SET::poll_time() { for (i=0; icheck_app_status_files(); + updated |= atp->check_app_status(); } return updated; diff --git a/client/app.h b/client/app.h index a4b7f6aa27..c8d3f6e2e2 100644 --- a/client/app.h +++ b/client/app.h @@ -28,6 +28,7 @@ #include #include #include "client_types.h" +#include "boinc_api.h" class CLIENT_STATE; typedef int PROCESS_ID; @@ -55,11 +56,14 @@ class ACTIVE_TASK { public: #ifdef _WIN32 HANDLE pid_handle, thread_handle, quitRequestEvent; +#else + key_t shm_key; #endif RESULT* result; WORKUNIT* wup; APP_VERSION* app_version; PROCESS_ID pid; + APP_CLIENT_SHM *app_client_shm; int slot; // which slot (determines directory) int state; int exit_status; @@ -98,7 +102,8 @@ public: int suspend(); int unsuspend(); - bool check_app_status_files(); + int get_cpu_time(); + bool check_app_status(); double est_time_to_completion(); bool read_stderr_file(); @@ -120,6 +125,7 @@ public: int restart_tasks(); void request_tasks_exit(); void kill_tasks(); + void check_apps(); int get_free_slot(int total_slots); int write(FILE*); diff --git a/client/cs_apps.C b/client/cs_apps.C index ed522ffef5..cf505c5a52 100644 --- a/client/cs_apps.C +++ b/client/cs_apps.C @@ -59,15 +59,17 @@ int CLIENT_STATE::cleanup_and_exit() { } int CLIENT_STATE::exit_tasks() { + // TODO: unsuspend active tasks so they have a chance to checkpoint // Send a request to the tasks to exit active_tasks.request_tasks_exit(); - // Wait a second for them to exit normally - active_tasks.wait_for_exit(1); - - // And then just kill them - active_tasks.kill_tasks(); + // Wait a second for them to exit normally, if they don't then kill them + if (active_tasks.wait_for_exit(1)) + active_tasks.kill_tasks(); + // Check their final CPU time + active_tasks.check_apps(); + return 0; } diff --git a/configure.in b/configure.in index 930cf2ed2c..48ff2a5cd6 100644 --- a/configure.in +++ b/configure.in @@ -29,7 +29,7 @@ dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS(fcntl.h malloc.h strings.h sys/time.h unistd.h sys/systeminfo.h sys/resource.h sys/types.h dirent.h sys/utsname.h netdb.h netinet/in.h arpa/inet.h signal.h sys/wait.h sys/file.h) +AC_CHECK_HEADERS(fcntl.h malloc.h strings.h sys/time.h unistd.h sys/systeminfo.h sys/resource.h sys/types.h dirent.h sys/utsname.h netdb.h netinet/in.h arpa/inet.h signal.h sys/wait.h sys/file.h sys/ipc.h sys/shm.h) AC_CHECK_HEADERS(mysql/include/mysql_com.h mysql/mysql_com.h) dnl Checks for typedefs, structures, and compiler characteristics. diff --git a/lib/msg_queue.h b/lib/msg_queue.h index bfd206ee86..299b63cbbc 100644 --- a/lib/msg_queue.h +++ b/lib/msg_queue.h @@ -1,4 +1,12 @@ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_IPC_H +#include +#endif +#ifdef HAVE_SYS_MSG_H #include +#endif extern int create_message_queue(key_t); extern int receive_message(key_t,void*,size_t,bool); diff --git a/lib/shmem.C b/lib/shmem.C index fc38372667..c18d55dc40 100755 --- a/lib/shmem.C +++ b/lib/shmem.C @@ -27,12 +27,14 @@ #include "shmem.h" -int create_shmem(key_t key, int size, void** pp){ +int create_shmem(key_t key, int size, void** pp) { int id; + char buf[256]; assert(pp!=NULL); id = shmget(key, size, IPC_CREAT|0777); if (id < 0) { - perror("create_shmem: shmget"); + sprintf(buf, "create_shmem: shmget: key: %x size: %d", (unsigned int)key, size); + perror(buf); return -1; } return attach_shmem(key, pp); @@ -64,17 +66,19 @@ int destroy_shmem(key_t key){ int attach_shmem(key_t key, void** pp){ void* p; + char buf[256]; int id; assert(pp!=NULL); - //fprintf(stderr, "%x\n", key); id = shmget(key, 0, 0); if (id < 0) { - perror("attach_shmem: shmget"); + sprintf(buf, "attach_shmem: shmget: key: %x mem_addr: %d", (unsigned int)key, (int)pp); + perror(buf); return -1; } p = shmat(id, 0, 0); if ((int)p == -1) { - perror("attach_shmem: shmat"); + sprintf(buf, "attach_shmem: shmat: key: %x mem_addr: %d", (unsigned int)key, (int)pp); + perror(buf); return -1; } *pp = p; @@ -88,3 +92,20 @@ int detach_shmem(void* p) { if (retval) perror("detach_shmem: shmdt"); return retval; } + +int shmem_info(key_t key) { + int id; + struct shmid_ds buf; + char buf2[256]; + + id = shmget(key, 0, 0); + if (id < 0) { + sprintf(buf2, "shmem_info: shmget: key: %x", (unsigned int)key); + perror(buf2); + return -1; + } + shmctl(id, IPC_STAT, &buf); + fprintf( stderr, "id: %d, size: %d, nattach: %d\n", id, buf.shm_segsz, buf.shm_nattch ); + + return 0; +} diff --git a/lib/shmem.h b/lib/shmem.h index 7cd2641aed..5285f9a747 100755 --- a/lib/shmem.h +++ b/lib/shmem.h @@ -19,3 +19,5 @@ extern int attach_shmem(key_t, void**); // detach from a shared-mem segment // extern int detach_shmem(void*); + +extern int shmem_info(key_t key); diff --git a/todo b/todo index e7fff6591c..82e276409a 100755 --- a/todo +++ b/todo @@ -4,7 +4,7 @@ BUGS (arranged from high to low priority) - window closes and does not reopen when workunit finishes and new workunit starts - CPU time updates infrequently (every 10 seconds), - should there be a user control for this? + add user control for this (HD write frequency) - Client treats URL "maggie/ap/" different than URL "maggie/ap", though this isn't really a bug it might be good to fix anyway - global battery/user active prefs are always true in the client @@ -17,8 +17,6 @@ HIGH-PRIORITY (should do for beta test) - Implement Screensaver "blank screen" functionality -multiple preference sets - implement server watchdogs est_time_to_completion doesn't work for non-running tasks @@ -37,6 +35,7 @@ THINGS TO TEST (preferably with test scripts) - WU failure: too many errors - WU failure: too many good results - credit is granted even if result arrives very late +- multiple preference sets ----------------------- MEDIUM-PRIORITY (should do before public release) -----------------------