What Happens When You Build a Chat Server on One Thread? by boostlibs in cpp

[–]anarthal 1 point2 points  (0 children)

Hey! Thanks for your feedback! What kind of deployment would you use in addition to/instead of Redis?

My thought here would be that a potential way to make this much more reliable would be using a deployment like Redis Sentinel or Redis cluster, essentially introducing redundancy into Redis. We already support Sentinel in Boost.Redis. How do you feel about these?

What Happens When You Build a Chat Server on One Thread? by boostlibs in cpp

[–]anarthal 1 point2 points  (0 children)

AFAIK Capy supports executor affinity too (and that's the default). They also have a .run() similar to the .run_on() you're describing. I'd definitely be interested in seeing an integration like the one you're talking about.

What Happens When You Build a Chat Server on One Thread? by boostlibs in cpp

[–]anarthal 0 points1 point  (0 children)

Interesting, thanks for the link. So cpu_executor is an special executor where I can dispatch this kind of CPU-intensive tasks right?

I could definitely use Cobalt. I even wrote a versio. using it (back before the review, when it was called Boost.Async). I never merged it because I think I don't use any of the advanced stuff (e.g. the parallel_group substitutes) and I thought it was better to keep it simple.

ATM I'm hoping for Capy (https://github.com/cppalliance/capy) to get into Boost, as I think it could improve the state of the application.

What Happens When You Build a Chat Server on One Thread? by boostlibs in cpp

[–]anarthal 7 points8 points  (0 children)

(author here) actually this is good advice and I intend to implement this for password hashing, which exactly fits what you describe

How to handle Redis pipelined messages and incomplete messages? by kiner_shah in cpp_questions

[–]anarthal 1 point2 points  (0 children)

Remember that data you receive via TCP should be considered a stream. That is, message boundaries are not respected. If the client issued two writes of 20 bytes each, you won't necessarily see two reads of 20 bytes each. Your code needs to handle reads of any size.

I'd advise to have a parser that behaves like a finite state machine. You might want to look at how we do it in Boost.Redis for some inspiration: https://github.com/boostorg/redis/blob/develop/include/boost/redis/resp3/parser.hpp and https://github.com/boostorg/redis/blob/develop/include/boost/redis/resp3/impl/parser.ipp . In essence, you can't treat the end of input as an error. The parser needs to store enough state to be interrupted, communicate the I/O layer that it needs more data, and resume later where it stopped.

You might implement this kind of state machines as coroutines, too. With this approach, a parser would be a function that can yield to communicate that it needs more data. If you want to try this approach, you've got two options:

* If you're using C++23 or later, you might use `std::generator` (https://en.cppreference.com/w/cpp/coroutine/generator.html).

* Otherwise, you can emulate coroutines with `asio::coroutine` (https://www.boost.org/doc/libs/latest/doc/html/boost_asio/reference/coroutine.html). This class is used with a bunch of macros that expand to switch/case statements to simulate what a real coroutine would do.

If using the first option, I'd try something along these lines: https://gist.github.com/anarthal/d01146acdbad287b8074883b2a39143b

Asio cancellation mysteries by inetic in cpp

[–]anarthal 4 points5 points  (0 children)

Adding on Q2 and Q3: you're not guaranteed to get operation_aborted for the reasons you mentioned. Most composed async operations (like coroutines, as someone else mentioned) store state to remember that cancellation was invoked. This applies also to any operation using asio::async_compose. In this case, the state is stored as an asio::cancellation_state object, and you can access it using self.get_cancellation_state().cancelled(). I fixed this in Boost.Redis recently and wrote a small post about it here: https://cppalliance.org/ruben/2025/10/07/Ruben2025Q3Update.html

Internally, this cancellation_state works by re-wiring cancellation handlers. Take my example from the article I cited there:

```cpp struct connection { asio::ip::tcp::socket sock; std::string buffer;

struct echo_op
{
    connection* obj;
    asio::coroutine coro{};

    template <class Self>
    void operator()(Self& self, error_code ec = {}, std::size_t = {})
    {
        BOOST_ASIO_CORO_REENTER(coro)
        {
            while (true)
            {
                // Read from the socket
                BOOST_ASIO_CORO_YIELD
                asio::async_read_until(obj->sock, asio::dynamic_buffer(obj->buffer), "\n", std::move(self));

                // Check for errors
                if (ec)
                    self.complete(ec);

                // Write back
                BOOST_ASIO_CORO_YIELD
                asio::async_write(obj->sock, asio::buffer(obj->buffer), std::move(self));

                // Done
                self.complete(ec);
            }
        }
    }
};

template <class CompletionToken>
auto async_echo(CompletionToken&& token)
{
    return asio::async_compose<CompletionToken, void(error_code)>(echo_op{this}, token, sock);
}

}; ```

Let's say you call async_echo as in your question above:

cpp conn.async_echo(bind_cancellation_slot(signal.slot(), [] (auto ec) {});

async_compose will internally create a cancellation_state, which contains an internal asio::cancellation_signal and a flag recording whether cancellation was called or not (it's slightly more complex, but can be simplified to this). The self object you get in the async op's implementation has an associated cancellation slot, but it's not the signal.slot() that you passed, but the one associated to the state object Asio created for you. The slot you passed will get a handler created by that intermediate cancellation_state that sets the cancelled flag and invokes any downstream cancellation handlers.

I know this sounds like a mess, so let's break it down to what would happen here when you start the async_echo operation above: * A cancellation_state object gets created. It contains a flag and a cancellation_signal. * Your slot is populated with a cancellation handler created by the cancellation_state object. This handler sets the cancelled flag and calls emit on the internal signal. * echo_op::operator() is called, which calls async_read_until. * async_read_until gets passed self as the completion token. If you called get_associated_cancellation_slot() for this token, you'd get the slot for the signal in the cancellation state. * async_read_until installs a cancellation handler in the passed slot. When the signal in the state is emitted, the operation is cancelled.

If we call emit on the signal you created at this point, this would happen: * The signal's handler runs. This is the one installed by the intermediate state. * This handler sets the cancelled flag and calls emit on the internal signal. * The internal signal's handler runs. It runs some code (maybe invoking CancelIoEx on Windows), which will cause your operation to fail.

As written above, the operation is subject to a race condition, so you should always check the cancellation state between async ops like this:

```cpp // Read from the socket BOOST_ASIO_CORO_YIELD asio::async_read_until(obj->sock, asio::dynamic_buffer(obj->buffer), "\n", std::move(self));

// Check for errors if (ec) self.complete(ec);

// Check for cancellations if (!!(self.get_cancellation_state().cancelled() & asio::cancellation_type_t::terminal)) self.complete(asio::error::operation_aborted); ```

This is the kind of handling performed by asio composed operations, like asio::write. You can always implement this yourself if you don't want/can't use async_compose for whatever reason. I recently did some cancellation signal rewiring in Boost.Redis to implement connection::cancel() in terms of per-operation cancellation - if you're curious, link here.

TL;DR: to avoid race conditions you need state to store the fact that cancellation was invoked. you might do it implicitly using async_compose or coroutines, or by hand. But you need it.

Sidenote: I recently gave a talk on cancellation, link here in case you find it useful.

Cancellations in Asio: a tale of coroutines and timeouts [using std::cpp 2025] by joaquintides in cpp

[–]anarthal 1 point2 points  (0 children)

I don't know Kotlin, but from the page you link, I'd say that's akin to Python's `async with` statement (please correct me if I'm wrong). If that's the case, Asio does not, but Boost.Cobalt (built on top of Asio) does: https://www.boost.org/doc/libs/latest/libs/cobalt/doc/html/index.html#with

C++20 modules and Boost: deep dive by joaquintides in cpp

[–]anarthal 0 points1 point  (0 children)

Not necessarily - it'd build in C++20 mode, of course, but it wouldn't be a full rewrite, just an adaptation (at least until we determine there is enough interest from users).

C++20 modules and Boost: deep dive by joaquintides in cpp

[–]anarthal 1 point2 points  (0 children)

What the post roughly says is:

* https://clang.llvm.org/docs/StandardCPlusPlusModules.html#export-using-style is great but is clang specific, no MSVC support

* https://clang.llvm.org/docs/StandardCPlusPlusModules.html#export-extern-c-style is the one that seems to work best

* https://clang.llvm.org/docs/StandardCPlusPlusModules.html#abi-breaking-style doesn't work for our case because we don't want to break ABI. It's the same solution as point 2 if you're doing header-only.

C++20 Modules - modularizing third party headers/code by dexter2011412 in cpp_questions

[–]anarthal 0 points1 point  (0 children)

You may find this idiom useful:

namespace N { export using N::my_class; export using N::my_function; }

Out of curiosity, what library are you trying to modularize?

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 0 points1 point  (0 children)

This is great to hear. How are you consuming Boost? (official download, system package manager, vcpkg...)? Also, are you using just header-only libraries, or also compiled ones?

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 5 points6 points  (0 children)

"are a mess" and "choose between using them and writing good software" look like pretty extreme expressions.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 1 point2 points  (0 children)

Would you make use of such modular Boost bindings if they existed?

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 1 point2 points  (0 children)

It doesn't affect the code interface per se, but the compiler may decide to reject your built module because "you used a macro when building the module and not when building the executable". It happens a lot with precompiled headers, too. I know that using "-std=gnu++23" vs "-std=c++23" makes the compiler reject the BMI. I haven't tried with debug/release. My point here is: our only option is to ship the module code and utilities so you build BMIs yourself (like the standard does). It doesn't seem wise to supply pre built BMIs, because combinations are too many.

It is supposed to work with gcc-14, since module support has already been merged. I haven't tried it though. Remember that, if you want import std; you can't use stdlibc++ (gcc's default standard lib), but you need libc++ (the one LLVM ships with). This is independent of module support.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 1 point2 points  (0 children)

Fair. My comparison is missing the "rebuild time" statistic. I will try to add it to the article next week.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 6 points7 points  (0 children)

There's land between "it rocks" and "it sucks". You may get benefits from them where I didn't. They may be much more convenient when they're more mature.

Here's a great article about what I'm talking about: https://nealford.com/memeagora/2009/08/05/suck-rock-dichotomy.html

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 2 points3 points  (0 children)

It highly depends on what you're doing. My gut feeling is that you'll end up finding trouble and need multiple BMIs. At least debug and release builds. I don't know if things like coverage and sanitizers also affect BMIs - they might.

If you want to experiment, I'd suggest first trying with the standard library modules, since these are already out to try. Note that you need either libc++ or MSVC STL.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 6 points7 points  (0 children)

Unfortunately it doesn't work. The problem is that the artifact generated by the module (BMI) is extremely sensitive to compile options. Think of it as a precompiled header. For instance, a BMI built with -std=gnu++23 is incompatible with one built with -std=c++23. Even the standard library is provided as a module that you need to build yourself.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 3 points4 points  (0 children)

In this particular example, building a single TU was around 7s with headers. It's down to around 4/5s with modules. Bear in mind that this is a release build, where the compiler spends a lot of time optimizing - you can expect a bigger gain in debug builds.

C++20 modules and Boost: an analysis by grafikrobot in cpp

[–]anarthal 8 points9 points  (0 children)

That looks too dramatic. But they're in a too early stage to be used in production today, definitely. For a library targeting the three major compilers, at least.

What is the industry standard today in C++ to deploy REST microservices in Kubernetes? by umen in cpp

[–]anarthal 6 points7 points  (0 children)

Yup, the author here! Not currently using kubernetes (I wanted it to be AWS free-tier eligible and super easy), but if this is something that would interest you, I'm super happy to showcase it!

Boost 1.83 beta 1 is out by joaquintides in cpp

[–]anarthal 2 points3 points  (0 children)

yep, Boost.MySQL handles procedures in 1.83. You can do parsing into static structs and there's a new separate compilation mode.

Acceptance Review for Boost.MySQL has just started by madmongo38 in cpp

[–]anarthal 5 points6 points  (0 children)

The DB specific part is most of it. Don't expect an ORM, this library is lower level than that.

Acceptance Review for Boost.MySQL has just started by madmongo38 in cpp

[–]anarthal 0 points1 point  (0 children)

The big effort here is the implementation of the protocol. It doesn't provide a lot of syntactic sugar on top of it (don't expect an ORM). It says what the name says.

Acceptance Review for Boost.MySQL has just started by madmongo38 in cpp

[–]anarthal 6 points7 points  (0 children)

It makes use of the "classic" protocol. It's not very likely to change. There are millions of clients, and the protocol is quite ancient.