= Low-level validator framework = BOINC's [ValidationSimple simple validator framework] is sufficient in almost all cases. If for some reason you need more control, you can use the low-level framework (on which the simple framework is based). To make a validator program using the low-level framework, link validator.cpp with two application-specific functions: {{{ int check_set( vector results, DB_WORKUNIT& wu, int& canonicalid, double& credit, bool& retry ); }}} * '''check_set()''' takes a set of results (all with outcome=SUCCESS). It reads and compares their output files. If there is a quorum of matching results, it selects one of them as the canonical result, returning its ID. In this case it also returns the credit to be granted for correct results for this workunit. * If, when an output file for a result has a nonrecoverable error (e.g. the directory is there but the file isn't, or the file is present but has invalid contents), then it must set the result's outcome (in memory, not database) to outcome=RESULT_OUTCOME_VALIDATE_ERROR and validate_state=VALIDATE_STATE_INVALID. Use BOINC's [BackendUtilities back-end utility functions] (in sched/validate_util.cpp) to get file pathnames and to distinguish recoverable and nonrecoverable file-open errors. * If a canonical result is found, check_set() must set the validate_state field of each non-ERROR result (in memory, not database) to either validate_state=VALIDATE_STATE_VALID or validate_state=VALIDATE_STATE_INVALID. * If a recoverable error occurs while reading output files (e.g. a directory wasn't visible due to NFS mount failure) then check_set() should return retry=true. This tells the validator to arrange for this WU to be processed again in a few hours. * check_set() should return nonzero if a major error occurs. This tells the validator to write an error message and exit. {{{ int check_pair(RESULT& new_result, RESULT& canonical_result, bool& retry); }}} * '''check_pair()''' compares a new result to the canonical result. In the absence of errors, it sets the new result's validate_state to either VALIDATE_STATE_INVALID or VALIDATE_STATE_VALID. * If it has a nonrecoverable error reading an output file of either result, or if the new result's output file is invalid, it must set the new result's outcome (in memory, not database) to VALIDATE_ERROR. * If it has a recoverable error while reading an output file of either result, it returns retry=true, which causes the validator to arrange for the WU to be examined again in a few hours. * check_pair() should return nonzero if a major error occurs. This tells the validator to write an error message and exit. Neither function should delete files or access the BOINC database. Examples of these two functions may be found in validate_util2.cpp, which implements the [ValidationSimple simple validator framework]. == Pseudocode == {{{ int check_set( vector results, DB_WORKUNIT& wu, int& canonicalid, double& credit, bool& retry ); Define N := length of result vector, and let M := N. check_set() will ALWAYS be called with N>=wu.min_quorum. check_set() will ALWAYS be called with ALL results satisfying result.outcome == RESULT_OUTCOME_SUCCESS result.validate_state == VALIDATE_STATE_INIT check_set() should NEVER modify wu [although it is not declared const] [1] Syntax pass (optional) for (all N results) { if (one or more of the result's output files can be read and one or more of those files contains erroneous or invalid or incorrect output, i.e. bad file syntax) { set result.outcome=RESULT_OUTCOME_VALIDATE_ERROR; set result.validate_state=VALIDATE_STATE_INVALID; decrement counter: M = M-1; } // erroneous or incorrect or invalid output files else if (result has a potentially recoverable error, i.e. NFS directory not mounted, server is unreachable, upload server unreachable) { dont not modify result.validate_state; dont not modify result.outcome; decrement counter M = M-1; set retry=true; } // recoverable error else if (every output file of the result is unreadable or fails to exist) { set result.outcome=RESULT_OUTCOME_VALIDATE_ERROR; set result.validate_state=VALIDATE_STATE_INIT; decrement counter: M = M-1; } // all result output files unreadable or nonexistent } // end of syntax pass loop over all N results Define REMAINING RESULTS to be those that do NOT fall into one of the three categories above. There are M of these. If the syntax pass has been skipped, then M == N. if (M < wu.min_quorum) { don't modify canonicalid; don't modify credit; leave retry as set above; leave result.outcome unchanged for M remaining results; leave result.validate_state unchanged for M remaining results; return 0; } // fewer than min_quorum results remain At any point in this process, if a major error occurs, check_set() should return nonzero. This will cause the validator to exit. If this happens, it does not matter how you have set or modified result.outcome, result.validate_state, retry, credit, or canonicalid. // END OF OPTIONAL SYNTAX PASS [2] Comparison pass (required). We have M>=wu.min_quorum REMAINING RESULTS results with result.outcome == RESULT_OUTCOME_SUCCESS result.validate_state == VALIDATE_STATE_INIT All the output files of all of these results are readable. All of the output files for a given result are, when taken "in isolation" apparently valid. [If these conditions are not met then you must do the "syntax pass" above.] if (one of these results is determined to be THE correct [canonical] result) { for (correct result) { set result.validate_state=VALIDATE_STATE_VALID; set canonicalid=result.id; } // canonical result for (the REMAINING M - 1 results) { // NOTE: what is below can be done by calling // check_pair(result, canonical_result) if (result is correct, matches canonical) { result.validate_state=VALIDATE_STATE_VALID; } else { result.validate_state=VALIDATE_STATE_INVALID; } } // loop over remaining M-1 results set credit; leave retry as set from the syntax pass above; return 0; } // found canonical result else { // You are UNABLE to determine if one of the M REMAINING RESULTS // is correct, so: do not modify result.outcome for ANY of M remaining results; do not modify result.validate_state for ANY of M remaining results; do not set credit; do not set canonicalid; leave retry as set from the syntax pass; return 0; } // did not find canonical result At any point in this process, if a major error occurs, check_set() should return nonzero. This will cause the validator to exit. If this happens, it does not matter how you have set result.outcome, result.validate_state, retry, credit, or canonicalid for ANY of the results. // end of Comparison pass }}}