boost.asio composed operation run in strand - boost-asio

The code:
In thread 1:
boost::async_read(socket, buffer, strand.wrap(read_handler));
In thread 2:
strand.post([](){socket.async_write_some(buffer, strand.wrap(write_handler))});
It is clear that read_handler, async_write_some, write_handler protected by strand, they will not concurrent. However, async_read is an composed operation, it will call zero or more times to async_read_some, those async_read_some also need protect by strand or else they might concurrent with async_write_some in thread 2.
But from the code, strand only wrap read_handler, how asio make all intermediate operations(async_read_some) also wrapped by the strand?

In short, asio_handler_invoke enables one to customize the invocation of handlers in the context of a different handler. In this case, the object returned from strand.wrap() has a custom asio_handler_invoke strategy associated with it that will dispatch handlers into the strand that wrapped the initial handler. Conceptually, it is as follows:
template <typename Handler>
struct strand_handler
{
void operator()();
Handler handler_;
boost::asio::strand dispatcher_;
};
// Customize invocation of Function within context of custom_handler.
template <typename Function>
void asio_handler_invoke(Function function, strand_handler* context)
{
context->dispatcher_.dispatch(function);
}
strand_handler wrapped_completion_handler = strand.wrap(completion_handler);
using boost::asio::asio_handler_invoke;
asio_handler_invoke(intermediate_handler, &wrapped_completion_handler);
The custom asio_handler_invoke hook is located via argument-dependent lookup. This detail is documented in the Handler requirement:
Causes the function object f to be executed as if by calling f().
The asio_handler_invoke() function is located using argument-dependent lookup. The function boost::asio::asio_handler_invoke() serves as a default if no user-supplied function is available.
For more details on asio_handler_invoke, consider reading this answer.
Be aware that an operation may be attempted within the initiating function. The documentation is specific that intermediate handlers will be invoked within the same context as the final completion handler. Therefore, given:
assert(strand.running_in_this_thread());
boost::async_read(socket, buffer, strand.wrap(read_handler));
the boost::async_read itself must be invoked within the context of strand to be thread-safe. See this answer for more details on thread-safety and strands.

Related

Mono switchIfEmpty emitting twice

I have a scenario where i need to find a method that return a string response from external API. I have two possibilities for this response give me a valid response (with parameter 1 or parameter 2) or if both responses are not valid, return a final empty publisher to chain.
Mono<String> checkResponse(String parameter)
Check if call checkResponse(parameter1) is acceptable, ignore second call (switchIfEmpty) and continue chain, or
Check if call checkResponse(parameter2) is acceptable and continue chain, or
return Mono.Empty() and discard chain
Actually i have
checkResponse(stringArg1)
.switchIfEmpty(checkResponse(stringArg2))
.flatMapMany ...
.flatMap ...
method
public Mono<String> checkResponse(String s)
return webClient.post()
.uri(URI)
.body(BodyInserters.fromValue(s))
.retrieve()
.bodyToMono(String.class)
But switchIfEmpty is always executing.
Regards,
Are you sure that it's actually emitting twice?
There are two aspects of Project Reactor that is important to understand:
On Assembly
On Subscription
This code:
checkResponse(stringArg1)
.switchIfEmpty(checkResponse(stringArg2));
will assemble the Monos for both checkResponse calls.
In essence, the checkResponse-method is called twice - however only the Mono returned from the first checkResponse-call will be subscribed to as long as it emits an item.
You can verify this behaviour with this:
checkResponse(stringArg1)
.doOnSubscribe(s -> System.out.println("First checkResponse subscription"))
.switchIfEmpty(checkResponse(stringArg2)
.doOnSubscribe(s -> System.out.println("Second checkResponse subscription"))
);
Something that's very typical of reactive code is that top-level code within a method that returns a Mono/Flux usually executes at assembly time while all the lambdas passed to their various operators such as map/flatMap/concatMap/etc execute at subscription time.
To illustrate:
public Mono<String> getName(int id) {
// Assembly time
System.out.println("This executes at assembly time");
return userRepo.get(id)
.map(user -> {
// Subscription time
System.out.println("This executes at subscription time");
return user.name;
});
}
If assembling the Mono might be expensive while it may never be subscribed to like in your case here, you can defer assembly until subscription-time using Mono.defer:
checkResponse(stringArg1)
.switchIfEmpty(Mono.defer(() -> checkResponse(stringArg2)));
Actually there's difference between assembly time and subscription time.
Assembly time is when you create your pipeline by building the reactive chain.
Subscription time is when the execution triggered and the data starts to flow. You should consider using callbacks and lambdas since they are lazily evaluated.
So your checkResponse() method called "twice" on assembly time, because it is not a lambda, but just a regular method. And it returns Mono
You can use Mono.defer(() -> checkResponse()) and delay the execution and assembling inner mono until you subscribed.

Determining when a Flow returns no data

The Kotlin flow states the following:
A suspending function asynchronously returns a single value, but how
can we return multiple asynchronously computed values? This is where
Kotlin Flows come in.
However, if the source of my flow is such that when it completes but returns no data, is there a way to determine that from the flow? For example, if the source of the flow calls a backend API but the API returns no data, is there a way to determine when the flow completes and has no data?
There is an onEmpty method that will invoke an action if the flow completes without emitting any items.
It can also be used to emit a special value to the flow to indicate that it was empty. For example, if you have a flow of events you can emit an EmptyDataEvent.
Or you can just do whatever it is that you want to do inside this onEmpty lambda.
Highly related is also the onCompletion method
If the API returns a Flow, it is usually expected to return 0 or more elements. That flow will typically be collected by some piece of code in order to process the values. The collect() call is a suspending function, and will return when the flow completes:
val flow = yourApiCallReturningFlow()
flow.collect { element ->
// process element here
}
// if we're here, the flow has completed
Any other terminal operator like first(), toList(), etc. will handle the flow's completion in some way (and may even cancel the flow early).
I'm not sure about what you're looking for here, but for example there is a terminal operator count:
val flow = yourApiCallReturningFlow()
val hasAnyElement = flow.count() == 0
There is also onEmpty that allows to emit some specific values instead of the empty flow.
I guess it depends on what you want to do when there are items in the flow.
You can just do toList() on the flow and check if it's empty

Why do Env variables like signer_account_id cause error: ProhibitedInView?

I'm getting a ProhibitedInView error for a simple view function that does the following
export function getInfo(): Info {
const caller = context.sender
return infos.getSome(caller)
}
and realised that it's because of the context.sender call.
My assumption/understanding is that as long as a tx doesn't change the state, they can be considered ViewFunctions.
I found a warning against this on the Potential Gotchas, but why do the following functions cause view functions to fail? What does "binding methods that are exposed from nearcore" mean?
signer_account_id
signer_account_pk
predecessor_account_id
On the context of a view functions, there is no such thing as:
signer_account_id
signer_account_pk
predecessor_account_id
It is not required to sign any message to run a view function, and moreover it is not allowed. It is more like inspecting a contract anonymously, nothing should be paid for that, and the result is independent from the caller. Because of this there is no signer_account_id and signer_account_pk.
On the other hand, it is not possible to make a cross-contract call, if you are initiating this call in view mode. Because of this there is no predecessor_account_id available, since signer_account_id is not available, and it is impossible that this was called from another contract.

NullReferenceException on bool, int, or other stack variable

First of all: the title of this post does not match the actual question I have.
But I am also supplying the answer to the original problem (NullRefExcp on bool), so other users will find it's solution here by the chosen title.
I have a class, similar to the following:
ref class CTest
{
bool m_bInit;
void func()
{
if (!m_bInit)
return;
...
}
...
}
Today I had the problem that func crashed with a NullReferenceException at some point although it had been executed successfully many times before.
The exception occured in the line if (!m_bInit)!
I know, you all are saying now, that this is impossible. But it actually was this line. The reason was following:
I have two different variables, both named oTest, but at different places. One of them was initialized: oTest = gcnew CTest. Calling func on this oTest worked well. The first call of func on the other oTest failed with the exception from above. The curious thing is, that the crash seems to happen at the query on m_bInit, also the stacktrace of the exception tells so. But this was just the first place where a member of the not initialized object (it was still nullptr) was called.
Therefore, the advice for other users with the same problem: Check the stack backwards to find a function call on an object that is nullptr/null.
My question now is:
Why does the execution not fail on the first call of a function of oTest which is nullptr?
Why is the function entered and executed until the first access to a member?
Actually, in my case 3 functions were entered and a couple of variables were created on the stack and on the heap...
This code:
void func()
{
if (!m_bInit)
return;
...
}
could actually be written as:
void func()
{
if (!this->m_bInit)
return;
...
}
Hopefully now you can see where the problem comes from.
A member function call is just a regular function call that includes the this parameter implicitly (it's passed along with the other parameters).
The C++/CLI compiler won't perform a nullptr check when calling non-virtual functions - it emits a call MSIL opcode.
This is not actually the case in C#, since the C# compiler will emit the callvirt MSIL opcode even for non-virtual functions. This opcode forces the JIT to perform a null check on the target instance. The only ways you could get this error in C# is by calling the function via reflection or by generating your own IL that uses the call opcode.

asio strand and data synchronization

I found that asio's doc about synchronization by strand is obscure. It just says that in a thread pool design asio app, the handler can be run in any thread which had call io_service::run(). Using a strand to warp these handler can make their execution concurrent correctly. In its example3, all handle_read are wrap by strand, and I think the variables in Connection class such as buffer has been synchronized by strand, different thread calls handle_read will gets up-to-date data, that is OK. But what about there is a data member defined in Connection class which also accessed by a handler was not wrap by strand? I think this is a problem, isn't it?
In its doc example3, why handle_accept was not wrap by a strand? The new_connection_ is accessed by multi threads: new_connection_.reset called by thread A and server::handle_accept called by thread B. I think it needs data synchronization here or else thread B might use a out-of-date new_connection_ that its reset have not been called yet.
HTTP Server 3 is designed in such a way that it does not actually need a strand.
A fundamental trait of Boost.Asio is that a handler will be called at most once for a given operation. This behavior allows for the call path of asynchronous programming to be envisioned more as a call chain.
For example, examine the illustrated call chain for the server accepting connections:
server::server(...)
{
start_accept(); --.
} |
.----------------'
| .----------------------------------------.
V V |
void server::start_accept() |
{ |
new_connection_.reset(new connection(...)); |
acceptor_.async_accept(..., handle_accept); --. |
} | |
.--------------------------------------------' |
| |
V |
void server::handle_accept(...) |
{ |
if (!error) |
{ |
new_connection_->start(); |
} |
start_accept(); ---------------------------------'
}
As shown in the illustration, only a single asynchronous event chain is present. With no possibility of concurrent execution of the handlers or operations on new_connection, it is said to be running in an implicit strand. The thread in which the handler, server::handle_accept, runs is inconsequential.
The connection::handle_read call chains and more details about strands are answered in this question.
I think you are somehow missing the meaning of strand. It does not synchronize data access. It syncronize handler calls. This can be understood as "all handlers wrapped by given strand will NOT be called concurrently".
So, your first questsion: if some handler is not wrapped by strand - it can be called concurrently now. So its subject to sync issues and/or RC. Note if you wrap in one place does not mean you are protected from RC; this should be done in every call. Since strand does not know what you calling from other thread unless you use .wrap
Second question: In given example start_accept setting up accept handler handle_accept, and handle_accept is setting new accept handler (via calling start_accept). So, they will not be called concurrently since you cannot create 2 or more async_accept events. Sure, if other thread call start_accept for same "Server" instance - this example can/will fail, but doing so is a clear mistake.