├── .editorconfig ├── .github └── workflows │ └── test.yml ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── bench ├── Bench.cpp ├── Bench.h ├── CMakeLists.txt ├── README.md ├── io │ ├── BenchIO.cpp │ ├── BenchIO.h │ ├── BenchIOBoostCore.cpp │ ├── BenchIOBoostCore.h │ ├── BenchIOHelp.cpp │ ├── BenchIOTCPClient.cpp │ ├── BenchIOTCPClient.h │ ├── BenchIOTCPClientBoost.cpp │ ├── BenchIOTCPClientBoost.h │ ├── BenchIOTCPServer.cpp │ ├── BenchIOTCPServer.h │ ├── BenchIOTools.cpp │ ├── BenchIOTools.h │ ├── CMakeLists.txt │ ├── README.md │ ├── config-io_uring-aio.json │ ├── config-io_uring-boost.json │ ├── config-strong.json │ ├── config-weak-many-cores.json │ ├── config-weak.json │ └── results │ │ ├── epoll │ │ ├── res_debian11_32core_64log_2500mhz.md │ │ ├── res_ubuntu22_24core_48log_4800mhz.md │ │ ├── res_wsl1_12core_24log_3700mhz_strong.md │ │ └── res_wsl1_12core_24log_3700mhz_weak_many_cores.md │ │ ├── io_uring │ │ ├── res_aio_io_uring_vs_epoll_ubuntu24_24core_4800mhz.md │ │ ├── res_aio_io_uring_vs_epoll_ubuntu24_8core_apple_m3.md │ │ ├── res_aio_vs_boost_ubuntu24_24core_4800mhz.md │ │ ├── res_aio_vs_boost_ubuntu24_8core_apple_m3.md │ │ ├── res_boost_io_uring_vs_epoll_ubuntu24_24core_4800mhz.md │ │ └── res_boost_io_uring_vs_epoll_ubuntu24_8core_apple_m3.md │ │ ├── iocp │ │ ├── res_windows11_12core_24log_3700mhz_weak.md │ │ └── res_windows11_12core_24log_3700mhz_weak_many_cores.md │ │ └── kqueue │ │ └── res_mac10_4core_8log_2500mhz.md ├── mcspqueue │ ├── BenchMultiConsumerQueue.cpp │ ├── BenchMultiConsumerQueueTemplate.hpp │ ├── BenchMultiConsumerQueueTrivial.cpp │ ├── CMakeLists.txt │ ├── README.md │ ├── config.json │ └── results │ │ ├── res_debian5_8core_18log_2300mhz.md │ │ ├── res_mac10_4core_8log_2500mhz.md │ │ ├── res_ubuntu5_8core_8log_2000mhz.md │ │ ├── res_win10_6core_12log_3700mhz.md │ │ └── res_wsl1_6core_12log_3700mhz.md ├── mpscqueue │ ├── BenchMultiProducerQueue.cpp │ ├── BenchMultiProducerQueueTemplate.hpp │ ├── BenchMultiProducerQueueTrivial.cpp │ ├── CMakeLists.txt │ ├── README.md │ ├── config.json │ └── results │ │ ├── res_debian5_8core_16log_2300mhz.md │ │ ├── res_mac10_4core_8log_2500mhz.md │ │ ├── res_ubuntu5_8core_8log_2000mhz.md │ │ ├── res_win10_6core_12log_3700mhz.md │ │ └── res_wsl1_6core_12log_3700mhz.md ├── report.py └── taskscheduler │ ├── BenchTaskScheduler.cpp │ ├── BenchTaskSchedulerTemplate.hpp │ ├── BenchTaskSchedulerTrivial.cpp │ ├── CMakeLists.txt │ ├── README.md │ ├── config.json │ └── results │ ├── res_debian11_32core_64log_2500mhz.md │ ├── res_debian5_8core_18log_2300mhz.md │ ├── res_mac10_4core_8log_2500mhz.md │ ├── res_ubuntu5_8core_8log_2000mhz.md │ ├── res_win10_6core_12log_3700mhz.md │ └── res_wsl1_6core_12log_3700mhz.md ├── examples ├── CMakeLists.txt ├── README.md ├── iocore_01_tcp_hello │ ├── CMakeLists.txt │ └── main.cpp ├── iocore_02_ssl_hello │ ├── CMakeLists.txt │ ├── Certs.cpp │ ├── Certs.h │ └── main.cpp ├── iocore_03_pipeline │ ├── CMakeLists.txt │ └── main.cpp ├── iocore_04_tcp_periodic │ ├── CMakeLists.txt │ └── main.cpp ├── scheduler_01_simple_task │ ├── CMakeLists.txt │ └── main.cpp ├── scheduler_02_coroutine_task │ ├── CMakeLists.txt │ └── main.cpp ├── scheduler_03_multistep_task │ ├── CMakeLists.txt │ └── main.cpp └── scheduler_04_interacting_tasks │ ├── CMakeLists.txt │ └── main.cpp ├── src ├── CMakeLists.txt └── mg │ ├── aio │ ├── CMakeLists.txt │ ├── IOCore.cpp │ ├── IOCore.h │ ├── IOCore_IOCP.cpp │ ├── IOCore_epoll.cpp │ ├── IOCore_iouring.cpp │ ├── IOCore_kqueue.cpp │ ├── IOTask.cpp │ ├── IOTask.h │ ├── IOTask_IOCP.cpp │ ├── IOTask_epoll.cpp │ ├── IOTask_iouring.cpp │ ├── IOTask_kqueue.cpp │ ├── README.md │ ├── SSLSocket.cpp │ ├── SSLSocket.h │ ├── TCPServer.cpp │ ├── TCPServer.h │ ├── TCPSocket.cpp │ ├── TCPSocket.h │ ├── TCPSocketCtl.cpp │ ├── TCPSocketCtl.h │ ├── TCPSocketIFace.cpp │ ├── TCPSocketIFace.h │ ├── TCPSocketSubscription.cpp │ └── TCPSocketSubscription.h │ ├── box │ ├── Algorithm.h │ ├── Assert.cpp │ ├── Assert.h │ ├── Atomic.h │ ├── BinaryHeap.h │ ├── CMakeLists.txt │ ├── ConditionVariable.cpp │ ├── ConditionVariable.h │ ├── Coro.cpp │ ├── Coro.h │ ├── Definitions.h │ ├── DoublyList.h │ ├── Error.cpp │ ├── Error.h │ ├── ForwardList.h │ ├── IOVec.cpp │ ├── IOVec.h │ ├── InterruptibleMutex.cpp │ ├── InterruptibleMutex.h │ ├── Log.cpp │ ├── Log.h │ ├── MultiConsumerQueue.h │ ├── MultiConsumerQueueBase.cpp │ ├── MultiConsumerQueueBase.h │ ├── MultiProducerQueueIntrusive.h │ ├── Mutex.cpp │ ├── Mutex.h │ ├── RefCount.h │ ├── SharedPtr.h │ ├── Signal.cpp │ ├── Signal.h │ ├── StringFunctions.cpp │ ├── StringFunctions.h │ ├── Sysinfo.h │ ├── Sysinfo_Apple.cpp │ ├── Sysinfo_Linux.cpp │ ├── Sysinfo_Win.cpp │ ├── Thread.cpp │ ├── Thread.h │ ├── ThreadFunc.h │ ├── ThreadLocalPool.h │ ├── Thread_Unix.cpp │ ├── Thread_Win.cpp │ ├── Time.h │ ├── Time_Unix.cpp │ ├── Time_Win.cpp │ └── TypeTraits.h │ ├── net │ ├── Buffer.cpp │ ├── Buffer.h │ ├── CMakeLists.txt │ ├── DomainToIP.cpp │ ├── DomainToIP.h │ ├── Host.cpp │ ├── Host.h │ ├── SSL.cpp │ ├── SSL.h │ ├── SSLBioMem.cpp │ ├── SSLBioMem.h │ ├── SSLContext.cpp │ ├── SSLContext.h │ ├── SSLContextOpenSSL.cpp │ ├── SSLContextOpenSSL.h │ ├── SSLOpenSSL.cpp │ ├── SSLOpenSSL.h │ ├── SSLStream.cpp │ ├── SSLStream.h │ ├── SSLStreamOpenSSL.cpp │ ├── SSLStreamOpenSSL.h │ ├── Socket.cpp │ ├── Socket.h │ ├── Socket_Unix.cpp │ ├── Socket_Win.cpp │ ├── URL.cpp │ └── URL.h │ ├── sch │ ├── CMakeLists.txt │ ├── README.md │ ├── Task.cpp │ ├── Task.h │ ├── TaskScheduler.cpp │ └── TaskScheduler.h │ ├── sio │ ├── CMakeLists.txt │ ├── Socket.cpp │ ├── Socket.h │ ├── Socket_Unix.cpp │ ├── Socket_Win.cpp │ ├── TCPServer.cpp │ ├── TCPServer.h │ ├── TCPSocket.cpp │ └── TCPSocket.h │ ├── stub │ ├── BoxStub.cpp │ └── CMakeLists.txt │ └── test │ ├── CMakeLists.txt │ ├── CommandLine.cpp │ ├── CommandLine.h │ ├── Message.cpp │ ├── Message.h │ ├── MetricMovingAverage.cpp │ ├── MetricMovingAverage.h │ ├── MetricSpeed.cpp │ ├── MetricSpeed.h │ ├── Random.cpp │ └── Random.h ├── test ├── CMakeLists.txt ├── UnitTest.cpp ├── UnitTest.h ├── UnitTestSSLCerts.cpp ├── UnitTestSSLCerts.h ├── aio │ ├── UnitTestTCPServer.cpp │ └── UnitTestTCPSocketIFace.cpp ├── box │ ├── UnitTestAlgorithm.cpp │ ├── UnitTestAtomic.cpp │ ├── UnitTestBinaryHeap.cpp │ ├── UnitTestConditionVariable.cpp │ ├── UnitTestDoublyList.cpp │ ├── UnitTestError.cpp │ ├── UnitTestForwardList.cpp │ ├── UnitTestIOVec.cpp │ ├── UnitTestInterruptibleMutex.cpp │ ├── UnitTestLog.cpp │ ├── UnitTestMultiConsumerQueue.cpp │ ├── UnitTestMultiProducerQueue.cpp │ ├── UnitTestMutex.cpp │ ├── UnitTestRefCount.cpp │ ├── UnitTestSharedPtr.cpp │ ├── UnitTestSignal.cpp │ ├── UnitTestString.cpp │ ├── UnitTestSysinfo.cpp │ ├── UnitTestThreadLocalPool.cpp │ └── UnitTestTime.cpp ├── main.cpp ├── net │ ├── UnitTestBuffer.cpp │ ├── UnitTestDomainToIP.cpp │ ├── UnitTestHost.cpp │ ├── UnitTestSSL.cpp │ └── UnitTestURL.cpp ├── sch │ └── UnitTestTaskScheduler.cpp └── sio │ ├── UnitTestTCPServer.cpp │ └── UnitTestTCPSocket.cpp └── tla ├── InterruptibleMutex.cfg ├── InterruptibleMutex.tla ├── MCSPQueue.cfg ├── MCSPQueue.tla ├── README.md ├── TaskScheduler.cfg └── TaskScheduler.tla /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | [{*.{h,cpp},CMakeLists.txt}] 10 | indent_style = tab 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project("ServerBox") 4 | 5 | option(MG_ENABLE_TEST "Configure the tests" 1) 6 | option(MG_ENABLE_BENCH "Configure the benchmarks" 0) 7 | 8 | if (NOT DEFINED CMAKE_CXX_STANDARD) 9 | message(STATUS "Using C++20 standard as default") 10 | set(CMAKE_CXX_STANDARD 20) 11 | else() 12 | message(STATUS "Using C++${CMAKE_CXX_STANDARD} standard as explicitly requested") 13 | endif() 14 | 15 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 16 | add_compile_options( 17 | -Wall -Wextra -Wpedantic -Werror -Wno-unknown-warning-option -Wunused-function 18 | -Wno-invalid-offsetof -Wno-unused-value -Wno-deprecated-copy 19 | -Wno-gnu-zero-variadic-macro-arguments 20 | ) 21 | 22 | set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-pthread") 23 | set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-pthread") 24 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 25 | add_compile_options( 26 | # This is needed at least for correct macro handling. Default behaviour won't 27 | # expand __VA_ARGS__ correctly. 28 | /Zc:preprocessor 29 | /WX /wd4266 /wd4324 /wd4355 /wd4365 /wd4458 /wd4514 /wd4548 /wd4625 /wd4626 30 | /wd4668 /wd4710 /wd4820 /wd5026 /wd5027 /wd5039 /wd5045 /wd5105 /wd5219 /wd26439 31 | /wd26800 32 | # It ignores 'break' and 'fallthrough' done via a macro which makes it annoying 33 | # and pointless. 34 | /wd5262 35 | # Info message about a function being inlined. 36 | /wd4711 37 | ) 38 | endif() 39 | 40 | if (CMAKE_SYSTEM_NAME STREQUAL "Linux") 41 | execute_process(COMMAND uname -r OUTPUT_VARIABLE UNAME_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) 42 | message(-- " Kernel version: " ${UNAME_RESULT}) 43 | string(REGEX MATCH "[0-9]+.[0-9]+" LINUX_KERNEL_VERSION ${UNAME_RESULT}) 44 | endif () 45 | 46 | add_subdirectory(src) 47 | if (MG_ENABLE_TEST) 48 | add_subdirectory(test) 49 | endif() 50 | if (MG_ENABLE_BENCH) 51 | add_subdirectory(bench) 52 | endif() 53 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "x64-Release", 16 | "generator": "Ninja", 17 | "configurationType": "RelWithDebInfo", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "cmakeCommandArgs": "", 21 | "buildCommandArgs": "", 22 | "ctestCommandArgs": "", 23 | "inheritEnvironments": [ "msvc_x64_x64" ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vladislav Shpilevoy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bench/Bench.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/Atomic.h" 5 | #include "mg/box/StringFunctions.h" 6 | #include "mg/box/Time.h" 7 | 8 | #include "mg/test/Random.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace mg { 14 | namespace bench { 15 | 16 | MG_STRFORMAT_PRINTF(1, 2) 17 | void 18 | Report( 19 | const char *aFormat, 20 | ...) 21 | { 22 | va_list va; 23 | va_start(va, aFormat); 24 | ReportV(aFormat, va); 25 | va_end(va); 26 | } 27 | 28 | MG_STRFORMAT_PRINTF(1, 0) 29 | void 30 | ReportV( 31 | const char *aFormat, 32 | va_list aArg) 33 | { 34 | std::string buf = mg::box::StringVFormat(aFormat, aArg); 35 | printf("%s\n", buf.c_str()); 36 | } 37 | 38 | BenchSuiteGuard::BenchSuiteGuard( 39 | const char* aName) 40 | { 41 | Report("======== [%s] suite", aName); 42 | } 43 | 44 | MG_STRFORMAT_PRINTF(2, 3) 45 | BenchCaseGuard::BenchCaseGuard( 46 | const char* aFormat, 47 | ...) 48 | { 49 | va_list va; 50 | va_start(va, aFormat); 51 | std::string buf = mg::box::StringVFormat(aFormat, va); 52 | va_end(va); 53 | Report("==== [%s] case", buf.c_str()); 54 | } 55 | 56 | MG_STRFORMAT_PRINTF(2, 3) 57 | TimedGuard::TimedGuard( 58 | const char* aFormat, 59 | ...) 60 | : myStartMs(mg::box::GetMillisecondsPrecise()) 61 | , myIsStopped(false) 62 | , myDuration(0) 63 | { 64 | va_list va; 65 | va_start(va, aFormat); 66 | myName = mg::box::StringVFormat(aFormat, va); 67 | va_end(va); 68 | } 69 | 70 | void 71 | TimedGuard::Stop() 72 | { 73 | MG_BOX_ASSERT(!myIsStopped); 74 | myIsStopped = true; 75 | myDuration = GetMilliseconds(); 76 | } 77 | 78 | void 79 | TimedGuard::Report() 80 | { 81 | MG_BOX_ASSERT(myIsStopped); 82 | mg::bench::Report("== [%s] took %.6lf ms", 83 | myName.c_str(), myDuration); 84 | } 85 | 86 | double 87 | TimedGuard::GetMilliseconds() 88 | { 89 | MG_BOX_ASSERT(myIsStopped); 90 | return mg::box::GetMillisecondsPrecise() - myStartMs; 91 | } 92 | 93 | const char* 94 | BenchLoadTypeToString( 95 | BenchLoadType aVal) 96 | { 97 | switch (aVal) 98 | { 99 | case BENCH_LOAD_EMPTY: return "empty"; 100 | case BENCH_LOAD_NANO: return "nano"; 101 | case BENCH_LOAD_MICRO: return "micro"; 102 | case BENCH_LOAD_HEAVY: return "heavy"; 103 | default: MG_BOX_ASSERT(false); return nullptr; 104 | } 105 | } 106 | 107 | BenchLoadType 108 | BenchLoadTypeFromString( 109 | const char* aVal) 110 | { 111 | if (mg::box::Strcmp(aVal, "empty") == 0) 112 | return BENCH_LOAD_EMPTY; 113 | if (mg::box::Strcmp(aVal, "nano") == 0) 114 | return BENCH_LOAD_NANO; 115 | if (mg::box::Strcmp(aVal, "micro") == 0) 116 | return BENCH_LOAD_MICRO; 117 | if (mg::box::Strcmp(aVal, "heavy") == 0) 118 | return BENCH_LOAD_HEAVY; 119 | MG_BOX_ASSERT_F(false, "Uknown load type '%s'", aVal); 120 | return BENCH_LOAD_EMPTY; 121 | } 122 | 123 | static inline void 124 | BenchMakeWork( 125 | int aCount) 126 | { 127 | for (int i = 0; i < aCount; ++i) 128 | { 129 | mg::box::AtomicBool flag(mg::tst::RandomBool()); 130 | flag.Store(true); 131 | } 132 | } 133 | 134 | void 135 | BenchMakeNanoWork() 136 | { 137 | BenchMakeWork(20); 138 | } 139 | 140 | void 141 | BenchMakeMicroWork() 142 | { 143 | BenchMakeWork(100); 144 | } 145 | 146 | void 147 | BenchMakeHeavyWork() 148 | { 149 | BenchMakeWork(500); 150 | } 151 | 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /bench/Bench.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/StringFunctions.h" 4 | #include "mg/test/CommandLine.h" 5 | 6 | #define MG_BENCH_FALSE_SHARING_BARRIER(name) \ 7 | MG_UNUSED_MEMBER char name[128] 8 | 9 | namespace mg { 10 | namespace bench { 11 | 12 | MG_STRFORMAT_PRINTF(1, 2) 13 | void Report( 14 | const char *aFormat, 15 | ...); 16 | 17 | MG_STRFORMAT_PRINTF(1, 0) 18 | void ReportV( 19 | const char *aFormat, 20 | va_list aArg); 21 | 22 | struct BenchSuiteGuard 23 | { 24 | BenchSuiteGuard( 25 | const char* aName); 26 | }; 27 | 28 | struct BenchCaseGuard 29 | { 30 | MG_STRFORMAT_PRINTF(2, 3) 31 | BenchCaseGuard( 32 | const char* aFormat, 33 | ...); 34 | }; 35 | 36 | struct TimedGuard 37 | { 38 | MG_STRFORMAT_PRINTF(2, 3) 39 | TimedGuard( 40 | const char* aFormat, 41 | ...); 42 | 43 | void Stop(); 44 | 45 | void Report(); 46 | 47 | double GetMilliseconds(); 48 | 49 | private: 50 | double myStartMs; 51 | std::string myName; 52 | bool myIsStopped; 53 | double myDuration; 54 | }; 55 | 56 | enum BenchLoadType 57 | { 58 | BENCH_LOAD_EMPTY, 59 | BENCH_LOAD_NANO, 60 | BENCH_LOAD_MICRO, 61 | BENCH_LOAD_HEAVY, 62 | }; 63 | 64 | const char* BenchLoadTypeToString( 65 | BenchLoadType aVal); 66 | 67 | BenchLoadType BenchLoadTypeFromString( 68 | const char* aVal); 69 | 70 | static inline BenchLoadType 71 | BenchLoadTypeFromString( 72 | const std::string& aVal) 73 | { 74 | return BenchLoadTypeFromString(aVal.c_str()); 75 | } 76 | 77 | void BenchMakeNanoWork(); 78 | 79 | void BenchMakeMicroWork(); 80 | 81 | void BenchMakeHeavyWork(); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_library(bench 4 | Bench.cpp 5 | ) 6 | target_link_libraries(bench 7 | mgbox 8 | mgboxstub 9 | mgtest 10 | ) 11 | target_include_directories(bench PUBLIC 12 | ${CMAKE_SOURCE_DIR}/src 13 | ${CMAKE_SOURCE_DIR}/bench 14 | ) 15 | 16 | add_subdirectory(io) 17 | add_subdirectory(mcspqueue) 18 | add_subdirectory(mpscqueue) 19 | add_subdirectory(taskscheduler) 20 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | See `/results/` folders for reports. 4 | 5 | ## Method 6 | 7 | The idea is not just run some target features against artificial load. The point is to compare it with an alternative implementation. 8 | 9 | The benchmarks are implemented in multiple executables. Each exe uses one implementation of a certain feature. 10 | 11 | For example, take `TaskScheduler`. There are 2 exes: 12 | 13 | * `bench_taskscheduler`. It runs `TaskScheduler`, the real task scheduler with all its features. 14 | * `bench_taskscheduler_trivial`. It runs an alternative task scheduler implemented in a very trivial way, extremely simple. It lacks most features but is quite fast in some scenarios. 15 | 16 | Both exes run exactly the same bench scenarios, but using different scheduler implementations. The same works for other features. For instance, `IOCore` is compared with `boost::asio`; the lock-free queues are compared with trivial mutex-locked queues. 17 | 18 | ## Running 19 | 20 | The executables can be run locally either directly or via a script. 21 | 22 | **Direct** run is just starting the exe, providing the parameters, observing the output. The parameters better see in the code or look which ones are passed by the `config.json`. Can run individual tests, or can run one of them multiple times and get aggregated info printed. Like min/median/max values of a target metric. 23 | 24 | **Script** is how to run many benchmarks with multiple scenarios and compare different implementations. The script `report.py` takes a JSON config which provides exes and scenarios to test (see `report.py --help`); runs the exes on all scenarios; generates a markdown report. Like the ones stored in `results` folders. The easiest way to understand how to run it is to look at the example configs and at the source code. 25 | -------------------------------------------------------------------------------- /bench/io/BenchIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace mg { 4 | namespace bench { 5 | namespace io { 6 | 7 | class Instance 8 | { 9 | public: 10 | virtual ~Instance() = default; 11 | 12 | virtual bool IsFinished() const = 0; 13 | private: 14 | }; 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bench/io/BenchIOBoostCore.cpp: -------------------------------------------------------------------------------- 1 | #include "BenchIOBoostCore.h" 2 | 3 | #include "Bench.h" 4 | #include "mg/box/ThreadFunc.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace bench { 10 | namespace io { 11 | 12 | static void 13 | BenchIOTCPClientIOCtxWorker( 14 | boost::asio::io_context& aCtx) 15 | { 16 | boost::asio::executor_work_guard 17 | work(aCtx.get_executor()); 18 | aCtx.run(); 19 | } 20 | 21 | BoostIOCore::BoostIOCore() 22 | : myCtx(nullptr) 23 | { 24 | } 25 | 26 | BoostIOCore::~BoostIOCore() 27 | { 28 | Stop(); 29 | } 30 | 31 | void 32 | BoostIOCore::Start( 33 | uint32_t aThreadCount) 34 | { 35 | MG_BOX_ASSERT(myCtx == nullptr); 36 | myCtx = new boost::asio::io_context(); 37 | myWorkers.reserve(aThreadCount); 38 | for (uint32_t i = 0; i < aThreadCount; ++i) 39 | { 40 | mg::box::Thread *t = new mg::box::ThreadFunc("mgben.io", 41 | std::bind(BenchIOTCPClientIOCtxWorker, std::ref(*myCtx))); 42 | myWorkers.push_back(t); 43 | t->Start(); 44 | } 45 | } 46 | 47 | void 48 | BoostIOCore::Stop() 49 | { 50 | if (myCtx == nullptr) 51 | return; 52 | myCtx->stop(); 53 | for (mg::box::Thread* t : myWorkers) 54 | t->StopAndDelete(); 55 | myWorkers.clear(); 56 | delete myCtx; 57 | myCtx = nullptr; 58 | } 59 | 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bench/io/BenchIOBoostCore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Thread.h" 4 | 5 | #include 6 | 7 | F_DECLARE_CLASS(boost, asio, io_context) 8 | 9 | namespace mg { 10 | namespace bench { 11 | namespace io { 12 | 13 | class BoostIOCore 14 | { 15 | public: 16 | BoostIOCore(); 17 | ~BoostIOCore(); 18 | 19 | void Start( 20 | uint32_t aThreadCount); 21 | 22 | void Stop(); 23 | 24 | boost::asio::io_context& GetCtx() { return *myCtx; } 25 | 26 | private: 27 | boost::asio::io_context* myCtx; 28 | std::vector myWorkers; 29 | }; 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bench/io/BenchIOTCPClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BenchIO.h" 4 | 5 | #include "mg/aio/IOCore.h" 6 | 7 | namespace mg { 8 | namespace bench { 9 | namespace io { 10 | 11 | class Reporter; 12 | 13 | namespace aiotcpcli { 14 | 15 | class Client; 16 | 17 | struct Settings 18 | { 19 | Settings(); 20 | 21 | uint32_t myRecvSize; 22 | uint32_t myMsgParallel; 23 | uint32_t myIntCount; 24 | uint32_t myPayloadSize; 25 | uint32_t myDisconnectPeriod; 26 | std::vector myPorts; 27 | uint32_t myClientsPerPort; 28 | uint32_t myThreadCount; 29 | uint64_t myTargetMessageCount; 30 | mg::net::Host myHostNoPort; 31 | }; 32 | 33 | struct Stat 34 | { 35 | Stat(); 36 | 37 | mg::box::AtomicU64 myMessageCount; 38 | }; 39 | 40 | class Instance final : public mg::bench::io::Instance 41 | { 42 | public: 43 | Instance( 44 | const Settings& aSettings, 45 | Reporter& aReporter); 46 | 47 | ~Instance() final; 48 | 49 | bool IsFinished() const final; 50 | 51 | private: 52 | std::vector myClients; 53 | mg::aio::IOCore myCore; 54 | Stat myStat; 55 | const Settings mySettings; 56 | }; 57 | 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bench/io/BenchIOTCPClientBoost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BenchIO.h" 4 | #include "BenchIOBoostCore.h" 5 | #include "mg/net/Host.h" 6 | 7 | namespace mg { 8 | namespace bench { 9 | namespace io { 10 | 11 | class Reporter; 12 | 13 | namespace bsttcpcli { 14 | 15 | class Client; 16 | 17 | struct Settings 18 | { 19 | Settings(); 20 | 21 | uint32_t myRecvSize; 22 | uint32_t myMsgParallel; 23 | uint32_t myIntCount; 24 | uint32_t myPayloadSize; 25 | uint32_t myDisconnectPeriod; 26 | std::vector myPorts; 27 | uint32_t myClientsPerPort; 28 | uint32_t myThreadCount; 29 | uint64_t myTargetMessageCount; 30 | mg::net::Host myHostNoPort; 31 | }; 32 | 33 | struct Stat 34 | { 35 | Stat(); 36 | 37 | mg::box::AtomicU64 myMessageCount; 38 | }; 39 | 40 | class Instance final : public mg::bench::io::Instance 41 | { 42 | public: 43 | Instance( 44 | const Settings& aSettings, 45 | Reporter& aReporter); 46 | 47 | ~Instance() final; 48 | 49 | bool IsFinished() const final; 50 | 51 | private: 52 | BoostIOCore myCore; 53 | Stat myStat; 54 | const Settings mySettings; 55 | }; 56 | 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bench/io/BenchIOTCPServer.cpp: -------------------------------------------------------------------------------- 1 | #include "BenchIOTCPServer.h" 2 | #include "BenchIOTools.h" 3 | 4 | #include "mg/aio/TCPServer.h" 5 | #include "mg/aio/TCPSocket.h" 6 | #include "mg/aio/TCPSocketSubscription.h" 7 | #include "mg/test/CommandLine.h" 8 | #include "mg/test/Message.h" 9 | 10 | namespace mg { 11 | namespace bench { 12 | namespace io { 13 | namespace aiotcpsrv { 14 | 15 | class Peer 16 | : public mg::aio::TCPSocketSubscription 17 | { 18 | public: 19 | Peer( 20 | mg::aio::IOCore& aCore, 21 | Reporter& aReporter, 22 | const Settings& aSettings, 23 | mg::net::Socket aSock) 24 | : mySocket(nullptr) 25 | , myRecvSize(aSettings.myRecvSize) 26 | , myReporter(aReporter) 27 | { 28 | myReporter.StatAddConnection(); 29 | mg::aio::TCPSocketParams sockParams; 30 | mySocket = new mg::aio::TCPSocket(aCore); 31 | ((mg::aio::TCPSocket*)mySocket)->Open(sockParams); 32 | mySocket->PostRecv(myRecvSize); 33 | mySocket->PostWrap(aSock, this); 34 | } 35 | 36 | void 37 | Delete(); 38 | 39 | private: 40 | ~Peer() = default; 41 | 42 | void 43 | OnRecv( 44 | mg::net::BufferReadStream& aStream) override 45 | { 46 | mg::tst::ReadMessage rmsg(aStream); 47 | mg::tst::WriteMessage wmsg; 48 | Message msg; 49 | while (rmsg.IsComplete()) 50 | { 51 | BenchIODecodeMessage(rmsg, msg); 52 | myReporter.StatAddMessage(); 53 | BenchIOEncodeMessage(wmsg, msg); 54 | } 55 | mySocket->SendRef(wmsg.TakeData()); 56 | mySocket->Recv(myRecvSize); 57 | } 58 | 59 | void 60 | OnClose() override 61 | { 62 | myReporter.StatDelConnection(); 63 | mySocket->Delete(); 64 | delete this; 65 | } 66 | 67 | mg::aio::TCPSocketIFace* mySocket; 68 | const uint64_t myRecvSize; 69 | Reporter& myReporter; 70 | }; 71 | 72 | class ServerSub final 73 | : public mg::aio::TCPServerSubscription 74 | { 75 | public: 76 | ServerSub( 77 | const Settings& aSettings, 78 | mg::aio::IOCore& aCore, 79 | Reporter& aReporter) 80 | : mySettings(aSettings) 81 | , myCore(aCore) 82 | , myReporter(aReporter) 83 | { 84 | } 85 | 86 | private: 87 | ~ServerSub() = default; 88 | 89 | void 90 | OnAccept( 91 | mg::net::Socket aSock, 92 | const mg::net::Host&) final 93 | { 94 | new Peer(myCore, myReporter, mySettings, aSock); 95 | } 96 | 97 | void 98 | OnClose() final 99 | { 100 | MG_BOX_ASSERT(!"Unreachable"); 101 | } 102 | 103 | const Settings& mySettings; 104 | mg::aio::IOCore& myCore; 105 | Reporter& myReporter; 106 | }; 107 | 108 | Settings::Settings() 109 | : myRecvSize(8192) 110 | , myThreadCount(MG_IOCORE_DEFAULT_THREAD_COUNT) 111 | { 112 | } 113 | 114 | Instance::Instance( 115 | const Settings& aSettings, 116 | Reporter& aReporter) 117 | : mySettings(aSettings) 118 | { 119 | myCore.Start(aSettings.myThreadCount); 120 | ServerSub* sub = new ServerSub(mySettings, myCore, aReporter); 121 | for (uint16_t port : mySettings.myPorts) 122 | { 123 | mg::box::Error::Ptr err; 124 | mg::aio::TCPServer::Ptr server = mg::aio::TCPServer::NewShared(myCore); 125 | MG_BOX_ASSERT(server->Bind(mg::net::HostMakeAllIPV4(port), err)); 126 | MG_BOX_ASSERT(server->Listen(mg::net::SocketMaxBacklog(), sub, err)); 127 | server.Unwrap(); 128 | } 129 | } 130 | 131 | Instance::~Instance() 132 | { 133 | MG_BOX_ASSERT(!"Unreachable"); 134 | } 135 | 136 | bool 137 | Instance::IsFinished() const 138 | { 139 | return false; 140 | } 141 | 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /bench/io/BenchIOTCPServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BenchIO.h" 4 | 5 | #include "mg/aio/IOCore.h" 6 | #include "mg/aio/TCPServer.h" 7 | 8 | namespace mg { 9 | namespace bench { 10 | namespace io { 11 | 12 | class Reporter; 13 | 14 | namespace aiotcpsrv { 15 | 16 | struct Settings 17 | { 18 | Settings(); 19 | 20 | uint32_t myRecvSize; 21 | std::vector myPorts; 22 | uint32_t myThreadCount; 23 | }; 24 | 25 | class Instance final : public mg::bench::io::Instance 26 | { 27 | public: 28 | Instance( 29 | const Settings& aSettings, 30 | Reporter& aReporter); 31 | 32 | ~Instance() final; 33 | 34 | bool IsFinished() const final; 35 | 36 | private: 37 | mg::aio::IOCore myCore; 38 | const Settings mySettings; 39 | }; 40 | 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bench/io/BenchIOTools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | #include "mg/box/Thread.h" 5 | #include "mg/test/MetricMovingAverage.h" 6 | #include "mg/test/MetricSpeed.h" 7 | 8 | #include 9 | 10 | F_DECLARE_CLASS(mg, tst, ReadMessage) 11 | F_DECLARE_CLASS(mg, tst, WriteMessage) 12 | 13 | namespace mg { 14 | namespace bench { 15 | namespace io { 16 | 17 | struct Message 18 | { 19 | Message(); 20 | 21 | uint64_t myTimestamp; 22 | uint32_t myIntCount; 23 | uint32_t myIntValue; 24 | uint64_t myPayloadSize; 25 | }; 26 | 27 | enum ReportMode 28 | { 29 | REPORT_MODE_ONLINE, 30 | REPORT_MODE_SUMMARY, 31 | }; 32 | 33 | struct MetricMoment 34 | { 35 | double myLatency; 36 | uint64_t mySpeed; 37 | }; 38 | 39 | class Reporter final : private mg::box::Thread 40 | { 41 | public: 42 | Reporter(); 43 | ~Reporter() final; 44 | 45 | void Start( 46 | ReportMode aMode); 47 | 48 | void Stop(); 49 | 50 | void Print(); 51 | 52 | void StatAddLatency( 53 | uint64_t aMsec); 54 | void StatAddMessage(); 55 | void StatAddConnection(); 56 | void StatDelConnection(); 57 | 58 | private: 59 | void Run() final; 60 | 61 | bool myIsRunning; 62 | bool myHasReport; 63 | mg::tst::MetricMovingAverage myLatencyAvg; 64 | mg::tst::MetricSpeed mySpeed; 65 | mg::box::AtomicU32 myConnectionCount; 66 | mg::box::AtomicU64 myMessageCount; 67 | uint64_t myDuration; 68 | uint64_t myMomentDeadline; 69 | ReportMode myMode; 70 | std::queue myMoments; 71 | }; 72 | 73 | void BenchIOEncodeMessage( 74 | mg::tst::WriteMessage& aOut, 75 | const Message& aMessage); 76 | void BenchIODecodeMessage( 77 | mg::tst::ReadMessage& aMessage, 78 | Message& aOut); 79 | 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /bench/io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | find_package(Boost) 4 | 5 | set(bench_io_src 6 | BenchIO.cpp 7 | BenchIOHelp.cpp 8 | BenchIOTCPClient.cpp 9 | BenchIOTCPServer.cpp 10 | BenchIOTools.cpp 11 | ) 12 | if(${Boost_FOUND}) 13 | set(BOOST_MACRO MG_BENCH_IO_HAS_BOOST=1) 14 | set(bench_io_src ${bench_io_src} 15 | BenchIOBoostCore.cpp 16 | BenchIOTCPClientBoost.cpp 17 | ) 18 | set(bench_io_include_dirs ${Boost_INCLUDE_DIRS}) 19 | if (MG_BOOST_USE_IOURING) 20 | set(BOOST_MACRO ${BOOST_MACRO} 21 | BOOST_ASIO_HAS_IO_URING=1 22 | BOOST_ASIO_DISABLE_EPOLL=1 23 | ) 24 | endif() 25 | else() 26 | set(BOOST_MACRO MG_BENCH_IO_HAS_BOOST=0) 27 | set(bench_io_include_dirs) 28 | endif() 29 | add_executable(bench_io ${bench_io_src}) 30 | target_compile_definitions(bench_io PRIVATE ${BOOST_MACRO}) 31 | target_include_directories(bench_io SYSTEM PUBLIC ${bench_io_include_dirs}) 32 | 33 | target_link_libraries(bench_io 34 | mgaio 35 | mgbox 36 | mgboxstub 37 | mgnet 38 | bench 39 | ) 40 | -------------------------------------------------------------------------------- /bench/io/config-io_uring-aio.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "Operating system name and version", 3 | "cpu": "Processor details", 4 | "comment-server": "Server: -thread_count 3 -mode server", 5 | "versions": { 6 | "epoll": { 7 | "name": "epoll scheduler", 8 | "short_name": "epoll", 9 | "exe": "bench_io_epoll", 10 | "cmd": "-backend mg_aio -report summary -mode client" 11 | }, 12 | "io_uring": { 13 | "name": "io_uring scheduler", 14 | "short_name": "io_uring", 15 | "exe": "bench_io_uring", 16 | "cmd": "-backend mg_aio -report summary -mode client" 17 | } 18 | }, 19 | "main_version": "epoll", 20 | "metric_key": "Message/sec(med)", 21 | "metric_name": "messages per second", 22 | "precision": 0.01, 23 | "scenarios": [ 24 | { 25 | "name": "Basic", 26 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 3500000", 27 | "count": 3 28 | }, 29 | { 30 | "name": "Single thread", 31 | "cmd": "-thread_count 1 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 3000000", 32 | "count": 3 33 | }, 34 | { 35 | "name": "3 threads 100kb messages", 36 | "cmd": "-thread_count 3 -connect_count_per_port 200 -message_payload_size 102400 -message_target_count 200000", 37 | "count": 3 38 | }, 39 | { 40 | "name": "Parallel messages", 41 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_parallel_count 5 -message_target_count 20000000", 42 | "count": 3 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /bench/io/config-io_uring-boost.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "Operating system name and version", 3 | "cpu": "Processor details", 4 | "comment-server": "Server: -thread_count 3 -mode server", 5 | "versions": { 6 | "epoll": { 7 | "name": "epoll scheduler", 8 | "short_name": "epoll", 9 | "exe": "bench_io_epoll", 10 | "cmd": "-backend boost_asio -report summary -mode client" 11 | }, 12 | "io_uring": { 13 | "name": "io_uring scheduler", 14 | "short_name": "io_uring", 15 | "exe": "bench_io_uring", 16 | "cmd": "-backend boost_asio -report summary -mode client" 17 | } 18 | }, 19 | "main_version": "epoll", 20 | "metric_key": "Message/sec(med)", 21 | "metric_name": "messages per second", 22 | "precision": 0.01, 23 | "scenarios": [ 24 | { 25 | "name": "Basic", 26 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 3500000", 27 | "count": 3 28 | }, 29 | { 30 | "name": "Single thread", 31 | "cmd": "-thread_count 1 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 3000000", 32 | "count": 3 33 | }, 34 | { 35 | "name": "3 threads 100kb messages", 36 | "cmd": "-thread_count 3 -connect_count_per_port 200 -message_payload_size 102400 -message_target_count 200000", 37 | "count": 3 38 | }, 39 | { 40 | "name": "Parallel messages", 41 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_parallel_count 5 -message_target_count 20000000", 42 | "count": 3 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /bench/io/config-strong.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "Operating system name and version", 3 | "cpu": "Processor details", 4 | "comment-server": "Server: -thread_count 5 -mode server", 5 | "versions": { 6 | "aio": { 7 | "name": "mg::aio network", 8 | "short_name": "mg::aio", 9 | "exe": "bench_io", 10 | "cmd": "-backend mg_aio -report summary -mode client" 11 | }, 12 | "boost": { 13 | "name": "boost::asio network", 14 | "short_name": "boost::asio", 15 | "exe": "bench_io", 16 | "cmd": "-backend boost_asio -report summary -mode client", 17 | "cond": "-test 1 -backend boost_asio" 18 | } 19 | }, 20 | "main_version": "aio", 21 | "metric_key": "Message/sec(med)", 22 | "metric_name": "messages per second", 23 | "precision": 0.01, 24 | "scenarios": [ 25 | { 26 | "name": "Basic", 27 | "cmd": "-thread_count 5 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 5000000", 28 | "count": 5 29 | }, 30 | { 31 | "name": "Single thread", 32 | "cmd": "-thread_count 1 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 2000000", 33 | "count": 5 34 | }, 35 | { 36 | "name": "5 threads 100kb messages", 37 | "cmd": "-thread_count 5 -connect_count_per_port 200 -message_payload_size 102400", 38 | "count": 5, 39 | "versions": { 40 | "aio": { 41 | "cmd": "-message_target_count 800000" 42 | }, 43 | "boost": { 44 | "cmd": "-message_target_count 500000" 45 | } 46 | } 47 | }, 48 | { 49 | "name": "Parallel messages", 50 | "cmd": "-thread_count 5 -connect_count_per_port 100 -message_payload_size 128 -message_parallel_count 5 -message_target_count 20000000", 51 | "count": 5 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /bench/io/config-weak-many-cores.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "Operating system name and version", 3 | "cpu": "Processor details", 4 | "comment-server": "Server: -thread_count 15 -mode server", 5 | "versions": { 6 | "aio": { 7 | "name": "mg::aio network", 8 | "short_name": "mg::aio", 9 | "exe": "bench_io", 10 | "cmd": "-backend mg_aio -report summary -mode client" 11 | }, 12 | "boost": { 13 | "name": "boost::asio network", 14 | "short_name": "boost::asio", 15 | "exe": "bench_io", 16 | "cmd": "-backend boost_asio -report summary -mode client", 17 | "cond": "-test 1 -backend boost_asio" 18 | } 19 | }, 20 | "main_version": "aio", 21 | "metric_key": "Message/sec(med)", 22 | "metric_name": "messages per second", 23 | "precision": 0.01, 24 | "scenarios": [ 25 | { 26 | "name": "Basic", 27 | "cmd": "-thread_count 10 -connect_count_per_port 500 -message_payload_size 128 -message_int_count 5 -message_target_count 5000000", 28 | "count": 5 29 | }, 30 | { 31 | "name": "Single thread", 32 | "cmd": "-thread_count 1 -connect_count_per_port 500 -message_payload_size 128 -message_int_count 5 -message_target_count 2000000", 33 | "count": 3 34 | }, 35 | { 36 | "name": "10 threads 100kb messages", 37 | "cmd": "-thread_count 10 -connect_count_per_port 600 -message_payload_size 102400", 38 | "count": 5, 39 | "versions": { 40 | "aio": { 41 | "cmd": "-message_target_count 800000" 42 | }, 43 | "boost": { 44 | "cmd": "-message_target_count 100000" 45 | } 46 | } 47 | }, 48 | { 49 | "name": "Parallel messages", 50 | "cmd": "-thread_count 10 -connect_count_per_port 500 -message_payload_size 128 -message_parallel_count 5 -message_target_count 20000000", 51 | "count": 5 52 | }, 53 | { 54 | "name": "Massively parallel", 55 | "cmd": "-thread_count 20 -connect_count_per_port 5000 -message_payload_size 128 -message_target_count 20000000", 56 | "count": 5 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /bench/io/config-weak.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "Operating system name and version", 3 | "cpu": "Processor details", 4 | "comment-server": "Server: -thread_count 3 -mode server", 5 | "versions": { 6 | "aio": { 7 | "name": "mg::aio network", 8 | "short_name": "mg::aio", 9 | "exe": "bench_io", 10 | "cmd": "-backend mg_aio -report summary -mode client" 11 | }, 12 | "boost": { 13 | "name": "boost::asio network", 14 | "short_name": "boost::asio", 15 | "exe": "bench_io", 16 | "cmd": "-backend boost_asio -report summary -mode client", 17 | "cond": "-test 1 -backend boost_asio" 18 | } 19 | }, 20 | "main_version": "aio", 21 | "metric_key": "Message/sec(med)", 22 | "metric_name": "messages per second", 23 | "precision": 0.01, 24 | "scenarios": [ 25 | { 26 | "name": "Basic", 27 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 2500000", 28 | "count": 3 29 | }, 30 | { 31 | "name": "Single thread", 32 | "cmd": "-thread_count 1 -connect_count_per_port 100 -message_payload_size 128 -message_int_count 5 -message_target_count 2000000", 33 | "count": 3 34 | }, 35 | { 36 | "name": "3 threads 100kb messages", 37 | "cmd": "-thread_count 3 -connect_count_per_port 200 -message_payload_size 102400", 38 | "count": 3, 39 | "versions": { 40 | "aio": { 41 | "cmd": "-message_target_count 200000" 42 | }, 43 | "boost": { 44 | "cmd": "-message_target_count 150000" 45 | } 46 | } 47 | }, 48 | { 49 | "name": "Parallel messages", 50 | "cmd": "-thread_count 3 -connect_count_per_port 100 -message_payload_size 128 -message_parallel_count 5 -message_target_count 20000000", 51 | "count": 3 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /bench/mcspqueue/BenchMultiConsumerQueue.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/box/MultiConsumerQueue.h" 4 | 5 | namespace mg { 6 | namespace bench { 7 | 8 | struct BenchValue 9 | { 10 | BenchValue() 11 | : myValue(nullptr) 12 | { 13 | } 14 | 15 | void* myValue; 16 | }; 17 | 18 | using BenchQueue = mg::box::MultiConsumerQueue; 19 | using BenchQueueConsumer = mg::box::MultiConsumerQueueConsumer; 20 | 21 | } 22 | } 23 | 24 | #include "BenchMultiConsumerQueueTemplate.hpp" 25 | -------------------------------------------------------------------------------- /bench/mcspqueue/BenchMultiConsumerQueueTrivial.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/box/ForwardList.h" 4 | #include "mg/box/Mutex.h" 5 | 6 | namespace mg { 7 | namespace bench { 8 | 9 | struct BenchValue 10 | { 11 | BenchValue(); 12 | 13 | BenchValue* myNext; 14 | }; 15 | 16 | using BenchValueList = mg::box::ForwardList; 17 | 18 | // Trivial queue takes a mutex lock on each operation. The simplest possible 19 | // implementation and the most typical one. And most of the time it is enough. 20 | // 21 | class BenchQueue 22 | { 23 | public: 24 | BenchQueue( 25 | uint32_t aSubQueueSize); 26 | 27 | void Push( 28 | BenchValue* aValue); 29 | 30 | BenchValue* Pop(); 31 | 32 | void Reserve( 33 | uint32_t aCount); 34 | 35 | private: 36 | mg::box::Mutex myMutex; 37 | BenchValueList myItems; 38 | }; 39 | 40 | class BenchQueueConsumer 41 | { 42 | public: 43 | BenchQueueConsumer(); 44 | 45 | BenchValue* Pop(); 46 | 47 | void Attach( 48 | BenchQueue* aQueue); 49 | 50 | void Detach(); 51 | private: 52 | BenchQueue* myQueue; 53 | }; 54 | 55 | ////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | BenchValue::BenchValue() 58 | : myNext(nullptr) 59 | { 60 | } 61 | 62 | BenchQueue::BenchQueue( 63 | uint32_t /*aSubQueueSize*/) 64 | { 65 | } 66 | 67 | void 68 | BenchQueue::Push( 69 | BenchValue* aValue) 70 | { 71 | myMutex.Lock(); 72 | myItems.Append(aValue); 73 | myMutex.Unlock(); 74 | } 75 | 76 | BenchValue* 77 | BenchQueue::Pop() 78 | { 79 | BenchValue* res = nullptr; 80 | myMutex.Lock(); 81 | if (!myItems.IsEmpty()) 82 | res = myItems.PopFirst(); 83 | myMutex.Unlock(); 84 | return res; 85 | } 86 | 87 | void 88 | BenchQueue::Reserve( 89 | uint32_t /*aCount*/) 90 | { 91 | } 92 | 93 | BenchQueueConsumer::BenchQueueConsumer() 94 | : myQueue(nullptr) 95 | { 96 | } 97 | 98 | BenchValue* 99 | BenchQueueConsumer::Pop() 100 | { 101 | return myQueue->Pop(); 102 | } 103 | 104 | void 105 | BenchQueueConsumer::Attach( 106 | BenchQueue* aQueue) 107 | { 108 | myQueue = aQueue; 109 | } 110 | 111 | void 112 | BenchQueueConsumer::Detach() 113 | { 114 | myQueue = nullptr; 115 | } 116 | 117 | } 118 | } 119 | 120 | #include "BenchMultiConsumerQueueTemplate.hpp" 121 | -------------------------------------------------------------------------------- /bench/mcspqueue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_executable(bench_mcspqueue 4 | BenchMultiConsumerQueue.cpp 5 | ) 6 | target_link_libraries(bench_mcspqueue 7 | mgbox 8 | bench 9 | ) 10 | 11 | add_executable(bench_mcspqueue_trivial 12 | BenchMultiConsumerQueueTrivial.cpp 13 | ) 14 | target_link_libraries(bench_mcspqueue_trivial 15 | mgbox 16 | bench 17 | ) 18 | -------------------------------------------------------------------------------- /bench/mcspqueue/README.md: -------------------------------------------------------------------------------- 1 | # MultiConsumerQueue 2 | 3 | The tests show `MultiConsumerQueue` versus a trivial queue. 4 | 5 | The trivial queue simply uses mutex locks for each push and pop. It has no explicit 'consumer' objects, is perfectly fair in terms of CPU usage, and is even good for many cases. But its performance degrades quickly as more threads are added and contention increases. 6 | 7 | ## Results 8 | 9 | The results very greatly depending on kind of load. The benchmarks test the cases when each popped item is just immediately discarded (empty load), and when the consumer thread spends on it a few microseconds simulating its processing (micro load). 10 | 11 | When the load is empty, the trivial queue outperforms `MultiConsumerQueue` at first a bit on some platforms, but quickly degrades. With more and more threads added the trivial queue eventually is unable to process even a million items per second due to extreme contention. It hits the bottom with 10 threads when the trivial queue is >x4.5 times slower. 12 | 13 | When the load is micro, the results are roughly the same. Indeed, most of the time is just spent on this fake 'processing', so both trivial queue and `MultiConsumerQueue` perform almost equally. Except that the trivial queue still hits mutex contention >x100 times more often. 14 | 15 | Can for sure say that if the consumers do any kind of work on the popped items, the queue itself won't be a bottleneck in any application. 16 | 17 | ## Alternative 18 | 19 | If the reader is interested, they can also take a look at https://github.com/cameron314/concurrentqueue. It is a multi-producer-multi-consumer queue which seems to use logic similar to `MultiConsumerQueue`. 20 | 21 | It also has benchmarks: https://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++.htm#benchmarks. 22 | 23 | Although these benches might be either too outdated or have accidentally lowered perf of other queues. For example, consider a 64bit instance, 8 cores 2.13GHz each. There are 2 consuming threads, the queue is pre-populated. The queue is lock-based - mutex on each push and pop. 24 | 25 | ``` 26 | Moodycamel lock-based queue: 1 340 000 pops per second; 27 | My trivial lock-based queue: 10 122 771 pops per second; 28 | ``` 29 | 30 | Hard to tell why their results are so low for the lock-based queue, but it might make all their results incomparable to the ones presented here. 31 | -------------------------------------------------------------------------------- /bench/mpscqueue/BenchMultiProducerQueue.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/box/MultiProducerQueueIntrusive.h" 4 | 5 | namespace mg { 6 | namespace bench { 7 | 8 | struct BenchValue 9 | { 10 | BenchValue(); 11 | 12 | BenchValue* myNext; 13 | }; 14 | 15 | using MultiProducerQueue = mg::box::MultiProducerQueueIntrusive; 16 | 17 | inline 18 | BenchValue::BenchValue() 19 | : myNext(nullptr) 20 | { 21 | } 22 | 23 | } 24 | } 25 | 26 | #include "BenchMultiProducerQueueTemplate.hpp" 27 | -------------------------------------------------------------------------------- /bench/mpscqueue/BenchMultiProducerQueueTrivial.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/box/ForwardList.h" 4 | #include "mg/box/Mutex.h" 5 | 6 | namespace mg { 7 | namespace bench { 8 | 9 | struct BenchValue 10 | { 11 | BenchValue(); 12 | 13 | BenchValue* myNext; 14 | }; 15 | 16 | // Trivial queue takes a mutex lock on each operation. The simplest possible 17 | // implementation and the most typical one. And most of the time it is enough. 18 | // 19 | class MultiProducerQueue 20 | { 21 | public: 22 | bool Push( 23 | BenchValue* aValue); 24 | 25 | BenchValue* PopAll( 26 | BenchValue*& aOutTail); 27 | 28 | private: 29 | mg::box::Mutex myLock; 30 | mg::box::ForwardList myValues; 31 | }; 32 | 33 | inline 34 | BenchValue::BenchValue() 35 | : myNext(nullptr) 36 | { 37 | } 38 | 39 | inline bool 40 | MultiProducerQueue::Push( 41 | BenchValue* aValue) 42 | { 43 | myLock.Lock(); 44 | bool res = myValues.IsEmpty(); 45 | myValues.Append(aValue); 46 | myLock.Unlock(); 47 | return res; 48 | } 49 | 50 | inline BenchValue* 51 | MultiProducerQueue::PopAll( 52 | BenchValue*& aOutTail) 53 | { 54 | myLock.Lock(); 55 | BenchValue* head = myValues.PopAll(aOutTail); 56 | myLock.Unlock(); 57 | return head; 58 | } 59 | 60 | } 61 | } 62 | 63 | #include "BenchMultiProducerQueueTemplate.hpp" 64 | -------------------------------------------------------------------------------- /bench/mpscqueue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_executable(bench_mpscqueue 4 | BenchMultiProducerQueue.cpp 5 | ) 6 | target_link_libraries(bench_mpscqueue 7 | mgbox 8 | bench 9 | ) 10 | 11 | add_executable(bench_mpscqueue_trivial 12 | BenchMultiProducerQueueTrivial.cpp 13 | ) 14 | target_link_libraries(bench_mpscqueue_trivial 15 | mgbox 16 | bench 17 | ) 18 | -------------------------------------------------------------------------------- /bench/mpscqueue/README.md: -------------------------------------------------------------------------------- 1 | # MultiProducerQueue 2 | 3 | The tests show `MultiProducerQueue` versus a trivial queue. 4 | 5 | The queue has no way to pop items one by one. Only all at the same time. 6 | 7 | The trivial queue simply uses mutex locks for each push and 'pop all'. It is perfectly fair in terms of CPU usage, and is even good for many cases. 8 | 9 | The benchmarks measure not only busy-looped producers and consumers, but also the more typical usage: blocking consumer. 10 | 11 | A consumer rarely can afford having a busy loop or even just a polling loop with sleeps. The more natural usage is when the consumer clears the queue and then sleeps exactly until the queue becomes non-empty. The producers are supposed to notify the consumer about this. 12 | 13 | The blocking consumption is implemented using `mg::box::Signal` class. The benchmarks measure both the busy-loop consumption and the blocking consumption. 14 | 15 | ## Results 16 | 17 | The trivial lock-based queue looses to `MultiProducerQueue` in every single bench (except 2 cases on Windows - these are highly unstable, jitter is up to x2 times even on sequential runs). 18 | 19 | The queue `MultiProducerQueue` is often faster than the lock-based one x1.5-x2 times. In rare cases can go up to x12 (macOS). 20 | 21 | The difference is clearer when producer thread count goes up. That leads to higher mutex contention in the trivial queue. The producers are getting blocked. In the lock-free queue the producers in case of a conflict just retry the push and it appears to be much faster. 22 | 23 | In busy-loop benchmarks the mutex contention is incomparable - the lock-free queue simply always has 0 mutex contention. 24 | 25 | In blocking consumption benchmarks the `mg::box::Signal` adds some contention even to the lock-free queue, because it has a mutex inside. But nonetheless the lock-free queue remains faster and has generally much less contention. Often the difference is 1-2 orders of magnitude. But in rare cases the blocking consumption can have more contention in `mg::box::Signal` while giving higher RPS. Unclear yet why that happened. Could be the bench being too unstable or the signal having hidden perf issues. 26 | -------------------------------------------------------------------------------- /bench/taskscheduler/BenchTaskScheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "Bench.h" 2 | 3 | #include "mg/sch/TaskScheduler.h" 4 | 5 | namespace mg { 6 | namespace bench { 7 | 8 | using Task = mg::sch::Task; 9 | using TaskScheduler = mg::sch::TaskScheduler; 10 | using TaskSchedulerThread = mg::sch::TaskSchedulerThread; 11 | 12 | } 13 | } 14 | 15 | #include "BenchTaskSchedulerTemplate.hpp" 16 | -------------------------------------------------------------------------------- /bench/taskscheduler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_executable(bench_taskscheduler 4 | BenchTaskScheduler.cpp 5 | ) 6 | target_link_libraries(bench_taskscheduler 7 | mgsch 8 | bench 9 | ) 10 | 11 | add_executable(bench_taskscheduler_trivial 12 | BenchTaskSchedulerTrivial.cpp 13 | ) 14 | target_link_libraries(bench_taskscheduler_trivial 15 | mgsch 16 | bench 17 | ) 18 | -------------------------------------------------------------------------------- /bench/taskscheduler/README.md: -------------------------------------------------------------------------------- 1 | # TaskScheduler 2 | 3 | The tests show `TaskScheduler` versus a trivial scheduler. 4 | 5 | The trivial scheduler simply uses mutex locks for each task post and in each worker thread to get a task for execution. It is perfectly fair in terms of CPU usage, doesn't pin tasks to worker threads, and isn't even that bad in performance for some cases. 6 | 7 | The result reports demonstrate how much more severe mutex contention is in the trivial task scheduler and how significantly its performance degrades with more threads added. Although on certain runs it can be close to `TaskScheduler` when contention is low. 8 | 9 | **Note** however, that the trivial scheduler is not a drop-in replacement of `TaskScheduler` - it lacks all the features except dumb execution of tasks: no task deadline, no task wakeup, no task signal, nothing else. Any attempt to add those features purely on mutex-based logic would require to rework it from scratch and inevitably make it unusably slower. 10 | 11 | Nonetheless, if the coroutine features are not needed, expected task/second load is going to be low, and thread count will be very small, then the trivial scheduler might be just fine for your case due to its extreme simplicity. 12 | 13 | ## Results 14 | 15 | See the `.md` files in the same folder for details. Overall summary is that `TaskScheduler` easily provides more than million tasks executed per second. In certain runs it can even reach 13 000 000. Can for sure say that if the tasks do any kind of work, the scheduler itself won't be a bottleneck in any application. 16 | 17 | Moreover, despite being quite overloaded with lots of features, in almost all cases it still outperforms the lock-based trivial task scheduler. When contention is low, the win isn't big either. But in certain runs apparently the speed difference reaches x7 on Linux, x11 on Mac, x5 on Windows. 18 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project("Examples") 4 | 5 | if (NOT CMAKE_BUILD_TYPE) 6 | set(CMAKE_BUILD_TYPE Release) 7 | endif() 8 | message(STATUS "Build type ${CMAKE_BUILD_TYPE}") 9 | 10 | if (NOT DEFINED CMAKE_CXX_STANDARD) 11 | message(STATUS "Using C++20 standard as default") 12 | set(CMAKE_CXX_STANDARD 20) 13 | else() 14 | message(STATUS "Using C++${CMAKE_CXX_STANDARD} standard as explicitly requested") 15 | endif() 16 | 17 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 18 | add_compile_options( 19 | -Wall -Wextra -Wpedantic -Werror -Wno-unknown-warning-option -Wunused-function 20 | -Wno-invalid-offsetof -Wno-unused-value -Wno-deprecated-copy 21 | -Wno-gnu-zero-variadic-macro-arguments 22 | ) 23 | 24 | set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-pthread") 25 | set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-pthread") 26 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 27 | add_compile_options( 28 | # This is needed at least for correct macro handling. Default behaviour won't 29 | # expand __VA_ARGS__ correctly. 30 | /Zc:preprocessor 31 | /WX /wd4266 /wd4324 /wd4355 /wd4365 /wd4458 /wd4514 /wd4548 /wd4625 /wd4626 32 | /wd4668 /wd4710 /wd4820 /wd5026 /wd5027 /wd5039 /wd5045 /wd5105 /wd5219 /wd26439 33 | /wd26800 34 | # It ignores 'break' and 'fallthrough' done via a macro which makes it annoying 35 | # and pointless. 36 | /wd5262 37 | # Info message about a function being inlined. 38 | /wd4711 39 | ) 40 | endif() 41 | 42 | set(MG_SERVERBOX_BUILD_DIR ${CMAKE_BINARY_DIR}/build_serverbox) 43 | set(MG_SERVERBOX_DIR ${MG_SERVERBOX_BUILD_DIR}/installed) 44 | 45 | add_custom_target(install_serverbox 46 | COMMAND ${CMAKE_COMMAND} 47 | -S ${CMAKE_SOURCE_DIR}/.. 48 | -B ${MG_SERVERBOX_BUILD_DIR} 49 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 50 | -DMG_ENABLE_TEST=0 51 | -DMG_ENABLE_BENCH=0 52 | -DCMAKE_INSTALL_PREFIX=${MG_SERVERBOX_DIR} 53 | 54 | COMMAND ${CMAKE_COMMAND} 55 | --build ${MG_SERVERBOX_BUILD_DIR} --config ${CMAKE_BUILD_TYPE} -j 56 | 57 | COMMAND ${CMAKE_COMMAND} 58 | --install ${MG_SERVERBOX_BUILD_DIR} --config ${CMAKE_BUILD_TYPE} 59 | 60 | COMMENT "Installing serverbox" 61 | ) 62 | 63 | add_subdirectory(iocore_01_tcp_hello) 64 | add_subdirectory(iocore_02_ssl_hello) 65 | add_subdirectory(iocore_03_pipeline) 66 | add_subdirectory(iocore_04_tcp_periodic) 67 | add_subdirectory(scheduler_01_simple_task) 68 | add_subdirectory(scheduler_02_coroutine_task) 69 | add_subdirectory(scheduler_03_multistep_task) 70 | add_subdirectory(scheduler_04_interacting_tasks) 71 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The folder provides series of examples how to use the most interesting parts of Serverbox. 4 | 5 | Each example can be built and run on its own, they only depend on Serverbox itself, and not on each other. 6 | 7 | They can be used as reference points when you want to try and build something of your own. 8 | 9 | The recommended way of reading them is firstly all `scheduler_*`, then all `iocore_*`. The sequence 01, 02, etc is not required, each example is self-sufficient build- and code-wise. But later examples might not explain some simple things already covered in the previous examples. 10 | 11 | ## Running 12 | 13 | The recommended way to use them is this (on non-Windows): 14 | ```Bash 15 | # Build them all. 16 | mkdir -p build 17 | cd build 18 | cmake .. 19 | make -j 20 | 21 | # Run any of them like this: 22 | ./scheduler_01_simple_task/scheduler_01_simple_task 23 | ``` 24 | 25 | On Windows platform they also compile and run, but one would have to run them via cmd/PowerShell/VisualStudio. 26 | -------------------------------------------------------------------------------- /examples/iocore_01_tcp_hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(iocore_01_tcp_hello main.cpp) 2 | add_dependencies(iocore_01_tcp_hello install_serverbox) 3 | 4 | target_include_directories(iocore_01_tcp_hello PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(iocore_01_tcp_hello PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | set(libs 11 | mgaio 12 | mgnet 13 | mgbox 14 | mgboxstub 15 | ) 16 | if(WIN32) 17 | set(libs ${libs} ws2_32.lib crypt32.lib) 18 | endif() 19 | target_link_libraries(iocore_01_tcp_hello 20 | ${libs} 21 | ) 22 | -------------------------------------------------------------------------------- /examples/iocore_02_ssl_hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(iocore_02_ssl_hello main.cpp Certs.cpp) 2 | add_dependencies(iocore_02_ssl_hello install_serverbox) 3 | 4 | find_package(OpenSSL REQUIRED) 5 | 6 | target_include_directories(iocore_02_ssl_hello PUBLIC 7 | ${MG_SERVERBOX_DIR}/include 8 | ${OPENSSL_INCLUDE_DIR} 9 | ) 10 | target_link_directories(iocore_02_ssl_hello PUBLIC 11 | ${MG_SERVERBOX_DIR}/lib 12 | ) 13 | set(libs 14 | mgaio 15 | mgnet 16 | mgbox 17 | mgboxstub 18 | ${OPENSSL_SSL_LIBRARY} 19 | ${OPENSSL_CRYPTO_LIBRARY} 20 | ) 21 | if(WIN32) 22 | set(libs ${libs} ws2_32.lib crypt32.lib) 23 | endif() 24 | target_link_libraries(iocore_02_ssl_hello 25 | ${libs} 26 | ) 27 | -------------------------------------------------------------------------------- /examples/iocore_02_ssl_hello/Certs.h: -------------------------------------------------------------------------------- 1 | #include "mg/box/Definitions.h" 2 | 3 | // Public certificate to use on server. 4 | extern const uint32_t theTestCertSize; 5 | extern const uint8_t theTestCert[]; 6 | 7 | // Private key to use on server. 8 | extern const uint32_t theTestKeySize; 9 | extern const uint8_t theTestKey[]; 10 | 11 | // Public certificate used to sign the server. Should be available to the clients. 12 | extern const uint32_t theTestCACertSize; 13 | extern const uint8_t theTestCACert[]; 14 | -------------------------------------------------------------------------------- /examples/iocore_03_pipeline/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(iocore_03_pipeline main.cpp) 2 | add_dependencies(iocore_03_pipeline install_serverbox) 3 | 4 | target_include_directories(iocore_03_pipeline PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(iocore_03_pipeline PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | set(libs 11 | mgaio 12 | mgnet 13 | mgsch 14 | mgbox 15 | mgboxstub 16 | ) 17 | if(WIN32) 18 | set(libs ${libs} ws2_32.lib crypt32.lib) 19 | endif() 20 | target_link_libraries(iocore_03_pipeline 21 | ${libs} 22 | ) 23 | -------------------------------------------------------------------------------- /examples/iocore_04_tcp_periodic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(iocore_04_tcp_periodic main.cpp) 2 | add_dependencies(iocore_04_tcp_periodic install_serverbox) 3 | 4 | target_include_directories(iocore_04_tcp_periodic PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(iocore_04_tcp_periodic PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | set(libs 11 | mgaio 12 | mgnet 13 | mgbox 14 | mgboxstub 15 | ) 16 | if(WIN32) 17 | set(libs ${libs} ws2_32.lib crypt32.lib) 18 | endif() 19 | target_link_libraries(iocore_04_tcp_periodic 20 | ${libs} 21 | ) 22 | -------------------------------------------------------------------------------- /examples/scheduler_01_simple_task/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(scheduler_01_simple_task main.cpp) 2 | add_dependencies(scheduler_01_simple_task install_serverbox) 3 | 4 | target_include_directories(scheduler_01_simple_task PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(scheduler_01_simple_task PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | target_link_libraries(scheduler_01_simple_task 11 | mgsch 12 | mgboxstub 13 | mgbox 14 | ) 15 | -------------------------------------------------------------------------------- /examples/scheduler_01_simple_task/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/sch/TaskScheduler.h" 2 | 3 | #include 4 | 5 | // 6 | // The most trivial example of TaskScheduler usage. A single shot task which just prints 7 | // something and gets deleted. 8 | // 9 | 10 | int 11 | main() 12 | { 13 | mg::sch::TaskScheduler sched("tst", 14 | 5 // Subqueue size. 15 | ); 16 | sched.Start(1); 17 | sched.Post(new mg::sch::Task([&](mg::sch::Task *self) { 18 | std::cout << "Executed in scheduler!\n"; 19 | delete self; 20 | })); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /examples/scheduler_02_coroutine_task/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(scheduler_02_coroutine_task main.cpp) 2 | add_dependencies(scheduler_02_coroutine_task install_serverbox) 3 | 4 | target_include_directories(scheduler_02_coroutine_task PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(scheduler_02_coroutine_task PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | target_link_libraries(scheduler_02_coroutine_task 11 | mgsch 12 | mgboxstub 13 | mgbox 14 | ) 15 | -------------------------------------------------------------------------------- /examples/scheduler_02_coroutine_task/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/sch/TaskScheduler.h" 2 | 3 | #include 4 | 5 | // 6 | // It is assumed you have seen the previous examples in this section, and some previously 7 | // explained things don't need another repetition. 8 | // 9 | ////////////////////////////////////////////////////////////////////////////////////////// 10 | // 11 | // A simple example of how to use C++ stack-less coroutines with TaskScheduler. 12 | // 13 | 14 | int 15 | main() 16 | { 17 | mg::sch::Task task; 18 | 19 | // Do not use lambda capture-params! The thing is that C++ coroutines are not 20 | // functions. They are **results** of functions. When a lambda returns a C++ 21 | // corotuine, it needs to be invoked to return this coroutine. And after invocation 22 | // the lambda is destroyed in this case. Which makes it not possible to use any lambda 23 | // data, like captures. See for further explanation: 24 | // https://stackoverflow.com/questions/60592174/lambda-lifetime-explanation-for-c20-coroutines 25 | // 26 | // Luckily, this problem is trivial to workaround. Just pass your values as arguments. 27 | // Then the coroutine object captures them. 28 | task.SetCallback([]( 29 | mg::sch::Task& aSelf) -> mg::box::Coro { 30 | 31 | // Imagine the task sends a request to some async network client or alike. 32 | std::cout << "Sending request ...\n"; 33 | 34 | // After sending, the task would wait for a response. Here it is simplified, so 35 | // the task just yields and gets continued right away. 36 | // Yield with no deadline or delay set simply re-schedules the task. 37 | co_await aSelf.AsyncYield(); 38 | 39 | std::cout << "Received response!\n"; 40 | co_await aSelf.AsyncYield(); 41 | std::cout << "Finish\n"; 42 | co_return; 43 | }(task)); 44 | 45 | // The scheduler is defined after the task, so the task's destructor is not called 46 | // before the scheduler is terminated. It would cause the task to be destroyed while 47 | // in use. 48 | // Normally one would allocate tasks on the heap and make them delete themselves when 49 | // they are finished. 50 | mg::sch::TaskScheduler scheduler("tst", 51 | 5 // Subqueue size. 52 | ); 53 | scheduler.Start(1); 54 | scheduler.Post(&task); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /examples/scheduler_03_multistep_task/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(scheduler_03_multistep_task main.cpp) 2 | add_dependencies(scheduler_03_multistep_task install_serverbox) 3 | 4 | target_include_directories(scheduler_03_multistep_task PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(scheduler_03_multistep_task PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | target_link_libraries(scheduler_03_multistep_task 11 | mgsch 12 | mgboxstub 13 | mgbox 14 | ) 15 | -------------------------------------------------------------------------------- /examples/scheduler_03_multistep_task/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/sch/TaskScheduler.h" 2 | 3 | #include 4 | 5 | // 6 | // It is assumed you have seen the previous examples in this section, and some previously 7 | // explained things don't need another repetition. 8 | // 9 | ////////////////////////////////////////////////////////////////////////////////////////// 10 | // 11 | // An example how a task can consist of multiple untrivial steps, with yields in between, 12 | // and with context used by those steps. 13 | // 14 | 15 | class MyTask 16 | : public mg::sch::Task 17 | { 18 | public: 19 | MyTask() 20 | : Task([this](mg::sch::Task* aSelf) { 21 | TaskSendRequest(aSelf); 22 | }) {} 23 | 24 | private: 25 | // Step 1. 26 | void 27 | TaskSendRequest( 28 | mg::sch::Task* aSelf) 29 | { 30 | // The scheduler always gives the "self" as an argument. This is not needed when 31 | // the task is inherited. But quite handy when a task is just a lambda. 32 | MG_BOX_ASSERT(aSelf == this); 33 | 34 | // Imagine that the task sends an HTTP request via some other module, and is woken 35 | // up, when the request ends. To execute the next step. For that the task changes 36 | // its callback which will be executed when the task is woken up next time. 37 | std::cout << "Sending request ...\n"; 38 | aSelf->SetCallback([this](mg::sch::Task* aSelf) { 39 | TaskRecvResponse(aSelf); 40 | }); 41 | mg::sch::TaskScheduler::This().Post(aSelf); 42 | } 43 | 44 | // Step 2. 45 | void 46 | TaskRecvResponse( 47 | mg::sch::Task* aSelf) 48 | { 49 | MG_BOX_ASSERT(aSelf == this); 50 | 51 | // Lets make another step, with a third callback which would be the final one. 52 | std::cout << "Received response!\n"; 53 | aSelf->SetCallback([this](mg::sch::Task *aSelf) { 54 | TaskFinish(aSelf); 55 | }); 56 | mg::sch::TaskScheduler::This().Post(aSelf); 57 | } 58 | 59 | // Step 3. 60 | void 61 | TaskFinish( 62 | mg::sch::Task* aSelf) 63 | { 64 | MG_BOX_ASSERT(aSelf == this); 65 | std::cout << "Finish\n"; 66 | } 67 | 68 | // Here one would normally put various members needed by that task for its context. 69 | // For example, request and user data related to this task. 70 | }; 71 | 72 | int 73 | main() 74 | { 75 | MyTask task; 76 | 77 | // The scheduler is defined after the task, so the task's destructor is not called 78 | // before the scheduler is terminated. It would cause the task to be destroyed while 79 | // in use. 80 | // Normally one would allocate tasks on the heap and make them delete themselves when 81 | // they are finished. 82 | mg::sch::TaskScheduler scheduler("tst", 83 | 5 // Subqueue size. 84 | ); 85 | scheduler.Start(1); 86 | scheduler.Post(&task); 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /examples/scheduler_04_interacting_tasks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(scheduler_04_interacting_tasks main.cpp) 2 | add_dependencies(scheduler_04_interacting_tasks install_serverbox) 3 | 4 | target_include_directories(scheduler_04_interacting_tasks PUBLIC 5 | ${MG_SERVERBOX_DIR}/include 6 | ) 7 | target_link_directories(scheduler_04_interacting_tasks PUBLIC 8 | ${MG_SERVERBOX_DIR}/lib 9 | ) 10 | target_link_libraries(scheduler_04_interacting_tasks 11 | mgsch 12 | mgboxstub 13 | mgbox 14 | ) 15 | -------------------------------------------------------------------------------- /examples/scheduler_04_interacting_tasks/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/sch/TaskScheduler.h" 2 | 3 | #include 4 | 5 | // 6 | // It is assumed you have seen the previous examples in this section, and some previously 7 | // explained things don't need another repetition. 8 | // 9 | ////////////////////////////////////////////////////////////////////////////////////////// 10 | // 11 | // A realistic example how one task might submit some async work to be executed by another 12 | // task, which in turn would wake the first one up, when the work is done. 13 | // 14 | 15 | static void 16 | TaskSubmitRequest( 17 | mg::sch::Task& aSender) 18 | { 19 | // A sub-task is created. But normally for such need one would have a pre-running task 20 | // or a thread or some other sort of async executor, which takes requests and executes 21 | // them. 22 | mg::sch::Task* worker = new mg::sch::Task(); 23 | worker->SetCallback([]( 24 | mg::sch::Task& aSelf, 25 | mg::sch::Task& aSender) -> mg::box::Coro { 26 | 27 | std::cout << "Worker: sent request\n"; 28 | // Lets simulate like if the request takes 1 second to get done. For that the 29 | // task would yield for 1 second and then get executed again. 30 | uint64_t t1 = mg::box::GetMilliseconds(); 31 | aSelf.SetDelay(1000); 32 | co_await aSelf.AsyncYield(); 33 | 34 | // This flag means the task was woken up because its deadline was due. 35 | // Technically, it could wake up spuriously, but here we know it can't happen. 36 | // Proper production code should still be ready to that though. 37 | MG_BOX_ASSERT(aSelf.IsExpired()); 38 | 39 | uint64_t t2 = mg::box::GetMilliseconds(); 40 | std::cout << "Worker: received response, took " << t2 - t1 << " msec\n"; 41 | 42 | // Wakeup the original task + let it know the request is actually done. The 43 | // original task would be able to tell that by seeing that it's got a signal, not 44 | // just a spurious wakeup. 45 | aSender.PostSignal(); 46 | 47 | // 'delete self' + co_return wouldn't work here. Because deletion of the self 48 | // would destroy the C++ coroutine object. co_return would then fail with 49 | // use-after-free. For such 'delete and exit' case there is the special helper. 50 | co_await aSelf.AsyncExitDelete(); 51 | }(*worker, aSender)); 52 | mg::sch::TaskScheduler::This().Post(worker); 53 | } 54 | 55 | int 56 | main() 57 | { 58 | mg::sch::Task task; 59 | 60 | // The scheduler is defined after the task, so the task's destructor is not called 61 | // before the scheduler is terminated. It would cause the task to be destroyed while 62 | // in use. 63 | // Normally one would allocate tasks on the heap and make them delete themselves when 64 | // they are finished. 65 | mg::sch::TaskScheduler scheduler("tst", 66 | 5 // Subqueue size. 67 | ); 68 | scheduler.Start(1); 69 | 70 | task.SetCallback([]( 71 | mg::sch::Task& aSelf) -> mg::box::Coro { 72 | 73 | // The task wants something to be done asynchronously. It would then submit the 74 | // work (could be another task, could be an HTTP client, or something alike) and 75 | // wait for a notification when it is done. 76 | std::cout << "Main: submit request\n"; 77 | TaskSubmitRequest(aSelf); 78 | 79 | // The waiting is in a loop to protect the code from spurious wakeups. 80 | do { 81 | // Make sure to signalize that the task wants to wait (infinitely) until a 82 | // signal comes. Without this the task would be just re-scheduled immediately 83 | // and it would be a busy-loop. 84 | aSelf.SetWait(); 85 | } while (!co_await aSelf.AsyncReceiveSignal()); 86 | 87 | std::cout << "Main: finish\n"; 88 | co_return; 89 | }(task)); 90 | scheduler.Post(&task); 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set(install_include_root "include/") 4 | set(install_lib_root "lib/") 5 | 6 | add_subdirectory(mg/aio) 7 | add_subdirectory(mg/sio) 8 | add_subdirectory(mg/box) 9 | add_subdirectory(mg/net) 10 | add_subdirectory(mg/sch) 11 | add_subdirectory(mg/stub) 12 | add_subdirectory(mg/test) 13 | -------------------------------------------------------------------------------- /src/mg/aio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set(mgaio_src 4 | IOCore.cpp 5 | IOTask.cpp 6 | TCPServer.cpp 7 | TCPSocket.cpp 8 | TCPSocketCtl.cpp 9 | TCPSocketIFace.cpp 10 | TCPSocketSubscription.cpp 11 | SSLSocket.cpp 12 | ) 13 | 14 | set(mgaio_libs mgnet) 15 | set(mgaio_macros) 16 | 17 | if(WIN32) 18 | set(mgaio_src ${mgaio_src} 19 | IOCore_IOCP.cpp 20 | IOTask_IOCP.cpp 21 | ) 22 | elseif(APPLE) 23 | set(mgaio_src ${mgaio_src} 24 | IOCore_kqueue.cpp 25 | IOTask_kqueue.cpp 26 | ) 27 | elseif(MG_AIO_USE_IOURING) 28 | if (LINUX_KERNEL_VERSION VERSION_LESS 5.19) 29 | message(FATAL_ERROR "io_uring is only supported for Linux >= 5.19") 30 | endif() 31 | set(mgaio_src ${mgaio_src} 32 | IOCore_iouring.cpp 33 | IOTask_iouring.cpp 34 | ) 35 | set(mgaio_libs ${mgaio_libs} -luring) 36 | set(mgaio_macros MG_IOCORE_USE_IOURING=1) 37 | else() 38 | set(mgaio_src ${mgaio_src} 39 | IOCore_epoll.cpp 40 | IOTask_epoll.cpp 41 | ) 42 | endif() 43 | 44 | add_library(mgaio ${mgaio_src}) 45 | target_compile_definitions(mgaio PUBLIC ${mgaio_macros}) 46 | 47 | target_include_directories(mgaio PUBLIC 48 | ${CMAKE_SOURCE_DIR}/src/ 49 | ) 50 | 51 | target_link_libraries(mgaio 52 | ${mgaio_libs} 53 | ) 54 | 55 | set(install_headers 56 | IOCore.h 57 | IOTask.h 58 | SSLSocket.h 59 | TCPServer.h 60 | TCPSocket.h 61 | TCPSocketIFace.h 62 | TCPSocketSubscription.h 63 | ) 64 | 65 | install(TARGETS mgaio DESTINATION "${install_lib_root}") 66 | install(FILES ${install_headers} DESTINATION "${install_include_root}/mg/aio/") 67 | -------------------------------------------------------------------------------- /src/mg/aio/TCPServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/aio/IOTask.h" 4 | #include "mg/box/Mutex.h" 5 | 6 | namespace mg { 7 | namespace aio { 8 | 9 | class IOCore; 10 | 11 | enum TCPServerState 12 | { 13 | TCP_SERVER_STATE_NEW, 14 | TCP_SERVER_STATE_BOUND, 15 | TCP_SERVER_STATE_LISTENING, 16 | TCP_SERVER_STATE_CLOSING, 17 | TCP_SERVER_STATE_CLOSED, 18 | }; 19 | 20 | class TCPServerSubscription 21 | { 22 | protected: 23 | ~TCPServerSubscription() = default; 24 | public: 25 | virtual void OnAccept( 26 | mg::net::Socket aSock, 27 | const mg::net::Host& aHost) = 0; 28 | // Close is signaled only if Listen() was successful beforehand. 29 | virtual void OnClose() = 0; 30 | }; 31 | 32 | class TCPServer 33 | : private IOSubscription 34 | { 35 | public: 36 | SHARED_PTR_RENEW_API(TCPServer) 37 | 38 | bool Bind( 39 | const mg::net::Host& aHost, 40 | mg::box::Error::Ptr& aOutErr); 41 | 42 | uint16_t GetPort() const; 43 | 44 | bool Listen( 45 | uint32_t aBacklog, 46 | TCPServerSubscription* aSub, 47 | mg::box::Error::Ptr& aOutErr); 48 | 49 | void PostClose(); 50 | bool IsClosed() const; 51 | IOCore& GetCore(); 52 | 53 | private: 54 | TCPServer(IOCore& aCore); 55 | ~TCPServer() override; 56 | 57 | void OnEvent( 58 | const IOArgs& aArgs) override; 59 | 60 | mutable mg::box::Mutex myMutex; 61 | TCPServerState myState; 62 | IOTask myTask; 63 | IOEvent myAcceptEvent; 64 | TCPServerSubscription* mySub; 65 | IOServerSocket* myBoundSocket; 66 | }; 67 | 68 | inline IOCore& 69 | TCPServer::GetCore() 70 | { 71 | return myTask.GetCore(); 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mg/aio/TCPSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/aio/TCPSocketIFace.h" 4 | 5 | namespace mg { 6 | namespace aio { 7 | 8 | struct TCPSocketParams 9 | { 10 | TCPSocketParams(); 11 | 12 | // No parameters yet. 13 | }; 14 | 15 | // Event-oriented asynchronous TCP socket. 16 | class TCPSocket 17 | : public TCPSocketIFace 18 | { 19 | public: 20 | TCPSocket( 21 | IOCore& aCore); 22 | 23 | void Open( 24 | const TCPSocketParams& aParams); 25 | 26 | private: 27 | ~TCPSocket() override = default; 28 | 29 | void OnEvent( 30 | const IOArgs& aArgs) override; 31 | 32 | void PrivSend(); 33 | void PrivSendAbort( 34 | mg::box::Error* aError); 35 | void PrivSendCommit( 36 | uint32_t aByteCount); 37 | bool PrivSendEventConsume(); 38 | 39 | void PrivRecv(); 40 | void PrivRecvAbort( 41 | mg::box::Error* aError); 42 | void PrivRecvCommit( 43 | uint32_t aByteCount); 44 | bool PrivRecvEventConsume(); 45 | 46 | // Offset in the first buffer for sending. It is > 0 when a whole buffer couldn't 47 | // be sent in one IO operation. 48 | uint32_t mySendOffset; 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/mg/aio/TCPSocketCtl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/aio/IOTask.h" 4 | #include "mg/box/Time.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace aio { 10 | 11 | class TCPSocketHandshake; 12 | struct TCPSocketCtlDomainRequest; 13 | 14 | struct TCPSocketCtlConnectParams 15 | { 16 | TCPSocketCtlConnectParams(); 17 | 18 | mg::net::Socket mySocket; 19 | std::string myEndpoint; 20 | mg::net::SockAddrFamily myAddrFamily; 21 | mg::box::TimeLimit myDelay; 22 | }; 23 | 24 | using TCPSocketHandshakeCallback = std::function; 25 | 26 | // The handshake is not just a callback, because the latter doesn't have a destructor 27 | // (= can't call 'delete' on bound pointers). Having a class gives more control, 28 | // although people can just use the default one with a callback too. 29 | class TCPSocketHandshake 30 | { 31 | public: 32 | TCPSocketHandshake( 33 | const TCPSocketHandshakeCallback& aCallback); 34 | virtual ~TCPSocketHandshake() = default; 35 | 36 | bool Update(); 37 | 38 | private: 39 | TCPSocketHandshakeCallback myCallback; 40 | }; 41 | 42 | // TCP socket control message. It is an internal helper, not a part of the public API. 43 | // It is a transport unit for commands about how to change the socket state. 44 | class TCPSocketCtl 45 | { 46 | public: 47 | TCPSocketCtl( 48 | IOTask* aTask); 49 | ~TCPSocketCtl(); 50 | 51 | // It is assumed the owner has one front ctl message, and one being in progress in 52 | // an IO worker. Front messages must be merged into the internal one. A queue of 53 | // messages can't be used, because some ctls like shutdown may affect the others. 54 | // And because it would waste more memory. 55 | void MergeFrom( 56 | TCPSocketCtl* aSrc); 57 | 58 | // 59 | // One control message can carry multiple commands. 60 | // 61 | 62 | void AddConnect( 63 | const TCPSocketCtlConnectParams& aParams); 64 | 65 | // Attach to an already connected socket. It is useful when TCPSocket is used to 66 | // wrap remotely accepted clients. 67 | void AddAttach( 68 | mg::net::Socket aSocket); 69 | 70 | // An optional handshake step. It is activated after a raw TCP connection is 71 | // established. Can be used for SSL, for custom protocols, for any kinds of 72 | // initial communications. 73 | void AddHandshake( 74 | TCPSocketHandshake* aHandshake); 75 | 76 | void AddShutdown(); 77 | 78 | // 79 | // For use inside of an IO worker. 80 | // 81 | 82 | bool IsIdle() const; 83 | 84 | bool HasShutdown() const; 85 | bool DoShutdown( 86 | mg::box::Error::Ptr& aOutErr); 87 | 88 | bool HasConnect() const; 89 | bool DoConnect( 90 | mg::box::Error::Ptr& aOutErr); 91 | 92 | bool HasAttach() const; 93 | void DoAttach(); 94 | 95 | bool HasHandshake() const; 96 | void DoHandshake(); 97 | 98 | private: 99 | void PrivEndConnect(); 100 | 101 | void PrivEndAttach(); 102 | 103 | void PrivStartHandshake(); 104 | void PrivEndHandshake(); 105 | 106 | bool myHasShutdown; 107 | 108 | bool myHasConnect; 109 | bool myConnectIsStarted; 110 | mg::net::Host myConnectHost; 111 | IOEvent myConnectEvent; 112 | TCPSocketCtlDomainRequest* myConnectDomain; 113 | mg::net::Socket myConnectSocket; 114 | mg::box::TimeLimit myConnectDelay; 115 | uint64_t myConnectStartDeadline; 116 | 117 | bool myHasAttach; 118 | mg::net::Socket myAttachSocket; 119 | 120 | bool myHasHandshake; 121 | TCPSocketHandshake* myHandshake; 122 | 123 | IOTask* myTask; 124 | }; 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/mg/aio/TCPSocketSubscription.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPSocketSubscription.h" 2 | 3 | namespace mg { 4 | namespace aio { 5 | 6 | void 7 | TCPSocketSubscription::OnConnect() 8 | { 9 | } 10 | 11 | void 12 | TCPSocketSubscription::OnConnectError( 13 | mg::box::Error*) 14 | { 15 | } 16 | 17 | void 18 | TCPSocketSubscription::OnRecv( 19 | mg::net::BufferReadStream&) 20 | { 21 | } 22 | 23 | void 24 | TCPSocketSubscription::OnRecvError( 25 | mg::box::Error*) 26 | { 27 | } 28 | 29 | void 30 | TCPSocketSubscription::OnSend( 31 | uint32_t) 32 | { 33 | } 34 | 35 | void 36 | TCPSocketSubscription::OnSendError( 37 | mg::box::Error*) 38 | { 39 | } 40 | 41 | void 42 | TCPSocketSubscription::OnError( 43 | mg::box::Error*) 44 | { 45 | } 46 | 47 | void 48 | TCPSocketSubscription::OnClose() 49 | { 50 | } 51 | 52 | void 53 | TCPSocketSubscription::OnEvent() 54 | { 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/mg/aio/TCPSocketSubscription.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | F_DECLARE_CLASS(mg, box, Error) 6 | F_DECLARE_CLASS(mg, net, BufferReadStream) 7 | 8 | namespace mg { 9 | namespace aio { 10 | 11 | // All the callbacks are called from an IO worker thread. 12 | struct TCPSocketSubscription 13 | { 14 | virtual ~TCPSocketSubscription() = default; 15 | 16 | virtual void OnConnect(); 17 | virtual void OnConnectError( 18 | mg::box::Error* aError); 19 | 20 | virtual void OnRecv( 21 | mg::net::BufferReadStream& aStream); 22 | virtual void OnRecvError( 23 | mg::box::Error* aError); 24 | 25 | virtual void OnSend( 26 | uint32_t aByteCount); 27 | virtual void OnSendError( 28 | mg::box::Error* aError); 29 | 30 | virtual void OnError( 31 | mg::box::Error* aError); 32 | virtual void OnClose(); 33 | virtual void OnEvent(); 34 | }; 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/mg/box/Algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | // Windows defines macros 'min' and 'max' which make impossible to use std::min/max 9 | // without hacks. Easier to just define them again with different naming. 10 | 11 | template 12 | const T& Max(const T& A, const T& B) 13 | { 14 | return A > B ? A : B; 15 | } 16 | 17 | template 18 | const T& Min(const T& A, const T& B) 19 | { 20 | return A < B ? A : B; 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mg/box/Assert.cpp: -------------------------------------------------------------------------------- 1 | #include "Assert.h" 2 | 3 | #include "mg/box/StringFunctions.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | void 11 | AssertS( 12 | const char* aExpression, 13 | const char* aFile, 14 | int aLine) 15 | { 16 | fprintf(stderr, "assertion failed: (%s) %s %u\n", aExpression, aFile, aLine); 17 | #if IS_PLATFORM_UNIX 18 | abort(); 19 | #else 20 | #if IS_COMPILER_MSVC 21 | #pragma warning(push) 22 | // "Dereferencing NULL pointer". 23 | #pragma warning(disable: 6011) 24 | #endif 25 | int* t = NULL; 26 | *t = 0; 27 | #if IS_COMPILER_MSVC 28 | #pragma warning(pop) 29 | #endif 30 | #endif 31 | } 32 | 33 | void 34 | AssertF( 35 | const char* aExpression, 36 | const char* aFile, 37 | int aLine, 38 | const char* aFormat, 39 | ...) 40 | { 41 | std::string expression = mg::box::StringFormat( 42 | "(%s): ", aExpression); 43 | 44 | va_list ap; 45 | va_start(ap, aFormat); 46 | expression += mg::box::StringVFormat(aFormat, ap); 47 | va_end(ap); 48 | AssertS(expression.c_str(), aFile, aLine); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/mg/box/Assert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #define MG_BOX_ASSERT_F(X, ...) do { \ 6 | if (!(X)) \ 7 | mg::box::AssertF(#X, __FILE__, __LINE__, __VA_ARGS__); \ 8 | } while(false) 9 | 10 | #define MG_BOX_ASSERT(X) do { \ 11 | if (!(X)) \ 12 | mg::box::AssertS(#X, __FILE__, __LINE__); \ 13 | } while(false) 14 | 15 | #if IS_BUILD_DEBUG 16 | #define MG_DEV_ASSERT MG_BOX_ASSERT 17 | #define MG_DEV_ASSERT_F MG_BOX_ASSERT_F 18 | #else 19 | #define MG_DEV_ASSERT MG_UNUSED 20 | #define MG_DEV_ASSERT_F MG_UNUSED 21 | #endif 22 | 23 | namespace mg { 24 | namespace box { 25 | 26 | void AssertS( 27 | const char* aExpression, 28 | const char* aFile, 29 | int aLine); 30 | 31 | MG_STRFORMAT_PRINTF(4, 5) 32 | void AssertF( 33 | const char* aExpression, 34 | const char* aFile, 35 | int aLine, 36 | const char* aFormat, 37 | ...); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/mg/box/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set(mgbox_src 4 | Assert.cpp 5 | ConditionVariable.cpp 6 | Coro.cpp 7 | Error.cpp 8 | InterruptibleMutex.cpp 9 | IOVec.cpp 10 | Log.cpp 11 | MultiConsumerQueueBase.cpp 12 | Mutex.cpp 13 | Signal.cpp 14 | StringFunctions.cpp 15 | Thread.cpp 16 | ) 17 | 18 | if(WIN32) 19 | set(mgbox_src ${mgbox_src} 20 | Sysinfo_Win.cpp 21 | Thread_Win.cpp 22 | Time_Win.cpp 23 | ) 24 | elseif(APPLE) 25 | set(mgbox_src ${mgbox_src} 26 | Sysinfo_Apple.cpp 27 | Thread_Unix.cpp 28 | Time_Unix.cpp 29 | ) 30 | else() 31 | set(mgbox_src ${mgbox_src} 32 | Sysinfo_Linux.cpp 33 | Thread_Unix.cpp 34 | Time_Unix.cpp 35 | ) 36 | endif() 37 | 38 | add_library(mgbox ${mgbox_src} "ThreadFunc.h") 39 | 40 | target_include_directories(mgbox PUBLIC 41 | ${CMAKE_SOURCE_DIR}/src/ 42 | ) 43 | 44 | set(install_headers 45 | Assert.h 46 | Atomic.h 47 | BinaryHeap.h 48 | ConditionVariable.h 49 | Coro.h 50 | Definitions.h 51 | DoublyList.h 52 | Error.h 53 | ForwardList.h 54 | InterruptibleMutex.h 55 | IOVec.h 56 | Log.h 57 | MultiConsumerQueue.h 58 | MultiConsumerQueueBase.h 59 | MultiProducerQueueIntrusive.h 60 | Mutex.h 61 | RefCount.h 62 | SharedPtr.h 63 | Signal.h 64 | StringFunctions.h 65 | Thread.h 66 | ThreadLocalPool.h 67 | Time.h 68 | TypeTraits.h 69 | ) 70 | 71 | install(TARGETS mgbox DESTINATION "${install_lib_root}") 72 | install(FILES ${install_headers} DESTINATION "${install_include_root}/mg/box/") 73 | -------------------------------------------------------------------------------- /src/mg/box/ConditionVariable.cpp: -------------------------------------------------------------------------------- 1 | #include "ConditionVariable.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | void 9 | ConditionVariable::Wait( 10 | Mutex& aMutex) 11 | { 12 | MG_BOX_ASSERT(aMutex.IsOwnedByThisThread()); 13 | MG_BOX_ASSERT(aMutex.myCount == 1); 14 | uint32_t tid = aMutex.myOwner; 15 | aMutex.myOwner = 0; 16 | aMutex.myCount = 0; 17 | 18 | myHandle.wait(aMutex.myHandle); 19 | 20 | MG_BOX_ASSERT(aMutex.myOwner == 0); 21 | MG_BOX_ASSERT(aMutex.myCount == 0); 22 | aMutex.myOwner = tid; 23 | aMutex.myCount = 1; 24 | } 25 | 26 | bool 27 | ConditionVariable::TimedWait( 28 | Mutex& aMutex, 29 | mg::box::TimeLimit aTimeLimit) 30 | { 31 | MG_BOX_ASSERT(aMutex.IsOwnedByThisThread()); 32 | MG_BOX_ASSERT(aMutex.myCount == 1); 33 | uint32_t tid = aMutex.myOwner; 34 | aMutex.myOwner = 0; 35 | aMutex.myCount = 0; 36 | 37 | uint64_t timeout = aTimeLimit.ToDurationFromNow().myValue; 38 | bool ok = myHandle.wait_for(aMutex.myHandle, 39 | std::chrono::milliseconds(timeout)) != std::cv_status::timeout; 40 | 41 | MG_BOX_ASSERT(aMutex.myOwner == 0); 42 | MG_BOX_ASSERT(aMutex.myCount == 0); 43 | aMutex.myOwner = tid; 44 | aMutex.myCount = 1; 45 | 46 | return ok; 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mg/box/ConditionVariable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Mutex.h" 4 | #include "mg/box/Time.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace box { 10 | 11 | // Condition variable allows to atomically unlock a mutex and 12 | // lock on a condition until a signal. This is vital for some 13 | // tasks (or very strongly simplifies them). For example, 14 | // assume that a thread wants an event. Event is a certain 15 | // value of a variable. The variable is protected by a mutex. 16 | // To check its value the thread needs to lock the mutex, 17 | // check the variable. If it does not match the needed value, 18 | // the thread should unlock the mutex and go to sleep. 19 | // 20 | // What if the event happens exactly after the thread unlocked 21 | // the mutex? In that case it will go to sleep forever, 22 | // because it missed the needed value already. Condition 23 | // variable solves exactly that problem - unlock a mutex and 24 | // go to sleep atomically. 25 | // 26 | class ConditionVariable 27 | { 28 | public: 29 | ConditionVariable() = default; 30 | 31 | void Wait( 32 | Mutex& aMutex); 33 | bool TimedWait( 34 | Mutex& aMutex, 35 | mg::box::TimeLimit aTimeLimit); 36 | 37 | void Signal() { myHandle.notify_one(); } 38 | void Broadcast() { myHandle.notify_all(); } 39 | 40 | private: 41 | ConditionVariable( 42 | const ConditionVariable&) = delete; 43 | ConditionVariable& operator=( 44 | const ConditionVariable&) = delete; 45 | 46 | std::condition_variable_any myHandle; 47 | }; 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mg/box/Coro.cpp: -------------------------------------------------------------------------------- 1 | #include "Coro.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | #if MG_CORO_IS_ENABLED 9 | 10 | static void 11 | CoroHandleUnref( 12 | CoroHandle aCoro) 13 | { 14 | if (!aCoro) 15 | return; 16 | // Do not destroy the current coro right away. It could be that it isn't the top 17 | // one. The coroutine stack must be unrolled from the top. Higher frames might 18 | // depend on lower (previous) ones, but not vice versa. 19 | CoroHandle top = aCoro; 20 | while (top.promise().myNext) 21 | top = top.promise().myNext.Handle(); 22 | while (CoroHandle prev = top.promise().myPrev) 23 | { 24 | prev.promise().myNext.Promise().myPrev = {}; 25 | prev.promise().myNext = {}; 26 | top = prev; 27 | } 28 | aCoro.destroy(); 29 | } 30 | 31 | ////////////////////////////////////////////////////////////////////////////////////// 32 | 33 | CoroRef::CoroRef( 34 | const CoroRef&) 35 | { 36 | // The constructor is not deleted at compile-time, because otherwise it isn't 37 | // usable inside std::function, which requires all its content to be copyable. 38 | MG_BOX_ASSERT(!"Coro can have only one owner"); 39 | } 40 | 41 | void 42 | CoroRef::Clear() 43 | { 44 | Coro c = std::move(myCoro); 45 | myCoro = {}; 46 | CoroHandleUnref(c); 47 | } 48 | 49 | CoroRef& 50 | CoroRef::operator=( 51 | CoroRef&& aObj) 52 | { 53 | Coro c = myCoro; 54 | myCoro = std::move(aObj.myCoro); 55 | aObj.myCoro = {}; 56 | CoroHandleUnref(c); 57 | return *this; 58 | } 59 | 60 | ////////////////////////////////////////////////////////////////////////////////////// 61 | 62 | void 63 | CoroOpFinal::await_suspend( 64 | CoroHandle aThisCoro) noexcept 65 | { 66 | CoroPromise& thisPromise = aThisCoro.promise(); 67 | CoroHandle prev = thisPromise.myPrev; 68 | if (!prev) 69 | return; 70 | thisPromise.myPrev = {}; 71 | 72 | CoroPromise& prevPromise = prev.promise(); 73 | prevPromise.myNext = {}; 74 | prevPromise.myFirst.promise().myLast = prev; 75 | prev.resume(); 76 | } 77 | 78 | ////////////////////////////////////////////////////////////////////////////////////// 79 | 80 | void 81 | CoroPromise::unhandled_exception() noexcept 82 | { 83 | MG_BOX_ASSERT(!"Unhandled exception from a coroutine"); 84 | } 85 | 86 | ////////////////////////////////////////////////////////////////////////////////////// 87 | 88 | CoroOpCall::CoroOpCall( 89 | Coro&& aNewCoro) 90 | : myNewCoro(std::move(aNewCoro)) 91 | { 92 | } 93 | 94 | CoroHandle 95 | CoroOpCall::await_suspend( 96 | CoroHandle aThisCoro) noexcept 97 | { 98 | // Place the new coro on top of the coro stack. 99 | 100 | CoroHandle newHandle = myNewCoro.Handle(); 101 | CoroPromise& newPromise = newHandle.promise(); 102 | CoroPromise& thisPromise = aThisCoro.promise(); 103 | 104 | newPromise.myPrev = aThisCoro; 105 | thisPromise.myNext = std::move(myNewCoro); 106 | 107 | newPromise.myFirst = thisPromise.myFirst; 108 | newPromise.myFirst.promise().myLast = newHandle; 109 | 110 | return newHandle; 111 | } 112 | 113 | #endif 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/mg/box/IOVec.cpp: -------------------------------------------------------------------------------- 1 | #include "IOVec.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | static_assert(sizeof(IOVec) == sizeof(IOVecNative), 11 | "IoVec matches native vec"); 12 | 13 | #if !IS_PLATFORM_WIN 14 | // Vectorized system calls can fail when try to send too many vectors. They are not 15 | // guaranteed to send only a part of data. 16 | static_assert(theIOVecMaxCount <= IOV_MAX, "Too big vector batch"); 17 | #endif 18 | 19 | #if IS_PLATFORM_WIN 20 | static_assert(offsetof(IOVec, mySize) == offsetof(WSABUF, len), 21 | "IOVec::mySize offset"); 22 | static_assert(offsetof(IOVec, myData) == offsetof(WSABUF, buf), 23 | "IOVec::myData offset"); 24 | 25 | static_assert(std::is_same< 26 | decltype(IOVec::mySize), decltype(WSABUF::len)>::value, 27 | "IOVec::mySize type"); 28 | 29 | // IOVec::myData is kept 'void*' to avoid explicit casts, but it is the same as 30 | // 'CHAR*' anyway. 31 | static_assert(std::is_same::value, 32 | "IOVec::myData type"); 33 | #else 34 | static_assert(offsetof(IOVec, mySize) == offsetof(iovec, iov_len), 35 | "IOVec::mySize offset"); 36 | static_assert(offsetof(IOVec, myData) == offsetof(iovec, iov_base), 37 | "IOVec::myData offset"); 38 | 39 | static_assert(std::is_same< 40 | decltype(IOVec::mySize), decltype(iovec::iov_len)>::value, 41 | "IOVec::mySize type"); 42 | 43 | static_assert(std::is_same< 44 | decltype(IOVec::myData), decltype(iovec::iov_base)>::value, 45 | "IOVec::myData type"); 46 | #endif 47 | 48 | void 49 | IOVecPropagate( 50 | IOVec*& aInOut, 51 | uint32_t& aInOutCount, 52 | uint32_t aByteOffset) 53 | { 54 | IOVec* cursor = aInOut; 55 | uint32_t count = aInOutCount; 56 | while (aByteOffset > 0) 57 | { 58 | if (aByteOffset < cursor->mySize) 59 | { 60 | cursor->mySize -= aByteOffset; 61 | cursor->myData = (uint8_t*)cursor->myData + aByteOffset; 62 | break; 63 | } 64 | MG_DEV_ASSERT(count > 0); 65 | aByteOffset -= cursor->mySize; 66 | cursor->myData = nullptr; 67 | cursor->mySize = 0; 68 | ++cursor; 69 | --count; 70 | } 71 | aInOut = cursor; 72 | aInOutCount = count; 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/mg/box/IOVec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #if IS_PLATFORM_WIN 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | namespace mg { 12 | namespace box { 13 | 14 | static constexpr uint32_t theIOVecMaxCount = 128; 15 | 16 | #if IS_PLATFORM_WIN 17 | using IOVecNative = WSABUF; 18 | #else 19 | using IOVecNative = iovec; 20 | #endif 21 | // 22 | // A wrapper around platform-specific scatter/gather buffers used for vectorized IO. 23 | // The important part is that the wrapper is in fact an alias to the native vector 24 | // object. It allows not to spend time on converting these objects into the native 25 | // ones. This is achieved by using same members and types as the original platform 26 | // does. 27 | // 28 | struct IOVec 29 | { 30 | #if IS_PLATFORM_WIN 31 | unsigned long mySize; 32 | void* myData; 33 | #else 34 | void* myData; 35 | size_t mySize; 36 | #endif 37 | }; 38 | 39 | // These functions would be wrong to define as IOVec methods, because they usually 40 | // operate on IOVec arrays. They would work as methods, but would look strange to call 41 | // a method on a first array member to 'convert' all the members. 42 | 43 | static inline IOVecNative* 44 | IOVecToNative( 45 | const IOVec* aVec) 46 | { 47 | return (IOVecNative*)aVec; 48 | } 49 | 50 | void IOVecPropagate( 51 | IOVec*& aInOut, 52 | uint32_t& aInOutCount, 53 | uint32_t aByteOffset); 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/mg/box/InterruptibleMutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/DoublyList.h" 4 | #include "mg/box/Signal.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace box { 10 | 11 | struct InterruptibleMutexWaiter 12 | { 13 | mg::box::Signal mySignal; 14 | InterruptibleMutexWaiter* myNext; 15 | InterruptibleMutexWaiter* myPrev; 16 | }; 17 | 18 | using InterruptibleMutexWakeupCallback = std::function; 19 | 20 | // The interruptible mutex allows one thread to take the mutex, get blocked 21 | // on something else while holding it, and the other threads can wake it up 22 | // (interrupt) to forcefully take the mutex for some other work. 23 | // 24 | // The guarantees are the following: 25 | // - There is no spin-locking when TryLock() is used. 26 | // - Some threads use TryLock() + can sleep while holding the lock, 27 | // then other threads via Lock() can take the ownership over to do some 28 | // work given that they won't be sleeping on the same condition. 29 | // 30 | // An example: 31 | // 32 | // - Thread-workers (any number): 33 | // if (TryLock()) 34 | // { 35 | // WaitOn(Signal); 36 | // Unlock(); 37 | // } 38 | // 39 | // - Thread-others (any number): 40 | // Lock([](){ Signal.Send(); }); 41 | // DoSomething(); // But don't sleep on the same Signal. 42 | // Unlock(). 43 | // 44 | // Then the 'other' threads are guaranteed to get the lock. And nothing will 45 | // ever deadlock. 46 | // 47 | class InterruptibleMutex 48 | { 49 | public: 50 | InterruptibleMutex(); 51 | ~InterruptibleMutex(); 52 | 53 | void Lock( 54 | const InterruptibleMutexWakeupCallback& aWakeup); 55 | bool TryLock(); 56 | void Unlock(); 57 | 58 | private: 59 | InterruptibleMutex( 60 | const InterruptibleMutex&) = delete; 61 | 62 | mg::box::Mutex myMutex; 63 | mg::box::DoublyList myWaiters; 64 | mg::box::AtomicU8 myState; 65 | }; 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/mg/box/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | 3 | namespace mg { 4 | namespace box { 5 | 6 | void 7 | Log( 8 | LogLevel aLevel, 9 | const char* aTag, 10 | int aLine, 11 | const char* aFile, 12 | const char* aFormat, 13 | ...) 14 | { 15 | va_list va; 16 | va_start(va, aFormat); 17 | LogV(aLevel, aTag, aLine, aFile, aFormat, va); 18 | va_end(va); 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/mg/box/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | 7 | #define MG_LOG(level, tag, format, ...) do { \ 8 | mg::box::Log((level), (tag), __LINE__, __FILE__, (format), ##__VA_ARGS__); \ 9 | } while (false) 10 | 11 | #define MG_LOG_ERROR(...) MG_LOG(mg::box::LOG_LEVEL_ERROR, __VA_ARGS__) 12 | #define MG_LOG_WARN(...) MG_LOG(mg::box::LOG_LEVEL_WARN, __VA_ARGS__) 13 | #define MG_LOG_INFO(...) MG_LOG(mg::box::LOG_LEVEL_INFO, __VA_ARGS__) 14 | #define MG_LOG_DEBUG(...) MG_LOG(mg::box::LOG_LEVEL_DEBUG, __VA_ARGS__) 15 | 16 | namespace mg { 17 | namespace box { 18 | 19 | enum LogLevel 20 | { 21 | LOG_LEVEL_ERROR = 1, 22 | LOG_LEVEL_WARN = 2, 23 | LOG_LEVEL_INFO = 3, 24 | LOG_LEVEL_DEBUG = 4, 25 | }; 26 | 27 | MG_STRFORMAT_PRINTF(5, 6) 28 | void Log( 29 | LogLevel aLevel, 30 | const char* aTag, 31 | int aLine, 32 | const char* aFile, 33 | const char* aFormat, 34 | ...); 35 | 36 | MG_STRFORMAT_PRINTF(5, 0) 37 | void LogV( 38 | LogLevel aLevel, 39 | const char* aTag, 40 | int aLine, 41 | const char* aFile, 42 | const char* aFormat, 43 | va_list aParams); 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/mg/box/MultiConsumerQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/MultiConsumerQueueBase.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | // This whole file is a type-friendly wrapper for the base 9 | // queue. For algorithm and API details go to there. 10 | 11 | template 12 | class MultiConsumerQueueConsumer; 13 | 14 | template 15 | class MultiConsumerQueue 16 | { 17 | public: 18 | MultiConsumerQueue( 19 | uint32_t aSubQueueSize); 20 | 21 | bool Push( 22 | T* aItem); 23 | 24 | bool PushPending( 25 | T* aItem); 26 | 27 | bool FlushPending(); 28 | 29 | void Reserve( 30 | uint32_t aCount); 31 | 32 | uint32_t SubQueueCount(); 33 | 34 | uint32_t ConsumerCount(); 35 | 36 | uint32_t Count(); 37 | 38 | private: 39 | MCQBaseQueue myBase; 40 | 41 | friend class MultiConsumerQueueConsumer; 42 | }; 43 | 44 | template 45 | class MultiConsumerQueueConsumer 46 | { 47 | public: 48 | MultiConsumerQueueConsumer(); 49 | 50 | MultiConsumerQueueConsumer( 51 | MultiConsumerQueue* aQueue); 52 | 53 | T* Pop(); 54 | 55 | void Attach( 56 | MultiConsumerQueue* aQueue); 57 | 58 | void Detach(); 59 | 60 | private: 61 | MCQBaseConsumer myBase; 62 | }; 63 | 64 | template 65 | inline 66 | MultiConsumerQueue::MultiConsumerQueue( 67 | uint32_t aSubQueueSize) 68 | : myBase(aSubQueueSize) 69 | { 70 | } 71 | 72 | template 73 | inline bool 74 | MultiConsumerQueue::Push( 75 | T* aItem) 76 | { 77 | return myBase.Push((void*) aItem); 78 | } 79 | 80 | template 81 | inline bool 82 | MultiConsumerQueue::PushPending( 83 | T* aItem) 84 | { 85 | return myBase.PushPending((void*) aItem); 86 | } 87 | 88 | template 89 | inline bool 90 | MultiConsumerQueue::FlushPending() 91 | { 92 | return myBase.FlushPending(); 93 | } 94 | 95 | template 96 | inline void 97 | MultiConsumerQueue::Reserve( 98 | uint32_t aCount) 99 | { 100 | myBase.Reserve(aCount); 101 | } 102 | 103 | template 104 | inline uint32_t 105 | MultiConsumerQueue::SubQueueCount() 106 | { 107 | return myBase.SubQueueCount(); 108 | } 109 | 110 | template 111 | inline uint32_t 112 | MultiConsumerQueue::ConsumerCount() 113 | { 114 | return myBase.ConsumerCount(); 115 | } 116 | 117 | template 118 | inline uint32_t 119 | MultiConsumerQueue::Count() 120 | { 121 | return myBase.Count(); 122 | } 123 | 124 | template 125 | MultiConsumerQueueConsumer::MultiConsumerQueueConsumer() 126 | { 127 | } 128 | 129 | template 130 | MultiConsumerQueueConsumer::MultiConsumerQueueConsumer( 131 | MultiConsumerQueue* aQueue) 132 | : myBase(&aQueue->myBase) 133 | { 134 | } 135 | 136 | template 137 | inline T* 138 | MultiConsumerQueueConsumer::Pop() 139 | { 140 | return (T*) myBase.Pop(); 141 | } 142 | 143 | template 144 | inline void 145 | MultiConsumerQueueConsumer::Attach( 146 | MultiConsumerQueue* aQueue) 147 | { 148 | myBase.Attach(&aQueue->myBase); 149 | } 150 | 151 | template 152 | inline void 153 | MultiConsumerQueueConsumer::Detach() 154 | { 155 | myBase.Detach(); 156 | } 157 | 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/mg/box/MultiProducerQueueIntrusive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Atomic.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | template 9 | class MultiProducerQueueIntrusive 10 | { 11 | public: 12 | MultiProducerQueueIntrusive() 13 | : myHead(nullptr) 14 | { 15 | } 16 | 17 | inline bool 18 | IsEmpty() 19 | { 20 | return myHead.LoadAcquire() == nullptr; 21 | } 22 | 23 | // Returns true if the object was first. That may be 24 | // useful if, for example, need to signal an event, when 25 | // the queue becomes not empty, to another thread maybe. 26 | // Then it makes sense to do the signal only when a first 27 | // object is added, because the thread signaling is 28 | // usually expensive. 29 | inline bool 30 | Push( 31 | T* aItem) 32 | { 33 | return PrivPushRange(aItem, aItem); 34 | } 35 | 36 | // Constant complexity instead of linear for normal 37 | // multiple items push. Works the best when order is not 38 | // important or reverse back to normal order is done along 39 | // with something else afterwards. 40 | inline bool 41 | PushManyFastReversed( 42 | T* aFirst, 43 | T* aLast) 44 | { 45 | if (aFirst == nullptr) 46 | return IsEmpty(); 47 | aLast->*myNext = nullptr; 48 | return PrivPushRange(aFirst, aLast); 49 | } 50 | 51 | bool 52 | PushManyFastReversed( 53 | T* aFirst) 54 | { 55 | if (aFirst == nullptr) 56 | return IsEmpty(); 57 | T* first = aFirst; 58 | T* last = aFirst; 59 | T* next; 60 | while ((next = last->*myNext) != nullptr) 61 | last = next; 62 | return PrivPushRange(first, last); 63 | } 64 | 65 | bool 66 | PushMany( 67 | T* aFirst) 68 | { 69 | if (aFirst == nullptr) 70 | return IsEmpty(); 71 | 72 | T* first = nullptr; 73 | T* last = aFirst; 74 | T* prev = aFirst; 75 | aFirst = aFirst->*myNext; 76 | if (aFirst == nullptr) 77 | return Push(last); 78 | 79 | T* next; 80 | do 81 | { 82 | next = aFirst->*myNext; 83 | aFirst->*myNext = prev; 84 | prev = aFirst; 85 | } while ((aFirst = next) != nullptr); 86 | first = prev; 87 | return PushManyFastReversed(first, last); 88 | } 89 | 90 | // Constant complexity instead of linear for normal pop. 91 | // Works the best, when the queue is used as an 92 | // intermediate store, and the order in it is not 93 | // important. 94 | inline T* 95 | PopAllFastReversed() 96 | { 97 | return myHead.ExchangeAcqRel(nullptr); 98 | } 99 | 100 | inline T* 101 | PopAll( 102 | T*& aOutTail) 103 | { 104 | T* curr = PopAllFastReversed(); 105 | aOutTail = curr; 106 | T* prev = nullptr; 107 | T* next = nullptr; 108 | while (curr != nullptr) 109 | { 110 | next = curr->*myNext; 111 | curr->*myNext = prev; 112 | prev = curr; 113 | curr = next; 114 | } 115 | return prev; 116 | } 117 | 118 | inline T* 119 | PopAll() 120 | { 121 | T* tail; 122 | return PopAll(tail); 123 | } 124 | 125 | private: 126 | bool 127 | PrivPushRange( 128 | T* aFirst, 129 | T* aLast) 130 | { 131 | T* oldHead = myHead.LoadAcquire(); 132 | do 133 | { 134 | aLast->*myNext = oldHead; 135 | } while (!myHead.CmpExchgWeakAcqRel(oldHead, aFirst)); 136 | return oldHead == nullptr; 137 | } 138 | 139 | mg::box::Atomic myHead; 140 | }; 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/mg/box/Mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "Mutex.h" 2 | 3 | #include "mg/box/Thread.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | mg::box::AtomicU64 theMutexStartContentCount(0); 9 | 10 | void 11 | MutexStatClear() 12 | { 13 | theMutexStartContentCount.StoreRelaxed(0); 14 | } 15 | 16 | uint64_t 17 | MutexStatContentionCount() 18 | { 19 | return theMutexStartContentCount.LoadRelaxed(); 20 | } 21 | 22 | void 23 | Mutex::Lock() 24 | { 25 | if (TryLock()) 26 | return; 27 | myHandle.lock(); 28 | myOwner = GetCurrentThreadId(); 29 | MG_DEV_ASSERT(myCount == 0); 30 | myCount = 1; 31 | } 32 | 33 | bool 34 | Mutex::TryLock() 35 | { 36 | if (!myHandle.try_lock()) 37 | { 38 | if (IsOwnedByThisThread()) 39 | { 40 | MG_DEV_ASSERT(myCount > 0); 41 | ++myCount; 42 | return true; 43 | } 44 | theMutexStartContentCount.IncrementRelaxed(); 45 | return false; 46 | } 47 | myOwner = GetCurrentThreadId(); 48 | MG_DEV_ASSERT(myCount == 0); 49 | myCount = 1; 50 | return true; 51 | } 52 | 53 | void 54 | Mutex::Unlock() 55 | { 56 | MG_BOX_ASSERT(IsOwnedByThisThread()); 57 | if (myCount == 1) 58 | { 59 | myOwner = 0; 60 | myCount = 0; 61 | myHandle.unlock(); 62 | return; 63 | } 64 | MG_DEV_ASSERT(myCount > 1); 65 | --myCount; 66 | } 67 | 68 | bool 69 | Mutex::IsOwnedByThisThread() const 70 | { 71 | return myOwner == GetCurrentThreadId(); 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mg/box/Mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/Atomic.h" 5 | 6 | #include 7 | #include 8 | 9 | F_DECLARE_CLASS(mg, box, ConditionVariable) 10 | 11 | namespace mg { 12 | namespace box { 13 | 14 | extern mg::box::AtomicU64 theMutexStartContentCount; 15 | 16 | void MutexStatClear(); 17 | 18 | uint64_t MutexStatContentionCount(); 19 | 20 | class Mutex 21 | { 22 | public: 23 | Mutex() : myOwner(0), myCount(0) {} 24 | ~Mutex() { MG_BOX_ASSERT(myOwner == 0 && myCount == 0); } 25 | 26 | void Lock(); 27 | bool TryLock(); 28 | void Unlock(); 29 | bool IsOwnedByThisThread() const; 30 | 31 | private: 32 | Mutex( 33 | const Mutex&) = delete; 34 | 35 | std::mutex myHandle; 36 | uint32_t myOwner; 37 | uint32_t myCount; 38 | 39 | friend class ConditionVariable; 40 | }; 41 | 42 | class MutexLock 43 | { 44 | public: 45 | MutexLock( 46 | Mutex& aMutex); 47 | 48 | ~MutexLock(); 49 | 50 | void Unlock(); 51 | 52 | private: 53 | MutexLock( 54 | const MutexLock&) = delete; 55 | MutexLock& operator=( 56 | const MutexLock&) = delete; 57 | 58 | Mutex* myMutex; 59 | }; 60 | 61 | ////////////////////////////////////////////////////////////// 62 | 63 | inline 64 | MutexLock::MutexLock( 65 | Mutex& aMutex) 66 | : myMutex(&aMutex) 67 | { 68 | myMutex->Lock(); 69 | } 70 | 71 | inline 72 | MutexLock::~MutexLock() 73 | { 74 | if (myMutex != nullptr) 75 | myMutex->Unlock(); 76 | } 77 | 78 | inline void 79 | MutexLock::Unlock() 80 | { 81 | if (myMutex == nullptr) 82 | return; 83 | myMutex->Unlock(); 84 | myMutex = nullptr; 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/mg/box/RefCount.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/Atomic.h" 5 | 6 | namespace mg { 7 | namespace box { 8 | 9 | class RefCount 10 | { 11 | public: 12 | explicit RefCount(); 13 | explicit RefCount( 14 | uint32_t aCount); 15 | 16 | // Delete any means to copy another ref count object. It is not clear what they 17 | // should do for the reference counted item. Increment? Keep intact? Better ban. 18 | RefCount( 19 | const RefCount&) = delete; 20 | RefCount& operator=( 21 | const RefCount&) = delete; 22 | ~RefCount() { MG_DEV_ASSERT(myCount.LoadRelaxed() == 0); } 23 | 24 | void Inc(); 25 | bool Dec(); 26 | uint32_t Get() const { return myCount.LoadRelaxed(); } 27 | 28 | private: 29 | // Signed for faster arithmetics and to catch negative count errors. 30 | mg::box::AtomicI32 myCount; 31 | }; 32 | 33 | ////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | inline 36 | RefCount::RefCount() 37 | // One ref for the counter's creator. 38 | : myCount(1) 39 | { 40 | } 41 | 42 | inline 43 | RefCount::RefCount( 44 | uint32_t aCount) 45 | : myCount((int32_t)aCount) 46 | { 47 | MG_DEV_ASSERT(aCount <= INT32_MAX); 48 | } 49 | 50 | inline void 51 | RefCount::Inc() 52 | { 53 | int32_t newValue = myCount.IncrementFetchRelaxed(); 54 | MG_DEV_ASSERT(newValue >= 1); 55 | } 56 | 57 | inline bool 58 | RefCount::Dec() 59 | { 60 | int32_t newValue = myCount.DecrementFetchRelaxed(); 61 | MG_DEV_ASSERT(newValue >= 0); 62 | return newValue == 0; 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/mg/box/Signal.cpp: -------------------------------------------------------------------------------- 1 | #include "Signal.h" 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/Atomic.h" 5 | 6 | namespace mg { 7 | namespace box { 8 | 9 | Signal::Signal() 10 | : myState(SIGNAL_STATE_EMPTY) 11 | { 12 | } 13 | 14 | Signal::~Signal() 15 | { 16 | // Lock-unlock to purge all the signal senders who didn't 17 | // leave the critical section yet. Deletion could be done 18 | // as a result of successful Receive() making it possible 19 | // that the signal sender didn't unlock the mutex yet 20 | // after setting the state signaled. 21 | myLock.Lock(); 22 | myLock.Unlock(); 23 | } 24 | 25 | void 26 | Signal::Send() 27 | { 28 | State oldState = SIGNAL_STATE_EMPTY; 29 | // If it was already set, then no need to proceed. The 30 | // condition variable is signaled by another thread. That 31 | // is one of the key features of Signal - lock-free signal 32 | // re-send if it is not consumed yet. 33 | if (!myState.CmpExchgStrongRelease(oldState, SIGNAL_STATE_PROBE)) 34 | return; 35 | 36 | // The signal must be done under a lock. Consider how the 37 | // blocking receive works: 38 | // 39 | // lock(); 40 | // while (!receive()) (1) 41 | // wait(); (2) 42 | // unlock(); 43 | // 44 | // If the signal wouldn't be locked, it could be emitted 45 | // between (1) and (2). Then wait() would be infinite even 46 | // though there is a pending signal. 47 | // 48 | // Another point why the send must be finished with a 49 | // lock - the receiver thread may decide to delete the 50 | // Signal object if Receive() succeeds. Consider this 51 | // example: 52 | // 53 | // Sender: Receiver: 54 | // sig.Send(); if (sig->Receive()) 55 | // delete sig; 56 | // 57 | // In case Send() wouldn't lock the mutex during setting 58 | // the signaled state, the receiver thread would receive 59 | // the signal and delete it making the sender thread 60 | // crash. 61 | myLock.Lock(); 62 | oldState = myState.ExchangeRelease(SIGNAL_STATE_SIGNALED); 63 | MG_BOX_ASSERT(oldState == SIGNAL_STATE_PROBE); 64 | myCond.Signal(); 65 | myLock.Unlock(); 66 | } 67 | 68 | bool 69 | Signal::Receive() 70 | { 71 | // It is not safe to return success if the state is probe. 72 | // Because the signal owner may decide to delete the 73 | // signal then, and the signal sender will try to lock 74 | // the mutex after its deletion. 75 | // Still the operation must remain lock-free, as it is one 76 | // of the key features of Signal. 77 | State oldState = SIGNAL_STATE_SIGNALED; 78 | return myState.CmpExchgStrongAcquire(oldState, SIGNAL_STATE_EMPTY); 79 | } 80 | 81 | void 82 | Signal::ReceiveBlocking() 83 | { 84 | if (Receive()) 85 | return; 86 | 87 | myLock.Lock(); 88 | while (!Receive()) 89 | myCond.Wait(myLock); 90 | myLock.Unlock(); 91 | } 92 | 93 | bool 94 | Signal::ReceiveTimed( 95 | mg::box::TimeLimit aTimeLimit) 96 | { 97 | if (Receive()) 98 | return true; 99 | 100 | myLock.Lock(); 101 | bool rc = Receive(); 102 | if (!rc) 103 | { 104 | myCond.TimedWait(myLock, aTimeLimit); 105 | rc = Receive(); 106 | // The receive still may return false, even if the 107 | // timeout didn't pass yet. This is usually enough, 108 | // and allows not to care about retries, getting 109 | // current time, calculating the timeout again, etc. 110 | // In case it would be ever needed, better introduce 111 | // ReceiveBlockingTimed() separately. 112 | } 113 | myLock.Unlock(); 114 | return rc; 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/mg/box/Signal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Atomic.h" 4 | #include "mg/box/ConditionVariable.h" 5 | #include "mg/box/Mutex.h" 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | // Signal is a highly efficient way of blocking delivery of a 11 | // flag between threads. 12 | // 13 | // Firstly, signal send wakes up only one thread instead of 14 | // all the threads waiting for it. That helps to avoid 15 | // unnecessary wakeups, when the signal can only be handled by 16 | // one thread anyway. So called "thundering herd problem". 17 | // 18 | // Secondly, the signal can be received in a single 19 | // lock-unlock combination. No need for a separate clear() 20 | // call. 21 | // 22 | // Thirdly, the signal does not care about spurious wakeups. 23 | // So the caller's code must have a protection against that 24 | // if necessary. Usually it is not necessary, and in Signal 25 | // you don't pay for it. 26 | // 27 | // Fourthly, Signal attempts to do all the operations in a 28 | // lock-free way first. 29 | class Signal 30 | { 31 | public: 32 | Signal(); 33 | 34 | ~Signal(); 35 | 36 | // Send wakes up one waiting thread, if there are any. If 37 | // there are no waiters, the signal stays here until a 38 | // receipt. If the signal is already installed, nothing 39 | // changes. To simulate broadcast the thread received the 40 | // signal first can check if there is more work to handle, 41 | // and re-send the signal to wake a next thread. 42 | void Send(); 43 | 44 | bool Receive(); 45 | 46 | void ReceiveBlocking(); 47 | 48 | // May return false not only in case of a timeout - also a 49 | // spurious wakeup is possible. 50 | bool ReceiveTimed( 51 | mg::box::TimeLimit aTimeLimit); 52 | 53 | private: 54 | // State of the signal is a protection against the case 55 | // when Send() is done, it appears to be the first Send(), 56 | // but it didn't took the lock yet. And now Receive() is 57 | // called, takes the signal, and decides to delete the 58 | // Signal object thinking it must not be used already. It 59 | // would not be true, because Send() would try to take the 60 | // lock now, and would crash if the Signal is deleted 61 | // already. 62 | enum State 63 | { 64 | // Signal is empty. 65 | SIGNAL_STATE_EMPTY, 66 | // Signal send is in progress. Sender will try to take 67 | // the lock now to finish the signaling. 68 | SIGNAL_STATE_PROBE, 69 | // Signal send is fully finished. Or is not finished, 70 | // but the lock is taken then. 71 | SIGNAL_STATE_SIGNALED, 72 | }; 73 | 74 | mg::box::ConditionVariable myCond; 75 | mg::box::Mutex myLock; 76 | mg::box::Atomic myState; 77 | }; 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/mg/box/StringFunctions.cpp: -------------------------------------------------------------------------------- 1 | #include "StringFunctions.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace box { 10 | 11 | const char* 12 | Strcasestr( 13 | const char* aString, 14 | const char* aToFind) 15 | { 16 | #if IS_PLATFORM_WIN 17 | char c, sc; 18 | if ((c = *aToFind++) != 0) { 19 | c = (char)tolower((unsigned char)c); 20 | size_t len = strlen(aToFind); 21 | do { 22 | do { 23 | if ((sc = *aString++) == 0) 24 | return (NULL); 25 | } while ((char)tolower((unsigned char)sc) != c); 26 | } while (_strnicmp(aString, aToFind, len) != 0); 27 | aString--; 28 | } 29 | return ((char*)aString); 30 | #else 31 | return strcasestr(aString, aToFind); 32 | #endif 33 | } 34 | 35 | void 36 | StringTrim( 37 | std::string& aStr) 38 | { 39 | size_t len = aStr.length(); 40 | while (true) 41 | { 42 | if (len == 0) 43 | { 44 | aStr.clear(); 45 | return; 46 | } 47 | if (!isspace(aStr[len - 1])) 48 | { 49 | aStr.resize(len); 50 | break; 51 | } 52 | --len; 53 | } 54 | size_t toCut = 0; 55 | while (toCut <= len && isspace(aStr[toCut])) 56 | ++toCut; 57 | aStr.erase(0, toCut); 58 | } 59 | 60 | uint32_t 61 | Vsnprintf( 62 | char* aBuffer, 63 | uint32_t aBufferSize, 64 | const char* aFmtString, 65 | va_list aArgList) 66 | { 67 | int rc = vsnprintf(aBuffer, aBufferSize, aFmtString, aArgList); 68 | MG_DEV_ASSERT(rc >= 0); 69 | return (uint32_t)rc; 70 | } 71 | 72 | std::string 73 | StringFormat( 74 | const char *aFormat, 75 | ...) 76 | { 77 | va_list va; 78 | va_start(va, aFormat); 79 | std::string res = StringVFormat(aFormat, va); 80 | va_end(va); 81 | return res; 82 | } 83 | 84 | std::string 85 | StringVFormat( 86 | const char *aFormat, 87 | va_list aParams) 88 | { 89 | va_list va; 90 | va_copy(va, aParams); 91 | int size = vsnprintf(nullptr, 0, aFormat, va); 92 | MG_DEV_ASSERT(size >= 0); 93 | va_end(va); 94 | ++size; 95 | char* data = new char[size]; 96 | va_copy(va, aParams); 97 | int size2 = vsnprintf(data, size, aFormat, va); 98 | MG_DEV_ASSERT(size2 + 1 == size); 99 | va_end(va); 100 | std::string res(data); 101 | delete[] data; 102 | return res; 103 | } 104 | 105 | bool 106 | StringToNumber( 107 | const char* aString, 108 | uint64_t& aOutNumber) 109 | { 110 | // Need to check for minus manually, because strtoull() 111 | // applies negation to the result. Despite the fact it 112 | // returns an unsigned type. Result is garbage. 113 | while (isspace(*aString)) 114 | ++aString; 115 | if (*aString == '-') 116 | return false; 117 | char* end; 118 | errno = 0; 119 | aOutNumber = strtoull(aString, &end, 10); 120 | return errno == 0 && *aString != 0 && *end == 0; 121 | } 122 | 123 | template 124 | static bool 125 | StringToUnsignedNumberTemplate( 126 | const char* aString, 127 | ResT& aOutNumber) 128 | { 129 | uint64_t res; 130 | if (StringToNumber(aString, res) && res <= aMax) 131 | { 132 | aOutNumber = (ResT)res; 133 | return true; 134 | } 135 | return false; 136 | } 137 | 138 | bool 139 | StringToNumber( 140 | const char* aString, 141 | uint16_t& aOutNumber) 142 | { 143 | return StringToUnsignedNumberTemplate(aString, aOutNumber); 144 | } 145 | 146 | bool 147 | StringToNumber( 148 | const char* aString, 149 | uint32_t& aOutNumber) 150 | { 151 | return StringToUnsignedNumberTemplate(aString, aOutNumber); 152 | } 153 | 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/mg/box/StringFunctions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace box { 10 | 11 | static inline uint32_t 12 | Strlen( 13 | const char* aString) 14 | { 15 | return (uint32_t)strlen(aString); 16 | } 17 | 18 | static inline int 19 | Strcmp( 20 | const char* aA, 21 | const char* aB) 22 | { 23 | return strcmp(aA, aB); 24 | } 25 | 26 | static inline int 27 | Strncmp( 28 | const char* aA, 29 | const char* aB, 30 | uint32_t aCount) 31 | { 32 | return strncmp(aA, aB, aCount); 33 | } 34 | 35 | static inline char* 36 | Strdup( 37 | const char* aStr) 38 | { 39 | #if IS_PLATFORM_WIN 40 | return _strdup(aStr); 41 | #else 42 | return strdup(aStr); 43 | #endif 44 | } 45 | 46 | static inline int 47 | Strcasecmp( 48 | const char* aA, 49 | const char* aB) 50 | { 51 | #if IS_PLATFORM_WIN 52 | return _stricmp(aA, aB); 53 | #else 54 | return strcasecmp(aA, aB); 55 | #endif 56 | } 57 | 58 | const char* 59 | Strcasestr( 60 | const char* aString, 61 | const char* aToFind); 62 | 63 | void StringTrim( 64 | std::string& aStr); 65 | 66 | MG_STRFORMAT_PRINTF(3, 0) 67 | uint32_t Vsnprintf( 68 | char* aBuffer, 69 | uint32_t aBufferSize, 70 | const char* aFmtString, 71 | va_list aArgList); 72 | 73 | MG_STRFORMAT_PRINTF(1, 2) 74 | std::string StringFormat( 75 | const char *aFormat, 76 | ...); 77 | 78 | MG_STRFORMAT_PRINTF(1, 0) 79 | std::string StringVFormat( 80 | const char *aFormat, 81 | va_list aParams); 82 | 83 | bool StringToNumber( 84 | const char* aString, 85 | uint16_t& aOutNumber); 86 | 87 | bool StringToNumber( 88 | const char* aString, 89 | uint32_t& aOutNumber); 90 | 91 | bool StringToNumber( 92 | const char* aString, 93 | uint64_t& aOutNumber); 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/mg/box/Sysinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | uint32_t SysGetCPUCoreCount(); 9 | 10 | bool SysIsWSL(); 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mg/box/Sysinfo_Apple.cpp: -------------------------------------------------------------------------------- 1 | #include "Sysinfo.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace mg { 10 | namespace box { 11 | 12 | static uint32_t SysCalcCPUCoreCount(); 13 | 14 | //////////////////////////////////////////////////////////////////////////// 15 | 16 | uint32_t 17 | SysGetCPUCoreCount() 18 | { 19 | static uint32_t coreCount = SysCalcCPUCoreCount(); 20 | return coreCount; 21 | } 22 | 23 | bool 24 | SysIsWSL() 25 | { 26 | return false; 27 | } 28 | 29 | //////////////////////////////////////////////////////////////////////////// 30 | 31 | static uint32_t 32 | SysCalcCPUCoreCount() 33 | { 34 | int32_t val = 0; 35 | size_t valSize = sizeof(val); 36 | int rc = sysctlbyname("hw.ncpu", &val, &valSize, nullptr, 0); 37 | MG_BOX_ASSERT_F(rc == 0, "Couldn't get CPU core count: %d %s", 38 | errno, strerror(errno)); 39 | MG_BOX_ASSERT_F(val > 0, "CPU core count should be > 0"); 40 | return val; 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/mg/box/Sysinfo_Linux.cpp: -------------------------------------------------------------------------------- 1 | #include "Sysinfo.h" 2 | 3 | #include "mg/box/Error.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MG_SYSINFO_BUFFER_SIZE 2048 11 | 12 | namespace mg { 13 | namespace box { 14 | 15 | static bool SysCheckIsWSL(); 16 | 17 | ////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | uint32_t 20 | SysGetCPUCoreCount() 21 | { 22 | static uint32_t coreCount = get_nprocs(); 23 | return coreCount; 24 | } 25 | 26 | bool 27 | SysIsWSL() 28 | { 29 | static bool isWSL = SysCheckIsWSL(); 30 | return isWSL; 31 | } 32 | 33 | ////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | static bool 36 | SysCheckIsWSL() 37 | { 38 | // WSL can't be detected at compile time because it runs the same binaries as real 39 | // Linux. But at runtime there are signs which tell this is WSL. In particular, 40 | // the OS version mentions Microsoft. It is available in 2 places, here both are 41 | // checked just in case. 42 | // 43 | // There are also no checks for WSL version: 1 or 2. Although they could be added 44 | // in the future by looking at 45 | // - /proc/mounts - presence of wslfs == WSL1; 46 | // - /proc/version might contain WSL2 string; 47 | // - /proc/cmdline is different on 1 and 2; 48 | // - some /proc/ subfolders are just absent on WSL1; 49 | // - /proc/version - has 'Microsoft' on WSL1, but 'microsoft' on WSL2. 50 | 51 | constexpr int bufSize = MG_SYSINFO_BUFFER_SIZE; 52 | ssize_t rc; 53 | char buf[bufSize]; 54 | mg::box::Error::Ptr err; 55 | 56 | int fd = open("/proc/version", O_RDONLY, 0); 57 | if (fd < 0) 58 | { 59 | err = mg::box::ErrorRaiseErrno("open(/proc/version)"); 60 | goto error; 61 | } 62 | rc = read(fd, buf, bufSize - 1); 63 | if (rc < 0) 64 | { 65 | err = mg::box::ErrorRaiseErrno("read(version)"); 66 | goto error; 67 | } 68 | close(fd); 69 | fd = -1; 70 | buf[rc] = 0; 71 | if (strcasestr(buf, "microsoft") != nullptr) 72 | return true; 73 | 74 | fd = open("/proc/sys/kernel/osrelease", O_RDONLY, 0); 75 | if (fd < 0) 76 | { 77 | err = mg::box::ErrorRaiseErrno("open(/sys/kernel/osrelease)"); 78 | goto error; 79 | } 80 | rc = read(fd, buf, bufSize - 1); 81 | if (rc < 0) 82 | { 83 | err = mg::box::ErrorRaiseErrno("read(osrelease)"); 84 | goto error; 85 | } 86 | close(fd); 87 | fd = -1; 88 | buf[rc] = 0; 89 | return strcasestr(buf, "microsoft") != nullptr; 90 | 91 | error: 92 | if (fd >= 0) 93 | close(fd); 94 | MG_BOX_ASSERT_F(false, "failed to check WSL: %s", err->myMessage.c_str()); 95 | return false; 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mg/box/Sysinfo_Win.cpp: -------------------------------------------------------------------------------- 1 | #include "Sysinfo.h" 2 | 3 | namespace mg { 4 | namespace box { 5 | 6 | uint32_t 7 | SysGetCPUCoreCount() 8 | { 9 | static uint32_t coreCount = []() { 10 | SYSTEM_INFO sysinfo; 11 | GetSystemInfo(&sysinfo); 12 | return sysinfo.dwNumberOfProcessors; 13 | }(); 14 | return coreCount; 15 | } 16 | 17 | bool 18 | SysIsWSL() 19 | { 20 | return false; 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mg/box/Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/StringFunctions.h" 5 | 6 | namespace mg { 7 | namespace box { 8 | 9 | void ThreadSetCurrentName( 10 | const char* aName); 11 | 12 | Thread::Thread( 13 | const char* aName) 14 | : myHandle(nullptr) 15 | , myName(aName) 16 | , myIsStopRequested(0) 17 | , myIsRunning(0) 18 | , myWasStarted(false) 19 | { 20 | } 21 | 22 | Thread::~Thread() 23 | { 24 | // Owner of the thread *must* stop it before destruction. 25 | // Or at least wait until it starts saying, that it is 26 | // not running. 27 | MG_BOX_ASSERT(!IsRunning()); 28 | // But even if a thread "is not running", in fact it 29 | // still can be 30 | // 1) in the trampoline, cleaning up some resources, 31 | // calling termination callbacks, etc. Deletion of 32 | // the thread memory can cause use-after-free in the 33 | // trampoline; 34 | // 2) not freed, because CloseHandle/pthread_join were 35 | // not called. 36 | // 37 | // The latter can happen IFF a user didn't do it by 38 | // himself. For example, if his thread function was not a 39 | // cycle waiting on 'StopRequested()'. A user could just 40 | // check 'IsRunning() == false', and delete the thread. 41 | // In this case the call below is rather about collection 42 | // of the thread handle, than about real waiting for a 43 | // termination. 44 | BlockingStop(); 45 | } 46 | 47 | void 48 | Thread::Start() 49 | { 50 | MG_BOX_ASSERT(!myWasStarted); 51 | MG_BOX_ASSERT(!StopRequested()); 52 | myWasStarted = true; 53 | MG_BOX_ASSERT(!myIsRunning.ExchangeRelease(true)); 54 | myHandle = new std::thread(&Thread::PrivTrampoline, this); 55 | } 56 | 57 | void 58 | Thread::Stop() 59 | { 60 | myLock.Lock(); 61 | myIsStopRequested.StoreRelease(true); 62 | myCond.Broadcast(); 63 | myLock.Unlock(); 64 | } 65 | 66 | void 67 | Thread::BlockingStop() 68 | { 69 | Stop(); 70 | myLock.Lock(); 71 | while (IsRunning()) 72 | myCond.Wait(myLock); 73 | if (myHandle != nullptr) 74 | { 75 | myHandle->join(); 76 | delete myHandle; 77 | myHandle = nullptr; 78 | } 79 | myLock.Unlock(); 80 | } 81 | 82 | void 83 | Thread::StopAndDelete() 84 | { 85 | BlockingStop(); 86 | delete this; 87 | } 88 | 89 | bool 90 | Thread::StopRequested() 91 | { 92 | return myIsStopRequested.LoadAcquire(); 93 | } 94 | 95 | bool 96 | Thread::IsRunning() 97 | { 98 | return myIsRunning.LoadAcquire(); 99 | } 100 | 101 | void 102 | Thread::PrivTrampoline() 103 | { 104 | ThreadSetCurrentName(myName.c_str()); 105 | Run(); 106 | myLock.Lock(); 107 | MG_BOX_ASSERT(myIsRunning.ExchangeRelease(false)); 108 | myCond.Broadcast(); 109 | myLock.Unlock(); 110 | } 111 | 112 | void 113 | SleepInfinite() 114 | { 115 | while (true) 116 | Sleep(UINT64_MAX); 117 | } 118 | 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/mg/box/Thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Atomic.h" 4 | #include "mg/box/ConditionVariable.h" 5 | #include "mg/box/Mutex.h" 6 | 7 | #include 8 | #include 9 | 10 | #define MG_THREADLOCAL thread_local 11 | 12 | namespace mg { 13 | namespace box { 14 | 15 | using ThreadId = uint32_t; 16 | 17 | class Thread 18 | { 19 | public: 20 | // Thread name should be a short string up to 15 bytes not 21 | // including terminating 0 (15 printable symbols). The 22 | // reason is that on Linux thread names are truncated to 23 | // 16 bytes. 24 | Thread( 25 | const char* aName = "anon_thread"); 26 | 27 | void Start(); 28 | 29 | void Stop(); 30 | 31 | void BlockingStop(); 32 | 33 | void StopAndDelete(); 34 | 35 | bool StopRequested(); 36 | 37 | bool IsRunning(); 38 | 39 | protected: 40 | virtual ~Thread(); 41 | 42 | private: 43 | void PrivTrampoline(); 44 | 45 | virtual void Run() = 0; 46 | 47 | Mutex myLock; 48 | ConditionVariable myCond; 49 | std::thread* myHandle; 50 | std::string myName; 51 | mg::box::AtomicBool myIsStopRequested; 52 | mg::box::AtomicBool myIsRunning; 53 | bool myWasStarted; 54 | }; 55 | 56 | void Sleep( 57 | uint64_t aDuration); 58 | 59 | void SleepInfinite(); 60 | 61 | ThreadId GetCurrentThreadId(); 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/mg/box/ThreadFunc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Thread.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | using ThreadCallback = std::function; 11 | 12 | // Wrap a callback into a thread. It is essentially 13 | // syntax sugar which allows not to inherit Thread class to 14 | // make something trivial. Similar to std::thread. 15 | class ThreadFunc 16 | : public Thread 17 | { 18 | public: 19 | ThreadFunc( 20 | const char *aName, 21 | const ThreadCallback& aCallback); 22 | 23 | ~ThreadFunc(); 24 | 25 | private: 26 | void Run() override; 27 | 28 | ThreadCallback myCallback; 29 | }; 30 | 31 | ////////////////////////////////////////////////////////////// 32 | 33 | inline 34 | ThreadFunc::ThreadFunc( 35 | const char *aName, 36 | const ThreadCallback& aCallback) 37 | : Thread(aName) 38 | , myCallback(aCallback) 39 | { 40 | } 41 | 42 | inline 43 | ThreadFunc::~ThreadFunc() 44 | { 45 | BlockingStop(); 46 | } 47 | 48 | inline void 49 | ThreadFunc::Run() 50 | { 51 | myCallback(); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/mg/box/Thread_Unix.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | 3 | #include "mg/box/Time.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace box { 10 | 11 | // Thread ID and PID on Linux are a sad story. There is no a 12 | // standard library wrapper for gettid() system call, and 13 | // Linus Torvalds personally says that caching of gettid() or 14 | // getpid() inside the standard lib is a bad idea. At least, 15 | // because it is not possible to detect when clone() is 16 | // called and the tid must be changed in the cache. So the 17 | // tid either should not be cached, or the application should 18 | // cache it by own means. Caching significantly improves 19 | // perf, because syscall(SYS_gettid) appeared to be very 20 | // slow, as any system call bypassing [vdso] optimization. 21 | static MG_THREADLOCAL ThreadId theThreadID = 0; 22 | 23 | void 24 | ThreadSetCurrentName( 25 | const char* aName) 26 | { 27 | // 16 bytes including terminating 0 is a kernel level 28 | // restriction. Can't be workarounded. Raise of an error 29 | // or doing nothing are not a solution - most of threads 30 | // (if not all) have names longer than 15 characters. 31 | // Name is truncated instead. 32 | char buffer[16]; 33 | strncpy(buffer, aName, 15); 34 | buffer[15] = 0; 35 | #if IS_PLATFORM_APPLE 36 | pthread_setname_np(buffer); 37 | #else 38 | int rc = pthread_setname_np(pthread_self(), buffer); 39 | MG_DEV_ASSERT(rc == 0); 40 | #endif 41 | 42 | } 43 | 44 | ThreadId 45 | GetCurrentThreadId() 46 | { 47 | // Formally, it could be possible to always just return 48 | // the thread ID, and fill it in the thread trampoline 49 | // before any other functions are called. But there is 50 | // one thread, not having a trampoline - the main thread. 51 | // The check is for him. 52 | if (theThreadID == 0) { 53 | #if IS_PLATFORM_APPLE 54 | uint64_t tid = 0; 55 | int rc = pthread_threadid_np(pthread_self(), &tid); 56 | MG_DEV_ASSERT(rc == 0); 57 | #else 58 | uint64_t tid = syscall(SYS_gettid); 59 | #endif 60 | MG_DEV_ASSERT((uint64_t)(ThreadId)tid == tid); 61 | theThreadID = (ThreadId)tid; 62 | } 63 | return theThreadID; 64 | } 65 | 66 | void 67 | Sleep( 68 | uint64_t aDuration) 69 | { 70 | uint64_t seconds = aDuration / 1000; 71 | if (seconds > UINT_MAX) 72 | { 73 | while (true) 74 | sleep(UINT_MAX); 75 | return; 76 | } 77 | if (seconds != 0) 78 | sleep((unsigned)seconds); 79 | usleep((aDuration % 1000) * 1000); 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/mg/box/Thread_Win.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | 3 | #include 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | #if IS_COMPILER_MSVC 9 | #pragma warning(push) 10 | // "Possible infinite loop.Execution restarts in the protected block". 11 | #pragma warning(disable: 6312) 12 | #endif 13 | 14 | void 15 | ThreadSetCurrentName( 16 | const char* aName) 17 | { 18 | typedef struct tagTHREADNAME_INFO 19 | { 20 | DWORD dwType; 21 | LPCSTR szName; 22 | DWORD dwThreadID; 23 | DWORD dwFlags; 24 | } THREADNAME_INFO; 25 | 26 | THREADNAME_INFO info; 27 | info.dwType = 0x1000; 28 | info.szName = aName; 29 | info.dwThreadID = (DWORD)-1; 30 | info.dwFlags = 0; 31 | 32 | __try 33 | { 34 | RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); 35 | } 36 | __except (EXCEPTION_CONTINUE_EXECUTION) 37 | { 38 | } 39 | } 40 | 41 | #if IS_COMPILER_MSVC 42 | #pragma warning(pop) 43 | #endif 44 | 45 | ThreadId 46 | GetCurrentThreadId() 47 | { 48 | return (ThreadId) ::GetCurrentThreadId(); 49 | } 50 | 51 | void 52 | Sleep( 53 | uint64_t aTimeMillis) 54 | { 55 | if (aTimeMillis >= MAXDWORD) 56 | { 57 | while (true) 58 | ::Sleep(INFINITE); 59 | return; 60 | } 61 | ::Sleep((DWORD)aTimeMillis); 62 | } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/mg/box/Time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | enum TimeLimitType 9 | { 10 | TIME_LIMIT_NONE, 11 | TIME_LIMIT_DURATION, 12 | TIME_LIMIT_POINT, 13 | }; 14 | 15 | struct TimePoint 16 | { 17 | explicit constexpr TimePoint( 18 | uint64_t aValue) : myValue(aValue) {} 19 | 20 | constexpr bool operator<=( 21 | const TimePoint& aOther) const { return myValue <= aOther.myValue; } 22 | 23 | uint64_t myValue; 24 | }; 25 | 26 | static constexpr TimePoint theTimePointInf{MG_TIME_INFINITE}; 27 | 28 | struct TimeDuration 29 | { 30 | explicit constexpr TimeDuration( 31 | int32_t aValue) : myValue(aValue >= 0 ? aValue : 0) {} 32 | explicit constexpr TimeDuration( 33 | uint32_t aValue) : myValue(aValue) {} 34 | explicit constexpr TimeDuration( 35 | int64_t aValue) : myValue(aValue >= 0 ? aValue : 0) {} 36 | explicit constexpr TimeDuration( 37 | uint64_t aValue) : myValue(aValue) {} 38 | 39 | TimePoint ToPointFromNow() const; 40 | 41 | uint64_t myValue; 42 | }; 43 | 44 | static constexpr TimeDuration theTimeDurationInf{MG_TIME_INFINITE}; 45 | 46 | struct TimeLimit 47 | { 48 | constexpr TimeLimit() : myType(TIME_LIMIT_NONE), myPoint(0) {} 49 | constexpr TimeLimit( 50 | const TimePoint& aPoint) : myType(TIME_LIMIT_POINT), myPoint(aPoint) {} 51 | constexpr TimeLimit( 52 | const TimeDuration& aDuration) : myType(TIME_LIMIT_DURATION), myDuration(aDuration) {} 53 | 54 | TimePoint ToPointFromNow() const; 55 | TimeDuration ToDurationFromNow() const; 56 | void Reset() { myType = TIME_LIMIT_NONE; myPoint.myValue = 0; } 57 | 58 | TimeLimitType myType; 59 | union 60 | { 61 | TimePoint myPoint; 62 | TimeDuration myDuration; 63 | }; 64 | }; 65 | 66 | // Returns the number of milliseconds since some point in the 67 | // past. Monotonic, won't go back on clock changes. 68 | uint64_t GetMilliseconds(); 69 | 70 | // Same but with higher precision. 71 | double GetMillisecondsPrecise(); 72 | 73 | // Same but with nanoseconds precision (might be less precise, depending on platform). 74 | uint64_t GetNanoseconds(); 75 | 76 | //////////////////////////////////////////////////////////////////////////// 77 | 78 | inline TimePoint 79 | TimeDuration::ToPointFromNow() const 80 | { 81 | uint64_t ts = GetMilliseconds(); 82 | if (ts >= UINT64_MAX - myValue) 83 | return theTimePointInf; 84 | return TimePoint(myValue + ts); 85 | } 86 | 87 | inline TimePoint 88 | TimeLimit::ToPointFromNow() const 89 | { 90 | if (myType == TIME_LIMIT_POINT) 91 | return myPoint; 92 | MG_DEV_ASSERT(myType == TIME_LIMIT_DURATION); 93 | return myDuration.ToPointFromNow(); 94 | } 95 | 96 | inline TimeDuration 97 | TimeLimit::ToDurationFromNow() const 98 | { 99 | if (myType == TIME_LIMIT_DURATION) 100 | return myDuration; 101 | MG_DEV_ASSERT(myType == TIME_LIMIT_POINT); 102 | if (myPoint.myValue == MG_TIME_INFINITE) 103 | return theTimeDurationInf; 104 | uint64_t ts = GetMilliseconds(); 105 | if (ts >= myPoint.myValue) 106 | return TimeDuration(0); 107 | return TimeDuration(myPoint.myValue - ts); 108 | } 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/mg/box/Time_Unix.cpp: -------------------------------------------------------------------------------- 1 | #include "Time.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | static inline timespec 11 | TimeGetTimespec() 12 | { 13 | timespec ts; 14 | #if IS_PLATFORM_APPLE 15 | const clockid_t clockid = CLOCK_MONOTONIC; 16 | #else 17 | // Boottime is preferable - it takes system suspension time into account. 18 | const clockid_t clockid = CLOCK_BOOTTIME; 19 | #endif 20 | MG_BOX_ASSERT(clock_gettime(clockid, &ts) == 0); 21 | return ts; 22 | } 23 | 24 | uint64_t 25 | GetMilliseconds() 26 | { 27 | timespec ts = TimeGetTimespec(); 28 | return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 29 | } 30 | 31 | double 32 | GetMillisecondsPrecise() 33 | { 34 | timespec ts = TimeGetTimespec(); 35 | return ts.tv_sec * 1000 + ts.tv_nsec / 1000000.0; 36 | } 37 | 38 | uint64_t 39 | GetNanoseconds() 40 | { 41 | timespec ts = TimeGetTimespec(); 42 | return ts.tv_sec * 1000000000 + ts.tv_nsec; 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/mg/box/Time_Win.cpp: -------------------------------------------------------------------------------- 1 | #include "Time.h" 2 | 3 | static_assert(sizeof(LARGE_INTEGER) == sizeof(uint64_t), "LARGE_INTEGER is 64 bit"); 4 | 5 | namespace mg { 6 | namespace box { 7 | 8 | static LONGLONG 9 | TimeCalcFrequency() 10 | { 11 | LARGE_INTEGER freq; 12 | ::QueryPerformanceFrequency(&freq); 13 | return freq.QuadPart; 14 | } 15 | 16 | static double 17 | TimeGetUnitsPerNs() 18 | { 19 | // Calculate it just once when request it first time. 20 | static double res = 1000000000.0 / TimeCalcFrequency(); 21 | return res; 22 | } 23 | 24 | static double 25 | TimeGetUnitsPerMs() 26 | { 27 | // Calculate it just once when request it first time. 28 | static double res = 1000.0 / TimeCalcFrequency(); 29 | return res; 30 | } 31 | 32 | uint64_t 33 | GetMilliseconds() 34 | { 35 | return ::GetTickCount64(); 36 | } 37 | 38 | double 39 | GetMillisecondsPrecise() 40 | { 41 | LARGE_INTEGER ts; 42 | ::QueryPerformanceCounter(&ts); 43 | return ts.QuadPart * TimeGetUnitsPerMs(); 44 | } 45 | 46 | uint64_t 47 | GetNanoseconds() 48 | { 49 | LARGE_INTEGER ts; 50 | ::QueryPerformanceCounter(&ts); 51 | return (uint64_t)(ts.QuadPart * TimeGetUnitsPerNs()); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/mg/box/TypeTraits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace box { 9 | 10 | template 11 | inline int64_t 12 | OffsetOf(const M T::*aMemberDecl) 13 | { 14 | // This is safe because there is no dereference. 15 | // &(ptr->member) turns into "ptr + offsetof(member)". 16 | // 17 | // The builtin offsetof() is not used, because it raises a 18 | // warning for any non-POD type. But in fact it is totally 19 | // fine unless 'virtual inheritance' is used (not the same 20 | // as virtual methods). 21 | return (int64_t)(&(((T*)(nullptr))->*aMemberDecl)); 22 | } 23 | 24 | template 25 | const T* 26 | ContainerOf(const M* aMember, const M T::*aMemberDecl) 27 | { 28 | return (const T*)((int64_t)aMember - OffsetOf(aMemberDecl)); 29 | } 30 | 31 | template 32 | T* 33 | ContainerOf(M* aMember, const M T::*aMemberDecl) 34 | { 35 | return (T*)((int64_t)aMember - OffsetOf(aMemberDecl)); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/mg/net/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | find_package(OpenSSL REQUIRED) 4 | 5 | set(mgnet_src 6 | Buffer.cpp 7 | DomainToIP.cpp 8 | Host.cpp 9 | Socket.cpp 10 | SSL.cpp 11 | SSLBioMem.cpp 12 | SSLContext.cpp 13 | SSLContextOpenSSL.cpp 14 | SSLOpenSSL.cpp 15 | SSLStream.cpp 16 | SSLStreamOpenSSL.cpp 17 | URL.cpp 18 | ) 19 | 20 | if(WIN32) 21 | set(mgnet_src ${mgnet_src} 22 | Socket_Win.cpp 23 | ) 24 | else() 25 | set(mgnet_src ${mgnet_src} 26 | Socket_Unix.cpp 27 | ) 28 | endif() 29 | 30 | add_library(mgnet ${mgnet_src}) 31 | 32 | target_include_directories(mgnet PUBLIC 33 | ${CMAKE_SOURCE_DIR}/src/ 34 | ${OPENSSL_INCLUDE_DIR} 35 | ) 36 | 37 | set(mgnet_libs 38 | mgbox 39 | ${OPENSSL_SSL_LIBRARY} 40 | ${OPENSSL_CRYPTO_LIBRARY} 41 | ) 42 | if(WIN32) 43 | set(mgnet_libs ${mgnet_libs} ws2_32.lib crypt32.lib) 44 | endif() 45 | 46 | target_link_libraries(mgnet 47 | ${mgnet_libs} 48 | ) 49 | 50 | set(install_headers 51 | Buffer.h 52 | DomainToIP.h 53 | Host.h 54 | Socket.h 55 | SSL.h 56 | SSLContext.h 57 | SSLContextOpenSSL.h 58 | SSLOpenSSL.h 59 | URL.h 60 | ) 61 | 62 | install(TARGETS mgnet DESTINATION "${install_lib_root}") 63 | install(FILES ${install_headers} DESTINATION "${install_include_root}/mg/net/") 64 | -------------------------------------------------------------------------------- /src/mg/net/DomainToIP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Error.h" 4 | #include "mg/box/Time.h" 5 | #include "mg/net/Host.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace mg { 11 | namespace net { 12 | 13 | class DomainToIPContext; 14 | class DomainToIPWorker; 15 | 16 | struct DomainEndpoint 17 | { 18 | mg::net::Host myHost; 19 | }; 20 | 21 | using DomainToIPCallback = std::function& aEndpoints, 24 | mg::box::Error* aError)>; 25 | 26 | struct DomainToIPRequest 27 | { 28 | DomainToIPRequest(); 29 | DomainToIPRequest( 30 | DomainToIPContext* aContext); 31 | DomainToIPRequest( 32 | DomainToIPRequest&& aOther); 33 | ~DomainToIPRequest(); 34 | 35 | DomainToIPRequest& operator=( 36 | DomainToIPRequest&& aOther); 37 | 38 | private: 39 | DomainToIPContext* myCtx; 40 | 41 | friend DomainToIPWorker; 42 | }; 43 | 44 | void DomainToIPAsync( 45 | DomainToIPRequest& aOutRequest, 46 | const char* aDomain, 47 | mg::box::TimeLimit aTimeLimit, 48 | const DomainToIPCallback& aCallback); 49 | 50 | void DomainToIPCancel( 51 | DomainToIPRequest& aRequest); 52 | 53 | bool DomainToIPBlocking( 54 | const char* aDomain, 55 | mg::box::TimeLimit aTimeLimit, 56 | std::vector& aOutEndpoints, 57 | mg::box::Error::Ptr& aOutError); 58 | 59 | static inline bool 60 | DomainToIPBlocking( 61 | const std::string& aDomain, 62 | mg::box::TimeLimit aTimeLimit, 63 | std::vector& aOutEndpoints, 64 | mg::box::Error::Ptr& aOutError) 65 | { 66 | return DomainToIPBlocking(aDomain.c_str(), aTimeLimit, aOutEndpoints, aOutError); 67 | } 68 | 69 | #if IS_BUILD_DEBUG 70 | void DomainToIPDebugSetProcessLatency( 71 | uint64_t aValue); 72 | 73 | void DomainToIPDebugSetError( 74 | mg::box::ErrorCode aCode); 75 | #endif 76 | 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/mg/net/Host.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/StringFunctions.h" 5 | 6 | #if IS_PLATFORM_WIN 7 | #include 8 | #include 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | namespace mg { 15 | namespace net { 16 | 17 | using Sockaddr = sockaddr; 18 | using SockaddrIn = sockaddr_in; 19 | using SockaddrIn6 = sockaddr_in6; 20 | using SockAddrFamily = int; 21 | using SocklenT = socklen_t; 22 | 23 | static constexpr SockAddrFamily ADDR_FAMILY_NONE = 0; 24 | 25 | static constexpr SockAddrFamily ADDR_FAMILY_IPV4 = AF_INET; 26 | static_assert(ADDR_FAMILY_IPV4 != ADDR_FAMILY_NONE, "ipv4 != none"); 27 | 28 | static constexpr SockAddrFamily ADDR_FAMILY_IPV6 = AF_INET6; 29 | static_assert(ADDR_FAMILY_IPV6 != ADDR_FAMILY_NONE, "ipv6 != none"); 30 | 31 | class Host 32 | { 33 | public: 34 | Host() { Clear(); } 35 | Host( 36 | const Sockaddr* aSockaddr) { Set(aSockaddr); } 37 | Host( 38 | const char* aHost) { if (!Set(aHost)) Clear(); } 39 | Host( 40 | const std::string& aHost) : Host(aHost.c_str()) {} 41 | 42 | void Set( 43 | const Sockaddr* aSockaddr); 44 | // Works with port number. Doesn't work with domain names. 45 | bool Set( 46 | const char* aHost); 47 | bool Set( 48 | const std::string& aHost) { return Set(aHost.c_str()); } 49 | void Clear() { memset(&myAddr, 0, sizeof(myAddr)); } 50 | void SetPort( 51 | uint16_t aPort); 52 | 53 | SocklenT GetSockaddrSize() const; 54 | uint16_t GetPort() const; 55 | 56 | bool IsIPV4() const { return myAddr.sa_family == ADDR_FAMILY_IPV4; } 57 | bool IsIPV6() const { return myAddr.sa_family == ADDR_FAMILY_IPV6; } 58 | bool IsSet() const { return myAddr.sa_family != ADDR_FAMILY_NONE; } 59 | bool IsEqualNoPort( 60 | const Host& aOther) const; 61 | 62 | std::string ToString() const; 63 | std::string ToStringNoPort() const; 64 | 65 | bool operator==( 66 | const Host& aRhs) const; 67 | bool operator!=( 68 | const Host& aRhs) const { return !(*this == aRhs); } 69 | 70 | union 71 | { 72 | Sockaddr myAddr; 73 | SockaddrIn myAddrIn; 74 | SockaddrIn6 myAddrIn6; 75 | }; 76 | }; 77 | 78 | ////////////////////////////////////////////////////////////////////////////////////// 79 | 80 | // Localhost IPV4. 81 | Host HostMakeLocalIPV4( 82 | uint16_t aPort); 83 | // Localhost IPV6. 84 | Host HostMakeLocalIPV6( 85 | uint16_t aPort); 86 | // All network interfaces IPV4. 87 | Host HostMakeAllIPV4( 88 | uint16_t aPort); 89 | // All network interfaces IPV6. 90 | Host HostMakeAllIPV6( 91 | uint16_t aPort); 92 | 93 | uint16_t NtoHs( 94 | uint16_t aValue); 95 | uint32_t NtoHl( 96 | uint32_t aValue); 97 | uint16_t HtoNs( 98 | uint16_t aValue); 99 | uint32_t HtoNl( 100 | uint32_t aValue); 101 | 102 | void SockaddrInCreate( 103 | SockaddrIn& aAddr); 104 | void SockaddrIn6Create( 105 | SockaddrIn6& aAddr); 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/mg/net/SSL.cpp: -------------------------------------------------------------------------------- 1 | #include "SSL.h" 2 | 3 | #include "mg/box/StringFunctions.h" 4 | #include "mg/net/SSLOpenSSL.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace net { 10 | 11 | const char* 12 | SSLErrorToString( 13 | uint32_t aCode) 14 | { 15 | return OpenSSLErrorToString(aCode); 16 | } 17 | 18 | mg::box::ErrorPtrRaised 19 | ErrorRaiseSSL( 20 | uint32_t aCode) 21 | { 22 | return ErrorRaiseSSL(aCode, nullptr); 23 | } 24 | 25 | mg::box::ErrorPtrRaised 26 | ErrorRaiseSSL( 27 | uint32_t aCode, 28 | const char* aComment) 29 | { 30 | return ErrorRaise(mg::box::ERR_SSL, aCode, SSLErrorToString(aCode), aComment); 31 | } 32 | 33 | MG_STRFORMAT_PRINTF(2, 3) 34 | mg::box::ErrorPtrRaised 35 | ErrorRaiseFormatSSL( 36 | uint32_t aCode, 37 | const char* aCommentFormat, 38 | ...) 39 | { 40 | va_list va; 41 | va_start(va, aCommentFormat); 42 | std::string comment = mg::box::StringVFormat(aCommentFormat, va); 43 | va_end(va); 44 | return ErrorRaiseSSL(aCode, comment.c_str()); 45 | } 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/mg/net/SSL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Error.h" 4 | 5 | namespace mg { 6 | namespace net { 7 | 8 | enum SSLVersion 9 | { 10 | SSL_VERSION_TLSv1, 11 | SSL_VERSION_TLSv1_1, 12 | SSL_VERSION_TLSv1_2, 13 | SSL_VERSION_TLSv1_3, 14 | // Select the highest compatible version. 15 | SSL_VERSION_ANY, 16 | SSL_VERSION_DEFAULT = SSL_VERSION_TLSv1_2, 17 | SSL_VERSION_NONE, 18 | }; 19 | 20 | enum SSLTrust 21 | { 22 | // Hardest verification. 23 | SSL_TRUST_STRICT = 0, 24 | // Accept self-signed certs, allow certain weak ciphers. 25 | SSL_TRUST_RELAXED = 1, 26 | // Accept untrusted certs. 27 | SSL_TRUST_UNSECURE = 2, 28 | // Cert is ignored, handshake proceeds regardless of any validation errors. 29 | SSL_TRUST_BYPASS_VERIFICATION = 3, 30 | SSL_TRUST_DEFAULT = SSL_TRUST_STRICT, 31 | }; 32 | 33 | enum SSLCipher 34 | { 35 | // TLS 1.2 36 | SSL_CIPHER_ECDHE_ECDSA_AES128_GCM_SHA256, 37 | SSL_CIPHER_ECDHE_ECDSA_AES256_GCM_SHA384, 38 | // TLS 1.3 39 | SSL_CIPHER_TLS_AES_128_GCM_SHA256, 40 | SSL_CIPHER_TLS_AES_256_GCM_SHA384, 41 | SSL_CIPHER_TLS_CHACHA20_POLY1305_SHA256, 42 | 43 | SSL_CIPHER_NONE, 44 | }; 45 | 46 | const char* SSLErrorToString( 47 | uint32_t aCode); 48 | 49 | mg::box::ErrorPtrRaised ErrorRaiseSSL( 50 | uint32_t aCode); 51 | 52 | mg::box::ErrorPtrRaised ErrorRaiseSSL( 53 | uint32_t aCode, 54 | const char* aComment); 55 | 56 | MG_STRFORMAT_PRINTF(2, 3) 57 | mg::box::ErrorPtrRaised ErrorRaiseFormatSSL( 58 | uint32_t aCode, 59 | const char* aCommentFormat, 60 | ...); 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/mg/net/SSLBioMem.cpp: -------------------------------------------------------------------------------- 1 | #include "SSLBioMem.h" 2 | 3 | #include "mg/net/SSLContextOpenSSL.h" 4 | 5 | namespace mg { 6 | namespace net { 7 | 8 | static int 9 | SSLBioMemOpenSSLWriteEx( 10 | BIO* aBase, 11 | const char* aData, 12 | size_t aInSize, 13 | size_t* aOutSize) 14 | { 15 | BIO_clear_retry_flags(aBase); 16 | BufferStream* bio = (BufferStream*)BIO_get_data(aBase); 17 | bio->WriteCopy(aData, aInSize); 18 | *aOutSize = aInSize; 19 | return 1; 20 | } 21 | 22 | static int 23 | SSLBioMemOpenSSLReadEx( 24 | BIO* aBase, 25 | char* aDst, 26 | size_t aInSize, 27 | size_t* aOutSize) 28 | { 29 | BIO_clear_retry_flags(aBase); 30 | BufferStream* bio = (BufferStream*)BIO_get_data(aBase); 31 | uint64_t size = bio->GetReadSize(); 32 | if (size == 0) 33 | { 34 | BIO_set_retry_read(aBase); 35 | *aOutSize = 0; 36 | return 0; 37 | } 38 | if (aInSize == 0) 39 | { 40 | *aOutSize = 0; 41 | return 0; 42 | } 43 | if (size < aInSize) 44 | aInSize = size; 45 | bio->ReadData(aDst, aInSize); 46 | *aOutSize = aInSize; 47 | return 1; 48 | } 49 | 50 | static long 51 | SSLBioMemOpenSSLCtrl( 52 | BIO* /*aBase*/, 53 | int aCmd, 54 | long /*aNum*/, 55 | void* /*aPtr*/) 56 | { 57 | // No need for other commands yet. SSL either doesn't use the others or ignores 58 | // their result anyway. 59 | return aCmd == BIO_CTRL_FLUSH; 60 | } 61 | 62 | static int 63 | SSLBioMemOpenSSLDestroy( 64 | BIO* /*aBase*/) 65 | { 66 | return 1; 67 | } 68 | 69 | static BIO_METHOD* 70 | SSLBioMemOpenSSLNewMethod() 71 | { 72 | int idx = BIO_get_new_index(); 73 | MG_BOX_ASSERT(idx != -1); 74 | BIO_METHOD* method = BIO_meth_new(idx, "bio_openssl"); 75 | MG_BOX_ASSERT(method != nullptr); 76 | bool ok = true; 77 | // No plain read/write are set. OpenSSL uses either _ex or plain callbacks. 78 | ok = ok && BIO_meth_set_write_ex(method, SSLBioMemOpenSSLWriteEx) == 1; 79 | ok = ok && BIO_meth_set_read_ex(method, SSLBioMemOpenSSLReadEx) == 1; 80 | ok = ok && BIO_meth_set_ctrl(method, SSLBioMemOpenSSLCtrl) == 1; 81 | ok = ok && BIO_meth_set_destroy(method, SSLBioMemOpenSSLDestroy) == 1; 82 | MG_BOX_ASSERT(ok); 83 | return method; 84 | } 85 | 86 | BIO* 87 | OpenSSLWrapBioMem( 88 | BufferStream& aStorage) 89 | { 90 | // Destroy is called when the bio is deleted. 91 | // Init OpenSSL before creating the method, because the 92 | // creation might need it. 93 | static BIO_METHOD* methodSingleton = SSLBioMemOpenSSLNewMethod(); 94 | BIO* res = BIO_new(methodSingleton); 95 | BIO_set_data(res, &aStorage); 96 | return res; 97 | } 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/mg/net/SSLBioMem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/Buffer.h" 4 | #include "mg/net/SSLContext.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace net { 10 | 11 | // This is a storage where SSL filter can store input and output data. But SSL library 12 | // can't operate on serverbox objects directly. For that the app is expected to 13 | // provide a proxy object. 14 | // 15 | // In OpenSSL it is BIO struct. Application can "inherit" it with such methods so they 16 | // would operate on serverbox storage under the hood. 17 | // 18 | // In mbedTLS there is API mbedtls_ssl_set_bio() which allows to proxy raw IO 19 | // per-sslcontext via callbacks. 20 | BIO* OpenSSLWrapBioMem( 21 | BufferStream& aStorage); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mg/net/SSLContext.cpp: -------------------------------------------------------------------------------- 1 | #include "SSLContext.h" 2 | 3 | namespace mg { 4 | namespace net { 5 | 6 | SSLContext::SSLContext( 7 | bool aIsServer) 8 | : myImpl(SSLContextBase::New(aIsServer)) 9 | { 10 | } 11 | 12 | SSLContext::~SSLContext() 13 | { 14 | myImpl->Delete(); 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/mg/net/SSLContext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/SSLContextOpenSSL.h" 4 | 5 | namespace mg { 6 | namespace net { 7 | 8 | class SSLStream; 9 | using SSLContextBase = SSLContextOpenSSL; 10 | 11 | ////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | // SSL configuration data from which SSL connections/streams can be spawned. 14 | class SSLContext 15 | { 16 | public: 17 | SHARED_PTR_API(SSLContext, myRef) 18 | 19 | public: 20 | bool AddRemoteCertFile( 21 | const char* aFile) { return myImpl->AddRemoteCertFile(aFile); } 22 | 23 | bool AddRemoteCert( 24 | const void* aData, 25 | uint32_t aSize) { return myImpl->AddRemoteCert(aData, aSize); } 26 | 27 | bool AddRemoteCertFromSystem() { return myImpl->AddRemoteCertFromSystem(); } 28 | 29 | bool AddLocalCertFile( 30 | const char* aCertFile, 31 | const char* aKeyFile, 32 | const char* aPassword) 33 | { return myImpl->AddLocalCertFile(aCertFile, aKeyFile, aPassword); } 34 | 35 | bool AddLocalCert( 36 | const void* aCertData, 37 | uint32_t aCertSize, 38 | const void* aKeyData, 39 | uint32_t aKeySize) 40 | { return myImpl->AddLocalCert(aCertData, aCertSize, aKeyData, aKeySize); } 41 | 42 | bool AddCipher( 43 | SSLCipher aCipher) { return myImpl->AddCipher(aCipher); } 44 | 45 | bool SetTrust( 46 | SSLTrust aTrust) { return myImpl->SetTrust(aTrust); } 47 | 48 | bool SetMinVersion( 49 | SSLVersion aVersion) { return myImpl->SetMinVersion(aVersion); } 50 | 51 | bool SetMaxVersion( 52 | SSLVersion aVersion) { return myImpl->SetMaxVersion(aVersion); } 53 | 54 | bool IsServer() const { return myImpl->IsServer(); } 55 | 56 | #if MG_OPENSSL_CTX_HAS_COMPARISON 57 | bool operator==( 58 | const SSLContext& aOther) const { return *myImpl == *aOther.myImpl; } 59 | bool operator!=( 60 | const SSLContext& aOther) const { return !(*this == aOther); } 61 | #endif 62 | 63 | private: 64 | SSLContext( 65 | bool aIsServer); 66 | SSLContext( 67 | const SSLContext&) = delete; 68 | SSLContext& operator=( 69 | const SSLContext&) const = delete; 70 | ~SSLContext(); 71 | 72 | SSLContextBase* myImpl; 73 | mg::box::RefCount myRef; 74 | 75 | friend SSLStream; 76 | }; 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/mg/net/SSLContextOpenSSL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/SSL.h" 4 | #include "mg/net/SSLOpenSSL.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace mg { 11 | namespace net { 12 | 13 | class SSLStreamOpenSSL; 14 | 15 | class SSLContextOpenSSL 16 | { 17 | private: 18 | SSLContextOpenSSL( 19 | bool aIsServer); 20 | SSLContextOpenSSL( 21 | const SSLContextOpenSSL&) = delete; 22 | ~SSLContextOpenSSL(); 23 | 24 | public: 25 | static SSLContextOpenSSL* New( 26 | bool aIsServer) { return new SSLContextOpenSSL(aIsServer); } 27 | 28 | void Delete(); 29 | 30 | public: 31 | bool AddRemoteCertFile( 32 | const char* aFile); 33 | 34 | bool AddRemoteCert( 35 | const void* aData, 36 | uint32_t aSize); 37 | 38 | bool AddRemoteCertFromSystem(); 39 | 40 | bool AddLocalCertFile( 41 | const char* aCertFile, 42 | const char* aKeyFile, 43 | const char* aPassword); 44 | 45 | bool AddLocalCert( 46 | const void* aCertData, 47 | uint32_t aCertSize, 48 | const void* aKeyData, 49 | uint32_t aKeySize); 50 | 51 | bool AddCipher( 52 | SSLCipher aCipher); 53 | 54 | bool SetTrust( 55 | SSLTrust aTrust); 56 | 57 | bool SetMinVersion( 58 | SSLVersion aVersion); 59 | 60 | bool SetMaxVersion( 61 | SSLVersion aVersion); 62 | 63 | bool IsServer() const { return myIsServer; } 64 | 65 | #if MG_OPENSSL_CTX_HAS_COMPARISON 66 | bool operator==( 67 | const SSLContextOpenSSL& aOther) const; 68 | #endif 69 | 70 | void PrivOn_SSL_CTX_Delete(); 71 | 72 | private: 73 | SSL_CTX* myConfig; 74 | SSLTrust myTrust; 75 | bool myIsServer; 76 | bool myHasLocalCert; 77 | bool myIsDeleted; 78 | std::string myPassword; 79 | std::vector myCiphers; 80 | 81 | friend SSLStreamOpenSSL; 82 | }; 83 | 84 | const SSLContextOpenSSL* OpenSSL_SSL_ToContext( 85 | const SSL* aSSL); 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/mg/net/SSLOpenSSL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/SSL.h" 4 | 5 | #include 6 | 7 | #if defined(OPENSSL_VERSION_PREREQ) 8 | #if OPENSSL_VERSION_PREREQ(3, 0) 9 | #define MG_OPENSSL_VERSION_GE_3_0 1 10 | #endif 11 | #endif 12 | 13 | #ifndef MG_OPENSSL_VERSION_GE_3_0 14 | #define MG_OPENSSL_VERSION_GE_3_0 0 15 | #endif 16 | 17 | // EVP key comparison function has only appeared in 3.0. 18 | #define MG_OPENSSL_CTX_HAS_COMPARISON MG_OPENSSL_VERSION_GE_3_0 19 | 20 | namespace mg { 21 | namespace net { 22 | 23 | void OpenSSLInit(); 24 | 25 | void OpenSSLDestroy(); 26 | 27 | // Use for codes returned from various SSL functions. They are marked as "happened in 28 | // serverbox" then when the result is converted to a string. 29 | uint32_t OpenSSLPackError( 30 | int aCode); 31 | 32 | const char* OpenSSLErrorToString( 33 | uint32_t aCode); 34 | 35 | const char* OpenSSLCipherToString( 36 | SSLCipher aCipher); 37 | 38 | SSLCipher OpenSSLCipherFromString( 39 | const char* aName); 40 | 41 | int OpenSSLVersionToInt( 42 | SSLVersion aVersion); 43 | 44 | SSLVersion OpenSSLVersionFromInt( 45 | int aVersion); 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/mg/net/SSLStream.cpp: -------------------------------------------------------------------------------- 1 | #include "SSLStream.h" 2 | 3 | #include "mg/net/SSLContext.h" 4 | #include "mg/net/SSLStreamOpenSSL.h" 5 | 6 | namespace mg { 7 | namespace net { 8 | 9 | SSLStream::SSLStream( 10 | SSLContext* aCtx) 11 | : myImpl(aCtx->myImpl) 12 | { 13 | } 14 | 15 | SSLStream::~SSLStream() = default; 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/mg/net/SSLStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/SSL.h" 4 | #include "mg/net/SSLStreamOpenSSL.h" 5 | 6 | namespace mg { 7 | namespace net { 8 | 9 | class SSLContext; 10 | using SSLStreamBase = SSLStreamOpenSSL; 11 | 12 | class SSLStream 13 | { 14 | public: 15 | SSLStream( 16 | SSLContext* aCtx); 17 | SSLStream( 18 | SSLContext::Ptr& aCtx) : SSLStream(aCtx.GetPointer()) {} 19 | SSLStream( 20 | const SSLStream&) = delete; 21 | ~SSLStream(); 22 | 23 | SSLStream& operator=( 24 | const SSLStream&) = delete; 25 | 26 | bool Update() { return myImpl.Update(); } 27 | void Connect() { myImpl.Connect(); } 28 | void Shutdown() { myImpl.Shutdown(); } 29 | bool SetHostName( 30 | const char* aName) { return myImpl.SetHostName(aName); } 31 | std::string GetHostName() const { return myImpl.GetHostName(); } 32 | 33 | void AppendNetInputCopy( 34 | const void* aData, 35 | uint64_t aSize) { myImpl.AppendNetInputCopy(aData, aSize); } 36 | 37 | void AppendNetInputRef( 38 | Buffer::Ptr&& aHead) { myImpl.AppendNetInputRef(std::move(aHead)); } 39 | 40 | uint64_t PopNetOutput( 41 | Buffer::Ptr& aOutHead) { return myImpl.PopNetOutput(aOutHead); } 42 | 43 | void AppendAppInputCopy( 44 | const void* aData, 45 | uint64_t aSize) { myImpl.AppendAppInputCopy(aData, aSize); } 46 | 47 | uint64_t PopAppOutput( 48 | Buffer::Ptr& aOutHead) { return myImpl.PopAppOutput(aOutHead); } 49 | 50 | bool HasError() const { return myImpl.HasError(); } 51 | bool IsConnected() const { return myImpl.IsConnected(); } 52 | bool IsClosed() const { return myImpl.IsClosed(); } 53 | bool IsClosingOrClosed() const { return myImpl.IsClosingOrClosed(); } 54 | bool IsEncrypted() const { return myImpl.IsEncrypted(); } 55 | 56 | SSLVersion GetVersion() const { return myImpl.GetVersion(); } 57 | SSLCipher GetCipher() const { return myImpl.GetCipher(); } 58 | uint32_t GetError() const { return myImpl.GetError(); } 59 | 60 | private: 61 | SSLStreamBase myImpl; 62 | }; 63 | 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/mg/net/SSLStreamOpenSSL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/SSL.h" 4 | #include "mg/net/SSLBioMem.h" 5 | 6 | #include 7 | 8 | namespace mg { 9 | namespace net { 10 | 11 | class SSLContextOpenSSL; 12 | 13 | enum SSLStreamOpenSSLState 14 | { 15 | SSL_STREAM_OPENSSL_STATE_NEW, 16 | SSL_STREAM_OPENSSL_STATE_CONNECTING, 17 | SSL_STREAM_OPENSSL_STATE_CONNECTED, 18 | SSL_STREAM_OPENSSL_STATE_CLOSING, 19 | SSL_STREAM_OPENSSL_STATE_CLOSED, 20 | }; 21 | 22 | class SSLStreamOpenSSL 23 | { 24 | public: 25 | SSLStreamOpenSSL( 26 | SSLContextOpenSSL* aCtx); 27 | 28 | ~SSLStreamOpenSSL(); 29 | 30 | bool Update(); 31 | void Connect(); 32 | void Shutdown(); 33 | bool SetHostName( 34 | const char* aName); 35 | std::string GetHostName() const; 36 | 37 | void AppendNetInputCopy( 38 | const void* aData, 39 | uint64_t aSize) { myNetInput.WriteCopy(aData, aSize); } 40 | 41 | void AppendNetInputRef( 42 | Buffer::Ptr&& aHead) { myNetInput.WriteRef(std::move(aHead)); } 43 | 44 | uint64_t PopNetOutput( 45 | Buffer::Ptr& aOutHead); 46 | 47 | void AppendAppInputCopy( 48 | const void* aData, 49 | uint64_t aSize); 50 | 51 | uint64_t PopAppOutput( 52 | Buffer::Ptr& aOutHead); 53 | 54 | bool HasError() const { return myError != 0; } 55 | bool IsConnected() const { return myState == SSL_STREAM_OPENSSL_STATE_CONNECTED; } 56 | bool IsClosed() const { return myState == SSL_STREAM_OPENSSL_STATE_CLOSED; } 57 | bool IsClosingOrClosed() const { 58 | return myState >= SSL_STREAM_OPENSSL_STATE_CLOSING; } 59 | bool IsEncrypted() const { return myState > SSL_STREAM_OPENSSL_STATE_NEW; } 60 | 61 | SSLVersion GetVersion() const; 62 | SSLCipher GetCipher() const; 63 | uint32_t GetError() const { return myError; } 64 | 65 | private: 66 | bool PrivUpdate(); 67 | bool PrivFlushAppInput(); 68 | int PrivAppWrite( 69 | const void* aData, 70 | uint64_t aSize); 71 | void PrivClose( 72 | uint32_t aErr); 73 | bool PrivCheckError( 74 | int aRetCode); 75 | void PrivSetState( 76 | SSLStreamOpenSSLState aState); 77 | 78 | mg::net::Buffer* PrivGetCachedBuf(); 79 | 80 | SSL* mySSL; 81 | BufferStream myNetInput; 82 | BufferStream myNetOutput; 83 | BufferStream myAppInput; 84 | Buffer::Ptr myCachedBuf; 85 | uint32_t myError; 86 | SSLStreamOpenSSLState myState; 87 | }; 88 | 89 | inline uint64_t 90 | SSLStreamOpenSSL::PopNetOutput( 91 | Buffer::Ptr& aOutHead) 92 | { 93 | PrivUpdate(); 94 | uint64_t res = myNetOutput.GetReadSize(); 95 | aOutHead = myNetOutput.PopData(); 96 | return res; 97 | } 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/mg/net/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | 3 | namespace mg { 4 | namespace net { 5 | 6 | bool 7 | SocketBindAny( 8 | Socket aSock, 9 | SockAddrFamily aAddrFamily, 10 | mg::box::Error::Ptr& aOutErr) 11 | { 12 | Host host; 13 | switch(aAddrFamily) 14 | { 15 | case ADDR_FAMILY_IPV4: 16 | host = mg::net::HostMakeAllIPV4(0); 17 | break; 18 | case ADDR_FAMILY_IPV6: 19 | host = mg::net::HostMakeAllIPV6(0); 20 | break; 21 | default: 22 | MG_BOX_ASSERT(!"Unknown address family"); 23 | break; 24 | } 25 | return SocketBind(aSock, host, aOutErr); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/mg/net/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Error.h" 4 | #include "mg/net/Host.h" 5 | 6 | #if IS_PLATFORM_WIN 7 | #include 8 | #endif 9 | 10 | namespace mg { 11 | namespace net { 12 | 13 | #if IS_PLATFORM_WIN 14 | using Socket = SOCKET; 15 | static constexpr Socket theInvalidSocket = INVALID_SOCKET; 16 | #else 17 | using Socket = int; 18 | static constexpr Socket theInvalidSocket = -1; 19 | #endif 20 | 21 | enum TransportProtocol 22 | { 23 | TRANSPORT_PROT_DEFAULT, 24 | TRANSPORT_PROT_TCP, 25 | }; 26 | 27 | // On some systems (like Apple) the maximal backlog size is a runtime value, not a 28 | // constant liek SOMAXCONN. 29 | uint32_t SocketMaxBacklog(); 30 | 31 | bool SocketBind( 32 | Socket aSock, 33 | const Host& aHost, 34 | mg::box::Error::Ptr& aOutErr); 35 | 36 | bool SocketBindAny( 37 | Socket aSock, 38 | SockAddrFamily aAddrFamily, 39 | mg::box::Error::Ptr& aOutErr); 40 | 41 | Host SocketGetBoundHost( 42 | Socket aSock); 43 | 44 | bool SocketSetKeepAlive( 45 | Socket aSock, 46 | bool aValue, 47 | uint32_t aTimeout, 48 | mg::box::Error::Ptr& aOutErr); 49 | 50 | bool SocketFixReuseAddr( 51 | Socket aSock, 52 | mg::box::Error::Ptr& aOutErr); 53 | 54 | bool SocketSetDualStack( 55 | Socket aSock, 56 | bool aValue, 57 | mg::box::Error::Ptr& aOutErr); 58 | 59 | bool SocketSetNoDelay( 60 | Socket aSock, 61 | bool aValue, 62 | mg::box::Error::Ptr& aOutErr); 63 | 64 | bool SocketShutdown( 65 | Socket aSock, 66 | mg::box::Error::Ptr& aOutErr); 67 | 68 | void SocketClose( 69 | Socket aSock); 70 | 71 | bool SocketCheckState( 72 | Socket aSock, 73 | mg::box::Error::Ptr& aOutErr); 74 | 75 | bool SocketIsAcceptErrorCritical( 76 | mg::box::ErrorCode aError); 77 | 78 | void SocketMakeNonBlocking( 79 | mg::net::Socket aSock); 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/mg/net/URL.cpp: -------------------------------------------------------------------------------- 1 | #include "URL.h" 2 | 3 | #include "mg/box/StringFunctions.h" 4 | 5 | namespace mg { 6 | namespace net { 7 | 8 | URL::URL() 9 | : myPort(0) 10 | { 11 | } 12 | 13 | URL 14 | URLParse( 15 | const char* aStr) 16 | { 17 | URL res; 18 | std::string str(aStr); 19 | size_t pos = str.find("://"); 20 | if (pos != std::string::npos) 21 | { 22 | res.myProtocol = str.substr(0, pos); 23 | str = str.substr(pos + 3); 24 | } 25 | pos = str.find('/'); 26 | if (pos != std::string::npos) 27 | { 28 | res.myTarget = str.substr(pos); 29 | str = str.substr(0, pos); 30 | } 31 | pos = str.find(':'); 32 | if (pos != std::string::npos) 33 | { 34 | std::string portStr = str.substr(pos + 1); 35 | uint16_t port; 36 | if (mg::box::StringToNumber(portStr.c_str(), port)) 37 | res.myPort = port; 38 | str = str.substr(0, pos); 39 | } 40 | res.myHost = str; 41 | return res; 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mg/net/URL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace mg { 8 | namespace net { 9 | 10 | struct URL 11 | { 12 | URL(); 13 | 14 | std::string myHost; 15 | std::string myProtocol; 16 | uint16_t myPort; 17 | std::string myTarget; 18 | }; 19 | 20 | URL URLParse( 21 | const char* aStr); 22 | 23 | static inline URL 24 | URLParse( 25 | const std::string& aStr) 26 | { 27 | return URLParse(aStr.c_str()); 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/mg/sch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_library(mgsch 4 | Task.cpp 5 | TaskScheduler.cpp 6 | ) 7 | 8 | target_include_directories(mgsch PUBLIC 9 | ${CMAKE_SOURCE_DIR}/src/ 10 | ) 11 | 12 | target_link_libraries(mgsch 13 | mgbox 14 | ) 15 | 16 | set(install_headers 17 | Task.h 18 | TaskScheduler.h 19 | ) 20 | 21 | install(TARGETS mgsch DESTINATION "${install_lib_root}") 22 | install(FILES ${install_headers} DESTINATION "${install_include_root}/mg/sch/") 23 | -------------------------------------------------------------------------------- /src/mg/sio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set(mgsio_src 4 | Socket.cpp 5 | TCPServer.cpp 6 | TCPSocket.cpp 7 | ) 8 | 9 | if(WIN32) 10 | set(mgsio_src ${mgsio_src} 11 | Socket_Win.cpp 12 | ) 13 | else() 14 | set(mgsio_src ${mgsio_src} 15 | Socket_Unix.cpp 16 | ) 17 | endif() 18 | 19 | add_library(mgsio ${mgsio_src}) 20 | 21 | target_include_directories(mgsio PUBLIC 22 | ${CMAKE_SOURCE_DIR}/src/ 23 | ) 24 | 25 | set(mgsio_libs mgnet) 26 | 27 | target_link_libraries(mgsio 28 | ${mgsio_libs} 29 | ) 30 | 31 | set(install_headers 32 | Socket.h 33 | TCPServer.h 34 | TCPSocket.h 35 | ) 36 | 37 | install(TARGETS mgsio DESTINATION "${install_lib_root}") 38 | install(FILES ${install_headers} DESTINATION "${install_include_root}/mg/sio/") 39 | -------------------------------------------------------------------------------- /src/mg/sio/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | 3 | #include "mg/box/IOVec.h" 4 | 5 | #include "mg/net/Buffer.h" 6 | 7 | namespace mg { 8 | namespace sio { 9 | 10 | int64_t 11 | SocketSend( 12 | mg::net::Socket aSock, 13 | const void* aData, 14 | uint64_t aSize, 15 | mg::box::Error::Ptr& aOutErr) 16 | { 17 | mg::box::IOVec vec; 18 | vec.myData = (void*)aData; 19 | if (aSize <= UINT32_MAX) 20 | vec.mySize = (uint32_t)aSize; 21 | else 22 | vec.mySize = UINT32_MAX; 23 | return SocketSend(aSock, &vec, 1, aOutErr); 24 | } 25 | 26 | int64_t 27 | SocketSend( 28 | mg::net::Socket aSock, 29 | const mg::net::Buffer* aHead, 30 | uint32_t aByteOffset, 31 | mg::box::Error::Ptr& aOutErr) 32 | { 33 | mg::box::IOVec bufs[mg::box::theIOVecMaxCount]; 34 | uint32_t count = mg::net::BuffersToIOVecsForWrite( 35 | aHead, aByteOffset, bufs, mg::box::theIOVecMaxCount); 36 | return SocketSend(aSock, bufs, count, aOutErr); 37 | } 38 | 39 | int64_t 40 | SocketSend( 41 | mg::net::Socket aSock, 42 | const mg::net::BufferLinkList& aList, 43 | uint32_t aByteOffset, 44 | mg::box::Error::Ptr& aOutErr) 45 | { 46 | mg::box::IOVec bufs[mg::box::theIOVecMaxCount]; 47 | uint32_t count = mg::net::BuffersToIOVecsForWrite( 48 | aList, aByteOffset, bufs, mg::box::theIOVecMaxCount); 49 | return SocketSend(aSock, bufs, count, aOutErr); 50 | } 51 | 52 | int64_t 53 | SocketRecv( 54 | mg::net::Socket aSock, 55 | void* aData, 56 | uint64_t aSize, 57 | mg::box::Error::Ptr& aOutErr) 58 | { 59 | mg::box::IOVec vec; 60 | vec.myData = aData; 61 | if (aSize <= UINT32_MAX) 62 | vec.mySize = (uint32_t)aSize; 63 | else 64 | vec.mySize = UINT32_MAX; 65 | return SocketRecv(aSock, &vec, 1, aOutErr); 66 | } 67 | 68 | int64_t 69 | SocketRecv( 70 | mg::net::Socket aSock, 71 | mg::net::Buffer* aHead, 72 | mg::box::Error::Ptr& aOutErr) 73 | { 74 | mg::box::IOVec bufs[mg::box::theIOVecMaxCount]; 75 | uint32_t count = mg::net::BuffersToIOVecsForRead( 76 | aHead, bufs, mg::box::theIOVecMaxCount); 77 | return SocketRecv(aSock, bufs, count, aOutErr); 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/mg/sio/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/Socket.h" 4 | 5 | F_DECLARE_STRUCT(mg, box, IOVec) 6 | F_DECLARE_CLASS(mg, net, Buffer) 7 | F_DECLARE_CLASS(mg, net, BufferLinkList) 8 | 9 | namespace mg { 10 | namespace sio { 11 | 12 | mg::net::Socket SocketCreate( 13 | mg::net::SockAddrFamily aAddrFamily, 14 | mg::net::TransportProtocol aProtocol, 15 | mg::box::Error::Ptr& aOutErr); 16 | 17 | mg::net::Socket SocketAccept( 18 | mg::net::Socket aServer, 19 | mg::net::Host& aOutPeer, 20 | mg::box::Error::Ptr& aOutErr); 21 | 22 | bool SocketListen( 23 | mg::net::Socket aSock, 24 | uint32_t aBacklog, 25 | mg::box::Error::Ptr& aOutErr); 26 | 27 | bool SocketConnectStart( 28 | mg::net::Socket aSock, 29 | const mg::net::Host& aHost, 30 | mg::box::Error::Ptr& aOutErr); 31 | 32 | bool SocketConnectUpdate( 33 | mg::net::Socket aSock, 34 | mg::box::Error::Ptr& aOutErr); 35 | 36 | int64_t SocketSend( 37 | mg::net::Socket aSock, 38 | const void* aData, 39 | uint64_t aSize, 40 | mg::box::Error::Ptr& aOutErr); 41 | 42 | int64_t SocketSend( 43 | mg::net::Socket aSock, 44 | const mg::box::IOVec* aBuffers, 45 | uint32_t aBufferCount, 46 | mg::box::Error::Ptr& aOutErr); 47 | 48 | int64_t SocketSend( 49 | mg::net::Socket aSock, 50 | const mg::net::Buffer* aHead, 51 | uint32_t aByteOffset, 52 | mg::box::Error::Ptr& aOutErr); 53 | 54 | int64_t SocketSend( 55 | mg::net::Socket aSock, 56 | const mg::net::BufferLinkList& aList, 57 | uint32_t aByteOffset, 58 | mg::box::Error::Ptr& aOutErr); 59 | 60 | int64_t SocketRecv( 61 | mg::net::Socket aSock, 62 | void* aData, 63 | uint64_t aSize, 64 | mg::box::Error::Ptr& aOutErr); 65 | 66 | int64_t SocketRecv( 67 | mg::net::Socket aSock, 68 | mg::box::IOVec* aBuffers, 69 | uint32_t aBufferCount, 70 | mg::box::Error::Ptr& aOutErr); 71 | 72 | int64_t SocketRecv( 73 | mg::net::Socket aSock, 74 | mg::net::Buffer* aHead, 75 | mg::box::Error::Ptr& aOutErr); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/mg/sio/TCPServer.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPServer.h" 2 | 3 | #include "mg/sio/Socket.h" 4 | 5 | namespace mg { 6 | namespace sio { 7 | 8 | TCPServer::TCPServer() 9 | : myState(TCP_SERVER_STATE_CLOSED) 10 | , mySocket(mg::net::theInvalidSocket) 11 | { 12 | } 13 | 14 | TCPServer::~TCPServer() 15 | { 16 | Close(); 17 | } 18 | 19 | bool 20 | TCPServer::Bind( 21 | const mg::net::Host& aHost, 22 | mg::box::Error::Ptr& aOutErr) 23 | { 24 | MG_DEV_ASSERT(myState == TCP_SERVER_STATE_CLOSED); 25 | mg::net::Socket sock = SocketCreate(aHost.myAddr.sa_family, 26 | mg::net::TRANSPORT_PROT_TCP, aOutErr); 27 | if (sock == mg::net::theInvalidSocket) 28 | return false; 29 | if (!mg::net::SocketFixReuseAddr(sock, aOutErr) || 30 | !mg::net::SocketBind(sock, aHost, aOutErr)) 31 | { 32 | mg::net::SocketClose(sock); 33 | return false; 34 | } 35 | myState = TCP_SERVER_STATE_BOUND; 36 | mySocket = sock; 37 | return true; 38 | } 39 | 40 | uint16_t 41 | TCPServer::GetPort() const 42 | { 43 | MG_DEV_ASSERT(myState == TCP_SERVER_STATE_BOUND || 44 | myState == TCP_SERVER_STATE_LISTENING); 45 | mg::net::Host host = mg::net::SocketGetBoundHost(mySocket); 46 | return host.GetPort(); 47 | } 48 | 49 | bool 50 | TCPServer::Listen( 51 | uint32_t aBacklog, 52 | mg::box::Error::Ptr& aOutErr) 53 | { 54 | MG_DEV_ASSERT(myState == TCP_SERVER_STATE_BOUND); 55 | if (!SocketListen(mySocket, aBacklog, aOutErr)) 56 | { 57 | Close(); 58 | return false; 59 | } 60 | myState = TCP_SERVER_STATE_LISTENING; 61 | return true; 62 | } 63 | 64 | mg::net::Socket 65 | TCPServer::Accept( 66 | mg::net::Host& aOutPeer, 67 | mg::box::Error::Ptr& aOutErr) 68 | { 69 | MG_DEV_ASSERT(myState == TCP_SERVER_STATE_LISTENING); 70 | mg::net::Socket res = SocketAccept(mySocket, aOutPeer, aOutErr); 71 | if (res == mg::net::theInvalidSocket && aOutErr.IsSet()) 72 | Close(); 73 | return res; 74 | } 75 | 76 | void 77 | TCPServer::Close() 78 | { 79 | if (myState == TCP_SERVER_STATE_CLOSED) 80 | return; 81 | myState = TCP_SERVER_STATE_CLOSED; 82 | mg::net::SocketClose(mySocket); 83 | mySocket = mg::net::theInvalidSocket; 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/mg/sio/TCPServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Error.h" 4 | 5 | #include "mg/net/Socket.h" 6 | 7 | namespace mg { 8 | namespace sio { 9 | 10 | enum TCPServerState 11 | { 12 | TCP_SERVER_STATE_CLOSED, 13 | TCP_SERVER_STATE_BOUND, 14 | TCP_SERVER_STATE_LISTENING, 15 | }; 16 | 17 | class TCPServer 18 | { 19 | public: 20 | TCPServer(); 21 | ~TCPServer(); 22 | 23 | bool Bind( 24 | const mg::net::Host& aHost, 25 | mg::box::Error::Ptr& aOutErr); 26 | 27 | uint16_t GetPort() const; 28 | 29 | bool Listen( 30 | uint32_t aBacklog, 31 | mg::box::Error::Ptr& aOutErr); 32 | 33 | mg::net::Socket Accept( 34 | mg::net::Host& aOutPeer, 35 | mg::box::Error::Ptr& aOutErr); 36 | 37 | void Close(); 38 | 39 | private: 40 | TCPServerState myState; 41 | mg::net::Socket mySocket; 42 | }; 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mg/sio/TCPSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Error.h" 4 | 5 | #include "mg/net/Buffer.h" 6 | #include "mg/net/Socket.h" 7 | 8 | namespace mg { 9 | namespace sio { 10 | 11 | enum TCPSocketState 12 | { 13 | TCP_SOCKET_STATE_CLOSED, 14 | TCP_SOCKET_STATE_CONNECTING, 15 | TCP_SOCKET_STATE_CONNECTED, 16 | }; 17 | 18 | class TCPSocket 19 | { 20 | public: 21 | TCPSocket(); 22 | ~TCPSocket(); 23 | 24 | bool Connect( 25 | const mg::net::Host& aHost, 26 | mg::box::Error::Ptr& aOutErr); 27 | void Wrap( 28 | mg::net::Socket aSock); 29 | bool Update( 30 | mg::box::Error::Ptr& aOutErr); 31 | void Close(); 32 | 33 | bool IsConnecting() const; 34 | bool IsConnected() const; 35 | bool IsClosed() const; 36 | 37 | void SendRef( 38 | const mg::net::Buffer* aHead); 39 | void SendRef( 40 | mg::net::Buffer::Ptr&& aHead); 41 | void SendRef( 42 | const void* aData, 43 | uint64_t aSize); 44 | void SendCopy( 45 | const void* aData, 46 | uint64_t aSize); 47 | 48 | int64_t Recv( 49 | mg::net::Buffer* aHead, 50 | mg::box::Error::Ptr& aOutErr); 51 | int64_t Recv( 52 | void* aData, 53 | uint64_t aSize, 54 | mg::box::Error::Ptr& aOutErr); 55 | 56 | private: 57 | bool PrivUpdateSend( 58 | mg::box::Error::Ptr& aOutErr); 59 | bool PrivUpdateConnect( 60 | mg::box::Error::Ptr& aOutErr); 61 | 62 | TCPSocketState myState; 63 | mg::net::Socket mySocket; 64 | mg::net::BufferLinkList myOutput; 65 | uint32_t mySendOffset; 66 | }; 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/mg/stub/BoxStub.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Assert.h" 2 | #include "mg/box/Log.h" 3 | 4 | #include 5 | #include 6 | 7 | // 8 | // The file implements missing functions from mg::box in some trivial way to get the tests 9 | // compile. 10 | // 11 | // A real project should not link with the stub-lib and should define its own functions. 12 | // 13 | 14 | namespace mg { 15 | namespace box { 16 | 17 | static const char* 18 | LogLevelToStringWithPadding( 19 | LogLevel aLevel) 20 | { 21 | switch(aLevel) 22 | { 23 | case LOG_LEVEL_ERROR: 24 | return "ERROR"; 25 | case LOG_LEVEL_WARN: 26 | return " WARN"; 27 | case LOG_LEVEL_INFO: 28 | return " INFO"; 29 | case LOG_LEVEL_DEBUG: 30 | return "DEBUG"; 31 | default: 32 | MG_BOX_ASSERT(!"Unknown log level"); 33 | return nullptr; 34 | } 35 | } 36 | 37 | void 38 | LogV( 39 | LogLevel aLevel, 40 | const char* aTag, 41 | int /* aLine */, 42 | const char* /* aFile */, 43 | const char* aFormat, 44 | va_list aParams) 45 | { 46 | static std::mutex mutex; 47 | std::unique_lock lock(mutex); 48 | printf("%s (%s): ", LogLevelToStringWithPadding(aLevel), aTag); 49 | vprintf(aFormat, aParams); 50 | printf("\n"); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/mg/stub/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_library(mgboxstub 4 | BoxStub.cpp 5 | ) 6 | 7 | target_include_directories(mgboxstub PUBLIC 8 | ${CMAKE_SOURCE_DIR}/src/ 9 | ) 10 | 11 | install(TARGETS mgboxstub DESTINATION "${install_lib_root}") 12 | -------------------------------------------------------------------------------- /src/mg/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set(mgtest_src 4 | CommandLine.cpp 5 | Message.cpp 6 | MetricMovingAverage.cpp 7 | MetricSpeed.cpp 8 | Random.cpp 9 | ) 10 | 11 | add_library(mgtest ${mgtest_src}) 12 | 13 | target_include_directories(mgtest PUBLIC 14 | ${CMAKE_SOURCE_DIR}/src/ 15 | ) 16 | 17 | target_link_libraries(mgtest 18 | mgbox 19 | ) 20 | -------------------------------------------------------------------------------- /src/mg/test/CommandLine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace tst { 10 | 11 | class CommandLine 12 | { 13 | public: 14 | CommandLine( 15 | int aArgc, 16 | const char* const* aArgv); 17 | 18 | bool IsPresent( 19 | const char* aName) const; 20 | bool IsTrue( 21 | const char* aName) const; 22 | 23 | const std::string& GetStr( 24 | const char* aName) const; 25 | bool GetStr( 26 | const char* aName, 27 | std::string& aOutRes) const; 28 | 29 | uint64_t GetU64( 30 | const char* aName) const; 31 | bool GetU64( 32 | const char* aName, 33 | uint64_t& aOutRes) const; 34 | 35 | uint32_t GetU32( 36 | const char* aName) const; 37 | bool GetU32( 38 | const char* aName, 39 | uint32_t& aOutRes) const; 40 | 41 | uint16_t GetU16( 42 | const char* aName) const; 43 | bool GetU16( 44 | const char* aName, 45 | uint16_t& aOutRes) const; 46 | 47 | private: 48 | struct Pair 49 | { 50 | std::string myKey; 51 | std::string myValue; 52 | }; 53 | 54 | const Pair* PrivFind( 55 | const char* aName) const; 56 | 57 | const Pair& PrivGet( 58 | const char* aName) const; 59 | 60 | std::vector myArgs; 61 | }; 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/mg/test/Message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/net/Buffer.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace tst { 9 | 10 | struct MessageHeader 11 | { 12 | MessageHeader() : myBodySize(0) {} 13 | 14 | uint64_t myBodySize; 15 | }; 16 | 17 | ////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | class ReadMessage 20 | { 21 | public: 22 | ReadMessage( 23 | mg::net::BufferReadStream& aStream); 24 | 25 | bool IsComplete() const; 26 | void Open(); 27 | void Close(); 28 | uint64_t GetAvailSize() const { return myStream->GetReadSize(); } 29 | 30 | void ReadUInt8( 31 | uint8_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 32 | void ReadUInt16( 33 | uint16_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 34 | void ReadUInt32( 35 | uint32_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 36 | void ReadUInt64( 37 | uint64_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 38 | 39 | void ReadInt8( 40 | int8_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 41 | void ReadInt16( 42 | int16_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 43 | void ReadInt32( 44 | int32_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 45 | void ReadInt64( 46 | int64_t& aOut) { ReadData(&aOut, sizeof(aOut)); } 47 | 48 | void ReadBool( 49 | bool& aOut) { ReadData(&aOut, sizeof(aOut)); } 50 | void ReadString( 51 | std::string& aOut); 52 | void ReadData( 53 | void* aData, 54 | uint64_t aSize); 55 | void SkipBytes( 56 | uint64_t aSize); 57 | 58 | private: 59 | mg::net::BufferReadStream* myStream; 60 | MessageHeader myHeader; 61 | bool myIsOpen; 62 | }; 63 | 64 | ////////////////////////////////////////////////////////////////////////////////////// 65 | 66 | class WriteMessage 67 | { 68 | public: 69 | WriteMessage(); 70 | 71 | uint64_t GetTotalSize() const { return myTotalSize; } 72 | mg::net::Buffer::Ptr TakeData(); 73 | 74 | void Open(); 75 | void Close(); 76 | 77 | void WriteUInt8( 78 | uint8_t aValue) { WriteData(&aValue, sizeof(aValue)); } 79 | void WriteUInt16( 80 | uint16_t aValue) { WriteData(&aValue, sizeof(aValue)); } 81 | void WriteUInt32( 82 | uint32_t aValue) { WriteData(&aValue, sizeof(aValue)); } 83 | void WriteUInt64( 84 | uint64_t aValue) { WriteData(&aValue, sizeof(aValue)); } 85 | 86 | void WriteInt8( 87 | uint8_t aValue) { WriteData(&aValue, sizeof(aValue)); } 88 | void WriteInt16( 89 | uint16_t aValue) { WriteData(&aValue, sizeof(aValue)); } 90 | void WriteInt32( 91 | uint32_t aValue) { WriteData(&aValue, sizeof(aValue)); } 92 | void WriteInt64( 93 | uint64_t aValue) { WriteData(&aValue, sizeof(aValue)); } 94 | 95 | void WriteBool( 96 | bool aValue) { WriteData(&aValue, sizeof(aValue)); } 97 | void WriteString( 98 | const std::string& aStr) { WriteString(aStr.c_str(), aStr.length()); } 99 | void WriteString( 100 | const char* aStr); 101 | void WriteString( 102 | const char* aStr, 103 | uint64_t aLen); 104 | void WriteData( 105 | const void* aData, 106 | uint64_t aSize); 107 | 108 | private: 109 | mg::net::Buffer::Ptr myHead; 110 | uint64_t mySize; 111 | uint64_t myTotalSize; 112 | mg::net::Buffer* myPos; 113 | uint8_t* myHeaderPos; 114 | }; 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/mg/test/MetricMovingAverage.cpp: -------------------------------------------------------------------------------- 1 | #include "MetricMovingAverage.h" 2 | 3 | namespace mg { 4 | namespace tst { 5 | 6 | void 7 | MetricMovingAverage::Reset( 8 | uint32_t aBucketCount) 9 | { 10 | myBuckets.clear(); 11 | myBuckets.resize(aBucketCount); 12 | myBucketIdx = 0; 13 | myCurSum.store(0, std::memory_order_relaxed); 14 | myCurCount = 0; 15 | } 16 | 17 | void 18 | MetricMovingAverage::Add( 19 | uint64_t aValue) 20 | { 21 | myCurSum.fetch_add(aValue, std::memory_order_relaxed); 22 | myCurCount.fetch_add(1, std::memory_order_relaxed); 23 | } 24 | 25 | double 26 | MetricMovingAverage::Get() const 27 | { 28 | uint64_t count = 0; 29 | uint64_t sum = 0; 30 | for (const MetricMovingAverageBucket& b : myBuckets) 31 | { 32 | count += b.myCount; 33 | sum += b.mySum; 34 | } 35 | if (count > 0) 36 | return (double)sum / count; 37 | return 0; 38 | } 39 | 40 | void 41 | MetricMovingAverage::GoToNextBucket() 42 | { 43 | MetricMovingAverageBucket& b = myBuckets[myBucketIdx]; 44 | b.mySum = myCurSum.exchange(0, std::memory_order_relaxed); 45 | b.myCount = myCurCount.exchange(0, std::memory_order_relaxed); 46 | myBucketIdx = (myBucketIdx + 1) % myBuckets.size(); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mg/test/MetricMovingAverage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace tst { 10 | 11 | struct MetricMovingAverageBucket 12 | { 13 | MetricMovingAverageBucket() : mySum(0), myCount(0) {} 14 | 15 | uint64_t mySum; 16 | uint64_t myCount; 17 | }; 18 | 19 | class MetricMovingAverage 20 | { 21 | public: 22 | MetricMovingAverage() { Reset(0); } 23 | 24 | void Reset( 25 | uint32_t aBucketCount); 26 | void Add( 27 | uint64_t aValue); 28 | double Get() const; 29 | void GoToNextBucket(); 30 | 31 | private: 32 | std::atomic_uint64_t myCurSum; 33 | std::atomic_uint64_t myCurCount; 34 | std::vector myBuckets; 35 | int myBucketIdx; 36 | }; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/mg/test/MetricSpeed.cpp: -------------------------------------------------------------------------------- 1 | #include "MetricSpeed.h" 2 | 3 | namespace mg { 4 | namespace tst { 5 | 6 | void 7 | MetricSpeed::Reset( 8 | uint32_t aBucketCount) 9 | { 10 | myCurSum.store(0, std::memory_order_relaxed); 11 | myBucketIdx = 0; 12 | myBuckets.clear(); 13 | myBuckets.resize(aBucketCount, 0); 14 | } 15 | 16 | uint64_t 17 | MetricSpeed::Get() const 18 | { 19 | uint64_t sum = 0; 20 | for (const uint64_t& b : myBuckets) 21 | { 22 | sum += b; 23 | } 24 | return sum; 25 | } 26 | 27 | void 28 | MetricSpeed::GoToNextBucket() 29 | { 30 | myBuckets[myBucketIdx] = myCurSum.exchange(0, std::memory_order_relaxed); 31 | myBucketIdx = (myBucketIdx + 1) % myBuckets.size(); 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/mg/test/MetricSpeed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace tst { 10 | 11 | class MetricSpeed 12 | { 13 | public: 14 | MetricSpeed() { Reset(0); } 15 | 16 | void Reset( 17 | uint32_t aBucketCount); 18 | void Add() { myCurSum.fetch_add(1, std::memory_order_relaxed); } 19 | uint64_t Get() const; 20 | void GoToNextBucket(); 21 | 22 | private: 23 | std::atomic_uint64_t myCurSum; 24 | std::vector myBuckets; 25 | int myBucketIdx; 26 | }; 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/mg/test/Random.cpp: -------------------------------------------------------------------------------- 1 | #include "Random.h" 2 | 3 | #include "mg/box/Thread.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace mg { 9 | namespace tst { 10 | 11 | using RandomGenerator = std::mt19937; 12 | 13 | static RandomGenerator* 14 | RandomGeneratorNew() 15 | { 16 | RandomGenerator* myGenerator = new RandomGenerator(); 17 | myGenerator->seed((unsigned)(time(nullptr) % UINT_MAX)); 18 | return myGenerator; 19 | } 20 | 21 | static RandomGenerator* 22 | RandomGeneratorGetLocal() 23 | { 24 | static MG_THREADLOCAL RandomGenerator* res = nullptr; 25 | if (res == nullptr) 26 | res = RandomGeneratorNew(); 27 | return res; 28 | } 29 | 30 | uint32_t 31 | RandomUInt32() 32 | { 33 | return RandomUniformUInt32(0, UINT32_MAX); 34 | } 35 | 36 | uint32_t 37 | RandomUniformUInt32( 38 | uint32_t aMin, 39 | uint32_t aMax) 40 | { 41 | std::uniform_int_distribution dist(aMin, aMax); 42 | return dist(*RandomGeneratorGetLocal()); 43 | } 44 | 45 | bool 46 | RandomBool() 47 | { 48 | std::uniform_int_distribution dist(0, 1); 49 | return dist(*RandomGeneratorGetLocal()) == 1; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/mg/test/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Definitions.h" 4 | 5 | namespace mg { 6 | namespace tst { 7 | 8 | uint32_t RandomUInt32(); 9 | 10 | uint32_t RandomUniformUInt32( 11 | uint32_t aMin, 12 | uint32_t aMax); 13 | 14 | bool RandomBool(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | option(MG_IS_CI "Whether is running in CI" 0) 4 | 5 | if (MG_IS_CI) 6 | add_compile_definitions(MG_IS_CI=1) 7 | else() 8 | add_compile_definitions(MG_IS_CI=0) 9 | endif() 10 | 11 | add_executable(test 12 | main.cpp 13 | UnitTest.cpp 14 | UnitTestSSLCerts.cpp 15 | aio/UnitTestTCPServer.cpp 16 | aio/UnitTestTCPSocketIFace.cpp 17 | box/UnitTestAlgorithm.cpp 18 | box/UnitTestAtomic.cpp 19 | box/UnitTestBinaryHeap.cpp 20 | box/UnitTestConditionVariable.cpp 21 | box/UnitTestDoublyList.cpp 22 | box/UnitTestError.cpp 23 | box/UnitTestForwardList.cpp 24 | box/UnitTestInterruptibleMutex.cpp 25 | box/UnitTestIOVec.cpp 26 | box/UnitTestLog.cpp 27 | box/UnitTestMultiConsumerQueue.cpp 28 | box/UnitTestMultiProducerQueue.cpp 29 | box/UnitTestMutex.cpp 30 | box/UnitTestRefCount.cpp 31 | box/UnitTestSharedPtr.cpp 32 | box/UnitTestSignal.cpp 33 | box/UnitTestString.cpp 34 | box/UnitTestSysinfo.cpp 35 | box/UnitTestThreadLocalPool.cpp 36 | box/UnitTestTime.cpp 37 | net/UnitTestBuffer.cpp 38 | net/UnitTestDomainToIP.cpp 39 | net/UnitTestHost.cpp 40 | net/UnitTestSSL.cpp 41 | net/UnitTestURL.cpp 42 | sch/UnitTestTaskScheduler.cpp 43 | sio/UnitTestTCPServer.cpp 44 | sio/UnitTestTCPSocket.cpp 45 | ) 46 | 47 | target_include_directories(test PUBLIC 48 | ${CMAKE_SOURCE_DIR}/src 49 | ${CMAKE_SOURCE_DIR}/test 50 | ) 51 | 52 | target_link_libraries(test 53 | mgaio 54 | mgbox 55 | mgboxstub 56 | mgnet 57 | mgsch 58 | mgsio 59 | mgtest 60 | ) 61 | -------------------------------------------------------------------------------- /test/UnitTest.cpp: -------------------------------------------------------------------------------- 1 | #include "UnitTest.h" 2 | 3 | #include "mg/box/Time.h" 4 | 5 | #include 6 | 7 | namespace mg { 8 | namespace unittests { 9 | 10 | MG_STRFORMAT_PRINTF(1, 2) 11 | void 12 | Report( 13 | const char *aFormat, 14 | ...) 15 | { 16 | va_list va; 17 | va_start(va, aFormat); 18 | ReportV(aFormat, va); 19 | va_end(va); 20 | } 21 | 22 | MG_STRFORMAT_PRINTF(1, 0) 23 | void 24 | ReportV( 25 | const char *aFormat, 26 | va_list aArg) 27 | { 28 | vprintf(aFormat, aArg); 29 | printf("\n"); 30 | fflush(stdout); 31 | } 32 | 33 | TestSuiteGuard::TestSuiteGuard( 34 | const char* aName) 35 | : myStartMs(mg::box::GetMillisecondsPrecise()) 36 | { 37 | Report("======== [%s] start", aName); 38 | } 39 | 40 | TestSuiteGuard::~TestSuiteGuard() 41 | { 42 | Report("======== took %.6lf ms", mg::box::GetMillisecondsPrecise() - myStartMs); 43 | } 44 | 45 | MG_STRFORMAT_PRINTF(2, 3) 46 | TestCaseGuard::TestCaseGuard( 47 | const char* aFormat, 48 | ...) 49 | : myStartMs(mg::box::GetMillisecondsPrecise()) 50 | { 51 | va_list va; 52 | va_start(va, aFormat); 53 | ReportV(aFormat, va); 54 | va_end(va); 55 | } 56 | 57 | TestCaseGuard::~TestCaseGuard() 58 | { 59 | Report("took %.6lf ms", mg::box::GetMillisecondsPrecise() - myStartMs); 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/UnitTest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mg/box/Assert.h" 4 | #include "mg/box/Thread.h" 5 | 6 | #include 7 | 8 | #if IS_COMPILER_CLANG 9 | // Clang does not put namespaces into __FUNCTION__. 10 | // __PRETTY_FUNCTION__ contains the whole signature, including 11 | // namespaces. Unfortunately, Clang does not feature a macros, 12 | // which would print function name + namespaces without arguments. 13 | #define FUNCTION_NAME_WITH_NAMESPACE __PRETTY_FUNCTION__ 14 | #else 15 | #define FUNCTION_NAME_WITH_NAMESPACE __FUNCTION__ 16 | #endif 17 | 18 | #define TEST_CHECK MG_BOX_ASSERT 19 | #define TEST_YIELD_PERIOD 5 20 | #define TEST_TIMEOUT 300000 // 5 mins 21 | 22 | namespace mg { 23 | namespace unittests { 24 | 25 | MG_STRFORMAT_PRINTF(1, 2) 26 | void Report( 27 | const char *aFormat, 28 | ...); 29 | 30 | MG_STRFORMAT_PRINTF(1, 0) 31 | void ReportV( 32 | const char *aFormat, 33 | va_list aArg); 34 | 35 | struct TestSuiteGuard 36 | { 37 | TestSuiteGuard( 38 | const char* aName); 39 | 40 | ~TestSuiteGuard(); 41 | 42 | private: 43 | double myStartMs; 44 | }; 45 | 46 | struct TestCaseGuard 47 | { 48 | MG_STRFORMAT_PRINTF(2, 3) 49 | TestCaseGuard( 50 | const char* aFormat, 51 | ...); 52 | 53 | ~TestCaseGuard(); 54 | 55 | private: 56 | double myStartMs; 57 | }; 58 | 59 | template 60 | static inline void 61 | Wait(T&& aCondition, uint32_t aPeriodMs = TEST_YIELD_PERIOD, uint32_t aTimeout = TEST_TIMEOUT) 62 | { 63 | uint64_t deadline = mg::box::GetMilliseconds() + aTimeout; 64 | while (!aCondition()) 65 | { 66 | TEST_CHECK(mg::box::GetMilliseconds() < deadline); 67 | if (aPeriodMs != 0) 68 | mg::box::Sleep(aPeriodMs); 69 | } 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/UnitTestSSLCerts.h: -------------------------------------------------------------------------------- 1 | #include "mg/box/Definitions.h" 2 | 3 | namespace mg { 4 | namespace unittests { 5 | 6 | // Private key and public certificate, version 1. 7 | extern const uint32_t theUnitTestCert1Size; 8 | extern const uint8_t theUnitTestCert1[]; 9 | 10 | extern const uint32_t theUnitTestKey1Size; 11 | extern const uint8_t theUnitTestKey1[]; 12 | 13 | // Private key and public certificate, version 2. 14 | extern const uint32_t theUnitTestCert2Size; 15 | extern const uint8_t theUnitTestCert2[]; 16 | 17 | extern const uint32_t theUnitTestKey2Size; 18 | extern const uint8_t theUnitTestKey2[]; 19 | 20 | // Private key and 2 public certificates signed by a CA, 21 | // version 1. 22 | extern const uint32_t theUnitTestCert31Size; 23 | extern const uint8_t theUnitTestCert31[]; 24 | 25 | extern const uint32_t theUnitTestCert32Size; 26 | extern const uint8_t theUnitTestCert32[]; 27 | 28 | extern const uint32_t theUnitTestCert33ExpiredSize; 29 | extern const uint8_t theUnitTestCert33Expired[]; 30 | 31 | extern const uint32_t theUnitTestKey3Size; 32 | extern const uint8_t theUnitTestKey3[]; 33 | 34 | extern const uint32_t theUnitTestCACert3Size; 35 | extern const uint8_t theUnitTestCACert3[]; 36 | // The same cert, but PEM format. 37 | extern const uint32_t theUnitTestCACert3PemSize; 38 | extern const char theUnitTestCACert3Pem[]; 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/box/UnitTestAlgorithm.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Algorithm.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace box { 8 | 9 | static void 10 | UnitTestMinMax() 11 | { 12 | TEST_CHECK(mg::box::Min(0, 0) == 0); 13 | TEST_CHECK(mg::box::Min(1, 0) == 0); 14 | TEST_CHECK(mg::box::Min(0, 1) == 0); 15 | 16 | TEST_CHECK(mg::box::Max(0, 0) == 0); 17 | TEST_CHECK(mg::box::Max(1, 0) == 1); 18 | TEST_CHECK(mg::box::Max(0, 1) == 1); 19 | 20 | int a = 0; 21 | constexpr int b = 0; 22 | TEST_CHECK(mg::box::Min(a, b) == 0); 23 | TEST_CHECK(mg::box::Max(a, b) == 0); 24 | } 25 | 26 | void 27 | UnitTestAlgorithm() 28 | { 29 | TestSuiteGuard suite("Algorithm"); 30 | 31 | UnitTestMinMax(); 32 | } 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/box/UnitTestConditionVariable.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/ConditionVariable.h" 2 | 3 | #include "mg/box/Atomic.h" 4 | #include "mg/box/ThreadFunc.h" 5 | 6 | #include "UnitTest.h" 7 | 8 | #define UNIT_TEST_CONDVAR_TIMEOUT 10000 9 | 10 | namespace mg { 11 | namespace unittests { 12 | namespace box { 13 | 14 | // The tests require some synchronization between threads to 15 | // make the tests stable and reproducible. And it should not 16 | // be cond vars, since they are being tested here. This is a 17 | // simple ping-pong a couple of functions to sync threads 18 | // using only CPU means. 19 | 20 | static inline void 21 | UnitTestCondVarSend( 22 | mg::box::AtomicU32& aCounter, 23 | uint32_t& aOutNextCounter) 24 | { 25 | aOutNextCounter = aCounter.IncrementFetchRelaxed() + 1; 26 | } 27 | 28 | static inline void 29 | UnitTestCondVarReceive( 30 | mg::box::AtomicU32& aCounter, 31 | uint32_t& aNextCounter) 32 | { 33 | while (aCounter.LoadRelaxed() != aNextCounter) 34 | continue; 35 | ++aNextCounter; 36 | } 37 | 38 | static void 39 | UnitTestConditionVariableBasic() 40 | { 41 | mg::box::ConditionVariable var; 42 | mg::box::Mutex mutex; 43 | mg::box::AtomicU32 stepCounter(0); 44 | uint32_t next = 0; 45 | mg::box::ThreadFunc worker("mgtst", [&]() { 46 | uint32_t workerNext = 1; 47 | 48 | // Test that simple lock/unlock work correct. 49 | UnitTestCondVarReceive(stepCounter, workerNext); 50 | 51 | mutex.Lock(); 52 | UnitTestCondVarSend(stepCounter, workerNext); 53 | var.Wait(mutex); 54 | TEST_CHECK(mutex.IsOwnedByThisThread()); 55 | mutex.Unlock(); 56 | mutex.Lock(); 57 | UnitTestCondVarSend(stepCounter, workerNext); 58 | 59 | // Test that timed wait correctly returns a timeout 60 | // error. 61 | UnitTestCondVarReceive(stepCounter, workerNext); 62 | 63 | var.TimedWait(mutex, mg::box::TimeDuration(100)); 64 | TEST_CHECK(mutex.IsOwnedByThisThread()); 65 | UnitTestCondVarSend(stepCounter, workerNext); 66 | 67 | // Test that timed wait does not set the flag if 68 | // there was no a timeout. 69 | UnitTestCondVarReceive(stepCounter, workerNext); 70 | 71 | UnitTestCondVarSend(stepCounter, workerNext); 72 | // Wait signal. 73 | TEST_CHECK(var.TimedWait(mutex, 74 | mg::box::TimeDuration(UNIT_TEST_CONDVAR_TIMEOUT))); 75 | TEST_CHECK(mutex.IsOwnedByThisThread()); 76 | 77 | UnitTestCondVarReceive(stepCounter, workerNext); 78 | UnitTestCondVarSend(stepCounter, workerNext); 79 | // Wait broadcast. 80 | TEST_CHECK(var.TimedWait(mutex, 81 | mg::box::TimeDuration(UNIT_TEST_CONDVAR_TIMEOUT))); 82 | TEST_CHECK(mutex.IsOwnedByThisThread()); 83 | 84 | mutex.Unlock(); 85 | }); 86 | worker.Start(); 87 | 88 | // Test that simple lock/unlock work correct. 89 | UnitTestCondVarSend(stepCounter, next); 90 | UnitTestCondVarReceive(stepCounter, next); 91 | mutex.Lock(); 92 | var.Signal(); 93 | mutex.Unlock(); 94 | UnitTestCondVarReceive(stepCounter, next); 95 | 96 | // Test that timed wait correctly returns a timeout 97 | // error. 98 | UnitTestCondVarSend(stepCounter, next); 99 | UnitTestCondVarReceive(stepCounter, next); 100 | 101 | // Test that timed wait does not set the flag if 102 | // was no a timeout. 103 | UnitTestCondVarSend(stepCounter, next); 104 | UnitTestCondVarReceive(stepCounter, next); 105 | mutex.Lock(); 106 | var.Signal(); 107 | mutex.Unlock(); 108 | 109 | UnitTestCondVarSend(stepCounter, next); 110 | UnitTestCondVarReceive(stepCounter, next); 111 | mutex.Lock(); 112 | var.Broadcast(); 113 | mutex.Unlock(); 114 | 115 | worker.BlockingStop(); 116 | } 117 | 118 | void 119 | UnitTestConditionVariable() 120 | { 121 | TestSuiteGuard suite("ConditionVariable"); 122 | 123 | UnitTestConditionVariableBasic(); 124 | } 125 | 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /test/box/UnitTestIOVec.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/IOVec.h" 2 | 3 | #include "mg/box/Assert.h" 4 | 5 | #include "UnitTest.h" 6 | 7 | namespace mg { 8 | namespace unittests { 9 | namespace box { 10 | namespace iovec { 11 | 12 | static void 13 | UnitTestIOVecPropagate() 14 | { 15 | TestCaseGuard guard("Propagate"); 16 | mg::box::IOVec vecs[4]; 17 | mg::box::IOVec* pos = nullptr; 18 | uint32_t count = 0; 19 | 20 | auto fillVecs = [&]() { 21 | vecs[0].myData = (void*)"123"; 22 | vecs[0].mySize = 3; 23 | vecs[1].myData = (void*)"4567"; 24 | vecs[1].mySize = 4; 25 | vecs[2].myData = nullptr; 26 | vecs[2].mySize = 0; 27 | vecs[3].myData = (void*)"89"; 28 | vecs[3].mySize = 2; 29 | pos = vecs; 30 | count = 4; 31 | }; 32 | 33 | fillVecs(); 34 | mg::box::IOVecPropagate(pos, count, 0); 35 | TEST_CHECK(count == 4); 36 | TEST_CHECK(pos == vecs); 37 | TEST_CHECK(vecs[0].mySize == 3 && memcmp(vecs[0].myData, "123", 3) == 0); 38 | TEST_CHECK(vecs[1].mySize == 4 && memcmp(vecs[1].myData, "4567", 4) == 0); 39 | TEST_CHECK(vecs[2].mySize == 0 && vecs[2].myData == nullptr); 40 | TEST_CHECK(vecs[3].mySize == 2 && memcmp(vecs[3].myData, "89", 2) == 0); 41 | 42 | mg::box::IOVecPropagate(pos, count, 2); 43 | TEST_CHECK(count == 4); 44 | TEST_CHECK(pos == vecs); 45 | TEST_CHECK(vecs[0].mySize == 1 && memcmp(vecs[0].myData, "3", 1) == 0); 46 | TEST_CHECK(vecs[1].mySize == 4 && memcmp(vecs[1].myData, "4567", 4) == 0); 47 | 48 | fillVecs(); 49 | mg::box::IOVecPropagate(pos, count, 3); 50 | TEST_CHECK(count == 3); 51 | TEST_CHECK(pos == vecs + 1); 52 | TEST_CHECK(vecs[1].mySize == 4 && memcmp(vecs[1].myData, "4567", 4) == 0); 53 | 54 | fillVecs(); 55 | mg::box::IOVecPropagate(pos, count, 5); 56 | TEST_CHECK(count == 3); 57 | TEST_CHECK(pos == vecs + 1); 58 | TEST_CHECK(vecs[1].mySize == 2 && memcmp(vecs[1].myData, "67", 2) == 0); 59 | TEST_CHECK(vecs[2].mySize == 0 && vecs[2].myData == nullptr); 60 | TEST_CHECK(vecs[3].mySize == 2 && memcmp(vecs[3].myData, "89", 2) == 0); 61 | 62 | fillVecs(); 63 | mg::box::IOVecPropagate(pos, count, 7); 64 | TEST_CHECK(count == 2); 65 | TEST_CHECK(pos == vecs + 2); 66 | TEST_CHECK(vecs[2].mySize == 0 && vecs[2].myData == nullptr); 67 | TEST_CHECK(vecs[3].mySize == 2 && memcmp(vecs[3].myData, "89", 2) == 0); 68 | 69 | fillVecs(); 70 | mg::box::IOVecPropagate(pos, count, 8); 71 | TEST_CHECK(count == 1); 72 | TEST_CHECK(pos == vecs + 3); 73 | TEST_CHECK(vecs[3].mySize == 1 && memcmp(vecs[3].myData, "9", 1) == 0); 74 | 75 | fillVecs(); 76 | mg::box::IOVecPropagate(pos, count, 9); 77 | TEST_CHECK(count == 0); 78 | TEST_CHECK(pos == vecs + 4); 79 | } 80 | } 81 | 82 | void 83 | UnitTestIOVec() 84 | { 85 | using namespace iovec; 86 | TestSuiteGuard suite("IOVec"); 87 | 88 | UnitTestIOVecPropagate(); 89 | } 90 | 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/box/UnitTestLog.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Log.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace box { 8 | namespace log { 9 | 10 | static void 11 | UnitTestLogBasic() 12 | { 13 | TestCaseGuard guard("Basic"); 14 | 15 | MG_LOG_ERROR("test.01", "abc"); 16 | MG_LOG_ERROR("test.02", "abc %d %s", 1, "2"); 17 | 18 | MG_LOG_WARN("test.03", "abc"); 19 | MG_LOG_WARN("test.04", "abc %d %s", 1, "2"); 20 | 21 | MG_LOG_INFO("test.05", "abc"); 22 | MG_LOG_INFO("test.06", "abc %d %s", 1, "2"); 23 | 24 | MG_LOG_DEBUG("test.07", "abc"); 25 | MG_LOG_DEBUG("test.08", "abc %d %s", 1, "2"); 26 | 27 | mg::box::Log(mg::box::LOG_LEVEL_ERROR, "test.09", 123, "file", 28 | "abc %d %s", 1, "2"); 29 | mg::box::Log(mg::box::LOG_LEVEL_WARN, "test.10", 123, "file", 30 | "abc %d %s", 1, "2"); 31 | mg::box::Log(mg::box::LOG_LEVEL_INFO, "test.11", 123, "file", 32 | "abc %d %s", 1, "2"); 33 | mg::box::Log(mg::box::LOG_LEVEL_DEBUG, "test.12", 123, "file", 34 | "abc %d %s", 1, "2"); 35 | } 36 | } 37 | 38 | void 39 | UnitTestLog() 40 | { 41 | using namespace log; 42 | TestSuiteGuard suite("Log"); 43 | 44 | UnitTestLogBasic(); 45 | } 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/box/UnitTestMutex.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Mutex.h" 2 | 3 | #include "mg/box/ThreadFunc.h" 4 | #include "mg/box/Time.h" 5 | 6 | #include "UnitTest.h" 7 | 8 | #include 9 | 10 | namespace mg { 11 | namespace unittests { 12 | namespace box { 13 | 14 | static void 15 | UnitTestMutexBasic() 16 | { 17 | mg::box::Mutex mutex; 18 | uint32_t counter = 0; 19 | const uint32_t threadCount = 10; 20 | std::vector threads; 21 | threads.reserve(threadCount); 22 | for (uint32_t i = 0; i < threadCount; ++i) 23 | { 24 | threads.push_back(new mg::box::ThreadFunc("mgtst", [&]() { 25 | uint64_t deadline = mg::box::GetMilliseconds() + 2000; 26 | uint64_t yield = 0; 27 | while (mg::box::GetMilliseconds() < deadline) 28 | { 29 | mg::box::MutexLock lock(mutex); 30 | TEST_CHECK(counter == 0); 31 | counter++; 32 | // Also test recursiveness. 33 | mutex.Lock(); 34 | TEST_CHECK(counter == 1); 35 | counter--; 36 | mutex.Unlock(); 37 | TEST_CHECK(counter == 0); 38 | if (++yield % 1000 == 0) 39 | mg::box::Sleep(1); 40 | } 41 | })); 42 | } 43 | for (mg::box::ThreadFunc* f : threads) 44 | f->Start(); 45 | for (mg::box::ThreadFunc* f : threads) 46 | delete f; 47 | TEST_CHECK(counter == 0); 48 | } 49 | 50 | void 51 | UnitTestMutex() 52 | { 53 | TestSuiteGuard suite("Mutex"); 54 | 55 | UnitTestMutexBasic(); 56 | } 57 | 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/box/UnitTestRefCount.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/RefCount.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace box { 8 | 9 | static void 10 | UnitTestRefCountBasic() 11 | { 12 | TestCaseGuard guard("RefCount basic"); 13 | { 14 | // Default constructor. 15 | mg::box::RefCount ref; 16 | TEST_CHECK(ref.Get() == 1); 17 | 18 | // Last dec returns true. 19 | TEST_CHECK(ref.Dec()); 20 | TEST_CHECK(ref.Get() == 0); 21 | ref.Inc(); 22 | ref.Inc(); 23 | TEST_CHECK(!ref.Dec()); 24 | TEST_CHECK(ref.Dec()); 25 | } 26 | { 27 | // Non-default constructor. 28 | mg::box::RefCount ref(3); 29 | TEST_CHECK(ref.Get() == 3); 30 | TEST_CHECK(!ref.Dec()); 31 | TEST_CHECK(!ref.Dec()); 32 | TEST_CHECK(ref.Dec()); 33 | 34 | ref.Inc(); 35 | TEST_CHECK(ref.Dec()); 36 | } 37 | } 38 | 39 | void 40 | UnitTestRefCount() 41 | { 42 | TestSuiteGuard suite("RefCount"); 43 | 44 | UnitTestRefCountBasic(); 45 | } 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/box/UnitTestSignal.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Signal.h" 2 | 3 | #include "mg/box/ThreadFunc.h" 4 | 5 | #include "UnitTest.h" 6 | 7 | #include 8 | 9 | namespace mg { 10 | namespace unittests { 11 | namespace box { 12 | 13 | static void 14 | UnitTestSignalBasic() 15 | { 16 | mg::box::Signal s; 17 | TEST_CHECK(!s.Receive()); 18 | 19 | s.Send(); 20 | TEST_CHECK(s.Receive()); 21 | TEST_CHECK(!s.Receive()); 22 | 23 | s.Send(); 24 | s.ReceiveBlocking(); 25 | TEST_CHECK(!s.Receive()); 26 | 27 | s.Send(); 28 | TEST_CHECK(s.ReceiveTimed(mg::box::TimeDuration(1000000))); 29 | TEST_CHECK(!s.Receive()); 30 | 31 | s.Send(); 32 | TEST_CHECK(s.ReceiveTimed(mg::box::TimeDuration(0))); 33 | TEST_CHECK(!s.Receive()); 34 | 35 | TEST_CHECK(!s.ReceiveTimed(mg::box::TimeDuration(1))); 36 | } 37 | 38 | static void 39 | UnitTestSignalStressSendAndReceive() 40 | { 41 | // The test checks that right after Receive() returns 42 | // success, it is safe to do with the signal anything. 43 | // Including deletion. Even if Send() in another thread is 44 | // not finished yet. Big number of signals is required so 45 | // as to catch the moment when the Send() thread is 46 | // interrupted right after setting the new state, but 47 | // before unlocking the mutex. 48 | const uint32_t count = 10000000; 49 | std::vector signals; 50 | signals.reserve(count); 51 | for (uint32_t i = 0; i < count; ++i) 52 | signals.push_back(new mg::box::Signal()); 53 | mg::box::ThreadFunc worker("mgtst", [&]() { 54 | uint32_t count = (uint32_t)signals.size(); 55 | for (uint32_t i = 0; i < count; ++i) 56 | signals[i]->Send(); 57 | }); 58 | worker.Start(); 59 | uint64_t yield = 0; 60 | for (uint32_t i = 0; i < count; ++i) 61 | { 62 | while (!signals[i]->Receive()) 63 | { 64 | if (++yield % 10000 == 0) 65 | mg::box::Sleep(1); 66 | } 67 | delete signals[i]; 68 | } 69 | worker.BlockingStop(); 70 | } 71 | 72 | void 73 | UnitTestSignal() 74 | { 75 | TestSuiteGuard suite("Signal"); 76 | 77 | UnitTestSignalBasic(); 78 | UnitTestSignalStressSendAndReceive(); 79 | } 80 | 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/box/UnitTestString.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/StringFunctions.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace box { 8 | 9 | static void 10 | UnitTestStringStrcasecmp() 11 | { 12 | TestCaseGuard guard("Strcasecmp()"); 13 | 14 | TEST_CHECK(mg::box::Strcasecmp("", "") == 0); 15 | TEST_CHECK(mg::box::Strcasecmp("aBcDe", "AbCdE") == 0); 16 | TEST_CHECK(mg::box::Strcasecmp("aBcDe", "Ab") > 0); 17 | TEST_CHECK(mg::box::Strcasecmp("aBc", "AbCdE") < 0); 18 | TEST_CHECK(mg::box::Strcasecmp("a", "b") < 0); 19 | TEST_CHECK(mg::box::Strcasecmp("a", "B") < 0); 20 | TEST_CHECK(mg::box::Strcasecmp("A", "b") < 0); 21 | TEST_CHECK(mg::box::Strcasecmp("b", "a") > 0); 22 | TEST_CHECK(mg::box::Strcasecmp("b", "A") > 0); 23 | TEST_CHECK(mg::box::Strcasecmp("B", "a") > 0); 24 | } 25 | 26 | static void 27 | UnitTestStringTrim() 28 | { 29 | TestCaseGuard guard("StringTrim()"); 30 | 31 | std::string str; 32 | mg::box::StringTrim(str); 33 | TEST_CHECK(str.empty()); 34 | 35 | str = "a"; 36 | mg::box::StringTrim(str); 37 | TEST_CHECK(str == "a"); 38 | 39 | str = "a b"; 40 | mg::box::StringTrim(str); 41 | TEST_CHECK(str == "a b"); 42 | 43 | str = " \r \t \n ab c \t \r \n "; 44 | mg::box::StringTrim(str); 45 | TEST_CHECK(str == "ab c"); 46 | } 47 | 48 | static void 49 | UnitTestStringFormat() 50 | { 51 | TestCaseGuard guard("StringFormat()"); 52 | 53 | TEST_CHECK(mg::box::StringFormat("") == ""); 54 | TEST_CHECK(mg::box::StringFormat("abc") == "abc"); 55 | TEST_CHECK(mg::box::StringFormat( 56 | "a %d %s b c %u", 1, "str", 2U) == "a 1 str b c 2"); 57 | } 58 | 59 | template 60 | static void 61 | UnitTestStringToNumberBasicTemplate() 62 | { 63 | NumT num = 0; 64 | TEST_CHECK(!mg::box::StringToNumber("", num)); 65 | TEST_CHECK(!mg::box::StringToNumber(" ", num)); 66 | TEST_CHECK(!mg::box::StringToNumber("abc", num)); 67 | TEST_CHECK(!mg::box::StringToNumber("123abc", num)); 68 | TEST_CHECK(!mg::box::StringToNumber("123 ", num)); 69 | TEST_CHECK(!mg::box::StringToNumber( 70 | "999999999999999999999999999999999999999999999", num)); 71 | 72 | num = 1; 73 | TEST_CHECK(mg::box::StringToNumber("0", num) && num == 0); 74 | num = 0; 75 | TEST_CHECK(mg::box::StringToNumber("1", num) && num == 1); 76 | num = 0; 77 | TEST_CHECK(mg::box::StringToNumber("123", num) && num == 123); 78 | num = 0; 79 | TEST_CHECK(mg::box::StringToNumber(" \t \n \r 200", num) && num == 200); 80 | 81 | uint64_t max = aMax; 82 | std::string maxStr; 83 | if (max < UINT64_MAX) 84 | maxStr = mg::box::StringFormat("%llu", (unsigned long long)max + 1); 85 | else 86 | maxStr = mg::box::StringFormat("%llu0", (unsigned long long)UINT64_MAX); 87 | num = 0; 88 | TEST_CHECK(!mg::box::StringToNumber(maxStr.c_str(), num)); 89 | } 90 | 91 | template 92 | static void 93 | UnitTestStringToNumberUnsignedTemplate( 94 | const char* aTypeName) 95 | { 96 | TestCaseGuard guard("StringToNumber(%s)", aTypeName); 97 | 98 | UnitTestStringToNumberBasicTemplate(); 99 | NumT num = 0; 100 | TEST_CHECK(!mg::box::StringToNumber("-1", num)); 101 | } 102 | 103 | static void 104 | UnitTestStringToNumberUnsigned() 105 | { 106 | UnitTestStringToNumberUnsignedTemplate("uint16"); 107 | UnitTestStringToNumberUnsignedTemplate("uint32"); 108 | UnitTestStringToNumberUnsignedTemplate("uint64"); 109 | } 110 | 111 | void 112 | UnitTestString() 113 | { 114 | TestSuiteGuard suite("String"); 115 | 116 | UnitTestStringStrcasecmp(); 117 | UnitTestStringTrim(); 118 | UnitTestStringFormat(); 119 | UnitTestStringToNumberUnsigned(); 120 | } 121 | 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /test/box/UnitTestSysinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/box/Sysinfo.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace box { 8 | 9 | void 10 | UnitTestSysinfo() 11 | { 12 | TestSuiteGuard suite("Sysinfo"); 13 | 14 | // Just ensure it is not crashing. 15 | Report("Is WSL: %d", (int)mg::box::SysIsWSL()); 16 | Report("Core count: %u", mg::box::SysGetCPUCoreCount()); 17 | } 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/net/UnitTestURL.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/net/URL.h" 2 | 3 | #include "UnitTest.h" 4 | 5 | namespace mg { 6 | namespace unittests { 7 | namespace net { 8 | namespace url { 9 | 10 | static void 11 | UnitTestURLBasic() 12 | { 13 | TestCaseGuard guard("Basic"); 14 | 15 | mg::net::URL url = mg::net::URLParse("127.0.0.1"); 16 | TEST_CHECK(url.myHost == "127.0.0.1"); 17 | TEST_CHECK(url.myProtocol.empty()); 18 | TEST_CHECK(url.myPort == 0); 19 | TEST_CHECK(url.myTarget.empty()); 20 | 21 | url = mg::net::URLParse("127.0.0.1:443"); 22 | TEST_CHECK(url.myHost == "127.0.0.1"); 23 | TEST_CHECK(url.myProtocol.empty()); 24 | TEST_CHECK(url.myPort == 443); 25 | TEST_CHECK(url.myTarget.empty()); 26 | 27 | url = mg::net::URLParse("www.google.com"); 28 | TEST_CHECK(url.myHost == "www.google.com"); 29 | TEST_CHECK(url.myProtocol.empty()); 30 | TEST_CHECK(url.myPort == 0); 31 | TEST_CHECK(url.myTarget.empty()); 32 | 33 | url = mg::net::URLParse("https://www.google.com"); 34 | TEST_CHECK(url.myHost == "www.google.com"); 35 | TEST_CHECK(url.myProtocol == "https"); 36 | TEST_CHECK(url.myPort == 0); 37 | TEST_CHECK(url.myTarget.empty()); 38 | 39 | url = mg::net::URLParse("https://www.google.com:100"); 40 | TEST_CHECK(url.myHost == "www.google.com"); 41 | TEST_CHECK(url.myProtocol == "https"); 42 | TEST_CHECK(url.myPort == 100); 43 | TEST_CHECK(url.myTarget.empty()); 44 | 45 | url = mg::net::URLParse("https://www.google.com/"); 46 | TEST_CHECK(url.myHost == "www.google.com"); 47 | TEST_CHECK(url.myProtocol == "https"); 48 | TEST_CHECK(url.myPort == 0); 49 | TEST_CHECK(url.myTarget == "/"); 50 | 51 | url = mg::net::URLParse("http://www.google.com:200/"); 52 | TEST_CHECK(url.myHost == "www.google.com"); 53 | TEST_CHECK(url.myProtocol == "http"); 54 | TEST_CHECK(url.myPort == 200); 55 | TEST_CHECK(url.myTarget == "/"); 56 | 57 | url = mg::net::URLParse("http://www.google.com/path/?arg1=value1&arg2=value2"); 58 | TEST_CHECK(url.myHost == "www.google.com"); 59 | TEST_CHECK(url.myProtocol == "http"); 60 | TEST_CHECK(url.myPort == 0); 61 | TEST_CHECK(url.myTarget == "/path/?arg1=value1&arg2=value2"); 62 | } 63 | } 64 | 65 | void 66 | UnitTestURL() 67 | { 68 | using namespace url; 69 | TestSuiteGuard suite("URL"); 70 | 71 | UnitTestURLBasic(); 72 | } 73 | 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/sio/UnitTestTCPServer.cpp: -------------------------------------------------------------------------------- 1 | #include "mg/sio/TCPServer.h" 2 | 3 | #include "mg/sio/TCPSocket.h" 4 | 5 | #include "UnitTest.h" 6 | 7 | namespace mg { 8 | namespace unittests { 9 | namespace sio { 10 | namespace tcpserver { 11 | 12 | static void 13 | UnitTestTCPServerBind() 14 | { 15 | TestCaseGuard guard("Bind"); 16 | 17 | // Local IPv4. 18 | mg::box::Error::Ptr err; 19 | mg::net::Host host = mg::net::HostMakeLocalIPV4(0); 20 | mg::sio::TCPServer server; 21 | TEST_CHECK(server.Bind(host, err)); 22 | server.Close(); 23 | 24 | // Any IPv4. 25 | host = mg::net::HostMakeAllIPV4(0); 26 | TEST_CHECK(server.Bind(host, err)); 27 | TEST_CHECK(server.Listen(mg::net::SocketMaxBacklog(), err)); 28 | 29 | // Fail when busy. 30 | mg::sio::TCPServer server2; 31 | host.SetPort(server.GetPort()); 32 | TEST_CHECK(!server2.Bind(host, err)); 33 | TEST_CHECK(err->myCode == mg::box::ERR_NET_ADDR_IN_USE); 34 | 35 | host.SetPort(0); 36 | TEST_CHECK(server2.Bind(host, err)); 37 | TEST_CHECK(server2.Listen(mg::net::SocketMaxBacklog(), err)); 38 | } 39 | 40 | static void 41 | UnitTestTCPServerListen() 42 | { 43 | TestCaseGuard guard("Listen"); 44 | 45 | mg::box::Error::Ptr err; 46 | mg::net::Host host = mg::net::HostMakeLocalIPV4(0); 47 | mg::sio::TCPServer server; 48 | TEST_CHECK(server.Bind(host, err)); 49 | TEST_CHECK(server.Listen(mg::net::SocketMaxBacklog(), err)); 50 | server.Close(); 51 | } 52 | 53 | static void 54 | UnitTestTCPServerAccept() 55 | { 56 | TestCaseGuard guard("Accept"); 57 | 58 | mg::box::Error::Ptr err; 59 | mg::net::Host host = mg::net::HostMakeLocalIPV4(0); 60 | mg::sio::TCPServer server; 61 | TEST_CHECK(server.Bind(host, err)); 62 | TEST_CHECK(server.Listen(mg::net::SocketMaxBacklog(), err)); 63 | // No clients yet. 64 | mg::net::Socket sock = mg::net::theInvalidSocket; 65 | for (int i = 0; i < 3; ++i) 66 | { 67 | sock = server.Accept(host, err); 68 | TEST_CHECK(sock == mg::net::theInvalidSocket); 69 | TEST_CHECK(!err.IsSet()); 70 | } 71 | 72 | // Make a client. 73 | host.SetPort(server.GetPort()); 74 | mg::sio::TCPSocket peer; 75 | TEST_CHECK(peer.Connect(host, err)); 76 | while (true) 77 | { 78 | bool isConnected = peer.IsConnected(); 79 | TEST_CHECK(isConnected || !peer.IsClosed()); 80 | TEST_CHECK(peer.Update(err)); 81 | if (sock == mg::net::theInvalidSocket) 82 | sock = server.Accept(host, err); 83 | TEST_CHECK(!err.IsSet()); 84 | if (isConnected && sock != mg::net::theInvalidSocket) 85 | break; 86 | } 87 | mg::net::SocketClose(sock); 88 | while (!peer.IsClosed()) 89 | { 90 | peer.Update(err); 91 | uint8_t buf; 92 | int64_t rc = peer.Recv(&buf, 1, err); 93 | TEST_CHECK(rc <= 0 && !err.IsSet()); 94 | } 95 | } 96 | 97 | static void 98 | UnitTestTCPServerClose() 99 | { 100 | TestCaseGuard guard("Close"); 101 | 102 | mg::sio::TCPServer server; 103 | // Multiple close of a not started server. 104 | server.Close(); 105 | server.Close(); 106 | 107 | // Close a bound server. 108 | mg::box::Error::Ptr err; 109 | mg::net::Host host = mg::net::HostMakeLocalIPV4(0); 110 | TEST_CHECK(server.Bind(host, err)); 111 | server.Close(); 112 | 113 | // Close a listening server. 114 | TEST_CHECK(server.Bind(host, err)); 115 | TEST_CHECK(server.Listen(mg::net::SocketMaxBacklog(), err)); 116 | server.Close(); 117 | } 118 | } 119 | 120 | void 121 | UnitTestTCPServer() 122 | { 123 | using namespace tcpserver; 124 | TestSuiteGuard suite("TCPServer"); 125 | 126 | UnitTestTCPServerBind(); 127 | UnitTestTCPServerListen(); 128 | UnitTestTCPServerAccept(); 129 | UnitTestTCPServerClose(); 130 | } 131 | 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tla/InterruptibleMutex.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT TotalInvariant 2 | 3 | CONSTANTS 4 | 5 | TargetCount = 5 6 | NULL = NULL 7 | 8 | wid1 = wid1 9 | wid2 = wid2 10 | wid3 = wid3 11 | WorkerThreadIDs = {wid1, wid2, wid3} 12 | MainThreadID = wid1 13 | 14 | SYMMETRY Perms 15 | 16 | SPECIFICATION Spec 17 | 18 | CHECK_DEADLOCK TRUE 19 | -------------------------------------------------------------------------------- /tla/MCSPQueue.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT TotalInvariant 2 | 3 | CONSTANTS 4 | 5 | Count = 6 6 | BlockSize = 3 7 | NULL = NULL 8 | GARBAGE = GARBAGE 9 | cons1 = cons1 10 | cons2 = cons2 11 | cons3 = cons3 12 | ConsumerIDs = {cons1, cons2, cons3} 13 | 14 | SYMMETRY Perms 15 | 16 | SPECIFICATION Spec 17 | 18 | PROPERTY TerminalProperty 19 | 20 | CHECK_DEADLOCK FALSE 21 | -------------------------------------------------------------------------------- /tla/README.md: -------------------------------------------------------------------------------- 1 | # What is TLA+ 2 | It is a language which allows to write specifications for systems and algorithms using a mathematical-like language which is extended with bits of programming languages and temporal logic. 3 | 4 | A specification can be verified if it is formally correct in a sense that its state machine has no deadlocks, infinite loops, and other logical bugs. That gives a much deeper assurance than any unit tests that the verified algorithm is truly correct. 5 | 6 | # How to run 7 | One way is to install a TLA+ GUI tool called "Toolbox" and verify a spec in it. The other way is via the command line. 8 | 9 | * Download the `tla2tools.jar` file from the official repository: https://github.com/tlaplus/tlaplus/releases#latest-tla-files. 10 | * Install this `.jar` file somehow. For instance, on Mac it can be saved into `/Library/Java/Extensions/`. 11 | * Define an alias for bash: `alias tlap='java -XX:+IgnoreUnrecognizedVMOptions -XX:+UseParallelGC -cp /Library/Java/Extensions/tla2tools.jar tlc2.TLC'` in your `.bash_profile` file or wherever is preferred. Replace the path with the place where the file is really stored, if it is different from this example. 12 | * In a console do: `tlap .tla`. 13 | 14 | In `.cfg` files there are parameters which can be tweaked to verify specific corner cases explicitly. But keep in mind that increasing the parameters too much often leads to a state machine explosion (verification can run for hours, days) without actually covering more. 15 | 16 | There are options which can be seen via `tlap -h` and in a bit different (somewhat extended, somewhat reduced) form here: https://lamport.azurewebsites.net/tla/current-tools.pdf. 17 | 18 | There is no good documentation for syntax and builtin functions and modules except the video course from Leslie Lamport and the book *'Specifying Systems'*. Sometimes bits of useful info can be found here: https://learntla.com/tla. Also can ask questions in the official Google Group: https://groups.google.com/g/tlaplus. 19 | 20 | # Links 21 | * Latest TLA+: https://github.com/tlaplus/tlaplus/releases#latest-tla-files 22 | * Official doc: https://lamport.azurewebsites.net/tla/current-tools.pdf. 23 | * Unofficial doc: https://learntla.com/tla 24 | * Google Group: https://groups.google.com/g/tlaplus. 25 | -------------------------------------------------------------------------------- /tla/TaskScheduler.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT TotalInvariant 2 | 3 | CONSTANTS 4 | 5 | ExecTarget = 2 6 | NULL = NULL 7 | 8 | wid1 = wid1 9 | wid2 = wid2 10 | WorkerThreadIDs = {wid1, wid2} 11 | 12 | uid1 = uid1 13 | uid2 = uid2 14 | UserThreadIDs = {uid1, uid2} 15 | 16 | tid1 = tid1 17 | tid2 = tid2 18 | TaskIDs = {tid1, tid2} 19 | 20 | SYMMETRY Perms 21 | 22 | SPECIFICATION Spec 23 | 24 | PROPERTY TerminalProperty 25 | 26 | CHECK_DEADLOCK FALSE 27 | --------------------------------------------------------------------------------