Library for Handling Asynchronous Events in C++
Total Page:16
File Type:pdf, Size:1020Kb
Masaryk University Faculty of Informatics Library for Handling Asynchronous Events in C++ Bachelor’s Thesis Branislav Ševc Brno, Spring 2019 Declaration Hereby I declare that this paper is my original authorial work, which I have worked out on my own. All sources, references, and literature used or excerpted during elaboration of this work are properly cited and listed in complete reference to the due source. Branislav Ševc Advisor: Mgr. Jan Mrázek i Acknowledgements I would like to thank my supervisor, Jan Mrázek, for his valuable guid- ance and highly appreciated help with avoiding death traps during the design and implementation of this project. ii Abstract The subject of this work is design, implementation and documentation of a library for the C++ programming language, which aims to sim- plify asynchronous event-driven programming. The Reactive Blocks Library (RBL) allows users to define program as a graph of intercon- nected function blocks which control message flow inside the graph. The benefits include decoupling of program’s logic and the method of execution, with emphasis on increased readability of program’s logical behavior through understanding of its reactive components. This thesis focuses on explaining the programming model, then proceeds to defend design choices and to outline the implementa- tion layers. A brief analysis of current competing solutions and their comparison with RBL, along with overhead benchmarks of RBL’s abstractions over pure C++ approach is included. iii Keywords C++ Programming Language, Inversion of Control, Event-driven Pro- gramming, Asynchronous Programming, Declarative Programming, Functional Programming iv Contents Introduction 1 Motivation . .1 Thesis Structure . .4 1 Programming Concepts 5 1.1 Imperative and Procedural Programming Paradigm .....5 1.2 Inversion of Control ......................6 1.3 Event-driven Programming Paradigm ............6 1.4 Declarative Programming Paradigm .............7 1.5 Functional Programming Paradigm .............7 2 Design 8 2.1 Operation as a Block .....................8 2.2 Program as a Graph of Blocks .................9 2.3 Events ............................. 10 2.4 Synchronous and Asynchronous Blocks ........... 10 2.5 Nested Graph Composition .................. 13 2.6 Syntactic Sugars ....................... 14 3 Implementation 15 3.1 Core Functionality ...................... 15 3.2 Built-in Blocks ........................ 23 3.3 Executor Blocks ........................ 27 3.4 Algorithm Blocks ....................... 31 3.5 Introspection Layer ...................... 34 3.6 Expression Template Layer .................. 36 4 Evaluation 43 4.1 Use Case Comparison ..................... 43 4.1.1 Boost.Asio . 43 4.1.2 RxCpp . 43 4.1.3 Intel® Threading Building Blocks . 44 4.1.4 Coroutines . 45 4.2 Performance Analysis ..................... 48 4.2.1 Overhead Analysis . 48 4.2.2 Performance Benchmarks . 51 v 4.3 Debugging .......................... 53 5 Conclusion 54 5.1 Future Work .......................... 54 A Asynchronous API Model 56 A.1 Asynchronous Operations .................. 56 A.2 Boost.Asio – An Asynchronous API Example ........ 56 B Technical Details 62 B.1 Build requirements ...................... 62 B.2 Third-party libraries ..................... 62 B.3 Project Structure ....................... 64 Bibliography 67 vi Introduction Most of today’s computer systems and their software have to react on some form of external changes that do not have an exactly defined time of occurrence. The practice of handling these events falls into the field of event-driven programming (see 1.3). Notices of external events can originate from the system’s hard- ware (I/O devices, network, timers, etc.) or from software that exposes its application programming interface (API) as a set of asynchronous func- tions (see A.1). An example of such software is an operating system and its time, peripheral I/O (e.g. networking) and task scheduling related API1. Modern programming languages, including C++, provide sup- port for asynchronous code to some extent. C++’s implementation mainly comprises of built-in language primitives for multi-threaded programming and associated synchronization [2, Chapter 41]. Despite not being viewed as a conservative and lower-level language, C++ still lacks the standard concepts and utilities (as of the C++17 standard2), which would provide higher-level abstractions for more convenient event-driven programming. In recent years, frameworks and libraries, e.g. Reactive Exten- sions (Rx) [3], which aim to simplify event-driven programming, have started to appear in various higher-level languages. While a few of such libraries, including Rx, have also been written for C++ (elabo- rated in 4.1.2), we have decided to design and implement our own library with increased generalization and transparency over existing solutions. We have given it the name Reactive Blocks Library, or RBL. Motivation The aim of this work is to implement a C++ library to support general- purpose event-driven programming with a higher level of abstraction than the plain C++17 standard provides. The library should make 1. E.g. POSIX aio (7), POSIX timer_create (2) [1]. 2. This should change with the arrival of C++20 and its coroutines, see 4.1.4. 1 asynchronous event-driven programming easier in terms of code size, readability, and expressiveness. We would like to be able to write programs based on asynchronous event handling with an emphasis on code clarity in terms of event consequences. Functional and declarative programming concepts can aid us to express the behavior of an event-driven program in a cleaner and informatively more condensed way. The usual approach to event handling is done via IoC (see 1.2) and its most straightforward application – callback functions, as in A.1. To understand a program written as a set of callbacks, the user has to thoroughly read the associated logic and scrape the information about the structure of execution of these callbacks. The purpose of RBL is to separate this information from the se- mantics of individual functions or operations. That is, by segregating the logic among function blocks, which are in return loosely connected to form a graph of consequent operations with the desired collective behavior. A code with callbacks may look like this: void callback_1(){ ...; asynchronous_operation_2(callback_2);...; } void callback_2(){ ...; asynchronous_operation_3(callback_3);...; } void callback_3(){ ...; asynchronous_operation_2(callback_2);...; } As you can also see in Listing A.1, it may not be clear what the flow of execution is, especially in larger programs with multiple asyn- chronous branches, chained operations, and cycles. 2 Our library should capture the dependencies of operations more explicitly. Since the ellipses (...) could represent many statements, the information about event consequences is more densely contained within a syntax like so (considering the ... parts may be grouped into operations as well): ...; asynchronous_operation_1 -> asynchronous_operation_2; ...; asynchronous_operation_2 -> asynchronous_operation_3; ...; asynchronous_operation_3 -> asynchronous_operation_2; ...; From this notation, the imagination of the dependencies is straight- forward: asynchronous_operation_1 asynchronous_operation_2 asynchronous_operation_3 Figure 1: Operation Consequences Since standard exceptions that represent errors during an operation propagate in the same direction as return values, error handling has to be partly reworked for the conformance with the IoC paradigm. Additional goals are therefore: • ability to formulate handling of exceptional cases a similar way as the processing of data; • basic introspection and debugging support, which is typically weakened by each abstraction; • extensibility and modularity, i.e. having different logical parts, or layers. 3 Thesis Structure Chapter 1 – Programming Concepts familiarizes us with the concepts forming the building ground of this thesis. Chapter 2 – Design explains the design choices and the programming paradigm that RBL introduces. Chapter 3 – Implementation outlines RBL’s API layer by layer. Chapter 4 – Evaluation compares RBL against the existing solutions and analyzes the benefits and disadvantages of RBL from both conve- nience and performance perspectives. Appendix A – Asynchronous API Model describes the traditional interface of an asynchronous API in C++. Appendix B – Technical Details summarizes the structure and tech- nologies used by the project. 4 1 Programming Concepts To become fluent with RBL, one has to understand the concepts upon which it stands. RBL shifts asynchronous programming from the tra- ditional, procedural approach to the sphere of declarative and functional programming, which is more suitable for event-driven programming. All of this is done thanks to the Inversion of Control (IoC) principle. RBL encompasses all these concepts in an interesting way. This section further explains the terms and their roles in RBL. 1.1 Imperative and Procedural Programming Paradigm Perhaps the oldest programming paradigm is writing a program as a list of sequentially executed commands that manipulate the state of a program and its acting environment. The imperative programming paradigm remains to be the most popular way to program. It is so, because of its closeness to the hardware that performs the computa- tions (in terms of instructions), which allows us to achieve maximum performance. Procedural programming is a form of imperative, with commands