Synchronous and asynchronous callbacks

Source: Internet
Author: User

Synchronous and asynchronous callbacks

Havoc (Original address: http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)

Here are two guidelines for using the callback design API, and add to my notes posts about minor API designs points. I've been on different occasions before about the "Sync vs. Async" callback. This problem really bothers the API designers and users.
Recently, this issue was raised in my handling of Hammersmith (a scale API for MongoDB based on the callback callback). I think this (referred to as the callback-based API, the translator's note) is somewhat unaccustomed to a large number of JVM code writers (new consideration) , because the traditional JVM uses blocking APIs and threads. For me, I'm already familiar with writing client-side code based on event loops.

Introduced
    • A synchronous (synchronous) callback executes before the function returns, that is, when the API is called callback is also in context (the original word is: While the API receiving the callback remains on the stack) . The example can be this: list.foreach(callback); foreach() when you return, you can expect that the callback has been executed on all elements.
    • An asynchronous (asynchronous) or deferred (deferred) callback that executes after the function returns, or at least in the stack space of another thread. The schema for the delay (deferral) includes the thread and main loop "main loops" (other names include the event loops,dispatchers,executors). Asynchronous callbacks are popular in IO-related APIs, for example socket.connect(callback); , you might expect that connect() callback might not have been called when it returns, because he is waiting for the connection to be established.
Guide

Based on past experience, I use these two rules:

    • The callbacks given cannot always be synchronous or asynchronous, but should be described as part of the API documentation.
    • Asynchronous callbacks need to be called directly by the main loop or the Central distribution component (centrally dispatch mechanism) , that is, do not have unnecessary blocks of code in the stack where the callback executes, especially if the blocks may contain locks.
What is the difference between a synchronous callback and an asynchronous callback?

Synchronous and asynchronous callbacks are not the same problem for developers, including apps and class libraries.
Synchronous callbacks:

    • Executing in the original (original) thread (triggering the callback and callback execution is a thread, where the original thread is referred to as the thread that triggers the callback, the translator notes), so it does not need to be concerned about thread safety.
    • In the C + + language, data stored on the stack can be read, such as local variables.
    • In either language, they can access data that is bound to the current thread, such as the thread-local type variable. For example, many Java Web frameworks create variables of type thread-local in the current transaction or request.
    • You can assume that the state of some applications is the same, such as an object exists, a timer is not triggered, IO Access does not occur, or the state of a structure associated with either program.
      Asynchronous callbacks:

    • May be called in another thread (in the thread-based asynchronous schema), so the application needs to have any resource that the callback accesses synchronously.

    • You cannot access any of the original stacks or threads, such as local variables or thread-local type data.
    • If a lock is held in the original thread, then the callback should be called outside the lock.
    • You need to assume that other threads or events may have modified the state of the application.

It is not possible to say which callback method is better and has its merits. Consider:
list.foreach(callback)
In most cases, you might be surprised if callback is delayed and does not do anything in the current thread.
But:
socket.connect(callback)
If you don't delay calling callback it's completely pointless, why set up a callback?
These two examples tell us why a given callback is defined as synchronous or asynchronous, and they are not interchangeable and have different uses.

Select synchronous or asynchronous, but cannot use both

Obviously, you might need to execute callbacks immediately in some scenarios (when the data is ready), and in some other scenario callbacks you need to delay the call (the socket is not ready). A very tempting approach is to perform synchronous callbacks in the event of an asynchronous execution in another situation, where possible. But not a good idea.
Because synchronous and asynchronous have different rules, they produce different bugs. Triggering asynchronous callbacks in a test environment, however, is typical for some unusual scenarios in a production environment that are run in sync.
It is difficult to require application developers to plan and test both synchronous and asynchronous scenarios at the same time, so the simplest is to use library functions: If a callback is delayed in some cases, the delay is always used .

Example: GIO

The Gsimpleasyncresult document in the Gio Library has a typical example of the current problem, and slides directly over the description section to see an example of an asynchronous baking cake. (Gsimpleasyncresult can be equated with future or promise in some frameworks.) In this class, two methods are provided, and the Complete_in_idle () function gives the callback to a "free handle" ( a one-time event loop that is immediately dispatched, the original is: just an immediately-dispatched one-shot Main loop event ) to delay the call, and the normal complete () function synchronously triggers the callback. It is recommended in the documentation to use Complete_in_idle () as much as possible, unless you know that you are already in a callback that does not hold any locks (that is, you are in a call chain from one asynchronous callback to another, there is no need to do asynchronous processing again).

Gsimpleasyncresult is designed to implement an IO API similar to G_file_read_async (), and developers can assume that all callbacks that use these APIs are asynchronous.

Gio is used this way and is forced to be declared in the document because developers have been suffering from the time when they were developed (due to unclear documentation).

Synchronizing resources requires delaying running all of the callback functions they trigger

In fact, the rule is that a class library needs to release all locks it holds before the callback triggers. The simplest way to release all locks is to make the callback asynchronous, delay invoking it until the context returns to the main loop, or invoke it in the context of another thread. (That is, all the code in the current context-including the code that freed the lock-executes before the callback is executed in the main loop or another thread, the translator notes)

This is important because it is not expected that the application will not touch your API inside the callback. If you hold the lock and the program (within the callback, the translator) touches your API, the program will deadlock. (or if you use a recursive lock, you'll also encounter horrible problems.) )

Instead of delaying the callback into the main loop or thread, the synchronized resource can attempt to free all of its locks, but he is very painful, because the lock may happen in context, and you end up having to return a callback function to the function in each context, leaving the callback function to the bearer of the most external lock. Then release the lock and trigger the callback. Bah.

Example: Hammersmith without the use of Akka

In the previously mentioned Hammersmith, the pseudo code given below generates a deadlock:

connection.query({ cursor => /* iterate cursor here, touching connection again */ })

The connection of MongoDB is accessed in turn when the cursor is traversed. The callback function that triggers query in the code of the Connection object ..., and this connection object also holds the connection lock. While not working properly, this code is handy for developers. If a class library does not delay invoking a callback function, the application developer needs to defer calling it itself. Most application developers make mistakes at the beginning, and once they catch errors and fix them, their code is messed up by some asynchronous architectures.

Hammersmith inherited this problem from Netty, and Netty used connection as well; Netty did not attempt to delay invoking the callback function (I can do this immediately, after all, there is not a clear default/Standard/Universal/valid in Java) method to delay invoking the callback function).

At first, my amendment to it was to add a line pool to run the program callback. Unfortunately, this recommended thread pool class for use with Netty does not solve the deadlock problem, and I can only fix it. (All thread pools that resolve deadlock problems have unlimited capacity and resources ...) )

Finally it can be used, but think about it, if the callback-based API is popular, and each jar package uses a callback function in its API, you must have a thread pool. It is drunk to think about it. This is probably why Netty to gamble on this issue. It is too difficult to make a policy control on an underlying network class library.

Example: AKKA Actor

Partly because I was looking for a more solution, I migrated Hammersmith to Akka. Akka implements the actor mode. Actors are message-based rather than callback-based, and typically messages must be delayed. In fact, Akka also deliberately forces you to use a actorref to communicate with the actor, and all associated actorref messages go through a dispatcher (event loop). Assuming you have two actors to communicate, they will use ! or "send Message" functions:

actorOne ! Request("Hello")// then in actorOnesender ! Reply("World")

These messages are distributed through the event loop. I wanted my deadlock problem to be resolved in this mode, but I was in a little trouble when I called the callback function with the lock-the same problem was triggered again. This time the lock locks the actor itself while the actor is handling the message.

Actors in Akka can receive messages from other actors or from Future , Akka encapsulates the sender in a named Channel object. By ! sending a message to an actor, the message is given to dispather for delay, but not to the future; Therefore, the Channel method on the API contract ! does not determine whether it is currently using synchronous or asynchronous.

So here's the problem, one of the actors in actor mode is that an actor can only run in one thread at a time; the actor does not allow another message to enter while processing a message. Therefore, it is dangerous to generate synchronous calls outside the actor, and if there is a lock on the actor, a deadlock will result if the actor itself is tried again in the callback using synchronous invocation.

I wrapped the MongoDB connection into an actor and immediately reproduced the same deadlock problem as in Netty, which was to try to access the connection again in the callback of the query when I was traversing the cursor. The callback for query is ! triggered by the method when it is sent to the future. This Channel ! approach breaks my first rule (it is not defined as synchronous or asynchronous on API conventions), but I expect it to always be asynchronous, and I accidentally break my second rule, which also holds the lock when the callback is triggered.

If it were me, I would fix the problem by placing the delay in the API convention, Channel.! but, as Akka writes, if you implement such an actor, send a reply, and your program's reply handler wants to reverse the actor itself, you have to delay sending these replies manually. I stumbled across this implementation, but there should be a better way to achieve it:

private def asyncSend(channel: AkkaChannel[Any], message: Any) = {    Future(channel ! message, self.timeout)(self.dispatcher)}

The unpleasant thing about this solution is that in order to give a reply to the future for asynchronous processing, the actor's reply is two async.

Fortunately, for Akka, at least it has a solution-using dispatcher. If you use pure Netty, I have to use a dedicated thread pool.

Akka gives the answer to "how callback is asynchronous," but it also requires some special-purpose (special-casing) future to ensure that they are indeed asynchronous.
(* * Update: **akka team is ready to resolve this issue, see details.) )

Summarize

The situation in a JVM environment that is not using Akka is worse than the akka I have found, because there is no dispatcher to use.
Callback-based APIs run well when there is an event loop , because it is important to have the ability to trigger callbacks asynchronously.
That's why callbacks in client-side JavaScript and node. js, like the GTK + UI component library, are running flawlessly. But if you start writing callback-based APIs in the JVM, there is no default solution. You have to borrow some event Loop class library (Akka works well), or reinvent the wheel, or use the thread pool anywhere.

Because the callback-based API is so fashionable ... If you are going to write one, I think you will encounter this topic earlier. Finish the call.

Synchronous and asynchronous callbacks

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.