Table of Contents
The BOINC application programming interface (API)
The BOINC API is a set of C++ functions. Most of the functions have a C interface, so that they can be used from programs written in C and other languages. Unless otherwise specified, the functions return an integer error code; zero indicates success. To use the API include the header file:
#include "boinc_api.h"
BOINC applications may have an associate graphics program, which can act as a screensaver. The API for these graphics apps is here.
Initialization
Initialization must be done before calling other BOINC functions. For sequential (single-threaded) apps, call
boinc_init();
Parallel apps
If your app uses multiple threads or processes for parallelism, initialize using
BOINC_OPTIONS options;
boinc_options_defaults(options);
options.multi_thread = true; // if your app's main process uses multiple threads
options.multi_process = true; // if your app uses multiple processes
boinc_init_options(&options);
Do this before creating any threads or processes, or storing the PID.
GPU and coprocessor apps
If your app uses GPUs or coprocessors, initialize using
BOINC_OPTIONS options;
boinc_options_defaults(options);
options.normal_thread_priority = true;
boinc_init_options(&options);
On Windows, this causes the application to run at normal thread priority, so that the GPU will run at full speed even if the CPUs are loaded.
Termination
When the application has completed it must call
int boinc_finish(int status);
status
is nonzero if an error was encountered.
This call does not return.
Do not call exit(0)
.
If you do, BOINC will restart the app, which is probably not what you want.
Alternatively, if you want to show a message to the user (e.g. because of an error condition that the user can remedy) use
boinc_finish_message(int status, const char* msg, bool is_notice);
If is_notice is true, the message will be shown as a notice in the GUI (works with 7.5+ clients; for others, no message will be shown).
Resolving file names
Applications that use named input or output files must call
int boinc_resolve_filename(char *logical_name, char *physical_name, int len);
or
int boinc_resolve_filename_s(char *logical_name, std::string& physical_name);
to convert logical file names to physical names. For example, instead of
f = fopen("my_file", "r");
the application might use
string resolved_name;
retval = boinc_resolve_filename_s("my_file", resolved_name);
if (retval) fail("can't resolve filename");
f = boinc_fopen(resolved_name.c_str(), "r");
Don't use boinc_resolve_filename()
for files with the copy_file attribute,
or for temporary files.
It must be used for all other input or output files specified in the job templates,
or files that are part of the application version.
I/O wrappers
Applications should replace fopen()
calls with
boinc_fopen(char* path, char* mode);
This deals with platform-specific problems.
On Windows, where security and indexing programs can briefly lock files,
boinc_fopen()
does several retries at 1-second intervals.
On Unix, where signals can cause fopen()
to fail with EINTR
,
boinc_fopen
checks for this and does a few retries; it also sets the 'close-on-exec' flag.
Checkpointing
Long jobs may want to periodically write the current state of the computation to disk. This is known as checkpointing. The checkpoint file must include everything required to restart the computation at the same point. On startup, the application reads the checkpoint file to determine where to begin computation. If the BOINC client quits or exits, the computation can be restarted from the most recent checkpoint.
Most applications are able to checkpoint only at specific points, e.g. at the end the outer loop. Whan the application is at such a point, it must call
int boinc_time_to_checkpoint();
If this returns nonzero (True) then the application should checkpoint immediately (i.e., write the state file and flush all output files), then call
void boinc_checkpoint_completed();
boinc_time_to_checkpoint()
is fast,
so it can be called frequently (hundreds or thousands of times a second).
boinc_time_to_checkpoint returns true only when sufficient time has passed since the last checkpoint. This minimum interval is the maximum of:
- A user preference (e.g. laptop users might want to checkpoint infrequently).
- An optional application-supplied, specified by calling
boinc_set_min_checkpoint_period(int nsecs);
If you're using replication, make sure your application generates the same results regardless of where and how often it restarts. This requires:
- In writing the checkpoint file, use conversion codes that don't lose precision; e.g., use %e for doubles.
- If your app uses random numbers, save and restore the state of the RNG. If you use rand(), you can do this by surrounding every boinc_time_to_checkpoint() with the following:
int x = rand();
if (boinc_time_to_checkpoint()) {
...
}
srand(x);
Write x to the checkpoint file, and do a srand(x) when restarting from a checkpoint.
Critical sections
void boinc_begin_critical_section();
void boinc_end_critical_section();
Call these around code segments during which you don't want to be
suspended or killed by the core client.
Since r14694, critical sections are reentrant.
This means that you can begin critical section multiple times,
but each begin
must have a matching end
call.
NOTE: This is done automatically while checkpointing.
Atomic file update
To facilitate atomic checkpoint,
an application can write to output and state files using the MFILE
class.
class MFILE {
public:
int open(char* path, char* mode);
int _putchar(char);
int puts(char*);
int printf(char* format, ...);
size_t write(const void* buf, size_t size, size_t nitems);
int close();
int flush();
};
MFILE buffers data in memory and writes to disk only on flush()
or close()
.
This lets you write output files and state files more or less atomically.
Reporting progress
The BOINC Manager displays the percent done of tasks in progress. To keep this display current, an application should periodically call
boinc_fraction_done(double fraction_done);
The fraction_done
argument is an estimate of the workunit fraction complete (from 0 to 1).
This function is fast and can be called frequently (once per second or more).
The sequence of arguments in successive calls should be non-decreasing.
(An application should not 'reset' and start over if an error occurs;
it should call boinc_finish() with a nonzero error code.)
Many applications can supply only an approximate fraction done. If your application can supply an accurate fraction done, set the "exact fraction done" attribute of the app.
Timing information
int boinc_wu_cpu_time(double &cpu_time);
gets the total CPU time (from the beginning of the work unit, not just since the last restart).
double boinc_elapsed_time();
returns the elapsed runtime (i.e. wall-clock time during which the job was not suspended) since the start of the current episode. The elapsed time from earlier episodes is in APP_INIT_DATA::starting_elapsed_time (only from 6.10+ clients).
Standalone mode
BOINC applications can be run in "standalone" mode for testing, or under the control of the BOINC client. You might want your application to behave differently in the two cases. For example you might want to output debugging information if the application is running standalone. To determine if the application is running in standalone mode or under the control of the BOINC client, call
int boinc_is_standalone(void);
This returns non-zero (True) if the application is running standalone, and zero (False) if the application is running under the control of the BOINC client.
Registering a timer handler
typedef void (*FUNC_PTR)();
void boinc_register_timer_callback(FUNC_PTR);
This registers a timer handler function, which will be called once per second.
Temporary exit
If an application is unable to run because of a transient condition, it should call
int boinc_temporary_exit(int delay, const char* reason=NULL, bool is_notice=false);
This will exit the application and tell the BOINC client to restart it again in at least delay seconds. (This works with 6.10.25+ client; on other clients, it will potentially restart immediately). Reason, if supplied, is shown to the user as the explanation for the deferral. If is_notice is true, it's shown as a notice (this should be used only for conditions that the user can fix).
Examples:
- A GPU application fails to allocate GPU RAM because non-BOINC programs have GPU RAM allocated.
- The Vboxwrapper sees that an incompatible version of VirtualBox is installed. In this case "is_notice" would be true, because the user can update VirtualBox.