blog.think-async.com Open in urlscan Pro
2607:f8b0:4006:809::2013  Public Scan

URL: http://blog.think-async.com/
Submission: On November 16 via api from US — Scanned from CA

Form analysis 0 forms found in the DOM

Text Content

skip to main | skip to sidebar


THINKING ASYNCHRONOUSLY IN C++




WEDNESDAY, APRIL 14, 2010


SYSTEM ERROR SUPPORT IN C++0X - PART 5



[ part 1, part 2, part 3, part 4 ]



CREATING YOUR OWN ERROR CONDITIONS

User-extensibility in the <system_error> facility is not limited to error codes:
error_condition permits the same customisation.


WHY CREATE CUSTOM ERROR CONDITIONS?

To answer this question, let's revisit the distinction between error_code and
error_condition:


 * class error_code - represents a specific error value returned by an operation
   (such as a system call).
   
 * class error_condition - something that you want to test for and, potentially,
   react to in your code.
   

This suggests some use cases for custom error conditions:


 * Abstraction of OS-specific errors.
   
   Let's say you're writing a portable wrapper around getaddrinfo(). Two
   interesting error conditions are the tentative "the name does not resolve at
   this time, try again later", and the authoritative "the name does not
   resolve". The getaddrinfo() function reports these errors as follows:
   
   * On POSIX platforms, the errors are EAI_AGAIN and EAI_NONAME, respectively.
     The error values are in a distinct "namespace" to errno values. This means
     you will have to implement a new error_category to capture the errors.
   * On Windows, the errors are WSAEAI_AGAIN and WSAEAI_NONAME. Although the
     names are similar to the POSIX errors, they share the GetLastError
     "namespace". Consequently, you may want to reuse std::system_category() to
     capture and represent getaddrinfo() errors on this platform.
   
   To avoid discarding information, you want to preserve the original
   OS-specific error code while providing two error conditions (called
   name_not_found_try_again and name_not_found, say) that API users can test
   against.
   

 * Giving context-specific meaning to generic error codes.
   
   Most POSIX system calls use errno to report errors. Rather than define new
   errors for each function, the same errors are reused and you may have to look
   at the corresponding man page to determine the meaning. If you implement your
   own abstractions on top of these system calls, this context is lost to the
   user.
   
   For example, say you want to implement a simple database where each entry is
   stored in a separate flat file. When you try to read an entry, the database
   calls open() to access the file. This function sets errno to ENOENT if the
   file does not exist.
   
   As the database's storage mechanism is abstracted from the user, it could be
   surprising to ask them to test for no_such_file_or_directory. Instead, you
   can create your own context-specific error condition, no_such_entry, which is
   equivalent to ENOENT.
   

 * Testing for a set of related errors.
   
   As your codebase grows, you might find the same set of errors are checked
   again and again. Perhaps you need to respond to low system resources:
   
   * not_enough_memory
   * resource_unavailable_try_again
   * too_many_files_open
   * too_many_files_open_in_system
   * ...
   
   in several places, but the subsequent action differs at each point of use.
   This shows that there is a more general condition, "the system resources are
   low", that you want to test for and react to in your code.
   
   A custom error condition, low_system_resources, can be defined so that its
   equivalence is based on a combination of other error conditions. This allows
   you to write each test as:
   
   
   if (ec == low_system_resources)
     ...
   
   
   and so eliminate the repetition of individual tests.
   

The definition of custom error conditions is similar to the method for
error_codes, as you will see in the steps below.


STEP 1: DEFINE THE ERROR VALUES

You need to create an enum for the error values, similar to std::errc:


enum class api_error
{
  low_system_resources = 1,
  ...
  name_not_found,
  ...
  no_such_entry
};


The actual values you use are not important, but you must ensure that they are
distinct and non-zero.


STEP 2: DEFINE AN ERROR_CATEGORY CLASS

An error_condition object consists of both an error value and a category. To
create a new category, you must derive a class from error_category:


class api_category_impl
  : public std::error_category
{
public:
  virtual const char* name() const;
  virtual std::string message(int ev) const;
  virtual bool equivalent(
      const std::error_code& code,
      int condition) const;
};


STEP 3: GIVE THE CATEGORY A HUMAN-READABLE NAME

The error_category::name() virtual function must return a string identifying the
category:


const char* api_category_impl::name() const
{
  return "api";
}


STEP 4: CONVERT ERROR CONDITIONS TO STRINGS



The error_category::message() function converts an error value into a string
that describes the error:


std::string api_category_impl::message(int ev) const
{
  switch (ev)
  {
  case api_error::low_system_resources:
    return "Low system resources";
  ...
  }
}


However, depending on your use case, it may be unlikely that you'll ever call
error_condition::message(). In that case, you can take a shortcut and simply
write:


std::string api_category_impl::message(int ev) const
{
  return "api error";
}


STEP 5: IMPLEMENT ERROR EQUIVALENCE

The error_category::equivalent() virtual function is used to define equivalence
between error codes and conditions. In fact, there are two overloads of the
error_category::equivalent() function. The first:


virtual bool equivalent(int code,
    const error_condition& condition) const;


is used to establish equivalence between error_codes in the current category
with arbitrary error_conditions. The second overload:


virtual bool equivalent(
    const error_code& code,
    int condition) const;


defines equivalence between error_conditions in the current category with
error_codes from any category. Since you are creating custom error conditions,
it is the second overload that you must override.


Defining equivalence is simple: return true if you want an error_code to be
equivalent to your condition, otherwise return false.




If your intent is to abstract OS-specific errors, you might implement
error_category::equivalent() like this:


bool api_category_impl::equivalent(
    const std::error_code& code,
    int condition) const
{
  switch (condition)
  {
  ...
  case api_error::name_not_found:
#if defined(_WIN32)
    return code == std::error_code(
        WSAEAI_NONAME, system_category());
#else
    return code = std::error_code(
        EAI_NONAME, getaddrinfo_category());
#endif
  ...
  default:
    return false;
  }
}


(Obviously getaddrinfo_category() needs to be defined somewhere too.)


The tests can be as complex as you like, and can even reuse other
error_condition constants. You may want to do this if you're creating a
context-specific error condition, or testing for a set of related errors:


bool api_category_impl::equivalent(
    const std::error_code& code,
    int condition) const
{
  switch (condition)
  {
  case api_error::low_system_resources:
    return code == std::errc::not_enough_memory
      || code == std::errc::resource_unavailable_try_again
      || code == std::errc::too_many_files_open
      || code == std::errc::too_many_files_open_in_system;
  ...
  case api_error::no_such_entry:
    return code == std::errc::no_such_file_or_directory;
  default:
    return false;
  }
}


STEP 6: UNIQUELY IDENTIFY THE CATEGORY

You should provide a function to return a reference to a category object:


const std::error_category& api_category();


such that it always returns a reference to the same object. As with custom error
codes, you can use a global:


api_category_impl api_category_instance;

const std::error_category& api_category()
{
  return api_category_instance;
}


or you can make use of C++0x's thread-safe static variables:


const std::error_category& api_category()
{
  static api_category_impl instance;
  return instance;
}


STEP 7: CONSTRUCT AN ERROR_CONDITION FROM THE ENUM

The <system_error> implementation requires a function called
make_error_condition() to associate an error value with a category:


std::error_condition make_error_condition(api_error e)
{
  return std::error_condition(
      static_cast<int>(e),
      api_category());
}


For completeness, you should also provide the equivalent function for
construction of an error_code. I'll leave that as an exercise for the reader.


STEP 8: REGISTER FOR IMPLICIT CONVERSION TO ERROR_CONDITION

Finally, for the api_error enumerators to be usable as error_condition
constants, enable the conversion constructor using the is_error_condition_enum
type trait:


namespace std
{
  template <>
  struct is_error_condition_enum<api_error>
    : public true_type {};
}


USING THE ERROR CONDITIONS

The api_error enumerators can now be used as error_condition constants, just as
you may use those defined in std::errc:


std::error_code ec;
load_resource("http://some/url", ec);
if (ec == api_error::low_system_resources)
  ...


As I've said several times before, the original error code is retained and no
information is lost. It doesn't matter whether that error code came from the
operating system or from an HTTP library with its own error category. Your
custom error conditions can work equally well with either.


Next, in what will probably be the final instalment, I'll discuss how to design
APIs that use the <system_error> facility.


Posted by chris at 1:30 pm 447 comments:

Labels: c++, c++0x, error_code, system_error



MONDAY, APRIL 12, 2010


SYSTEM ERROR SUPPORT IN C++0X - PART 4



[ part 1, part 2, part 3 ]



CREATING YOUR OWN ERROR CODES

As I stated in part 1, one of the principles behind the <system_error> facility
is user-extensibility. This means that you can use the mechanism just described
to define your own error codes.


In this section, I'll outline what you need to do. As a basis for a worked
example, I will assume you're writing an HTTP library and need errors that
correspond to the HTTP response codes.


STEP 1: DEFINE THE ERROR VALUES

You first need to define the set of error values. If you're using C++0x, you can
use an enum class, similar to std::errc:


enum class http_error
{
  continue_request = 100,
  switching_protocols = 101,
  ok = 200,
  ...
  gateway_timeout = 504,
  version_not_supported = 505
};


The errors are assigned values according to the HTTP response codes. The
importance of this will become obvious when it comes to using the error codes.
Whatever values you choose, errors should have non-zero values. As you may
recall, the <system_error> facility uses a convention where zero means success.


You can use regular (that is, C++03-compatible) enums by dropping the class
keyword:


enum http_error
{
  ...
};


Note: C++0x's enum class differs from enum in that the former encloses
enumerator names in the class scope. To access an enumerator you must prefix the
class name, as in http_error::ok. You can approximate this behaviour by wrapping
the plain enum in a namespace:


namespace http_error
{
  enum http_error_t
  {
    ...
  };
}


For the remainder of this example I will assume the use of enum class. Applying
the namespace-wrapping approach is left as an exercise for the reader.


STEP 2: DEFINE AN ERROR_CATEGORY CLASS

An error_code object consists of both an error value and a category. The error
category determines whether a value of 100 means http_error::continue_request,
std::errc::network_down (ENETDOWN on Linux), or something else entirely.


To create a new category, you must derive a class from error_category:


class http_category_impl
  : public std::error_category
{
public:
  virtual const char* name() const;
  virtual std::string message(int ev) const;
};


For the moment, this class will implement only error_category's pure virtual
functions.


STEP 3: GIVE THE CATEGORY A HUMAN-READABLE NAME

The error_category::name() virtual function must return a string identifying the
category:


const char* http_category_impl::name() const
{
  return "http";
}


This name does not need to be universally unique, as it is really only used when
writing an error code to a std::ostream. However, it would certainly be
desirable to make it unique within a given program.


STEP 4: CONVERT ERROR CODES TO STRINGS



The error_category::message() function converts an error value into a string
that describes the error:


std::string http_category_impl::message(int ev) const
{
  switch (ev)
  {
  case http_error::continue_request:
    return "Continue";
  case http_error::switching_protocols:
    return "Switching protocols";
  case http_error::ok:
    return "OK";
  ...
  case http_error::gateway_timeout:
    return "Gateway time-out";
  case http_error::version_not_supported:
    return "HTTP version not supported";
  default:
    return "Unknown HTTP error";
  }
}


When you call the error_code::message() member function, the error_code in turn
calls the above virtual function to obtain the error message.


It's important to remember that these error messages must stand alone; they may
be written (to a log file, say) at a point in the program when no additional
context is available. If you are wrapping an existing API that uses error
messages with "inserts", you'll have to create your own messages. For example,
if an HTTP API uses the message string "HTTP version %d.%d not supported", the
equivalent stand-alone message would be "HTTP version not supported".


The <system_error> facility provides no assistance when it comes to localisation
of these messages. It is likely that the messages emitted by your standard
library's error categories will be based on the current locale. If localisation
is a requirement in your program, I recommend using the same approach. (Some
history: The LWG was aware of the need for localisation, but there was no design
before the group that satisfactorily reconciled localisation with
user-extensibility. Rather than engage in some design-by-committee, the LWG
opted to say nothing in the standard about localisation of the error messages.)


STEP 5: UNIQUELY IDENTIFY THE CATEGORY

The identity of an error_category-derived object is determined by its address.
This means that when you write:


const std::error_category& cat1 = ...;
const std::error_category& cat2 = ...;
if (cat1 == cat2)
  ...


the if condition is evaluated as if you had written:


if (&cat1 == &cat2)
  ...


Following the example set by the standard library, you should provide a function
to return a reference to a category object:


const std::error_category& http_category();



This function must always return a reference to the same object. One way to do
that is to define a global object in a source file and return a reference to
that:


http_category_impl http_category_instance;

const std::error_category& http_category()
{
  return http_category_instance;
}


However, using a global does introduce issues to do with order of initialisation
across modules. An alternative approach is to use a locally scoped static
variable:


const std::error_category& http_category()
{
  static http_category_impl instance;
  return instance;
}


In this case, the category object is initialised on first use. C++0x also
guarantees that the initialisation is thread-safe. (C++03 makes no such
guarantee.)


History: In the early design stages, we considered using an integer or string to
identify an error_code's category. The main issue with that approach was
ensuring uniqueness in conjunction with user extensibility. If a category was
identified by integer or string, what was to stop collisions between two
unrelated libraries? Using object identity leverages the linker in preventing
different categories from having the same identity. Furthermore, storing a
pointer to a base class allows us to make error_codes polymorphic while keeping
them as copyable value types.


STEP 6: CONSTRUCT AN ERROR_CODE FROM THE ENUM

As I showed in part 3, the <system_error> implementation requires a function
called make_error_code() to associate an error value with a category. For the
HTTP errors, you would write this function as follows:


std::error_code make_error_code(http_error e)
{
  return std::error_code(
      static_cast<int>(e),
      http_category());
}


For completeness, you should also provide the equivalent function for
construction of an error_condition:


std::error_condition make_error_condition(http_error e)
{
  return std::error_condition(
      static_cast<int>(e),
      http_category());
}


Since the <system_error> implementation finds these functions using
argument-dependent lookup, you should put them in the same namespace as the
http_error type.


STEP 7: REGISTER FOR IMPLICIT CONVERSION TO ERROR_CODE

For the http_error enumerators to be usable as error_code constants, enable the
conversion constructor using the is_error_code_enum type trait:


namespace std
{
  template <>
  struct is_error_code_enum<http_error>
    : public true_type {};
}


STEP 8 (OPTIONAL): ASSIGN DEFAULT ERROR CONDITIONS

Some of the errors you define may have a similar meaning to the standard's errc
error conditions. For example, the HTTP response code 403 Forbidden means
basically the same thing as std::errc::permission_denied.


The error_category::default_error_condition() virtual function lets you define
an error_condition that is equivalent to a given error code. (See part 2 for the
definition of equivalence.) For the HTTP errors, you can write:


class http_category_impl
  : std::error_category
{
public:
  ...
  virtual std::error_condition
    default_error_condition(int ev) const;
};
...
std::error_condition
  http_category_impl::default_error_condition(
    int ev) const
{
  switch (ev)
  {
  case http_error::forbidden:
    return std::errc::permission_denied;
  default:
    return std::error_condition(ev, *this);
  }
}


If you choose not to override this virtual function, an error_code's default
error_condition is one with the same error value and category. This is the
behaviour of the default: case shown above.


USING THE ERROR CODES

You can now use the http_error enumerators as error_code constants, both when
setting an error:


void server_side_http_handler(
    ...,
    std::error_code& ec)
{
  ...
  ec = http_error::ok;
}


and when testing for one:


std::error_code ec;
load_resource("http://some/url", ec);
if (ec == http_error::ok)
  ...


Since the error values are based on the HTTP response codes, we can also set an
error_code directly from the response:


std::string load_resource(
    const std::string& url,
    std::error_code& ec)
{
  // send request ...

  // receive response ...

  int response_code;
  parse_response(..., &response_code);
  ec.assign(response_code, http_category());

  // ...
}


You can also use this technique when wrapping the errors produced by an existing
library.


Finally, if you defined an equivalence relationship in step 8, you can write:


std::error_code ec;
data = load_resource("http://some/url", ec);
if (ec == std::errc::permission_denied)
  ...


without needing to know the exact source of the error condition. As explained in
part 2, the original error code (e.g. http_error::forbidden) is retained so that
no information is lost.


In the next part, I'll show how to create and use custom error_conditions.


Posted by chris at 5:18 pm 169 comments:

Labels: c++, c++0x, error_code, system_error



FRIDAY, APRIL 09, 2010


SYSTEM ERROR SUPPORT IN C++0X - PART 3



[ part 1, part 2 ]



ENUMERATORS AS CLASS CONSTANTS

As we have seen, the <system_error> header defines enum class errc:


enum class errc
{
  address_family_not_supported,
  address_in_use,
  ...
  value_too_large,
  wrong_protocol_type,
};


the enumerators of which are placeholders for error_condition constants:


std::error_code ec;
create_directory("/some/path", ec);
if (ec == std::errc::file_exists)
  ...


Obviously, this is because there's an implicit conversion from errc to
error_condition using a single-argument constructor. Simple. Right?


IT'S NOT QUITE THAT SIMPLE

There's a few reasons why there's a bit more to it than that:


 * The enumerator provides an error value, but to construct an error_condition
   we need to know the category too. The <system_error> facility uses categories
   to support multiple error sources, and a category is an attribute of both
   error_code and error_condition.
   
 * The facility should be user-extensible. That is, users (as well as future
   extensions to the standard library) need to be able to define their own
   placeholders.
   
 * The facility should support placeholders for either error_code or
   error_condition. Although enum class errc provides placeholders for
   error_condition constants, other use cases may require constants of type
   error_code.
   
 * Finally, it should allow explicit conversion from an enumerator to error_code
   or error_condition. Portable programs may need to create error codes that are
   derived from the std::errc::* enumerators.
   

So, while it's true that the line:


if (ec == std::errc::file_exists)


implicitly converts from errc to error_condition, there are a few more steps
involved.


STEP 1: DETERMINE WHETHER THE ENUM IS AN ERROR CODE OR CONDITION

Two type traits are used to register enum types with the facility:


template <class T>
struct is_error_code_enum
  : public false_type {};

template <class T>
struct is_error_condition_enum
  : public false_type {};


If a type is registered using is_error_code_enum<> then it may be implicitly
converted to an error_code. Similarly, if a type is registered using
is_error_condition_enum<>, it can be implicitly converted to error_condition. By
default, types are registered for neither conversion (hence the use of
false_type above), but enum class errc is registered as follows:


template <>
struct is_error_condition_enum<errc>
  : true_type {};


The implicit conversion is accomplished by conditionally enabled conversion
constructors. This is probably implemented using SFINAE, but for simplicity you
need only think of it as:


class error_condition
{
  ...
  // Only available if registered
  // using is_error_condition_enum<>.
  template <class ErrorConditionEnum>
  error_condition(ErrorConditionEnum e);
  ...
};

class error_code
{
  ...
  // Only available if registered
  // using is_error_code_enum<>.
  template <class ErrorCodeEnum>
  error_code(ErrorCodeEnum e);
  ...
};


Therefore, when we write:


if (ec == std::errc::file_exists)


the compiler has to choose between these two overloads:


bool operator==(
    const error_code& a,
    const error_code& b);

bool operator==(
    const error_code& a,
    const error_condition& b);


It chooses the latter because the error_condition conversion constructor is
available, but the error_code one is not.


STEP 2: ASSOCIATE THE VALUE WITH AN ERROR CATEGORY

An error_condition object contains two attributes: value and category. Now that
we're in the constructor, these need to be initialised correctly.


This is accomplished by having the constructor call the function
make_error_condition(). To enable user-extensibility, this function is located
using argument-dependent lookup. Of course, since errc is in namespace std, its
make_error_condition() is found there too.


The implementation of make_error_condition() is simply:


error_condition make_error_condition(errc e)
{
  return error_condition(
      static_cast<int>(e),
      generic_category());
}


As you can see, this function uses the two-argument error_condition constructor
to explicitly specify both the error value and category.


If we were in the error_code conversion constructor (for an appropriately
registered enum type), the function called would be make_error_code(). In other
respects, the construction of error_code and error_condition is the same.




EXPLICIT CONVERSION TO ERROR_CODE OR ERROR_CONDITION

Although error_code is primarily intended for use with OS-specific errors,
portable code may want to construct an error_code from an errc enumerator. For
this reason, both make_error_code(errc) and make_error_condition(errc) are
provided. Portable code can use these as follows:


void do_foo(std::error_code& ec)
{
#if defined(_WIN32)
  // Windows implementation ...
#elif defined(linux)
  // Linux implementation ...
#else
  // do_foo not supported on this platform
  ec = make_error_code(std::errc::not_supported);
#endif
}


SOME HISTORY

The original <system_error> proposal defined error_code constants as objects:


extern error_code address_family_not_supported;
extern error_code address_in_use;
...
extern error_code value_too_large;
extern error_code wrong_protocol_type;


The LWG was concerned about the size overhead of so many global objects, and an
alternative approach was requested. We researched the possibility of using
constexpr, but that was ultimately found to be incompatible with some other
aspects of the <system_error> facility. This left conversion from enum as the
best available design.


Next, I'll start showing how you can extend the facility to add your own error
codes and conditions.


Posted by chris at 8:00 am 50 comments:

Labels: c++, c++0x, error_code, system_error



THURSDAY, APRIL 08, 2010


SYSTEM ERROR SUPPORT IN C++0X - PART 2



[ part 1 ]



ERROR_CODE VS ERROR_CONDITION

Of the 1000+ pages of C++0x draft, the casual reader is bound to notice one
thing: error_code and error_condition look virtually identical! What's going on?
Is it a copy/paste error?


IT'S WHAT YOU DO WITH IT THAT COUNTS

Let's review the descriptions I gave in part 1:


 * class error_code - represents a specific error value returned by an operation
   (such as a system call).
   
 * class error_condition - something that you want to test for and, potentially,
   react to in your code.
   

The classes are distinct types because they're intended for different uses. As
an example, consider a hypothetical function called create_directory():


void create_directory(
    const std::string& pathname,
    std::error_code& ec);


which you call like this:


std::error_code ec;
create_directory("/some/path", ec);


The operation can fail for a variety of reasons, such as:


 * Insufficient permission.
   
 * The directory already exists.
   
 * The path is too long.
   
 * The parent path doesn't exist.
   

Whatever the reason for failure, after create_directory() returns, the
error_code object ec will contain the OS-specific error code. On the other hand,
if the call was successful then ec contains a zero value. This follows the
tradition (used by errno and GetLastError()) of having 0 indicate success and
non-zero indicate failure.


If you're only interested in whether the operation succeeded or failed, you can
use the fact that error_code is convertible-to-bool:


std::error_code ec;
create_directory("/some/path", ec);
if (!ec)
{
  // Success.
}
else
{
  // Failure.
}


However, let's say you're interested in checking for the "directory already
exists" error. If that's the error then our hypothetical caller can continue
running. Let's have a crack at it:


std::error_code ec;
create_directory("/some/path", ec);
if (ec.value() == EEXIST) // No!
  ...


This code is wrong. You might get away with it on POSIX platforms, but don't
forget that ec will contain the OS-specific error. On Windows, the error is
likely to be ERROR_ALREADY_EXISTS. (Worse, the code doesn't check the error
code's category, but we'll cover that later.)


Rule of thumb: If you're calling error_code::value() then you're doing it wrong.


So here you have an OS-specific error code (EEXIST or ERROR_ALREADY_EXISTS) that
you want to check against an error condition ("directory already exists"). Yep,
that's right, you need an error_condition.


COMPARING ERROR_CODES AND ERROR_CONDITIONS

Here is what happens when you compare error_code and error_condition objects
(i.e. when you use operator== or operator!=):


 * error_code against error_code - checks for exact match.
   
 * error_condition against error_condition - checks for exact match.
   
 * error_code against error_condition - checks for equivalence.
   

I hope that it's now obvious that you should be comparing your OS-specific error
code ec against an error_condition object that represents "directory already
exists". C++0x provides one for exactly that: std::errc::file_exists. This means
you should write:


std::error_code ec;
create_directory("/some/path", ec);
if (ec == std::errc::file_exists)
  ...


This works because the library implementor has defined the equivalence between
the error codes EEXIST or ERROR_ALREADY_EXISTS and the error condition
std::errc::file_exists. In a future instalment I'll show how you can add your
own error codes and conditions with the appropriate equivalence definitions.


(Note that, to be precise, std::errc::file_exists is an enumerator of enum class
errc. For now you should think of the std::errc::* enumerators as placeholders
for error_condition constants. In a later part I'll explain how that works.)


HOW TO KNOW WHAT CONDITIONS YOU CAN TEST FOR

Some of the new library functions in C++0x have "Error conditions" clauses.
These clauses list the error_condition constants and the conditions under which
equivalent error codes will be generated.


A BIT OF HISTORY

The original error_code class was proposed for TR2 as a helper component for the
filesystem and networking libraries. In that design, an error_code constant is
implemented so that it matches the OS-specific error, where possible. When a
match is not possible, or where there are multiple matches, the library
implementation translates from the OS-specific error to the standard error_code,
after performing the underlying operation.


In email-based design discussions I learnt the value of preserving the original
error code. Subsequently, a generic_error class was prototyped but did not
satisfy. A satisfactory solution was found in renaming generic_error to
error_condition. In my experience, naming is one of the Hardest Problems in
Computer Science, and a good name will get you most of the way there.


Next up, a look at the mechanism that makes enum class errc work as
error_condition placeholders.


Posted by chris at 7:13 am 130 comments:

Labels: c++, c++0x, error_code, system_error



WEDNESDAY, APRIL 07, 2010


SYSTEM ERROR SUPPORT IN C++0X - PART 1



Among the many new library features in C++0x is a little header called
<system_error>. It provides a selection of utilities for managing, well, system
errors. The principal components defined in the header are:


 * class error_category
   
 * class error_code
   
 * class error_condition
   
 * class system_error
   
 * enum class errc
   

I had a hand in the design of this facility, so in this series of posts I will
try to capture the rationale, history, and intended uses of the various
components.



WHERE TO GET IT

A complete implementation, and one that supports C++03, is included in Boost.
I'd guess that, at this point in time, it is probably the best tested
implementation in terms of portability. Of course, you have to spell things
starting with boost::system:: rather than std::.


An implementation is included with GCC 4.4 and later. However, you must compile
your program with the -std=c++0x option in order to use it.


Finally, Microsoft Visual Studio 2010 will ship with an implementation of the
classes. The main limitation is that the system_category() does not represent
Win32 errors as was intended. More on what that means later.




(Note that these are just the implementations that I am aware of. There may be
others.)






OVERVIEW

Here are the types and classes defined by <system_error>, in a nutshell:


 * class error_category - intended as a base class, an error_category is used to
   define sources of errors or categories of error codes and conditions.
   
 * class error_code - represents a specific error value returned by an operation
   (such as a system call).
   
 * class error_condition - something that you want to test for and, potentially,
   react to in your code.
   
 * class system_error - an exception used to wrap error_codes when an error is
   to be reported via throw/catch.
   
 * enum class errc - a set of generic error condition values, derived from
   POSIX.
   
 * is_error_code_enum<>, is_error_condition_enum<>, make_error_code,
   make_error_condition - a mechanism for converting enum class error values
   into error_codes or error_conditions.
   
 * generic_category() - returns a category object used to classify the
   errc-based error codes and conditions.
   
 * system_category() - returns a category object used for error codes that
   originate from the operating system.
   


PRINCIPLES

This section lists some of the guiding principles I had in mind in designing the
facility. (I cannot speak for the others involved.) As with most software
projects, some were goals at the outset and some were picked up along the way.


NOT ALL ERRORS ARE EXCEPTIONAL

Simply put, exceptions are not always the right way to handle errors. (In some
circles this is a controversial statement, although I really don't understand
why.)


In network programming, for example, there are commonly encountered errors such
as:


 * You were unable to connect to a remote IP address.
   
 * Your connection dropped out.
   
 * You tried to open an IPv6 socket but no IPv6 network interfaces are
   available.
   

Sure, these might be exceptional conditions, but equally they may be handled as
part of normal control flow. If you reasonably expect it to happen, it's not
exceptional. Respectively:


 * The IP address is one of a list of addresses corresponding to a host name.
   You want to try connecting to the next address in the list.
   
 * The network is unreliable. You want to try to reestablish the connection and
   only give up after N failures.
   
 * Your program can drop back to using an IPv4 socket.
   

Another requirement, in the case of Asio, was a way to pass the result of an
asynchronous operation to its completion handler. In this case, I want the
operation's error code to be an argument to the handler callback. (An
alternative approach is to provide a means to rethrow an exception inside the
handler, such as .NET's BeginXYZ/EndXYZ asynchronous pattern. In my opinion,
that design adds complexity and makes the API more error-prone.)


Last, but not least, some domains will be unable or unwilling to use exceptions
due to code size and performance constraints.


In short: be pragmatic, not dogmatic. Use whatever error mechanism suits best in
terms of clarity, correctness, constraints, and, yes, even personal taste. Often
the right place to make the decision between exception and error code is at the
point of use. That means a system error facility should support both.


ERRORS COME FROM MULTIPLE SOURCES

The C++03 standard recognises errno as a source of error codes. This is used by
the stdio functions, some math functions, and so forth.


On POSIX platforms, many system operations do use errno to propagate errors.
POSIX defines additional errno error codes to cover these cases.


Windows, on the other hand, does not make use of errno beyond the standard C
library. Windows API calls typically report their errors via GetLastError.


When one considers network programming, the getaddrinfo family of functions uses
its own set of error codes (EAI_...) on POSIX, but shares the GetLastError()
"namespace" on Windows. Programs that integrate other libraries (for SSL,
regular expressions, or whatever) will encounter other categories of error code.


Programs should be able to manage these error codes in a consistent manner. I'm
especially concerned with enabling composition of operations to create
higher-level abstractions. Combining system calls, getaddrinfo, SSL and regular
expressions into one API should not force the user of that API to deal with an
explosion of error code types. The addition of a new error source to the
implementation of that API should not change the interface.


BE USER-EXTENSIBLE

Users of the standard library need to be able to add their own error sources.
This ability may just be used to integrate a third-party library, but is also
tied in with the desire to create higher-level abstractions. When developing a
protocol implementation such as HTTP, I want to be able to add a set of error
codes corresponding to the errors defined in the RFC.


PRESERVE THE ORIGINAL ERROR CODE

This was not one of my original goals: my thinking was that the standard would
provide a set of well-known error codes. If a system operation returned an
error, it was the responsibility of the library to translate the error into a
well-known code (if such a mapping made sense).


Fortunately, someone showed me the error of my ways. Translating an error code
discards information: the error returned by the underlying system call is lost.
This may not be a big deal in terms of program control flow, but it matters a
lot for program supportability. There is no doubt that programmers will use a
standardised error code object for logging and tracing, and the original error
may be vital in diagnosing problems.


This final principle segues nicely into my topic for part 2: error_code vs
error_condition. Stay tuned.


Posted by chris at 2:45 pm 156 comments:

Labels: c++, c++0x, error_code, system_error



TUESDAY, APRIL 06, 2010


BIND ILLUSTRATED



Asynchronous operations in Asio all expect a function object argument, the
completion handler, which they invoke when the asynchronous operation completes.
The signature of the handler depends on the type of operation. For example, a
handler posted using io_service::post() must have the signature:


void handler();


while an asynchronous wait operation expects:


void handler(error_code ec);


and asynchronous read/write operations want:


void handler(error_code ec, size_t length);


Non-trivial applications will need to pass some context to the completion
handler, such as a this pointer. One way to do this is to use a function object
adapter like boost::bind, std::tr1::bind or (as of C++0x) std::bind.


Unfortunately, for many C++ programmers, bind represents a little bit of magic.
This is not helped by the impenetrable compiler errors that confront you when
you use it incorrectly. And, in my experience, the underlying concept (where
some function arguments are bound up-front, while others are delayed until the
point of call) can present quite a steep learning curve.


I have put together some diagrams to help explain how bind works. For clarity, I
have taken a few liberties with C++ syntax (e.g. omitting the parameter types on
the function call operator) and (over-)simplified bind's implementation.
Finally, the examples are limited to those likely to be useful with Asio.
Comments and suggestions welcome.


--------------------------------------------------------------------------------

bind can be used to adapt a user-supplied function expecting one argument into a
function object that takes zero arguments. The bound value (123 in this example)
is stored in a function object and is automatically passed to the user-supplied
function as required:





[ click images for full size ]


Binding an argument can be used to turn a class member function into a
zero-argument function object. As you know, non-static member functions have an
implicit this parameter. This means that an appropriate pointer needs to be
bound into the function object:





Alternatively, the implicit this can be made explicit by adapting a member
function into a function object taking one argument:



Function objects will often use both bound arguments and arguments supplied at
the point of use. This can be done using member functions:



or non-member functions:



Sometimes the function object's point of use will supply arguments which are not
required to call the target function. bind will automatically discard these
surplus arguments:



The surplus argument(s) need not be the at the end of the function object
signature:



Finally, bind allows you to the reorder arguments to adapt the target function
to the necessary function object signature:



Posted by chris at 4:24 pm 181 comments:

Labels: asio, bind, boost, c++0x



MONDAY, APRIL 05, 2010


TIMEOUTS BY ANALOGY



Most networking-enabled applications have to deal with timeouts. Read or write
operations may continue indefinitely, and programs need a way to determine when
to tear down connections, resend requests, or take whatever other measures are
necessary.


Asio includes the deadline_timer class for managing timeouts. This class aims to
provide a minimal interface for scheduling events. Of course, minimalism gives
little in the way of design guidance, so some users struggle in finding an
elegant way to incorporate timers and timeouts into their programs.


From the minimalist perspective of Asio, there's no one true right way to do it.
(Perhaps there's no better proof of that than my design preferences having
changed over the years.) Yet that answer doesn't get programs written, so in
this post I will try to present a simple mental model for managing timers.



PARKING METERS

High-traffic, commercial areas near where I live have limited on-street parking.
The street parking that is available is metered. It's the usual drill:


   
   
 * Park your vehicle.
   
   
 * Feed some coins into the parking meter (or, as is more likely these days,
   swipe your credit card or send an SMS).
   
   
 * Go do whatever you came to do.
   
   
 * Make sure you return to your vehicle before the meter expires.
   
   

If you don't get back in time, you'd better hope your vehicle hasn't had a visit
from the parking inspector. A visit means a ticket under the wipers and a nasty
fine due.


Parking meters are a good analogy for reasoning about timeouts because it's easy
to identify the two actors:


   
   
 * The driver of the vehicle.
   
   
 * The parking inspector.
   
   

The driver performs the following steps:


    
    
 1. Feeds the meter.
    
    
 2. Leaves the vehicle to run some errands.
    
    
 3. Returns to the vehicle.
    
    
 4. If no ticket has been issued, repeats from step 1.
    
    
 5. If a fine has been issued, goes home.
    
    

The parking inspector's job is simple:


    
    
 1. Checks whether the meter has expired.
    
    
 2. If the meter has expired, writes up a ticket.
    
    
 3. If the meter has not expired, notes how much time is remaining.
    
    
 4. Goes off for a walk until the remaining time has elapsed.
    
    


USING THE ANALOGY TO INFORM PROGRAM DESIGN

Hopefully you've already guessed how these actors map to networked applications:


   
   
 * The driver represents your protocol handling code.
   
   
 * The parking inspector corresponds to your timeout management logic.
   
   

Let's take a look at how this works in a very simple use case.


// The "driver" actor.
void session::handle_read(error_code ec, size_t length)
{
  // On entering this function we have returned to the vehicle.

  if (!ec)
  {
    // Phew, no ticket. Feed the meter.
    my_timer.expires_from_now(seconds(5));

    // Process incoming data.
    // ...

    // Run some more errands.
    my_socket.async_read_some(buffer(my_buffer),
        bind(&session::handle_read, this, _1, _2));
  }
  else
  {
    // We got a ticket. Go home.
  }
}

// The "parking inspector" actor.
void session::handle_timeout(error_code ec)
{
  // On entering this function we are checking the meter.

  // Has the meter expired?
  if (my_timer.expires_from_now() < seconds(0))
  {
    // Write up a ticket.
    my_socket.close();
  }
  else
  {
    // Note remaining time and go for a walk.
    my_timer.async_wait(
        bind(&session::handle_timeout, this, _1));
  }
}


It's important to remember that the driver may need to run multiple errands each
time they leave the vehicle. In protocol terms, you might have a fixed-length
header followed by a variable-length body. You only want to "feed the meter"
once you have received a complete message:


// First part of the "driver" actor.
void session::handle_read_header(error_code ec)
{
  // We're not back at the vehicle yet.

  if (!ec)
  {
    // Process header.
    // ...

    // Run some more errands.
    async_read(my_socket, buffer(my_body),
        bind(&session::handle_read_body, this, _1));
  }
}

// Second part of the "driver" actor.
void session::handle_read_body(error_code ec)
{
  // On entering this function we have returned to the vehicle.

  if (!ec)
  {
    // Phew, no ticket. Feed the meter.
    my_timer.expires_from_now(seconds(5));

    // Process complete message.
    // ...

    // Run some more errands.
    async_read(my_socket, buffer(my_header),
        bind(&session::handle_read_header, this, _1));
  }
  else
  {
    // We got a ticket. Go home.
  }
}


There are many variations on this theme. For example, you may feed the meter
between consecutive errands, varying the amount of money inserted (i.e. setting
different length timeouts) depending on which errand comes next. In protocol
terms, that might mean allowing up to 30 seconds between messages, but only a
further 5 seconds is permitted once the message header has been received.


As I indicated earlier, there's no single right way to manage timeouts. In fact,
there are many different facets to this problem that are probably worth
exploring in their own right. However, I think that the approach shown here is
probably suited to most applications and I would recommend it as a starting
point when designing your timeout handling.


Posted by chris at 10:07 pm 341 comments:

Labels: asio, c++, timeout, timer

Older Posts Home

Subscribe to: Posts (Atom)


ABOUT ME

chris Author of the Boost.Asio library for networking in C++. View my complete
profile



BLOG ARCHIVE

 * ▼  2010 (8)
   * ▼  April (7)
     * System error support in C++0x - part 5
     * System error support in C++0x - part 4
     * System error support in C++0x - part 3
     * System error support in C++0x - part 2
     * System error support in C++0x - part 1
     * Bind illustrated
     * Timeouts by analogy
   * ►  March (1)

 * ►  2009 (4)
   * ►  August (2)
   * ►  July (2)

 * ►  2008 (4)
   * ►  October (1)
   * ►  June (1)
   * ►  May (1)
   * ►  March (1)

 * ►  2007 (3)
   * ►  August (1)
   * ►  April (1)
   * ►  January (1)

 * ►  2006 (4)
   * ►  November (1)
   * ►  October (1)
   * ►  September (2)




LINKS

 * Boost C++ Libraries
 * Boost.Asio Library