Miscellaneous¶
Error handling¶
Like in any other asynchronous invocation scheme it is important to be able to handle error conditions occurring while the asynchronous (and possibly remote) operation is executed. In HPX all error handling is based on standard C++ exception handling. Any exception thrown during the execution of an asynchronous operation will be transferred back to the original invocation locality, where it is rethrown during synchronization with the calling thread.
The source code for this example can be found here:
error_handling.cpp
.
Working with exceptions¶
For the following description we assume that the function raise_exception()
is executed by invoking the plain action raise_exception_type
.
void raise_exception()
{
HPX_THROW_EXCEPTION(hpx::no_success, "raise_exception", "simulated error");
}
HPX_PLAIN_ACTION(raise_exception, raise_exception_action);
The exception is thrown using the macro HPX_THROW_EXCEPTION
. The type
of the thrown exception is hpx::exception
. This associates
additional diagnostic information with the exception, such as file name and line
number, locality id and thread id, and stack backtrace from the point
where the exception was thrown.
Any exception thrown during the execution of an action is transferred back to
the (asynchronous) invocation site. It will be rethrown in this context when the
calling thread tries to wait for the result of the action by invoking either
future<>::get()
or the synchronous action invocation wrapper as shown here:
hpx::cout << "Error reporting using exceptions\n";
try {
// invoke raise_exception() which throws an exception
raise_exception_action do_it;
do_it(hpx::find_here());
}
catch (hpx::exception const& e) {
// Print just the essential error information.
hpx::cout << "caught exception: " << e.what() << "\n\n";
// Print all of the available diagnostic information as stored with
// the exception.
hpx::cout << "diagnostic information:"
<< hpx::diagnostic_information(e) << "\n";
}
hpx::cout << hpx::flush;
Note
The exception is transferred back to the invocation site even if it is executed on a different locality.
Additionally, this example demonstrates how an exception thrown by an (possibly
remote) action can be handled. It shows the use of
hpx::diagnostic_information
which retrieves all available diagnostic
information from the exception as a formatted string. This includes, for
instance, the name of the source file and line number, the sequence number of
the OS-thread and the HPX-thread id, the locality id and the stack
backtrace of the point where the original exception was thrown.
Under certain circumstances it is desirable to output only some of the diagnostics, or to output those using different formatting. For this case, HPX exposes a set of lower level functions as demonstrated in the following code snippet:
hpx::cout << "Detailed error reporting using exceptions\n";
try {
// Invoke raise_exception() which throws an exception.
raise_exception_action do_it;
do_it(hpx::find_here());
}
catch (hpx::exception const& e) {
// Print the elements of the diagnostic information separately.
hpx::cout << "{what}: " << hpx::get_error_what(e) << "\n";
hpx::cout << "{locality-id}: " << hpx::get_error_locality_id(e) << "\n";
hpx::cout << "{hostname}: " << hpx::get_error_host_name(e) << "\n";
hpx::cout << "{pid}: " << hpx::get_error_process_id(e) << "\n";
hpx::cout << "{function}: " << hpx::get_error_function_name(e) << "\n";
hpx::cout << "{file}: " << hpx::get_error_file_name(e) << "\n";
hpx::cout << "{line}: " << hpx::get_error_line_number(e) << "\n";
hpx::cout << "{os-thread}: " << hpx::get_error_os_thread(e) << "\n";
hpx::cout << "{thread-id}: " << std::hex << hpx::get_error_thread_id(e)
<< "\n";
hpx::cout << "{thread-description}: "
<< hpx::get_error_thread_description(e) << "\n";
hpx::cout << "{state}: " << std::hex << hpx::get_error_state(e)
<< "\n";
hpx::cout << "{stack-trace}: " << hpx::get_error_backtrace(e) << "\n";
hpx::cout << "{env}: " << hpx::get_error_env(e) << "\n";
}
hpx::cout << hpx::flush;
Working with error codes¶
Most of the API functions exposed by HPX can be invoked in two different
modes. By default those will throw an exception on error as described above.
However, sometimes it is desirable not to throw an exception in case of an error
condition. In this case an object instance of the hpx::error_code
type can be passed as the last argument to the API function. In case of an error
the error condition will be returned in that hpx::error_code
instance. The following example demonstrates extracting the full diagnostic
information without exception handling:
hpx::cout << "Error reporting using error code\n";
// Create a new error_code instance.
hpx::error_code ec;
// If an instance of an error_code is passed as the last argument while
// invoking the action, the function will not throw in case of an error
// but store the error information in this error_code instance instead.
raise_exception_action do_it;
do_it(hpx::find_here(), ec);
if (ec) {
// Print just the essential error information.
hpx::cout << "returned error: " << ec.get_message() << "\n";
// Print all of the available diagnostic information as stored with
// the exception.
hpx::cout << "diagnostic information:"
<< hpx::diagnostic_information(ec) << "\n";
}
hpx::cout << hpx::flush;
Note
The error information is transferred back to the invocation site even if it is executed on a different locality.
This example show how an error can be handled without having to resolve to
exceptions and that the returned hpx::error_code
instance can be
used in a very similar way as the hpx::exception
type above. Simply
pass it to the hpx::diagnostic_information
which retrieves all
available diagnostic information from the error code instance as a formatted
string.
As for handling exceptions, when working with error codes, under certain circumstances it is desirable to output only some of the diagnostics, or to output those using different formatting. For this case, HPX exposes a set of lower level functions usable with error codes as demonstrated in the following code snippet:
hpx::cout << "Detailed error reporting using error code\n";
// Create a new error_code instance.
hpx::error_code ec;
// If an instance of an error_code is passed as the last argument while
// invoking the action, the function will not throw in case of an error
// but store the error information in this error_code instance instead.
raise_exception_action do_it;
do_it(hpx::find_here(), ec);
if (ec) {
// Print the elements of the diagnostic information separately.
hpx::cout << "{what}: " << hpx::get_error_what(ec) << "\n";
hpx::cout << "{locality-id}: " << hpx::get_error_locality_id(ec) << "\n";
hpx::cout << "{hostname}: " << hpx::get_error_host_name(ec) << "\n";
hpx::cout << "{pid}: " << hpx::get_error_process_id(ec) << "\n";
hpx::cout << "{function}: " << hpx::get_error_function_name(ec)
<< "\n";
hpx::cout << "{file}: " << hpx::get_error_file_name(ec) << "\n";
hpx::cout << "{line}: " << hpx::get_error_line_number(ec) << "\n";
hpx::cout << "{os-thread}: " << hpx::get_error_os_thread(ec) << "\n";
hpx::cout << "{thread-id}: " << std::hex
<< hpx::get_error_thread_id(ec) << "\n";
hpx::cout << "{thread-description}: "
<< hpx::get_error_thread_description(ec) << "\n\n";
hpx::cout << "{state}: " << std::hex << hpx::get_error_state(ec)
<< "\n";
hpx::cout << "{stack-trace}: " << hpx::get_error_backtrace(ec) << "\n";
hpx::cout << "{env}: " << hpx::get_error_env(ec) << "\n";
}
hpx::cout << hpx::flush;
For more information please refer to the documentation of
hpx::get_error_what
, hpx::get_error_locality_id
,
hpx::get_error_host_name
, hpx::get_error_process_id
,
hpx::get_error_function_name
, hpx::get_error_file_name
,
hpx::get_error_line_number
, hpx::get_error_os_thread
,
hpx::get_error_thread_id
,
hpx::get_error_thread_description
,
hpx::get_error_backtrace
, hpx::get_error_env
, and
hpx::get_error_state
.
Lightweight error codes¶
Sometimes it is not desirable to collect all the ambient information about the error at the point where it happened as this might impose too much overhead for simple scenarios. In this case, HPX provides a lightweight error code facility which will hold the error code only. The following snippet demonstrates its use:
hpx::cout << "Error reporting using an lightweight error code\n";
// Create a new error_code instance.
hpx::error_code ec(hpx::lightweight);
// If an instance of an error_code is passed as the last argument while
// invoking the action, the function will not throw in case of an error
// but store the error information in this error_code instance instead.
raise_exception_action do_it;
do_it(hpx::find_here(), ec);
if (ec) {
// Print just the essential error information.
hpx::cout << "returned error: " << ec.get_message() << "\n";
// Print all of the available diagnostic information as stored with
// the exception.
hpx::cout << "error code:" << ec.value() << "\n";
}
hpx::cout << hpx::flush;
All functions which retrieve other diagnostic elements from the
hpx::error_code
will fail if called with a lightweight error_code
instance.
Utilities in HPX¶
In order to ease the burden of programming in HPX we have provided several utilities to users. The following section documents those facilies.
Checkpoint¶
A common need of users is to periodically backup an application. This practice
provides resiliency and potential restart points in code. We have developed the
concept of a checkpoint
to support this use case.
Found in hpx/util/checkpoint.hpp
, checkpoint
s are defined as objects
which hold a serialized version of an object or set of objects at a particular
moment in time. This representation can be stored in memory for later use or it
can be written to disk for storage and/or recovery at a later point. In order to
create and fill this object with data we use a function called
save_checkpoint
. In code the function looks like this:
hpx::future<hpx::util::checkpoint> hpx::util::save_checkpoint(a, b, c, ...);
save_checkpoint
takes arbitrary data containers such as int, double, float,
vector, and future and serializes them into a newly created checkpoint
object. This function returns a future
to a checkpoint
containing the
data. Let us look a simple use case below:
using hpx::util::checkpoint;
using hpx::util::save_checkpoint;
std::vector<int> vec{1,2,3,4,5};
hpx::future<checkpoint> save_checkpoint(vec);
Once the future is ready the checkpoint object will contain the vector
vec
and its five elements.
It is also possible to modify the launch policy used by save_checkpoint
.
This is accomplished by passing a launch policy as the first argument. It is
important to note that passing hpx::launch::sync
will cause
save_checkpoint
to return a checkpoint
instead of a future
to a
checkpoint
. All other policies passed to save_checkpoint
will return a
future
to a checkpoint
.
Sometimes checkpoint
s must be declared before they are used.
save_checkpoint
allows users to move pre-created checkpoint
s into the
function as long as they are the first container passing into the function (In
the case where a launch policy is used, the checkpoint
will immediately
follow the launch policy). An example of these features can be found below:
char character = 'd';
int integer = 10;
float flt = 10.01f;
bool boolean = true;
std::string str = "I am a string of characters";
std::vector<char> vec(str.begin(), str.end());
checkpoint archive;
// Test 1
// test basic functionality
hpx::shared_future<checkpoint> f_archive = save_checkpoint(
std::move(archive), character, integer, flt, boolean, str, vec);
Now that we can create checkpoint
s we now must be able to restore the
objects they contain into memory. This is accomplished by the function
restore_checkpoint
. This function takes a checkpoint
and fills its data
into the containers it is provided. It is important to remember that the
containers must be ordered in the same way they were placed into the
checkpoint
. For clarity see the example below:
char character2;
int integer2;
float flt2;
bool boolean2;
std::string str2;
std::vector<char> vec2;
restore_checkpoint(
f_archive.get(), character2, integer2, flt2, boolean2, str2, vec2);
The core utility of checkpoint
is in its ability to make certain data
persistent. Often this means that the data is needed to be stored in an object,
such as a file, for later use. For these cases we have provided two solutions:
stream operator overloads and access iterators.
We have created the two stream overloads
operator<<
and operator>>
to stream data
out of and into checkpoint
. You can see an
example of the overloads in use below:
double a9 = 1.0, b9 = 1.1, c9 = 1.2;
std::ofstream test_file_9("test_file_9.txt");
hpx::future<checkpoint> f_9 = save_checkpoint(a9, b9, c9);
test_file_9 << f_9.get();
test_file_9.close();
double a9_1, b9_1, c9_1;
std::ifstream test_file_9_1("test_file_9.txt");
checkpoint archive9;
test_file_9_1 >> archive9;
restore_checkpoint(archive9, a9_1, b9_1, c9_1);
This is the primary way to move data into and out of a checkpoint
. It is
important to note, however, that users should be cautious when using a stream
operator to load data an another function to remove it (or vice versa). Both
operator<<
and operator>>
rely on a .write()
and a .read()
function respectively. In order to know how much data to read from the
std::istream
, the operator<<
will write the size of the checkpoint
before writing the checkpoint
data. Correspondingly, the operator>>
will
read the size of the stored data before reading the data into new instance of
checkpoint
. As long as the user employs the operator<<
and
operator>>
to stream the data this detail can be ignored.
Important
Be careful when mixing operator<<
and operator>>
with other
facilities to read and write to a checkpoint
. operator<<
writes and
extra variable and operator>>
reads this variable back separately. Used
together the user will not encounter any issues and can safely ignore this
detail.
Users may also move the data into and out of a checkpoint
using the exposed
.begin()
and .end()
iterators. An example of this use case is
illustrated below.
std::ofstream test_file_7("checkpoint_test_file.txt");
std::vector<float> vec7{1.02f, 1.03f, 1.04f, 1.05f};
hpx::future<checkpoint> fut_7 = save_checkpoint(vec7);
checkpoint archive7 = fut_7.get();
std::copy(archive7.begin() // Write data to ofstream
, archive7.end() // ie. the file
, std::ostream_iterator<char>(test_file_7));
test_file_7.close();
std::vector<float> vec7_1;
std::vector<char> char_vec;
std::ifstream test_file_7_1("checkpoint_test_file.txt");
if (test_file_7_1)
{
test_file_7_1.seekg(0, test_file_7_1.end);
int length = test_file_7_1.tellg();
test_file_7_1.seekg(0, test_file_7_1.beg);
char_vec.resize(length);
test_file_7_1.read(char_vec.data(), length);
}
checkpoint archive7_1(std::move(char_vec)); // Write data to checkpoint
restore_checkpoint(archive7_1, vec7_1);
The HPX I/O-streams component¶
The HPX I/O-streams subsystem extends the standard C++ output streams
std::cout
and std::cerr
to work in the distributed setting of an HPX
application. All of the output streamed to``hpx::cout``will be dispatched to
std::cout
on the console locality. Likewise, all output generated
from hpx::cerr
will be dispatched to std::cerr
on the console
locality.
Note
All existing standard manipulators can be used in conjunction with
hpx::cout
and hpx::cerr
Historically, HPX also defines
hpx::endl
and hpx::flush
but those are just aliases for the
corresponding standard manipulators.
In order to use either hpx::cout
or hpx::cerr
application codes need to
#include <hpx/include/iostreams.hpp>
. For an example, please see the
simplest possible ‘Hello world’ program as included as an example with HPX:
// Copyright (c) 2007-2012 Hartmut Kaiser
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
///////////////////////////////////////////////////////////////////////////////
// The purpose of this example is to execute a HPX-thread printing
// "Hello World!" once. That's all.
//[hello_world_1_getting_started
// Including 'hpx/hpx_main.hpp' instead of the usual 'hpx/hpx_init.hpp' enables
// to use the plain C-main below as the direct main HPX entry point.
#include <hpx/hpx_main.hpp>
#include <hpx/include/iostreams.hpp>
int main()
{
// Say hello to the world!
hpx::cout << "Hello World!\n" << hpx::flush;
return 0;
}
//]
Additionally those applications need to link with the iostreams component. When
using cmake this can be achieved by using the COMPONENT_DEPENDENCIES
parameter, for instance:
include(HPX_AddExecutable)
add_hpx_executable(
hello_world
SOURCES hello_world.cpp
COMPONENT_DEPENDENCIES iostreams
)
Note
The hpx::cout
and hpx::cerr
streams buffer all output locally until a
std::endl
or std::flush
is encountered. That means that no output
will appear on the console as long as either of those is explicitly used.