Forum > Third party

STAX: Single Threaded Asynchronous Execution Framework

(1/3) > >>

Warfley:
Hi,

for the past few weeks I was working on my new project, the Single Threaded Asynchronous EXecution framework (STAX for short), which enables async/await style co-routines for FreePascal.
Today I am pretty satisfied with the feature set and the stability of this project, so I wanted to share it with you.

It is available on GitHub: https://github.com/Warfley/STAX

The basic idea behind STAX is to enable asynchronous execution flows without the use of multithreading. The idea is to split the control flow up into small tasks, which all run on the same thread. Tasks will run until they voluntarily give up their execution time, and another can be scheduled. This introduces two guarantees: 1. there will never be two tasks running simultaniously (i.e. on different CPUs), 2. Tasks will only be interrupted when they are allowing it, i.e. never during any critical section. This completely eliminates the possibility for race conditions, and therefore allows for writing asynchronous code without the requirement for locks or synchronization mechanisms.
This is a major advantage over classical threading, as locking and synchronization mechanisms create a lot of maintainance overhead and can easily introduce bugs like deadlocks.

To guarantee a high degree of concurrency, it must be ensured that tasks yield often to the scheduler. To archive this, the programs need to be designed to consist of multiple small tasks. Rather than one task including a lot of functionality, the functionality must be seperated into multiple smaller tasks, which will depend on one another. When one task requires the functionality of another task, it will schedule that task and then yield to the scheduler until that new task finished, also giving other waiting tasks the chance to be scheduled.
If all tasks are small and often wait for other tasks, a high degree of concurrency can be archived.

Another opportunity for tasks to yield to the scheduler is when waiting for events. This includes the simple sleeping for a certain amount of time, but also waiting for the system. A prime example is the waiting for blocking I/O. In networking applications receiving and sending is usally blocking, meaning when a system call to receive data is made, the system will block that thread until data is available. In STAX this waiting time, until data is available, can be used to schedule other tasks.
An example for this can be seen in the examples/tcptest folder, which implements a TCP echo server which can serve multiple clients on a single thread

Besides not requiring locks another advantage by having all tasks run on the same thread is, that this can be directly incorporated int LCL GUI applications. A very simple approach on how to use STAX in LCL applications can be seen in "examples/pong", where STAX is used to implement a two player Pong game using TCP, where the TCP connection is handled on the same thread as the GUI, being able to directly access the GUI without any form of synchronization mechanism.

To give a small example on how such a STAX program would look, here is the tcp server example:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program server; {$mode objfpc}{$H+} uses  stax, stax.asynctcp, stax.functional; // simple tcp echo serverprocedure HandleConnection(AExecutor: TExecutor; AConnection: TSocket);var  c: Char;begin  while True do  begin    // wait until a char was received    c := specialize Await<Char>(specialize AsyncReceive<Char>(AConnection));    Write(c);    // asynchronously send the response    AExecutor.RunAsync(specialize AsyncSend<Char>(AConnection, c));  end;end; procedure RunServer(AExecutor: TExecutor; AHost: string; APort: Integer);var  Sock: Tsocket;  Conn: TSocket;begin  Sock := TCPServerSocket(AHost, APort);  TCPServerListen(Sock, 10);  while True do  begin    Conn := specialize Await<TSocket>(AsyncAccept(Sock));    // Asynchronously handle the communication to have this task continue to accept new clients    AExecutor.RunAsync(specialize AsyncProcedure<Tsocket>(@HandleConnection, Conn));  end;end; var  exec: TExecutor;begin  exec := TExecutor.Create;  exec.RunAsync(specialize AsyncProcedure<String, Integer>(@RunServer, '0.0.0.0', 1337));  try    exec.Run;  except on E: EUnhandledError do    WriteLn('Unhandled error: ', E.Message);  end;  exec.Free;  ReadLn;end.
Besides tasks, STAX also support generators, which allow to write code that produces multiple results sequentially. Unlike tasks, where memory management can simply be done after finishing, generators can contain potentially infinite code, and might be shared between tasks. For this reason rather than manual memory management (as used for Tasks), they are implemented with reference counted COM interfaces to ease usage. As long as generators are only referenced via the IGenrator interface, no manual considerations for memory manamgement is required.

An example to iterate the filesystem recursively can be found in examples/generators/generatoriterationtest.pas:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program generatoriterationtest; {$mode objfpc}{$H+} uses  SysUtils, stax, stax.functional; procedure IterateDirectory(Yield: specialize TYieldFunction<String>; ADirectory: String);var  SearchRec: TSearchRec;  Entry, SubEntry: string;begin  if FindFirst(ADirectory + PathDelim + '*', faAnyFile, SearchRec) = 0 then  try    repeat      if (SearchRec.Name = '.') or (SearchRec.Name = '..') then        Continue;      Entry := ADirectory + PathDelim + SearchRec.Name;      Yield(Entry);      // recursive descent into directories      if (SearchRec.Attr and faDirectory) = faDirectory then        for SubEntry in specialize AsyncGenerator<String, String>(@IterateDirectory, Entry) do          Yield(SubEntry);    until FindNext(SearchRec) <> 0;  finally    FindClose(SearchRec);  end;end; procedure GeneratorIteratorTest(AExecutor: TExecutor);var  dir: String;begin  for dir in specialize AsyncGenerator<String, String>(@IterateDirectory, '.') do    WriteLn(dir);end; var  exec: TExecutor;begin  exec := TExecutor.Create;  try    exec.RunAsync(AsyncProcedure(@GeneratorIteratorTest));    exec.Run;  finally    exec.Free;  end;  ReadLn;end.  
More technical information can be found in the repositories README.md


I've developed and tested STAX under Windows 10 and Linux, both x86_64 systems. On Windows it works right out of the box. On linux it requires a small change to the RTL i.e. requires a custom FPC build. The required changes are stored as a diff in the fpc.patch of the FPCFiber repository (which is referenced as a submodule in the externals directory of the STAX repository). It can be applied with "git apply" in the local fpc-sources git repository.

For using STAX I am also developing some asynchronous libraries.

For now I am working on an networking lib AsyncNet, which shall provide the functionality of FCL-Net but for asynchronous use: Link. It is also provides with the asyncnet.sockets unit a replacement for the stax.asynctcp unit, now supporting also IPv6 as well as UDP. Besides this, it also includes DNS resolution functionality.

AlexTP:
I posted this intro-text to wiki:
https://wiki.freepascal.org/STAX

PierceNg:
Great stuff!

But existing web frameworks probably need to be modified or new ones written?

MarkMLl:
Well done, and hopefully it will spur the introduction of coroutines as a standard feature in FPC.


--- Quote from: PierceNg on August 31, 2021, 03:52:24 am ---But existing web frameworks probably need to be modified or new ones written?

--- End quote ---

I hate to tell you this but there's more to software than web stuff.

MarkMLl

engkin:
Thank you for sharing. Bookmarked!

Navigation

[0] Message Index

[#] Next page

Go to full version