Asynchronous mode

Argentum application can work in either:

  • synchronous or
  • asynchronous mode.

In the synchronous mode application gets executed this way:

  1. AG-runtime creates and initializes all constants in all modules in a well-defined order:
    • dependent modules are initialized after modules they are depend on,
    • constants within single module are initialized in the declaration order.
  2. The main module entry point gets executed (which actually performs all program logic).
  3. AG-runtime destructs all constants in the reverse order.

In this mode application has access to the following objects:

  1. Constant objects, that are always shared and immutable.
  2. Local objects created inside functions.
  3. Objects that are stored as fields of another objects.

There is no global variables and no mutable global state in this mode: by tracing parent pointers from any objects we reach some root that is always a local variable in some function.

Synchronous mode is the default operation mode. But if the application calls a sys_setMainObject function, it switches the environment in the asynchronous mode. This function makes its parameter object (and all its sub-objects by owning composition pointers) retained and owned by the runtime environment (gives them "static lifetime").

This makes it possible to call different AG-runtime and FFI functions with asynchronous callback delegates connected to these "static lifetime" objects.

How these delegates are activated? In asynchronous mode the AG runtime does not terminate application when the main module entry point finishes its work. Instead it starts the message loop that waits for callback requests to be posted to the main queue. And when these happens, it call these callbacks on the main AG thread. And this callback execution will never be interrupted with another callback request. Instead all callbacks from FFI and AG-runtime will be upheld in a queue and handled in one thread sequentially.

Btw, those FFI and runtime functions that want to call callbacks, could probably work on their own threads. It doesn't matter on which thread this callback is posted, it will be executed on the main AG thread. So no race conditions.

Example:

class AppState {
   fs = FileSystem;
   ...
}
app = AppState;
setMainObject(app);
app.fs.load("file.txt", app.&show(data Blob){
   // use `data` to modify `AppState`
   ...
   setMainObject(?Object)
});
sequenceDiagram Runtime ->>+ mainFn : call mainFn ->>+ Appstate : Create Appstate ->>+ fs : Create fs ->> Appstate: Appstate ->> mainFn : mainFn ->> Runtime : setMainObject Runtime ->> mainFn : mainFn ->>fs : load fs ->> mainFn : mainFn ->>- Runtime : return fs -) Runtime: loaded Runtime ->> Appstate : show Appstate ->>Runtime: setMainObject(none) Runtime->>Appstate : destroy Appstate ->> fs : destroy fs ->>- Appstate : Appstate ->>- Runtime: Runtime ->>Runtime: exit app

Here we call fileSystem.load method and pass there a delegate connected to the show method of the app instance. When the file will be loaded, fileSystem post this delegate to the AG main queue and our code will be executed on our thread in the appropriated moment with no race conditions.

As seen on the diagram AppState and fs objects gained the "static lifetime" they outlive main function.

The AG application itself can post asynchronous requests to its own queue - there is the operator for this:

 delegate ~ (parameters);

For example: myObjectInstance.itsMethod~(42) here we post to the main AG thread a request containing:

  • a weak pointer to a myObjectInstance (delegates always hold weak pointers),
  • the "itsMethod" entry point
  • the number 42.

When all current activity on the main AG thread ends, the runtime immediately calls myObjectInstance.itsMethod(42) (if myObjectInstance will be alive by then of course).

AG runtime has a handy function that posts delayed timer events:

sys_scheduleTimer(timestamp int, callback &());

This makes it possible to combine the "asap completion/continuation processes" with low priority or periodical timer events in one queue and handle them accordingly without races.

Typical example of asynchronous application:

class ApplicationState {
   screen = Scene;
   loader = HttpClient;
}
app = ApplicationState;
setMainObject(app);
scheduleTimer(now() + milliseconds(100), app.screen.redrawFrame);
app.loader.get("https://aglang.org/images/logo.gif", app.scene.&onImg(img){
   addItem(img).opacity.animateTo(256)
});

Conclusion: Asynchronous mode:

  • Unifies asynchronous callback handling across all modules and subsystems.
  • Allows to interop with multithreaded and asynchronous FFI code using correct and hassle-free way.
  • Eliminates typical errors when callbacks are called on random threads.
  • Eliminates all possibilities of races caused by accidental nesting of callbacks from different flows.
  • Explicitly combines the application state in one root object instead of scattering it across modules' global variables.

BTW multithreading in AG is just a small extension of async mode.

Leave a Reply

Your email address will not be published. Required fields are marked *