Boost.RunTimeServices: The Glue for Optional Runtime Features
Jul 16, 2025How Boost.RunTimeServices Emerged from Boost.HTTP.Proto Development
During the development of the Boost.HTTP.Proto library, we recognized the need for a flexible mechanism to install and access optional services at runtime without requiring prior knowledge of their specific implementations. For example, building a library with optional support for zlib and Brotli compression, even if those libraries weren’t installed on the user’s machine. This challenge led to the creation of Boost.RunTimeServices, a solution that offers several key benefits to both library developers and users, which I will briefly outline below.
Libraries With No Configuration Macros
One approach to managing optional dependencies in libraries is to use
configuration macros at build time, such as BOOST_HTTP_PROTO_HAS_ZLIB
or
BOOST_COOKIES_HAS_PSL
. However, this approach has major drawbacks:
- Combinatorial explosion of binary variants.
- Users can’t easily determine which features are enabled in a binary.
- Configuration macros leak into downstream libraries, compounding complexity.
- Changing features requires full rebuilds of all dependent code.
- Difficult to distribute a single binary via package managers.
With Boost.RunTimeServices, configuration macros become unnecessary. Features can be queried and installed at runtime. For example, installing an optional zlib inflate service:
rts::context rts_ctx;
rts::zlib::install_inflate_service(rts_ctx);
Then, a library can conditionally use the service:
if(cfg.decompression)
{
auto& svc = ctx.get_service<rts::zlib::inflate_service>();
svc.inflate(stream, rts::zlib::flush::finish);
}
Smaller Binaries by Stripping Unused Features
Since service interfaces are decoupled from implementations, unused services and
their dependencies can be eliminated by the linker. For example the following is
part of the implementation of rts::zlib::inflate_service
:
class inflate_service_impl
: public inflate_service
{
public:
using key_type = inflate_service;
int
init2(stream& st, int windowBits) const override
{
stream_cast sc(st);
return inflateInit2(sc.get(), windowBits);
}
int
inflate(stream& st, int flush) const override
{
stream_cast sc(st);
return ::inflate(sc.get(), flush);
}
// ...
}
The implementation class is only instantiated within:
inflate_service&
install_inflate_service(context& ctx)
{
return ctx.make_service<inflate_service_impl>();
}
Libraries interact only with the abstract interface:
struct BOOST_SYMBOL_VISIBLE
inflate_service
: public service
{
virtual int init2(stream& st, int windowBits) const = 0;
virtual int inflate(stream& st, int flush) const = 0;
// ...
};
If the user never calls install_inflate_service
, the implementation and its
dependencies are omitted from the binary.
In this particular example, having separate services for inflation and deflation
gives us more granularity on the matter. For instance, a client
application that uses Boost.HTTP.Proto will more likely only need to install
rts::zlib::inflate_service
, because it typically only needs to parse
compressed HTTP response messages and compression of HTTP requests almost never
happens in client applications. The reverse is true for server applications and
they might only need to install rts::zlib::deflate_service
, since client
requests usually arrive uncompressed and the server needs to compress responses
(if requested).
Libraries Built Independent of the Availability of Optional Services
Because a library that uses an optional service needs only the interface of that service, there is no need for a build-time dependency. Therefore, we can always build a single version of a library that takes advantage of all optional services if they are available at runtime.
For example, in the case of Boost.HTTP.Proto, one can use the library without any compression services, as users simply don’t install those services and there’s no need to link any extra libraries.
Another user can use the exact same binary of Boost.HTTP.Proto with zlib and Brotli decompression algorithms:
rts::context rts_ctx;
rts::zlib::install_inflate_service(rts_ctx); // links against boost_rts_zlib
rts::brotli::install_decoder_service(rts_ctx); // links against boost_rts_brotli
Optional Services in Downstream Libraries
Assume we want to create a library named Boost.Request that uses Boost.HTTP.Proto and Boost.HTTP.IO, and provides an easy-to-use interface for client-side usage. Such a library doesn’t need to care about optional services and can delegate that responsibility to the end user, allowing them to decide which services to install. For example, Boost.Request can internally query the availability of these services and make requests accordingly:
if(rts_ctx.has_service<brotli::decoder_service>())
encodings.append("br");
if(rts_ctx.has_service<zlib::inflate_service>())
{
encodings.append("deflate");
encodings.append("gzip");
}
if(!accept_encoding.empty())
request.set(field::accept_encoding, encodings.str());
Why This Needs to Be a Separate Library
This is a core library that many other libraries may want to use. For example, a user who installs zlib services expects them to be usable in both Boost.HTTP.Proto and Boost.WS.Proto:
rts::context rts_ctx;
rts::zlib::install_inflate_service(rts_ctx);
rts::zlib::install_deflate_service(rts_ctx);
// Usage site
http_proto::parser parser(rts_ctx);
ws_proto::stream stream(rts_ctx);
User libraries need to link against boost_rts
in order to access
rts::context
. Note that boost_rts
is a lightweight target with no dependency
on optional services like zlib or Brotli.
Existing Challenges
Minimum Library For Mandatory Symbols
A library that uses an optional service might still need to link against a
minimal version that provides necessary symbols such as error_category
instances, because we usually need to instantiate them inside the source and
can’t leave them in headers.
For example, assume a library that needs to call an API to provide the error message:
char const*
error_cat_type::
message(
int ev,
char*,
std::size_t) const noexcept
{
return c_api_get_error_message(ev);
}
This clearly can’t be left in the headers because it would require the existence
of the c_api_get_error_message
symbol at link time, which defeats the purpose
of optional services.
To allow optional linkage, a fallback could be provided:
char const*
error_cat_type::
message(
int ev,
char*,
std::size_t) const noexcept
{
return "service not available";
}
But the remaining question is: where should this implementation go if we want optional linkage against services? Currently, we place this code inside the core Boost.RunTimeServices library, which could become a scalability problem in the future as the number of services grows.
An Even Finer Grain Control Over Used and Unused Symbols
Even though separate services (e.g., inflate_service
, deflate_service
) help
the linker remove unused code; the granularity is still limited. For example, if
a library uses only inflate_service::init
, the linker still includes
inflate_service::init2
and other unused methods. This is because interfaces are
polymorphic and the linker can’t remove individual virtual methods:
class inflate_service_impl
: public inflate_service
{
public:
using key_type = inflate_service;
int
init(stream& st) const override
{
stream_cast sc(st);
return inflateInit(sc.get());
}
int
init2(stream& st, int windowBits) const override
{
stream_cast sc(st);
return inflateInit2(sc.get(), windowBits);
}
// ...
}
Space Overhead and Indirection Cost of Virtual Services
This is probably not an issue for most users, as these costs are negligible in real-world applications. However, a solution that provides the same functionality as virtual service interfaces but without these overheads would be highly desirable.
All Posts by This Author
- 07/16/2025 Boost.RunTimeServices: The Glue for Optional Runtime Features
- 01/10/2025 Boost.Http.Io and Boost.Http.Proto Project Highlights
- 10/25/2024 Boost.Http.Proto Project Highlights
- 07/10/2024 Mohammad's Q2 2024 Update
- 04/22/2024 Mohammad's Q1 2024 Update
- 01/10/2024 Mohammad's Q4 2023 Update
- 10/27/2023 Mohammad's Q3 2023 Update
- View All Posts...