BDE 3.123.0 Release¶
Schedule¶
The BDE team announces that the BDE 3.123.0 production release was completed on Thursday, August 31, 2023.
BDE 3.123.0 Highlights¶
bsl
Now Provides an Implementation of bsl::stop_token
¶
BDE 3.123.0 adds <bsl_stop_token.h>
, which provides an allocator-aware
implementation of the C++20 <stop_token>
header.
Stop Tokens and Stop Callbacks¶
The need for cancelling an outstanding operation often comes up in asynchronous
code. BDE 3.xxx.x introduces a general-purpose mechanism for using and
implementing such cancellation provided in the <bsl_stop_token.h>
header.
We intend to expand other BDE library APIs (in particular, those in the BHL
HTTP/2 communication library) to make use of this component in the near future.
The <bsl_stop_token.h>
header, provides functionality to facilitate
cooperative cancellation between threads. The bsl::stop_token
class can be
used to observe whether any thread has made a cancellation request on a
corresponding bsl::stop_source
object. A thread that is in possession of a
bsl::stop_token
object may also use the bsl::stop_callback
class
template to register callbacks that will be invoked when a cancellation request
is made. These classes provide the same API as their std
counterparts
defined in the C++20 <stop_token>
header, except that
bsl::stop_callback
is allocator-aware, and the programmer can optionally
supply an allocator when creating a bsl::stop_source
object.
Stop Token Usage From The Library Perspective¶
A library developer might offer the library clients the option of using
bsl::stop_token
to cancel asynchronous operations. For example,
bsl::stop_token
and bsl::stop_callback
can be used to implement a
condition variable wrapper that allows a wait to be interrupted by a
cancellation request. (In C++20, such functionality is available as
std::condition_variable_any
.) The wrapper must hold a bsl::stop_token
object that is used to check whether a cancellation has been requested before
entering a wait. The wrapper must also ensure that the thread that requests a
cancellation wakes up the condition variable if the condition variable is
waiting at the time, by registering a callback that calls broadcast
. For
simplicity, we will show how to implement one possible signature for the
wait
method. Note below that the implementation of the wait
method
registers a callback which will signal the condition variable d_condvar
upon cancellation.
class InterruptibleConditionVariable {
private:
bslmt::Condition d_condvar;
public:
void signal() { d_condvar.signal(); }
void broadcast() { d_condvar.broadcast(); }
template <class t_PREDICATE>
int wait(bslmt::Mutex *mutex,
t_PREDICATE pred,
const bsl::stop_token& stopToken)
// Atomically unlock the specified 'mutex' and suspend execution of
// the current thread until this condition object is signaled and the
// invocation of the specified 'pred' subsequently returns 'true' or
// wait is interrupted by a cancellation request observed by the
// specified 'stopToken'. Return 0 on success or cancellation via
// 'stopToken', and a nonzero value if the wait was interrupted for
// some other reason.
{
// Register a callback to be invoked by the thread that makes a
// cancellation request on the stop state owned by 'stopToken'. This
// callback simply invokes our 'broadcast' method, which will wake up
// any threads that are waiting below after having called 'wait' on
// this object.
bsl::stop_callback stopCb(stopToken, [this] { broadcast(); });
while (!stopToken.stop_requested()) {
if (pred()) {
return 0; // RETURN
}
const int ret = d_condvar.wait(mutex);
if (ret != 0) {
return ret; // RETURN
}
}
return 0;
}
};
Stop Token Usage From The Client’s Perspective¶
To illustrate the intended use of the bsl_stop_token
component, consider an
application built on top of a (future) BHL HTTP/2 API (which will soon be
updated to support bsl::stop_token
. This application will send the same
HTTP/2 requests to multiple hosts, and then cancel all the remaining
outstanding requests as soon as the first response is received from one of the
remote hosts.
int multiHostRequest(bhlb::HttpResponse<bhlb::StringBody> *result,
bhls::Connector *connector,
const bsl::span<const bhlb::Endpoint>& endpoints,
const bhlb::HttpRequest<bhlb::StringBody>& request)
{
// Create a stop source and get a stop token from it.
bsl::stop_source stopSource;
bsl::stop_token stopToken = stopSource.get_token();
bool resultLoaded = false;
bslmt::Latch doneLatch(bsl::ssize(endpoints));
bhlc::HttpResponseHandler handleResponse =
bhlc::HttpResponseHandler().onResponseReceived<bhlb::StringBody>(
[&](const bhlb::HttpResponse<bhlb::StringBody>& response) {
// Request cancellation of all connection attempts and
// any in-flight requests. Only one thread can call
// 'request_stop' and have it return 'true', so
// additional synchronization for loading the result is
// not necessary.
if (stopSource.request_stop()) {
// Load the result.
*result = response;
resultLoaded = true;
}
// Mark completion.
doneLatch.arrive();
return 0;
});
for (int i = 0; i < bsl::ssize(endpoints); ++i) {
// Connect to the remote endpoint.
int rc = connector->connect(
stopToken, // Pass the stop token to the 'connect' operation.
endpoints[i],
bhlb::ConnectOptions(),
[&](bhls::CompletionStatus::Enum status,
const bsl::shared_ptr<bhls::Socket>& socket) {
if (bhls::CompletionStatus::e_SUCCESS != status ||
stopToken.stop_requested()) {
// If connection failed or cancellation was requested
// already, do nothing other than mark completion.
doneLatch.arrive();
return; i // RETURN
}
// Create a connection and bind it into its close callback to
// ensure it's not destroyed until it has stopped.
auto connection =
bsl::make_shared<bhlc::ClientConnection>(socket);
connection->start(bdlf::BindUtil::bind(bdlf::noOp,
connection));
// Shut down the connection on stream closure.
handleResponse.onStreamClosed(bdlf::BindUtil::bind(
bhlc::ClientConnection::shutdown,
connection.get()));
// Send the request, passing it the same stop token.
int rc = bhlc::HttpMessagingUtil::sendRequest(
stopToken,
connection.get(),
request,
handleResponse);
if (0 != rc) {
// If sending the request failed, shut down the
// connection and mark completion.
connection->shutdown();
doneLatch.arrive();
}
});
if (0 != rc) {
// If connection attempt could not be started, mark completion.
doneLatch.arrive();
}
}
// Wait for all connections to complete.
doneLatch.wait();
return resultLoaded ? 0 : -1;
}
Without the stop_token
mechanism, cancelling multiple
connect
-sendRequest
call chains would require significantly more
bookkeeping and thread synchronization, as well as meticulous work to ensure
that cancellation requests cannot be missed, to achieve the same result. In
the example below, we illustrate the additional work that would be required
without stop_token
. Specifically, note the need for storing a multitude of
AttemptCancellationHandle
and bhlc::ClientConnection
objects
synchronized by a carefully managed bsmlt::Mutex
.
int multiHostRequest(bhlb::HttpResponse<bhlb::StringBody> *result,
bhls::Connector *connector,
const bsl::span<const bhlb::Endpoint>& endpoints,
const bhlb::HttpRequest<bhlb::StringBody>& request)
{
bool resultLoaded = false;
bslmt::Latch doneLatch(bsl::ssize(endpoints));
// Bookkeeping and synchronization required for cancellation
bslmt::Mutex cancellationMutex;
bsl::vector<bhls::Connector::AttemptCancellationHandle> cancelHandles(
endpoints.size());
bsl::vector<bsl::optional<bhlc::ClientConnection> > connections(
endpoints.size());
auto cancelAll = [&] {
// Request cancellation of all connection attempts and any
// in-flight requests. The 'cancellationMutex' must be owned by
// this thread.
for (int i = 0; i < bsl::ssize(endpoints); ++i) {
cancelHandles[i].cancel();
if (connections[i]) {
connections[i]->terminate();
}
}
};
bhlc::HttpResponseHandler handleResponse =
bhlc::HttpResponseHandler().onResponseReceived<bhlb::StringBody>(
[&](const bhlb::HttpResponse<bhlb::StringBody>& response) {
// Load the result.
bslmt::LockGuard<bslmt::Mutex> guard(&cancellationMutex);
*result = response;
cancelAll();
return 0;
});
// Hold the mutex to avoid the data race on `cancelHandles`.
bslmt::LockGuard<bslmt::Mutex> guard(&cancellationMutex);
for (int i = 0; i < bsl::ssize(endpoints); ++i) {
// Connect to the remote endpoint.
int rc = connector->connect(
&cancelHandles[i], // Record the cancellation handle.
endpoints[i],
bhlb::ConnectOptions(),
[&, i](bhls::CompletionStatus::Enum status,
const bsl::shared_ptr<bhls::Socket>& socket) {
if (bhls::CompletionStatus::e_SUCCESS != status) {
// If connection failed, mark completion.
doneLatch.arrive();
return; // RETURN
}
bslmt::LockGuard<bslmt::Mutex> guard(&cancellationMutex);
if (resultLoaded) {
// If 'result' is already loaded, mark completion.
doneLatch.arrive();
return; // RETURN
}
// Record the connection for lifetime management and
// cancellation.
connections[i].emplace(socket);
// Only mark completion when the connection has closed.
connections[i]->start(bdlf::BindUtil::bind(
bslmt::Latch::arrive,
&doneLatch));
// Shut down the connection on stream closure.
handleResponse.onStreamClosed(bdlf::BindUtil::bind(
bhlc::ClientConnection::shutdown,
&*connections[i]));
int rc = bhlc::HttpMessagingUtil::sendRequest(
&*connections[i],
request,
handleResponse);
if (0 != rc) {
// If sending the request failed, shut down the
// connection.
connections[i]->shutdown();
}
});
if (0 != rc) {
// If connection attempt could not be started, mark completion.
doneLatch.arrive();
}
}
guard.release()->unlock();
// Wait for all connections to complete.
doneLatch.wait();
return resultLoaded ? 0 : -1;
}
bdlb::Variant
Support for Perfect Forwarding in createInPlace
¶
BDE 3.123.0 adds support for perfect forwarding in the createInPlace
method
of bdlb::Variant
. This allows one to more efficiently construct objects
inside of variants. As an illustration, consider how one might represent an
abstract syntax tree for a simple grammar:
#include <bdlb_variant.h>
#include <bsl_memory.h>
using namespace BloombergLP;
// FORWARD DECLARATIONS
struct AddExpression;
using Expression = bdlb::Variant<int, AddExpression>;
// 'Expression' is an alias to a variant of an 'int' or another
// 'AddExpression' object. The 'int' alternative represents an integer
// literal expression, and the 'AddExpression' alternative represents an
// addition of two sub-expressions.
struct AddExpression {
// CREATORS
AddExpression(bsl::unique_ptr<Expression> lhs,
bsl::unique_ptr<Expression> rhs);
// Create a new 'AddExpression' syntax tree node representing the
// addition of the specified 'lhs' and 'rhs' sub-expressions.
};
Since AddExpression
must be constructed with unique pointers to its
left- and right-hand side sub-expressions, its constructor arguments cannot be
passed in a way that creates a copy. Let’s see how the new facilities in
bdlb::Variant
can come in handy here:
void makeExampleExpression()
{
bsl::unique_ptr<Expression> lhs = bsl::make_unique<Expression>(2);
bsl::unique_ptr<Expression> rhs = bsl::make_unique<Expression>(3);
Expression add;
add.createInPlace<AddExpression>(bsl::move(lhs), bsl::move(rhs));
}
We see that the moved-from lhs
and rhs
are forwarded to the constructor
of the AddExpression
when creating the add
object.
Other New Features¶
The
bdlb::NullableAllocatedValue
class now has additional operations making it more interface-compatible withbdlb::NullableValue
andbsl::optional
.
Fixed DRQSs:¶
Summary |
---|
Cycle removal: bslmt: Remove bslmt_threadgroup from bslmt_sluice.t.cpp |
C++20 Address build failures due to Appendix C [diff.cpp17.over] |
C++20: Correction to compiler version documentation |
Second batch of changes to NullableAllocatedValue |
Fix bsl::stop_token AIX build warning |
Fix bsl::stop_token Solaris build error |
clean up more include issues in bde |
Please fix BSLA_FALLTROUGH macro for clang cpp03 |
First batch of changes to NullableAllocatedValue |
remove unused and invalid for testing only includes in bde |
Please add CANONICAL header annotation to bslstl_ostream.h |
Update bslstl_syncbuf to more closely follow BDE conventions |
[C++20] Associative container tests don’t compile with msvc VS2019 for C++20 |
Cycle Removal: bslstl: Remove cycle with string-like types |
Cycle Removal: bsls: Remove cycle between bsls_fuzztest and timeinterval |
bslstl_iterator.h wrong iter_swap pulled in |
Cycle removal: bslmt: Remove cycles introduced by readwriterlockassert |
Cycle Removal: bslmf: Remove use of IsAccessibleBaseOf from various tests |
Cycle Removal: bdlde: Remove bdlde_hexencoder|decoder cycle |
balber_berencoder’s documentation looks wrong |
correct bdlb_nullableallocatedvalue.h warning from balb_testmessages.cpp and others |
Clean up the NullableAllocatedValue test driver |
bdlb::Variant::createInPlace perfect forwarding |
bdlma_pool.t.cpp: TC 8 fails on clang due to throw from ‘delete’. |
Please address unlikely leak potential in SharedPtrUtil::createUninitialized |
Implement ‘bsl::stop_token’ |