├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SConstruct ├── include └── coro │ ├── Common.hpp │ ├── Coroutine.hpp │ ├── Error.hpp │ ├── Event.hpp │ ├── Hub.hpp │ ├── Selector.hpp │ ├── Socket.hpp │ ├── SslSocket.hpp │ ├── Time.hpp │ └── coro.hpp ├── pkgboot.py ├── src ├── Common.cpp ├── Coroutine.Intel32.s ├── Coroutine.Intel64.asm ├── Coroutine.Intel64.s ├── Coroutine.Unix.inl ├── Coroutine.Win.inl ├── Coroutine.cpp ├── Error.cpp ├── Event.cpp ├── Hub.Win.inl ├── Hub.cpp ├── Hub.osx.inl ├── Selector.cpp ├── Socket.cpp ├── Socket.osx.inl ├── Socket.win.inl ├── SslSocket.cpp └── Time.cpp └── test ├── Basic.cpp ├── Event.cpp ├── Join.cpp ├── Selector.cpp ├── Socket.cpp ├── SocketDisconnect.cpp ├── Ssl.cpp ├── Stack.cpp ├── test.crt └── test.key /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | *.pch* 5 | *.idb 6 | src/Assets.cpp 7 | include/coro/Assets.hpp 8 | *.swp 9 | *.pyc 10 | *.sconsign.dblite 11 | bin 12 | lib 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.sln.docstates 17 | 18 | # Build results 19 | 20 | [Dd]ebug/ 21 | [Rr]elease/ 22 | x64/ 23 | build/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 28 | !packages/*/build/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | *_i.c 35 | *_p.c 36 | *.ilk 37 | *.meta 38 | *.obj 39 | *.pch 40 | *.pdb 41 | *.pgc 42 | *.pgd 43 | *.rsp 44 | *.sbr 45 | *.tlb 46 | *.tli 47 | *.tlh 48 | *.tmp 49 | *.tmp_proj 50 | *.log 51 | *.vspscc 52 | *.vssscc 53 | .builds 54 | *.pidb 55 | *.log 56 | *.scc 57 | 58 | # Visual C++ cache files 59 | ipch/ 60 | *.aps 61 | *.ncb 62 | *.opensdf 63 | *.sdf 64 | *.cachefile 65 | 66 | # Visual Studio profiler 67 | *.psess 68 | *.vsp 69 | *.vspx 70 | 71 | # Guidance Automation Toolkit 72 | *.gpState 73 | 74 | # ReSharper is a .NET coding add-in 75 | _ReSharper*/ 76 | *.[Rr]e[Ss]harper 77 | 78 | # TeamCity is a build add-in 79 | _TeamCity* 80 | 81 | # DotCover is a Code Coverage Tool 82 | *.dotCover 83 | 84 | # NCrunch 85 | *.ncrunch* 86 | .*crunch*.local.xml 87 | 88 | # Installshield output folder 89 | [Ee]xpress/ 90 | 91 | # DocProject is a documentation generator add-in 92 | DocProject/buildhelp/ 93 | DocProject/Help/*.HxT 94 | DocProject/Help/*.HxC 95 | DocProject/Help/*.hhc 96 | DocProject/Help/*.hhk 97 | DocProject/Help/*.hhp 98 | DocProject/Help/Html2 99 | DocProject/Help/html 100 | 101 | # Click-Once directory 102 | publish/ 103 | 104 | # Publish Web Output 105 | *.Publish.xml 106 | 107 | # NuGet Packages Directory 108 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 109 | #packages/ 110 | 111 | # Windows Azure Build Output 112 | csx 113 | *.build.csdef 114 | 115 | # Windows Store app package directory 116 | AppPackages/ 117 | 118 | # Others 119 | sql/ 120 | *.Cache 121 | ClientBin/ 122 | [Ss]tyle[Cc]op.* 123 | ~$* 124 | *~ 125 | *.dbmdl 126 | *.[Pp]ublish.xml 127 | *.pfx 128 | *.publishsettings 129 | 130 | # RIA/Silverlight projects 131 | Generated_Code/ 132 | 133 | # Backup & report files from converting an old project file to a newer 134 | # Visual Studio version. Backup files are not needed, because we have git ;-) 135 | _UpgradeReport_Files/ 136 | Backup*/ 137 | UpgradeLog*.XML 138 | UpgradeLog*.htm 139 | 140 | # SQL Server files 141 | App_Data/*.mdf 142 | App_Data/*.ldf 143 | 144 | 145 | #LightSwitch generated files 146 | GeneratedArtifacts/ 147 | _Pvt_Extensions/ 148 | ModelManifest.xml 149 | 150 | # ========================= 151 | # Windows detritus 152 | # ========================= 153 | 154 | # Windows image file caches 155 | Thumbs.db 156 | ehthumbs.db 157 | 158 | # Folder config file 159 | Desktop.ini 160 | 161 | # Recycle Bin used on file shares 162 | $RECYCLE.BIN/ 163 | 164 | # Mac desktop service store files 165 | .DS_Store 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Matt Fichman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coro 2 | ==== 3 | 4 | Modern coroutine library for C++, including an async socket I/O implementation (with SSL support). 5 | 6 | ## Basic Usage 7 | 8 | ``` 9 | auto c = coro::start([]() { 10 | std::cout << "I'm a coroutine!" << std::endl; 11 | coro::sleep(coro::Time::millisec(100)); 12 | }); 13 | coro::run(); // runs the coroutine dispatch loop 14 | ``` 15 | 16 | Support for OS X and Windows! 17 | 18 | ## Building 19 | 20 | ``` 21 | pip install scons 22 | cd coro 23 | scons 24 | ``` 25 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | 2 | import pkgboot 3 | 4 | class Coro(pkgboot.Package): 5 | defines = {} 6 | includes = [ 7 | '/usr/local/opt/openssl/include', 8 | ] 9 | libs = [ 10 | pkgboot.Lib('ws2_32', 'win32'), 11 | pkgboot.Lib('advapi32', 'win32'), 12 | pkgboot.Lib('user32', 'win32'), 13 | pkgboot.Lib('gdi32', 'win32'), 14 | pkgboot.Lib('libeay', 'win32'), 15 | pkgboot.Lib('ssleay', 'win32'), 16 | pkgboot.Lib('ssl', 'darwin'), 17 | pkgboot.Lib('crypto', 'darwin'), 18 | ] 19 | major_version = '0' 20 | minor_version = '0' 21 | patch = '0' 22 | 23 | Coro() 24 | -------------------------------------------------------------------------------- /include/coro/Common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | #ifndef CORO_COMMON_HPP 25 | #define CORO_COMMON_HPP 26 | 27 | #ifdef _WIN32 28 | #define VC_EXTRALEAN 29 | #define WIN32_LEAN_AND_MEAN 30 | #include 31 | #include 32 | #include 33 | #include 34 | #undef ERROR 35 | #undef Ptr 36 | #define ssize_t SSIZE_T 37 | #define SHUT_RD SD_RECEIVE 38 | #define SHUT_RW SD_BOTH 39 | #define SHUT_WR SD_SEND 40 | #endif 41 | 42 | #ifdef __APPLE__ 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #endif 55 | 56 | #ifdef __linux__ 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #endif 65 | 66 | #include 67 | #include 68 | #include 69 | 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | 86 | #ifndef CORO_STACK_SIZE 87 | #define CORO_STACK_SIZE 1048576 88 | //#define CORO_STACK_SIZE 102400 89 | #endif 90 | 91 | namespace coro { 92 | class Coroutine; 93 | class Channel; 94 | class Event; 95 | class Hub; 96 | class Selector; 97 | class Socket; 98 | 99 | template 100 | using Ptr = std::shared_ptr; 101 | 102 | template 103 | using WeakPtr = std::weak_ptr; 104 | 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /include/coro/Coroutine.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | #include "coro/Time.hpp" 27 | 28 | extern "C" { 29 | void __cdecl coroSwapContext(coro::Coroutine* from, coro::Coroutine* to); 30 | void __cdecl coroStart() throw(); 31 | } 32 | 33 | namespace coro { 34 | 35 | Ptr current(); 36 | Ptr main(); 37 | void yield(); 38 | void sleep(Time const& time); 39 | #ifdef _WIN32 40 | LONG WINAPI fault(LPEXCEPTION_POINTERS info); 41 | #else 42 | void fault(int signo, siginfo_t* info, void* context); 43 | #endif 44 | 45 | 46 | class Stack { 47 | // Lazily-allocated stack for a coroutine. When a new coroutine stack is 48 | // created, a block of virtual memory is reserved for the coroutine with memory 49 | // protection enabled. When the process first accesses a previously 50 | // unallocated for the coroutine, the page is allocated and the permissions set 51 | // to allow reads/writes. In this way, the coroutine only uses the stack 52 | // memory it needs. A coroutine's stack never shrinks; to garbage-collect the 53 | // stack, the coroutine must be destroyed. 54 | public: 55 | Stack(uint32_t size); 56 | ~Stack(); 57 | uint8_t* end() { return data_+size_; } 58 | uint8_t* begin() { return data_; } 59 | private: 60 | uint8_t* data_; 61 | uint64_t size_; 62 | friend class Coroutine; 63 | }; 64 | 65 | class ExitException { 66 | // Thrown when a coroutine is destroyed, but the coroutine hasn't exited yet. 67 | // The ExitException unwinds the coroutine's stack, so that destructors for all 68 | // stack objects are called. One should rarely (if ever) catch an 69 | // ExitException, and if an ExitException is caught, it should be thrown again 70 | // immediately. If a coroutine attempts to yield after an ExitException has 71 | // been thrown, then another ExitException is thrown from swap(). 72 | }; 73 | 74 | 75 | class Coroutine : public std::enable_shared_from_this { 76 | // A coroutine, or lightweight cooperative thread. A coroutine runs a function 77 | // that is allowed to suspend and resume at any point during its execution. 78 | public: 79 | enum Status { 80 | NEW, // Coroutine was just created, but hasn't been scheduled yet 81 | RUNNABLE, // Coroutine is stopped, but scheduled to run 82 | RUNNING, // Coroutine is currently running (this == current()) 83 | BLOCKED, // Coroutine is stopped and blocked on I/O 84 | WAITING, // Coroutine is stopped and waiting on an event 85 | DELETED, // Coroutine is running, but shutting down due to destructor call 86 | EXITED, // Coroutine has exited cleanly 87 | }; 88 | 89 | ~Coroutine(); 90 | 91 | template 92 | Coroutine(F func) : stack_(CORO_STACK_SIZE) { init(func); } 93 | Status status() const { return status_; } 94 | void join(); 95 | 96 | private: 97 | Coroutine(); // Special constructor for the main thread. 98 | void init(std::function const& func); 99 | void commit(uint64_t addr); 100 | void exit(); 101 | void start() throw(); 102 | void swap(); // Passes control to this coroutine 103 | void yield(); 104 | void block(); 105 | void unblock(); 106 | void wait(); // Enter WAITING state 107 | void notify(); // Exit WAITING state 108 | bool isMain() { return !stack_.begin(); } 109 | 110 | uint8_t* stackPointer_; // This field must be the first field in the coroutine 111 | std::function func_; 112 | Status status_; 113 | Stack stack_; 114 | Ptr event_; 115 | 116 | friend Ptr coro::current(); 117 | friend Ptr coro::main(); 118 | friend void coro::yield(); 119 | friend void coro::sleep(Time const& time); 120 | friend void ::coroStart() throw(); 121 | #ifdef _WIN32 122 | friend LONG WINAPI coro::fault(LPEXCEPTION_POINTERS info); 123 | #else 124 | friend void coro::fault(int signo, siginfo_t* info, void* context); 125 | #endif 126 | friend class coro::Hub; 127 | friend class coro::Socket; 128 | friend class coro::Event; 129 | friend class coro::Selector; 130 | }; 131 | 132 | } 133 | -------------------------------------------------------------------------------- /include/coro/Error.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | 27 | namespace coro { 28 | 29 | class SystemError { 30 | // Encapsulates an os-level error as an exception. 31 | public: 32 | SystemError(int error); 33 | SystemError(std::string const& msg) : error_(0), msg_(msg) {} 34 | SystemError(); 35 | 36 | int error() const { return error_; } 37 | std::string const& what() const { return msg_; } 38 | private: 39 | void operator=(SystemError const&) {} 40 | 41 | int error_; 42 | std::string const msg_; 43 | }; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /include/coro/Event.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | 27 | namespace coro { 28 | 29 | class EventRecord { 30 | public: 31 | EventRecord(Ptr coro); 32 | EventRecord(); 33 | Ptr coroutine() const { return coroutine_; } 34 | 35 | private: 36 | Ptr coroutine_; 37 | }; 38 | 39 | typedef size_t EventWaitToken; 40 | 41 | class Event { 42 | // An event is a coroutine synchronization primitive. A coroutine that waits 43 | // on an event is put to sleep until another coroutine wakes it via the notify 44 | // method. By calling wait() inside a conditional loop, a coroutine can 45 | // efficiently wait for a condition to become true -- thus enabling it to be 46 | // used for triggers, etc. 47 | public: 48 | virtual ~Event() {} 49 | void notifyAll(); // Notify all coroutines (add them to the runnable list) 50 | void wait(); // Add a coroutine to the wait set 51 | 52 | template 53 | void wait(F cond) { 54 | while (!cond()) { 55 | wait(); 56 | } 57 | } 58 | 59 | private: 60 | size_t waiters() const { return waiter_.size(); } 61 | EventWaitToken waitToken(Ptr waiter); 62 | bool waitTokenValid(Ptr waiter, EventWaitToken token); 63 | void waitTokenDel(EventWaitToken token); 64 | 65 | std::vector waiter_; 66 | friend class Selector; 67 | }; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /include/coro/Hub.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | #include "coro/Coroutine.hpp" 27 | 28 | namespace coro { 29 | 30 | Ptr hub(); 31 | void run(); 32 | 33 | class Timeout { 34 | // Record to track when a coroutine should be scheduled in the future. When 35 | // the time elapses, the coroutine is resumed. 36 | public: 37 | Timeout(Time const& time, Ptr coro) : time_(time), coroutine_(coro) {} 38 | bool operator<(Timeout const& rhs) const { return time_ > rhs.time_; } 39 | bool operator==(Timeout const& rhs) const { return time_ == rhs.time_; } 40 | 41 | Time const& time() const { return time_; } 42 | Ptr coroutine() const { return coroutine_; } 43 | private: 44 | Time time_; 45 | Ptr coroutine_; 46 | }; 47 | 48 | #ifdef _WIN32 49 | struct Overlapped { 50 | OVERLAPPED overlapped; 51 | Coroutine* coroutine; 52 | DWORD bytes; 53 | DWORD error; 54 | }; 55 | #endif 56 | 57 | class Hub { 58 | // The hub manages all coroutines, events, and I/O. It runs an event loop that 59 | // schedules coroutines to run when the events they are waiting on (channel, 60 | // I/O, etc.) have signaled. 61 | public: 62 | template 63 | Ptr start(F func) { 64 | Ptr coro(new Coroutine(func)); 65 | runnable_.push_back(coro); 66 | return coro; 67 | } 68 | void quiesce(); 69 | void poll(); 70 | void run(); 71 | #ifdef _WIN32 72 | HANDLE handle() const { return handle_; } 73 | #else 74 | int handle() const { return handle_; } 75 | #endif 76 | std::mutex const& mutex() const { return mutex_; } 77 | 78 | private: 79 | Hub(); 80 | std::vector> runnable_; 81 | std::priority_queue> timeout_; 82 | int blocked_; 83 | int waiting_; 84 | #ifdef _WIN32 85 | HANDLE handle_; 86 | #else 87 | int handle_; 88 | #endif 89 | std::mutex mutex_; 90 | Time now_; 91 | 92 | void timeoutIs(Timeout const& timeout); 93 | 94 | friend Ptr coro::hub(); 95 | friend void coro::sleep(Time const& time); 96 | friend class Coroutine; 97 | friend class Event; 98 | }; 99 | 100 | template 101 | Ptr start(F func) { 102 | return hub()->start(func); 103 | } 104 | 105 | template 106 | Ptr timer(F func, Time const& time) { 107 | return start([=] { 108 | std::function f = func; 109 | while (true) { 110 | f(); 111 | coro::sleep(time); 112 | } 113 | }); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /include/coro/Selector.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | #include "coro/Event.hpp" 27 | 28 | namespace coro { 29 | 30 | typedef std::function SelectorFunc; 31 | 32 | class SelectorRecord { 33 | public: 34 | SelectorRecord(Ptr event, SelectorFunc func, EventWaitToken token); 35 | 36 | Ptr event() const { return event_; } 37 | SelectorFunc func() const { return func_; } 38 | EventWaitToken token() const { return token_; } 39 | 40 | private: 41 | Ptr event_; 42 | SelectorFunc func_; 43 | EventWaitToken token_; 44 | }; 45 | 46 | class Selector { 47 | public: 48 | ~Selector(); 49 | Selector& on(Ptr event, SelectorFunc func); 50 | 51 | private: 52 | std::vector record_; 53 | }; 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /include/coro/Socket.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | 27 | namespace coro { 28 | 29 | class SocketAddr { 30 | // Represents an IPv4 or IPv6 address, with the host being an IP address 31 | // encoded the normal formats or a DNS name. 32 | public: 33 | SocketAddr(std::string const& host, short port) : host_(host), port_(port) {} 34 | struct sockaddr_in sockaddr() const; 35 | struct in_addr inaddr() const; 36 | std::string const& host() const { return host_; } 37 | short port() const { return port_; } 38 | 39 | private: 40 | std::string host_; 41 | short port_; 42 | }; 43 | 44 | class SocketCloseException { 45 | public: 46 | SocketCloseException() {} 47 | }; 48 | 49 | #ifdef _WIN32 50 | typedef SOCKET SocketHandle; 51 | #else 52 | typedef int SocketHandle; 53 | #endif 54 | 55 | class Socket { 56 | // Emulates Berkeley sockets using yields to block the coroutine when I/O 57 | // operations cannot complete right away. Actually, this class is more like 58 | // Python's adaptation of sockets. Failures throw exceptions rather than 59 | // returning error codes. 60 | public: 61 | Socket(int type=SOCK_STREAM, int protocol=IPPROTO_TCP); 62 | virtual ~Socket() { close(); } 63 | void bind(SocketAddr const& addr); 64 | void listen(int backlog); 65 | void shutdown(int how); 66 | void close(); 67 | virtual void connect(SocketAddr const& addr); 68 | virtual Ptr accept(); 69 | virtual ssize_t write(char const* buf, size_t len, int flags=0); // Execute 1 write() syscall 70 | virtual ssize_t read(char* buf, size_t len, int flags=0); // Execute 1 read() syscall 71 | void writeAll(char const* buf, size_t len, int flags=0); // Read whole buffer 72 | void readAll(char* buf, size_t len, int flags=0); // Write whole buffer 73 | int fileno() const; 74 | void setsockopt(int level, int option, int value); 75 | 76 | protected: 77 | Socket(SocketHandle sd, char const* bogus); 78 | SocketHandle acceptRaw(); 79 | 80 | private: 81 | SocketHandle sd_; 82 | }; 83 | 84 | } 85 | -------------------------------------------------------------------------------- /include/coro/SslSocket.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "coro/Common.hpp" 26 | #include "coro/Socket.hpp" 27 | 28 | namespace coro { 29 | 30 | class SslError { 31 | public: 32 | SslError(); 33 | std::string const& what() const { return what_; } 34 | 35 | private: 36 | std::string what_; 37 | }; 38 | 39 | class SslSocket : public Socket { 40 | public: 41 | SslSocket(int type=SOCK_STREAM, int protocol=IPPROTO_TCP); 42 | virtual ~SslSocket(); 43 | virtual Ptr accept(); 44 | virtual void listen(int backlog); 45 | virtual void connect(SocketAddr const& addr); 46 | virtual ssize_t write(char const* buf, size_t len, int flags=0); // Execute 1 write() syscall 47 | virtual ssize_t read(char* buf, size_t len, int flags=0); // Execte 1 read() syscall 48 | void useCertificateFile(std::string const& path); 49 | void usePrivateKeyFile(std::string const& path); 50 | 51 | private: 52 | SslSocket(SocketHandle sd, SSL_CTX* context); 53 | void writeAllRaw(char const* buf, size_t len, int flags); 54 | void writeFromBio(int flags); 55 | void readToBio(int flags); 56 | void handleReturn(ssize_t ret); 57 | 58 | SSL_CTX* context_; 59 | SSL* conn_; 60 | BIO* in_; 61 | BIO* out_; 62 | bool eof_; 63 | }; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /include/coro/Time.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2013 Matt Fichman 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 7 | * deal in the Software without restriction, including without limitation the 8 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | * sell 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 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS 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 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | * IN THE SOFTWARE. 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "coro/Common.hpp" 27 | 28 | namespace coro { 29 | 30 | class Time { 31 | public: 32 | Time() : Time(0) {} 33 | static Time sec(double sec) { return Time((int64_t)(sec*1000000)); } 34 | static Time millisec(int64_t ms) { return Time(ms*1000); } 35 | static Time microsec(int64_t us) { return Time(us); } 36 | static Time now(); 37 | 38 | bool operator<(Time const& rhs) const { return microsec_ < rhs.microsec_; } 39 | bool operator>(Time const& rhs) const { return microsec_ > rhs.microsec_; } 40 | bool operator==(Time const& rhs) const { return microsec_ == rhs.microsec_; } 41 | bool operator<=(Time const& rhs) const { return !operator>(rhs); } 42 | bool operator>=(Time const& rhs) const { return !operator<(rhs); } 43 | bool operator!=(Time const& rhs) const { return !operator==(rhs); } 44 | Time operator-(Time const& rhs) const { return Time(microsec_ - rhs.microsec_); } 45 | Time operator+(Time const& rhs) const { return Time(microsec_ + rhs.microsec_); } 46 | Time& operator+=(Time const& rhs) { microsec_ += rhs.microsec_; return *this; } 47 | Time& operator-=(Time const& rhs) { microsec_ -= rhs.microsec_; return *this; } 48 | 49 | #ifndef _WIN32 50 | struct timespec timespec() const; 51 | #endif 52 | int64_t millisec() const { return microsec_/1000; } 53 | int64_t microsec() const { return microsec_; } 54 | double sec() const { return (double)microsec_/1000000.; } 55 | private: 56 | Time(int64_t microsec) : microsec_(microsec) {} 57 | int64_t microsec_; 58 | }; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /include/coro/coro.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "Common.hpp" 26 | #include "Common.hpp" 27 | #include "Coroutine.hpp" 28 | #include "Error.hpp" 29 | #include "Event.hpp" 30 | #include "Hub.hpp" 31 | #include "Selector.hpp" 32 | #include "Socket.hpp" 33 | #include "SslSocket.hpp" 34 | #include "Time.hpp" 35 | -------------------------------------------------------------------------------- /pkgboot.py: -------------------------------------------------------------------------------- 1 | 2 | import platform 3 | import os 4 | import sys 5 | import shutil 6 | import subprocess 7 | import shlex 8 | import glob 9 | 10 | try: 11 | from SCons.Script import * 12 | except ImportError: 13 | pass 14 | 15 | def run_test(target, source, env): 16 | # Runs a single unit test and checks that the return code is 0 17 | try: 18 | subprocess.check_call(source[0].abspath) 19 | except subprocess.CalledProcessError, e: 20 | return 1 21 | 22 | def build_pch(target, source, env): 23 | try: 24 | args = ( 25 | str(env['CXX']), 26 | str(env['CXXFLAGS']), 27 | ' '.join('-I%s' % path for path in env['CPPPATH']), 28 | '-x', 29 | 'c++-header', 30 | str(source[0]), 31 | '-o', 32 | str(target[0]), 33 | ) 34 | print(' '.join(args)) 35 | subprocess.check_call(shlex.split(' '.join(args))) 36 | except subprocess.CalledProcessError, e: 37 | return 1 38 | 39 | class Lib: 40 | """ 41 | Defines a library that the build depends upon. 42 | """ 43 | def __init__(self, name, platforms): 44 | self.name = name 45 | self.platforms = platforms 46 | 47 | def __str__(self): 48 | return self.name 49 | 50 | def __repr__(self): 51 | return self.name 52 | 53 | class Package: 54 | """ 55 | Defines a workspace for a package. Automatically sets up all the usual SCons 56 | stuff, including a precompiled header file. 57 | """ 58 | defines = {} 59 | includes = [] 60 | libs = [] 61 | frameworks = [] 62 | path = [] 63 | lib_path = [] 64 | major_version = '0' 65 | minor_version = '0' 66 | patch = '0' 67 | kind = 'lib' 68 | frameworks = [] 69 | assets = [] 70 | 71 | def __init__(self): 72 | # Initializes a package, and sets up an SCons build environment given 73 | # the instructions found in the subclass definition. 74 | self._setup_vars() 75 | self._setup_env() 76 | self._setup_assets() 77 | if self.env['PLATFORM'] == 'win32': 78 | self._setup_win() 79 | else: 80 | self._setup_unix() 81 | self._setup_tests() 82 | self.build() # Run custom code for building the package 83 | 84 | def _lib_is_valid_for_platform(self, lib): 85 | if type(lib) == str: 86 | return True 87 | elif type(lib.platforms) == list: 88 | return self.env['PLATFORM'] in lib.platforms 89 | elif type(lib.platforms) == str: 90 | return self.env['PLATFORM'] == lib.platforms 91 | 92 | def _setup_assets(self): 93 | if len(self.assets) <= 0: 94 | return 95 | 96 | fd = open('include/%s/Assets.hpp' % self.name, 'w') 97 | fd.write('#pragma once\n') 98 | fd.write('namespace %s {\n' % self.name) 99 | fd.write('struct Asset {\n') 100 | fd.write(' char const* name;\n') 101 | fd.write(' char const* data;\n') 102 | fd.write(' size_t len;\n') 103 | fd.write('};\n') 104 | fd.write('extern Asset const assets[];\n') 105 | fd.write('}\n') 106 | fd.close() 107 | 108 | fd = open('src/Assets.cpp', 'w') 109 | fd.write('#include "%s/Common.hpp"\n' % self.name) 110 | fd.write('#include "%s/Assets.hpp"\n' % self.name) 111 | fd.write('namespace %s {\n' % self.name) 112 | fd.write('extern Asset const assets[] = {\n') 113 | for pattern in self.assets: 114 | for fn in glob.glob(pattern): 115 | fn = fn.replace('\\', '/') # Windows!! 116 | fd.write('{"%s",' % fn) 117 | r = open(fn, 'rb') # Windows!!! (binary mode required) 118 | data = r.read() 119 | length = len(data) 120 | data = ''.join(['\\x%02x' % ord(ch) for ch in data]) 121 | fd.write('"%s",%d},\n' % (data, length)) 122 | fd.write('{0, 0, 0},') 123 | fd.write('};\n') 124 | fd.write('}\n') 125 | fd.close() 126 | 127 | 128 | def _setup_vars(self): 129 | # Set up the basic configuration options 130 | (system, _, release, version, machine, proc) = platform.uname() 131 | self.name = self.__class__.__name__.lower() 132 | self.pch_header = '%s/Common.hpp' % self.name 133 | self.build_mode = ARGUMENTS.get('mode', 'debug') 134 | self.version = '.'.join((self.major_version, self.minor_version, self.patch)) 135 | self.branch = os.popen('git rev-parse --abbrev-ref HEAD').read().strip() 136 | self.revision = os.popen('git rev-parse HEAD').read().strip() 137 | self.defines.update({ 138 | '%s_VERSION' % self.name.upper(): self.version, 139 | '%s_REVISION' % self.name.upper(): self.revision, 140 | '%s_BRANCH' % self.name.upper(): self.branch, 141 | }) 142 | self.includes.extend([ 143 | 'include', 144 | 'src', 145 | ]) 146 | self.src = [] 147 | 148 | def _setup_env(self): 149 | # Create the SCons build environment, and fill it with default parameters. 150 | self.env = Environment(CPPPATH=['build/src']) 151 | self.env.Append(ENV=os.environ) 152 | self.env.VariantDir('build/src', 'src', duplicate=0) 153 | self.env.VariantDir('build/test', 'test', duplicate=0) 154 | 155 | def _setup_win(self): 156 | # Set up Windows-specific options 157 | if self.build_mode == 'debug': 158 | pass 159 | elif self.build_mode == 'release': 160 | self.env.Append(CXXFLAGS='/O2') 161 | else: 162 | assert not "Unknown build type" 163 | self.env.Append(CXXFLAGS='/W4 /WX /wd4100 /MD /EHsc /Zi /Gm /FS') 164 | self.env.Append(CXXFLAGS='/Fpbuild/Common.pch') 165 | self.env.Append(CXXFLAGS='/Yu%s' % self.pch_header) 166 | self.env.Append(LINKFLAGS='/DEBUG') 167 | self.env['AS'] = 'ml64' 168 | self.src += self.env.Glob('build/src/**.asm') 169 | # Add MASM assembly files 170 | 171 | self.includes.extend([ 172 | os.environ['LOCALAPPDATA']+'\\WinBrew\\include', 173 | ]) 174 | self.lib_path.extend([ 175 | os.environ['LOCALAPPDATA']+'\\WinBrew\\lib', 176 | ]) 177 | self.path.extend([ 178 | os.environ['PATH'], 179 | os.environ['LOCALAPPDATA']+'\\WinBrew\\bin', 180 | os.environ['LOCALAPPDATA']+'\\WinBrew\\lib', 181 | ]) 182 | # Extra Windows includes 183 | 184 | self._setup_build() 185 | 186 | pchenv = self.env.Clone() 187 | pchenv.Append(CXXFLAGS='/Yc%s' % self.pch_header) 188 | self.pch = pchenv.StaticObject('build/src/Common', 'build/src/Common.cpp') 189 | 190 | self._finish_build() 191 | 192 | def _setup_unix(self): 193 | # Set up OS X/Linux-specific options 194 | if self.build_mode == 'debug': 195 | self.env.Append(CXXFLAGS='-O0') 196 | elif self.build_mode == 'release': 197 | self.env.Append(CXXFLAGS='-O2') 198 | else: 199 | assert not "Unknown build type" 200 | 201 | self.env['CXX'] = 'clang++' 202 | self.env.Append(CXXFLAGS='-std=c++11 -stdlib=libc++ -g -Wall -Werror -fPIC') 203 | for framework in self.frameworks: 204 | self.env.Append(LINKFLAGS='-framework %s' % framework) 205 | self.env.Append(LINKFLAGS='-stdlib=libc++') 206 | self.env.Append(BUILDERS={'Pch': Builder(action=build_pch)}) 207 | self.src += self.env.Glob('build/src/**.s') 208 | # Add gas assembly files 209 | 210 | self._setup_build() 211 | 212 | pchenv = self.env.Clone() 213 | self.pch = pchenv.Pch('build/Common.pch', 'include/%s' % self.pch_header) 214 | self.env.Append(CXXFLAGS='-include-pch build/Common.pch') 215 | 216 | self._finish_build() 217 | 218 | def _setup_build(self): 219 | # Set up the basic build options 220 | self.libs = filter(self._lib_is_valid_for_platform, self.libs) 221 | self.libs = map(str, self.libs) 222 | 223 | self.env.Append(CPPDEFINES=self.defines) 224 | self.env.Append(CPPPATH=self.includes) 225 | self.env.Append(LIBPATH=self.lib_path) 226 | self.env.Append(LIBS=self.libs) 227 | 228 | def _finish_build(self): 229 | self.src += self.env.Glob('build/src/**.cpp') 230 | self.src += self.env.Glob('build/src/**.c') 231 | self.src = filter(lambda x: 'Common.cpp' not in x.name, self.src) 232 | self.env.Depends(self.src, self.pch) 233 | 234 | if self.env['PLATFORM'] == 'win32': 235 | self.lib = self.env.StaticLibrary('lib/%s' % self.name, (self.src, self.pch)) 236 | else: 237 | self.lib = self.env.SharedLibrary('lib/%s' % self.name, (self.src,)) 238 | if self.kind == 'bin': 239 | main = self.env.Glob('Main.cpp') 240 | self.env.Depends(main, self.pch) 241 | if self.env['PLATFORM'] == 'win32': 242 | self.program = self.env.Program('bin/%s' % self.name, (self.lib, main, self.pch)) 243 | else: 244 | self.program = self.env.Program('bin/%s' % self.name, (self.lib, main)) 245 | for tool in glob.glob('tools/*.cpp'): 246 | name = os.path.splitext(os.path.basename(tool.lower()))[0] 247 | self.env.Depends(tool, self.pch) 248 | tool = self.env.Program('bin/%s-%s' % (self.name, name), (self.lib, tool)) 249 | 250 | def _setup_tests(self): 251 | # Configure the test environment 252 | self.env.Append(BUILDERS={'Test': Builder(action=run_test)}) 253 | self.tests = [] 254 | testenv = self.env.Clone() 255 | testenv.Append(LIBS=self.lib) 256 | for test in self.env.Glob('build/test/**.cpp'): 257 | self.env.Depends(test, self.pch) 258 | name = test.name.replace('.cpp', '') 259 | if self.env['PLATFORM'] == 'win32': 260 | inputs = (test, self.pch) 261 | else: 262 | inputs = (test,) 263 | prog = testenv.Program('bin/test/%s' % name, inputs) 264 | if 'check' in COMMAND_LINE_TARGETS: 265 | self.tests.append(testenv.Test(name, prog)) 266 | if 'check' in COMMAND_LINE_TARGETS: 267 | self.env.Alias('check', self.tests) 268 | 269 | def build(self): 270 | pass 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /src/Common.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | 25 | -------------------------------------------------------------------------------- /src/Coroutine.Intel32.s: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2010 Matt Fichman 2 | ; 3 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 4 | ; of this software and associated documentation files (the "Software"), to deal 5 | ; in the Software without restriction, including without limitation the rights 6 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | ; copies of the Software, and to permit persons to whom the Software is 8 | ; furnished to do so, subject to the following conditions: 9 | ; 10 | ; The above copyright notice and this permission notice shall be included in 11 | ; all copies or substantial portions of the Software. 12 | ; 13 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 14 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | ; SOFTWARE. 20 | 21 | 22 | ; Pointer to the current coroutine. Used by Coroutine__resume() to set the 23 | ; 'caller' of the coroutine that is being resumed, and by Coroutine__yield() to 24 | ; switch from the current coroutine to the caller. 25 | 26 | .MODEL flat, C 27 | .STACK 100h 28 | 29 | .DATA 30 | EXTERN coroCurrent:DWORD 31 | 32 | .CODE 33 | coroSwapContext PROC ; (from, to) 34 | ; Resume the coroutine passed in as the first argument by saving the state 35 | ; of the current coroutine, and loading the other corountine's state. 36 | ; Then, 'return' to the caller of the other coroutine's yield() invocation. 37 | ; 38 | ; **** NOTE: If any of the 'push' instructions below change, then 39 | ; Coroutine.c must also be modified!!! 40 | ; 41 | ; On entry to this function, the stack looks like this; 42 | ; rsp+8 to 43 | ; rsp+4 from 44 | ; rsp+0 return address 45 | mov eax, [esp+8] ; Load 'to' 46 | mov ecx, [esp+4] ; Load 'from' 47 | mov [coroCurrent], eax 48 | ; Set the 'current coroutine' equal to ARG1 49 | 50 | push ebp 51 | push eax 52 | push ebx 53 | push ecx 54 | push edx 55 | push esi 56 | push edi 57 | 58 | assume fs:nothing 59 | push dword ptr fs:[0] 60 | push dword ptr fs:[4] 61 | push dword ptr fs:[8] 62 | assume fs:error 63 | ; Save structured exception handling chain 64 | 65 | mov [ecx+8], esp ; Save the sp for 'from' 66 | mov esp, [eax+8] ; Restore the sp for 'to' 67 | 68 | assume fs:nothing 69 | pop dword ptr fs:[8] 70 | pop dword ptr fs:[4] 71 | pop dword ptr fs:[0] 72 | assume fs:error 73 | ; Restore structured exception handling chain 74 | 75 | pop edi 76 | pop esi 77 | pop edx 78 | pop ecx 79 | pop ebx 80 | pop eax 81 | pop ebp 82 | ret 83 | coroSwapContext ENDP 84 | END 85 | -------------------------------------------------------------------------------- /src/Coroutine.Intel64.asm: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2010 Matt Fichman 2 | ; 3 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 4 | ; of this software and associated documentation files (the "Software"), to deal 5 | ; in the Software without restriction, including without limitation the rights 6 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | ; copies of the Software, and to permit persons to whom the Software is 8 | ; furnished to do so, subject to the following conditions: 9 | ; 10 | ; The above copyright notice and this permission notice shall be included in 11 | ; all copies or substantial portions of the Software. 12 | ; 13 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 14 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | ; SOFTWARE. 20 | 21 | 22 | ; Pointer to the current coroutine. Used by Coroutine__resume() to set the 23 | ; 'caller' of the coroutine that is being resumed, and by Coroutine__yield() to 24 | ; switch from the current coroutine to the caller. 25 | 26 | ;.MODEL flat, C 27 | ;.STACK 100h 28 | 29 | .DATA 30 | EXTERN coroCurrent:QWORD 31 | 32 | .CODE 33 | coroSwapContext PROC ; (from, to) 34 | ; Resume the coroutine passed in as the first argument by saving the state 35 | ; of the current coroutine, and loading the other corountine's state. 36 | ; Then, 'return' to the caller of the other coroutine's yield() invocation. 37 | ; 38 | ; **** NOTE: If any of the 'push' instructions below change, then 39 | ; Coroutine.c must also be modified!!! 40 | ; 41 | ; On entry to this function, the stack looks like this; 42 | ; rcx to 43 | ; rdx from 44 | ; rsp+0 return address 45 | mov [coroCurrent], rdx 46 | ; Set the 'current coroutine' equal to to 47 | 48 | push rbp 49 | push rax 50 | push rbx 51 | push rcx 52 | push rdx 53 | push rsi 54 | push rdi 55 | push r8 56 | push r9 57 | push r10 58 | push r11 59 | push r12 60 | push r13 61 | push r14 62 | push r15 63 | 64 | push qword ptr gs:[0] 65 | push qword ptr gs:[8] 66 | push qword ptr gs:[16] 67 | ; Save structured exception handling chain 68 | 69 | mov [rcx+16], rsp ; Save the sp for 'from' 70 | mov rsp, [rdx+16] ; Restore the sp for 'to' 71 | 72 | pop qword ptr gs:[16] 73 | pop qword ptr gs:[8] 74 | pop qword ptr gs:[0] 75 | ; Restore structured exception handling chain 76 | 77 | pop r15 78 | pop r14 79 | pop r13 80 | pop r12 81 | pop r11 82 | pop r10 83 | pop r9 84 | pop r8 85 | pop rdi 86 | pop rsi 87 | pop rdx 88 | pop rcx 89 | pop rbx 90 | pop rax 91 | pop rbp 92 | ret 93 | coroSwapContext ENDP 94 | END 95 | -------------------------------------------------------------------------------- /src/Coroutine.Intel64.s: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Matt Fichman 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | 22 | # Pointer to the current coroutine. Used by Coroutine__resume() to set the 23 | # caller of the coroutine that is being resumed, and by Coroutine__yield() to 24 | # switch from the current coroutine to the caller. 25 | 26 | 27 | .section __TEXT,__text,regular,pure_instructions 28 | .globl _coroSwapContext 29 | .align 4, 0x90 30 | _coroSwapContext: # (from, to) 31 | # Resume the coroutine passed in as the first argument by saving the state 32 | # of the current coroutine, and loading the other corountine's state. 33 | # Then, 'return' to the caller of the other coroutine's yield() invocation. 34 | # 35 | # **** NOTE: If any of the 'push' instructions below change, then 36 | # Coroutine.c must also be modified!!! 37 | # 38 | # On entry to this function, the stack looks like this: 39 | # rsi to 40 | # rdi from 41 | pushq %rbp 42 | pushq %rax 43 | pushq %rbx 44 | pushq %rcx 45 | pushq %rdx 46 | pushq %rsi 47 | pushq %rdi 48 | pushq %r8 49 | pushq %r9 50 | pushq %r10 51 | pushq %r11 52 | pushq %r12 53 | pushq %r13 54 | pushq %r14 55 | pushq %r15 56 | movq %rsp, 16(%rdi) # Save the sp for 'from' 57 | movq 16(%rsi), %rsp # Restore the sp for 'to' 58 | popq %r15 59 | popq %r14 60 | popq %r13 61 | popq %r12 62 | popq %r11 63 | popq %r10 64 | popq %r9 65 | popq %r8 66 | popq %rdi 67 | popq %rsi 68 | popq %rdx 69 | popq %rcx 70 | popq %rbx 71 | popq %rax 72 | popq %rbp 73 | ret 74 | -------------------------------------------------------------------------------- /src/Coroutine.Unix.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | namespace coro { 24 | 25 | /* 26 | static struct sigaction sigsegv; 27 | static struct sigaction sigbus; 28 | static char signalstack[SIGSTKSZ]; 29 | 30 | void fault(int signo, siginfo_t* info, void* context) { 31 | if (signo != SIGBUS && signo != SIGSEGV) { 32 | abort(); 33 | } 34 | uint64_t start = (uint64_t)coroCurrent->stack_.begin(); 35 | uint64_t end = (uint64_t)coroCurrent->stack_.end(); 36 | uint64_t addr = (uint64_t)info->si_addr; 37 | if (addr < start || addr >= end) { 38 | if (signo == SIGBUS) { 39 | struct sigaction self; 40 | sigaction(SIGBUS, &sigbus, &self); 41 | raise(SIGBUS); 42 | } else if (signo == SIGSEGV) { 43 | struct sigaction self; 44 | sigaction(SIGSEGV, &sigsegv, &self); 45 | raise(SIGSEGV); 46 | } 47 | } else { 48 | coroCurrent->commit(addr); 49 | } 50 | } 51 | */ 52 | 53 | void registerSignalHandlers() { 54 | // Set up the signal handlers for SIGBUS/SIGSEGV to catch stack protection 55 | // errors and grow the stack when it runs out of space. 56 | /* 57 | struct sigaction sa; 58 | memset(&sa, 0, sizeof(sa)); 59 | sigfillset(&sa.sa_mask); 60 | sa.sa_sigaction = ::coro::fault; 61 | sa.sa_flags = SA_SIGINFO|SA_ONSTACK; 62 | sigaction(SIGSEGV, &sa, &sigsegv); 63 | sigaction(SIGBUS, &sa, &sigbus); 64 | 65 | stack_t stack; 66 | stack.ss_sp = signalstack; 67 | stack.ss_flags = 0; 68 | stack.ss_size = sizeof(signalstack); 69 | sigaltstack(&stack, 0); 70 | */ 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Coroutine.Win.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | namespace coro { 25 | 26 | void registerSignalHandlers() { 27 | // Set up the signal handlers for SIGBUS/SIGSEGV to catch stack protection 28 | //AddVectoredExceptionHandler(1, fault); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Coroutine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Coroutine.hpp" 25 | #include "coro/Hub.hpp" 26 | #include "coro/Error.hpp" 27 | #include "coro/Event.hpp" 28 | 29 | extern "C" { 30 | coro::Coroutine* coroCurrent = coro::main().get(); 31 | } 32 | 33 | void coroStart() throw() { coroCurrent->start(); } 34 | 35 | #ifdef _WIN32 36 | #include "Coroutine.win.inl" 37 | #else 38 | #include "Coroutine.unix.inl" 39 | #endif 40 | 41 | 42 | namespace coro { 43 | 44 | #if defined(_WIN64) 45 | struct StackFrame { 46 | void* gs16; 47 | void* gs8; 48 | void* gs0; 49 | void* r15; 50 | void* r14; 51 | void* r13; 52 | void* r12; 53 | void* r11; 54 | void* r10; 55 | void* r9; 56 | void* r8; 57 | void* rdi; 58 | void* rsi; 59 | void* rdx; 60 | void* rcx; 61 | void* rbx; 62 | void* rax; 63 | void* rbp; 64 | void* returnAddr; 65 | void* padding; 66 | }; 67 | #elif defined(_WIN32) 68 | struct StackFrame { 69 | void* fs8; 70 | void* fs4; 71 | void* fs0; 72 | void* rdi; 73 | void* rsi; 74 | void* rdx; 75 | void* rcx; 76 | void* rbx; 77 | void* rax; 78 | void* rbp; 79 | void* returnAddr; // coroStart() stack frame here 80 | }; 81 | #else 82 | struct StackFrame { 83 | void* r15; 84 | void* r14; 85 | void* r13; 86 | void* r12; 87 | void* r11; 88 | void* r10; 89 | void* r9; 90 | void* r8; 91 | void* rdi; 92 | void* rsi; 93 | void* rdx; 94 | void* rcx; 95 | void* rbx; 96 | void* rax; 97 | void* rbp; 98 | void* returnAddr; 99 | void* padding; 100 | }; 101 | #endif 102 | 103 | uint64_t pageRound(uint64_t addr, uint64_t multiple) { 104 | // Rounds 'base' to the nearest 'multiple' 105 | return (addr/multiple)*multiple; 106 | } 107 | 108 | uint64_t pageSize() { 109 | // Returns the system page size. 110 | #ifdef _WIN32 111 | SYSTEM_INFO info; 112 | GetSystemInfo(&info); 113 | return info.dwPageSize*8; 114 | #else 115 | return sysconf(_SC_PAGESIZE); 116 | #endif 117 | } 118 | 119 | Stack::Stack(uint32_t size) : data_(0), size_(size) { 120 | // Lazily initializes a large stack for the coroutine. Initially, the 121 | // coroutine stack is quite small, to lower memory usage. As the stack grows, 122 | // the Coroutine::fault handler will commit memory pages for the coroutine. 123 | if (size == 0) { return; } 124 | data_ = (uint8_t*)malloc(size); 125 | assert(data_); 126 | } 127 | 128 | Stack::~Stack() { 129 | // Throw exception 130 | if (data_) { 131 | free(data_); 132 | } 133 | } 134 | 135 | Coroutine::Coroutine() : stack_(0) { 136 | // Constructor for the main coroutine. 137 | status_ = Coroutine::RUNNING; 138 | stackPointer_ = 0; 139 | event_.reset(new Event); 140 | } 141 | 142 | Coroutine::~Coroutine() { 143 | // Destroy the coroutine & clean up its stack 144 | if (!stack_.begin()) { 145 | // This is the main coroutine; don't free up anything, because we did 146 | // not allocate a stack for it. 147 | } else if (status_ != Coroutine::EXITED && status_ != Coroutine::NEW) { 148 | assert(status_ != Coroutine::BLOCKED); 149 | status_ = Coroutine::DELETED; 150 | swap(); // Allow the coroutine to clean up its stack 151 | assert(status_ == Coroutine::EXITED); 152 | } 153 | } 154 | 155 | void Coroutine::init(std::function const& func) { 156 | // Creates a new coroutine and allocates a stack for it. 157 | coro::main(); // Make sure main coroutine is set 158 | event_.reset(new Event); 159 | func_ = func; 160 | status_ = Coroutine::NEW; 161 | assert((((uint8_t*)this)+2*sizeof(uint8_t*))==(uint8_t*)&stackPointer_); 162 | 163 | StackFrame frame; 164 | memset(&frame, 0, sizeof(frame)); 165 | #ifdef _WIN64 166 | frame.gs0 = (void*)-1; // Root-level SEH handler 167 | frame.gs8 = stack_.end();// Top of stack 168 | frame.gs16 = stack_.begin(); // Bottom of stack 169 | #elif defined(_WIN32) 170 | frame.fs0 = (void*)-1; // Root-level SEH handler 171 | frame.fs4 = stack_.end();// Top of stack 172 | frame.fs8 = stack_.begin(); // Bottom of stack 173 | // See: http://stackoverflow.com/questions/9249576/seh-setup-for-fibers-with-exception-chain-validation-sehop-active 174 | #endif 175 | frame.returnAddr = (void*)coroStart; 176 | 177 | stackPointer_ = stack_.end(); 178 | stackPointer_ -= sizeof(frame); 179 | memcpy(stackPointer_, &frame, sizeof(frame)); 180 | } 181 | 182 | void Coroutine::yield() { 183 | // Yield temporarily to the hub. The hub will always reschedule a suspended 184 | // coroutine to run. 185 | assert(coroCurrent == this); 186 | switch (coroCurrent->status_) { 187 | case Coroutine::RUNNING: coroCurrent->status_ = Coroutine::RUNNABLE; break; 188 | case Coroutine::EXITED: break; 189 | case Coroutine::DELETED: break; // fallthrough 190 | case Coroutine::RUNNABLE: // fallthrough 191 | case Coroutine::BLOCKED: // fallthrough 192 | case Coroutine::NEW: // fallthrough 193 | default: assert(!"illegal state"); break; 194 | } 195 | main()->swap(); 196 | } 197 | 198 | void Coroutine::block() { 199 | // Block the current coroutine until some I/O event occurs. The coroutine will 200 | // not be rescheduled until explicitly scheduled. 201 | 202 | // Anchor the coroutine, so that it doesn't get GC'ed while blocked on I/O. 203 | Ptr anchor = shared_from_this(); 204 | assert(coroCurrent == this); 205 | switch (status_) { 206 | case Coroutine::RUNNING: status_ = Coroutine::BLOCKED; break; 207 | case Coroutine::EXITED: break; 208 | case Coroutine::DELETED: break; // fallthrough 209 | case Coroutine::RUNNABLE: // fallthrough 210 | case Coroutine::BLOCKED: // fallthrough 211 | case Coroutine::NEW: // fallthrough 212 | default: assert(!"illegal state"); break; 213 | } 214 | hub()->blocked_++; 215 | main()->swap(); 216 | } 217 | 218 | void Coroutine::unblock() { 219 | // Unblock the coroutine when an event occurs. 220 | switch (status_) { 221 | case Coroutine::BLOCKED: status_ = Coroutine::RUNNABLE; break; 222 | case Coroutine::RUNNING: // fallthrough 223 | case Coroutine::EXITED: // fallthrough 224 | case Coroutine::DELETED: // fallthrough 225 | case Coroutine::RUNNABLE: // fallthrough 226 | case Coroutine::NEW: // fallthrough 227 | default: assert(!"illegal state"); break; 228 | } 229 | hub()->blocked_--; 230 | hub()->runnable_.push_back(shared_from_this()); 231 | } 232 | 233 | void Coroutine::wait() { 234 | // Block the current coroutine until some event occurs. The coroutine will not 235 | // be rescheduled until explicitly scheduled. 236 | 237 | // Anchor the coroutine, so that it doesn't get GC'ed while waiting. 238 | // FixMe: Should this be the case? Maybe a waiting coroutine should be 239 | // collected. 240 | Ptr anchor = shared_from_this(); 241 | assert(coroCurrent == this); 242 | switch (status_) { 243 | case Coroutine::RUNNING: status_ = Coroutine::WAITING; break; 244 | case Coroutine::EXITED: break; 245 | case Coroutine::DELETED: break; // fallthrough 246 | case Coroutine::RUNNABLE: // fallthrough 247 | case Coroutine::BLOCKED: // fallthrough 248 | case Coroutine::NEW: // fallthrough 249 | default: assert(!"illegal state"); break; 250 | } 251 | hub()->waiting_++; 252 | main()->swap(); 253 | } 254 | 255 | void Coroutine::notify() { 256 | // Unblock the coroutine when an event occurs. 257 | switch (status_) { 258 | case Coroutine::WAITING: status_ = Coroutine::RUNNABLE; break; 259 | case Coroutine::RUNNABLE: return; 260 | case Coroutine::RUNNING: // fallthrough 261 | case Coroutine::EXITED: // fallthrough 262 | case Coroutine::DELETED: // fallthrough 263 | case Coroutine::NEW: // fallthrough 264 | default: assert(!"illegal state"); break; 265 | } 266 | hub()->waiting_--; 267 | hub()->runnable_.push_back(shared_from_this()); 268 | } 269 | 270 | void Coroutine::swap() { 271 | // Swaps control to this coroutine, causing the current coroutine to suspend. 272 | // This function returns when another coroutine swaps back to this coroutine. 273 | // If a coroutine dies it is asleep, then it throws an ExitException, which 274 | // will cause the coroutine to exit. 275 | Coroutine* current = coroCurrent; 276 | switch (status_) { 277 | case Coroutine::DELETED: break; 278 | case Coroutine::RUNNABLE: status_ = Coroutine::RUNNING; break; 279 | case Coroutine::NEW: status_ = Coroutine::RUNNING; break; 280 | case Coroutine::BLOCKED: status_ = Coroutine::RUNNING; break; 281 | case Coroutine::RUNNING: return; // already running 282 | case Coroutine::EXITED: assert(!"coroutine is dead"); break; 283 | default: assert(!"illegal state"); break; 284 | } 285 | coroCurrent = this; 286 | coroSwapContext(current, this); 287 | switch (coroCurrent->status_) { 288 | case Coroutine::DELETED: if (!coroCurrent->isMain()) { throw ExitException(); } break; 289 | case Coroutine::RUNNING: break; 290 | case Coroutine::RUNNABLE: // fallthrough 291 | case Coroutine::BLOCKED: // fallthrough 292 | case Coroutine::NEW: // fallthrough 293 | case Coroutine::EXITED: // fallthrough 294 | default: assert(!"illegal state"); break; 295 | } 296 | } 297 | 298 | void Coroutine::start() throw() { 299 | // This function runs the coroutine from the given entry point. 300 | try { 301 | func_(); 302 | exit(); // Fell off the end of the coroutine function 303 | } catch(ExitException const&) { 304 | exit(); // Coroutine was deallocated before exiting 305 | } catch(...) { 306 | assert(!"error: coroutine killed by exception"); 307 | } 308 | assert(!"error: unreachable"); 309 | } 310 | 311 | void Coroutine::exit() { 312 | // This function runs when the coroutine "falls of the stack," that is, when it finishes executing. 313 | assert(coroCurrent == this); 314 | switch (status_) { 315 | case Coroutine::DELETED: break; 316 | case Coroutine::RUNNING: status_ = Coroutine::EXITED; break; 317 | case Coroutine::EXITED: // fallthrough 318 | case Coroutine::RUNNABLE: // fallthrough 319 | case Coroutine::NEW: // fallthrough 320 | default: assert(!"illegal state"); break; 321 | } 322 | event_->notifyAll(); 323 | main()->swap(); 324 | assert(!"error: coroutine is dead"); 325 | } 326 | 327 | void Coroutine::join() { 328 | assert(current().get() != this); // Can't join on self -- deadlock 329 | while (status_ != Coroutine::EXITED) { 330 | event_->wait(); 331 | } 332 | } 333 | 334 | Ptr current() { 335 | // Returns the coroutine that is currently executing. 336 | return coroCurrent->shared_from_this(); 337 | } 338 | 339 | Ptr main() { 340 | // Returns the "main" coroutine (i.e., the main coroutine) 341 | static Ptr main; 342 | if (!main) { 343 | main.reset(new Coroutine); 344 | registerSignalHandlers(); 345 | } 346 | return main; 347 | } 348 | 349 | void yield() { 350 | current()->yield(); 351 | } 352 | 353 | void sleep(Time const& time) { 354 | hub()->timeoutIs(Timeout(time, current())); 355 | current()->wait(); 356 | } 357 | 358 | 359 | 360 | } 361 | -------------------------------------------------------------------------------- /src/Error.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Error.hpp" 25 | 26 | namespace coro { 27 | 28 | #ifdef _WIN32 29 | std::string winErrorMsg(DWORD error) { 30 | std::string buffer(1024,'0'); 31 | LPSTR buf = (LPSTR)buffer.c_str(); 32 | DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; 33 | DWORD len = FormatMessage(flags, 0, error, 0, buf, (DWORD)buffer.size(), 0); 34 | buffer.resize(len); 35 | return buffer; 36 | } 37 | #endif 38 | 39 | SystemError::SystemError() : 40 | #ifdef _WIN32 41 | SystemError(GetLastError()) { 42 | #else 43 | SystemError(errno) { 44 | #endif 45 | 46 | } 47 | 48 | SystemError::SystemError(int error) : 49 | #ifdef _WIN32 50 | error_(error), 51 | msg_(winErrorMsg(error_)) { 52 | #else 53 | error_(error), 54 | msg_(strerror(error_)) { 55 | 56 | assert(false); 57 | #endif 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/Event.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Event.hpp" 25 | #include "coro/Hub.hpp" 26 | 27 | namespace coro { 28 | 29 | 30 | EventRecord::EventRecord(Ptr coro) { 31 | coroutine_ = coro; 32 | } 33 | 34 | EventRecord::EventRecord() { 35 | } 36 | 37 | void Event::notifyAll() { 38 | std::vector waiter; 39 | waiter.swap(waiter_); 40 | for (auto record : waiter) { 41 | if (record.coroutine()) { 42 | record.coroutine()->notify(); 43 | } 44 | } 45 | assert(waiter_.size()==0); 46 | } 47 | 48 | void Event::wait() { 49 | waiter_.push_back(EventRecord(current())); 50 | current()->wait(); 51 | } 52 | 53 | size_t Event::waitToken(Ptr waiter) { 54 | waiter_.push_back(EventRecord(current())); 55 | return waiter_.size()-1; 56 | } 57 | 58 | bool Event::waitTokenValid(Ptr waiter, EventWaitToken token) { 59 | if (token < waiter_.size()) { 60 | return waiter_[token].coroutine() == waiter; 61 | } else { 62 | return false; 63 | } 64 | } 65 | 66 | void Event::waitTokenDel(EventWaitToken token) { 67 | assert(size_t(token) < waiter_.size() && "invalid wait token"); 68 | waiter_[token] = EventRecord(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Hub.Win.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | namespace coro { 24 | 25 | void Hub::poll() { 26 | // Poll For I/O events. If there are pending coroutines, then don't block 27 | // indefinitely -- just check for any ready I/O. If there are timers, block 28 | // only until the min timer is ready. 29 | size_t tasks = runnable_.size()+timeout_.size(); 30 | DWORD timeout = 0; 31 | if (!timeout_.empty() && runnable_.empty()) { 32 | auto const diff = timeout_.top().time()-Time::now(); 33 | if (diff > Time::sec(0)) { 34 | timeout = DWORD(diff.millisec()); 35 | } 36 | } 37 | if (tasks <= 0) { 38 | timeout = INFINITE; 39 | } 40 | if (timeout == 0 && blocked_ == 0) { 41 | return; 42 | } 43 | SetLastError(ERROR_SUCCESS); 44 | ULONG_PTR udata = 0; 45 | Overlapped* op = 0; 46 | OVERLAPPED** evt = (OVERLAPPED**)&op; 47 | DWORD bytes = 0; 48 | BOOL ret = GetQueuedCompletionStatus(handle_, (LPDWORD)&bytes, &udata, evt, timeout); 49 | if (!op) { return; } 50 | if (ret) { 51 | op->bytes = bytes; 52 | op->error = ERROR_SUCCESS; 53 | } else { 54 | op->bytes = 0; 55 | op->error = GetLastError(); 56 | } 57 | auto const coro = (Coroutine*)op->coroutine; 58 | assert(coro->status()!=Coroutine::EXITED); 59 | coro->unblock(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Hub.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Hub.hpp" 25 | #include "coro/Error.hpp" 26 | 27 | #ifdef __APPLE__ 28 | #include "Hub.osx.inl" 29 | #elif defined(_WIN32) 30 | #include "Hub.win.inl" 31 | #else 32 | #include "Hub.linux.inl" 33 | #endif 34 | 35 | namespace coro { 36 | 37 | Ptr hub() { 38 | static Ptr hub(new Hub); 39 | return hub; 40 | } 41 | 42 | void run() { 43 | hub()->run(); 44 | } 45 | 46 | Hub::Hub() : blocked_(0), waiting_(0), handle_(0) { 47 | // Creates a new hub to manage I/O and runnable coroutines. The hub is 48 | // responsible for scheduling coroutines to run. 49 | #if defined(_WIN32) 50 | WORD version = MAKEWORD(2, 2); 51 | WSADATA data; 52 | // Disable error dialog boxes 53 | SetErrorMode(GetErrorMode()|SEM_NOGPFAULTERRORBOX); 54 | if (WSAStartup(version, &data) != 0) { 55 | abort(); 56 | } 57 | handle_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); 58 | #elif defined(__APPLE__) 59 | handle_ = kqueue(); 60 | #elif defined(__linux__) 61 | handle_ = epoll_create(1); 62 | #endif 63 | if (!handle_) { 64 | throw SystemError(); 65 | } 66 | now_ = Time::now(); 67 | } 68 | 69 | void Hub::timeoutIs(Timeout const& timeout) { 70 | // Schedules a coroutine to run in the future 71 | timeout_.push(Timeout(timeout.time()+now_, timeout.coroutine())); 72 | } 73 | 74 | void Hub::quiesce() { 75 | // Run coroutines until they are all blocked on I/O or dead. 76 | std::vector> runnable; 77 | runnable.swap(runnable_); 78 | for (auto weak : runnable) { 79 | auto coroutine = weak.lock(); 80 | if (!coroutine) { continue; } 81 | main()->status_ = Coroutine::RUNNABLE; 82 | assert(coroutine->status()!=Coroutine::EXITED); 83 | coroutine->swap(); 84 | switch (coroutine->status()) { 85 | case Coroutine::EXITED: break; 86 | case Coroutine::DELETED: break; 87 | case Coroutine::RUNNABLE: 88 | runnable_.push_back(coroutine); 89 | break; 90 | case Coroutine::BLOCKED: 91 | case Coroutine::WAITING: 92 | break; 93 | case Coroutine::NEW: // fallthrough 94 | case Coroutine::RUNNING: // fallthrough 95 | default: assert(!"illegal coroutine state"); break; 96 | } 97 | } 98 | } 99 | 100 | void Hub::run() { 101 | // Run coroutines and handle I/O until the process exits 102 | assert(coro::current() == coro::main()); 103 | for (;;) { 104 | now_ = Time::now(); 105 | while (!timeout_.empty() && timeout_.top().time() <= now_) { 106 | auto const timeout = timeout_.top(); 107 | auto const coro = timeout.coroutine(); 108 | coro->notify(); 109 | timeout_.pop(); 110 | } 111 | quiesce(); 112 | if (runnable_.size()+blocked_+waiting_ <= 0) { 113 | return; // No more work to be done. 114 | } 115 | poll(); 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/Hub.osx.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | namespace coro { 25 | 26 | void Hub::poll() { 27 | // Poll for I/O events. If there are pending coroutines, then don't block 28 | // indefinitely -- just check for any ready I/O. If there are timers, block 29 | // only until the min timer is ready. 30 | size_t tasks = runnable_.size()+timeout_.size(); 31 | struct timespec timeout{0}; 32 | struct kevent event{0}; 33 | 34 | if (!timeout_.empty() && runnable_.empty()) { 35 | auto const diff = timeout_.top().time()-Time::now(); 36 | if (diff > Time::sec(0)) { 37 | timeout = diff.timespec(); 38 | } 39 | } 40 | // Don't sleep if the sleep time is less than 1 us. 41 | if (timeout.tv_nsec < 1000 && timeout.tv_sec == 0 && blocked_ == 0) { 42 | return; 43 | } 44 | 45 | int res = kevent(handle_, 0, 0, &event, 1, (tasks <= 0 ? 0 : &timeout)); 46 | if (res < 0) { 47 | throw SystemError(); 48 | } else if (res == 0) { 49 | // No events 50 | } else { 51 | auto const coro = (Coroutine*)event.udata; 52 | assert(coro->status()!=Coroutine::EXITED); 53 | coro->unblock(); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Selector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | #include "coro/Common.hpp" 25 | #include "coro/Selector.hpp" 26 | #include "coro/Coroutine.hpp" 27 | #include "coro/Event.hpp" 28 | 29 | namespace coro { 30 | 31 | SelectorRecord::SelectorRecord(Ptr event, SelectorFunc func, EventWaitToken token) { 32 | event_ = event; 33 | func_ = func; 34 | token_ = token; 35 | } 36 | 37 | Selector::~Selector() { 38 | current()->wait(); 39 | for (auto record : record_) { 40 | if (record.event()->waitTokenValid(current(), record.token())) { 41 | record.event()->waitTokenDel(record.token()); 42 | } else { 43 | record.func()(); 44 | } 45 | } 46 | } 47 | 48 | Selector& Selector::on(Ptr event, SelectorFunc func) { 49 | record_.push_back(SelectorRecord(event, func, event->waitToken(current()))); 50 | return *this; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Socket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Socket.hpp" 25 | #include "coro/Coroutine.hpp" 26 | #include "coro/Hub.hpp" 27 | #include "coro/Error.hpp" 28 | 29 | #ifdef __APPLE__ 30 | #include "Socket.osx.inl" 31 | #elif defined(_WIN32) 32 | #include "Socket.win.inl" 33 | #else 34 | #include "Socket.linux.inl" 35 | #endif 36 | 37 | namespace coro { 38 | 39 | struct in_addr SocketAddr::inaddr() const { 40 | // Attempts to translate from the input string as if it were a dotted quad 41 | // first. If this fails, then assume that the address string is a DNS name, 42 | // and do a DNS lookup. 43 | struct in_addr in{0}; 44 | if (host().empty()) { 45 | return in; 46 | } 47 | if (inet_pton(AF_INET, (char*)host().c_str(), &in) == 1) { 48 | return in; 49 | } 50 | struct addrinfo* res = 0; 51 | auto ret = getaddrinfo(host().c_str(), 0, 0, &res); 52 | if (ret) { 53 | throw SystemError(gai_strerror(ret)); 54 | } 55 | for(struct addrinfo* addr = res; addr; addr = addr->ai_next) { 56 | struct sockaddr_in* sin = (struct sockaddr_in*)addr->ai_addr; 57 | if (sin->sin_addr.s_addr) { 58 | in = sin->sin_addr; 59 | freeaddrinfo(res); 60 | return in; 61 | } 62 | } 63 | freeaddrinfo(res); 64 | assert(!"no addresses found"); 65 | return in; 66 | } 67 | 68 | struct sockaddr_in SocketAddr::sockaddr() const { 69 | // Returns the socket addr used by the low-level socket API 70 | struct sockaddr_in sin{0}; 71 | sin.sin_family = AF_INET; 72 | sin.sin_addr = inaddr(); 73 | sin.sin_port = htons(port()); 74 | return sin; 75 | } 76 | 77 | Socket::Socket(int type, int protocol) : sd_(0) { 78 | // Creates a new socket; throws a socket exception if creation fails 79 | hub(); // Make sure the hub is active 80 | sd_ = socket(AF_INET, type, protocol); 81 | if(sd_<0) { 82 | throw SystemError(); 83 | } 84 | #ifdef _WIN32 85 | if(!CreateIoCompletionPort((HANDLE)sd_, hub()->handle(), 0, 0)) { 86 | throw SystemError(); 87 | } 88 | #else 89 | setsockopt(SOL_SOCKET, SO_NOSIGPIPE, true); 90 | // Don't send SIGPIPE for this socket; handle the write() error instead. 91 | #endif 92 | } 93 | 94 | Socket::Socket(SocketHandle sd, char const* /* bogus */) : sd_(sd) { 95 | #ifdef _WIN32 96 | if(!CreateIoCompletionPort((HANDLE)sd_, hub()->handle(), 0, 0)) { 97 | throw SystemError(); 98 | } 99 | #else 100 | setsockopt(SOL_SOCKET, SO_NOSIGPIPE, true); 101 | // Don't send SIGPIPE for this socket; handle the write() error instead. 102 | #endif 103 | } 104 | 105 | Ptr Socket::accept() { 106 | return Ptr(new Socket(acceptRaw(), "")); 107 | } 108 | 109 | void Socket::bind(SocketAddr const& addr) { 110 | // Binds the socket to a port 111 | struct sockaddr_in sin = addr.sockaddr(); 112 | if (::bind(sd_, (struct sockaddr*)&sin, sizeof(sin)) < 0) { 113 | throw SystemError(); 114 | } 115 | } 116 | 117 | void Socket::listen(int backlog) { 118 | if (::listen(sd_, backlog)) { 119 | throw SystemError(); 120 | } 121 | } 122 | 123 | void Socket::setsockopt(int level, int option, int value) { 124 | if (::setsockopt(sd_, level, option, (char*)&value, sizeof(value))) { 125 | throw SystemError(); 126 | } 127 | } 128 | 129 | void Socket::writeAll(char const* buf, size_t len, int flags) { 130 | // Write all data in 'buf'. Block until all data is written, or the connection 131 | // is closed. Throws a SocketCloseException if the connection was closed by 132 | // the other end. 133 | while(len > 0) { 134 | ssize_t bytes = write(buf, len, flags); 135 | if (bytes == 0) { 136 | throw SocketCloseException(); 137 | } else if (bytes > 0) { 138 | buf += bytes; 139 | len -= bytes; 140 | } else { 141 | assert(!"unexpected negative byte value"); 142 | } 143 | } 144 | } 145 | 146 | void Socket::readAll(char* buf, size_t len, int flags) { 147 | // Read all data in 'buf'. Block until all data is read, or the connection is 148 | // closed. Throws a SocketCloseException if the connection was closed by the 149 | // other end. 150 | while(len > 0) { 151 | ssize_t bytes = read(buf, len, flags); 152 | if (bytes == 0) { 153 | throw SocketCloseException(); 154 | } else if (bytes > 0) { 155 | buf += bytes; 156 | len -= bytes; 157 | } else { 158 | assert(!"unexpected negative byte value"); 159 | } 160 | } 161 | } 162 | 163 | void Socket::shutdown(int how) { 164 | ::shutdown(sd_, how); 165 | } 166 | 167 | void Socket::close() { 168 | #ifdef _WIN32 169 | ::closesocket(sd_); 170 | sd_ = INVALID_SOCKET; 171 | #else 172 | ::close(sd_); 173 | sd_ = -1; 174 | #endif 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/Socket.osx.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | namespace coro { 24 | 25 | void Socket::connect(SocketAddr const& addr) { 26 | // Connect this socket to a remote socket 27 | if (fcntl(sd_, F_SETFL, O_NONBLOCK) < 0) { 28 | throw SystemError(); 29 | } 30 | 31 | // Set the socket in non-blocking mode, so that the call to connect() below 32 | // and calls to send/recv do not block. 33 | struct sockaddr_in sin = addr.sockaddr(); 34 | int ret = ::connect(sd_, (struct sockaddr*)&sin, sizeof(sin)); 35 | if (ret < 0 && errno != EINPROGRESS) { 36 | throw SystemError(); 37 | } 38 | 39 | int kqfd = hub()->handle(); 40 | int flags = EV_ADD|EV_ONESHOT|EV_EOF; 41 | struct kevent ev{0}; 42 | EV_SET(&ev, sd_, EVFILT_WRITE, flags, 0, 0, current().get()); 43 | if (kevent(kqfd, &ev, 1, 0, 0, 0) < 0) { 44 | throw SystemError(); 45 | } 46 | 47 | current()->block(); 48 | 49 | // Check for connect error code 50 | if (::read(sd_, 0, 0) < 0) { 51 | throw SystemError(); 52 | } 53 | } 54 | 55 | int Socket::acceptRaw() { 56 | // Accept a new incoming connection asynchronously. Register to wait for a READ 57 | // event, which signals that we can call accept() without blocking. 58 | int kqfd = hub()->handle(); 59 | int flags = EV_ADD|EV_ONESHOT; 60 | struct kevent ev{0}; 61 | EV_SET(&ev, sd_, EVFILT_READ, flags, 0, 0, current().get()); 62 | if (kevent(kqfd, &ev, 1, 0, 0, 0) < 0) { 63 | throw SystemError(); 64 | } 65 | // Wait until the socket becomes readable. At that point, there will be a 66 | // peer waiting in the accept queue. 67 | current()->block(); 68 | 69 | // Accept the peer, and create a new stream socket. 70 | struct sockaddr_in sin; 71 | socklen_t len = sizeof(sin); 72 | int sd = ::accept(sd_, (struct sockaddr*)&sin, &len); 73 | if (sd < 0) { 74 | throw SystemError(); 75 | } 76 | if (fcntl(sd, F_SETFL, O_NONBLOCK) < 0) { 77 | throw SystemError(); 78 | } 79 | return sd; 80 | } 81 | 82 | bool isSocketCloseError(int error) { 83 | // Return true if the error (as returned by send/recv) is an error that 84 | // indicates the socket was closed forcibly. These errors are converted into 85 | // SocketCloseExceptions. 86 | switch (error) { 87 | case EPIPE: 88 | case ENETRESET: 89 | case ECONNABORTED: 90 | case ECONNRESET: 91 | case ESHUTDOWN: 92 | return true; 93 | default: 94 | return false; 95 | } 96 | } 97 | 98 | ssize_t Socket::read(char* buf, size_t len, int flags) { 99 | // Read from the socket asynchronously. 100 | if (sd_ == -1) { 101 | throw SocketCloseException(); // Closed locally 102 | } 103 | 104 | ssize_t ret = recv(sd_, buf, len, flags); 105 | if (ret < 0) { 106 | if (isSocketCloseError(errno)) { 107 | throw SocketCloseException(); // Closed remotely 108 | } else if (EAGAIN != errno) { 109 | throw SystemError(); 110 | } 111 | } else { 112 | return ret; // Recv didn't block 113 | } 114 | 115 | // Recv blocked. Set up the kevent, and then try to call recv() again 116 | int const kqfd = hub()->handle(); 117 | int const kqflags = EV_ADD|EV_ONESHOT|EV_EOF; 118 | struct kevent ev{0}; 119 | EV_SET(&ev, sd_, EVFILT_READ, kqflags, 0, 0, current().get()); 120 | if (kevent(kqfd, &ev, 1, 0, 0, 0) < 0) { 121 | throw SystemError(); 122 | } 123 | current()->block(); 124 | 125 | if (sd_ == -1) { 126 | throw SocketCloseException(); 127 | } 128 | 129 | ret = recv(sd_, buf, len, flags); 130 | if (ret < 0) { 131 | if (isSocketCloseError(errno)) { 132 | throw SocketCloseException(); // Closed remotely 133 | } else { 134 | throw SystemError(); 135 | } 136 | } 137 | assert(ret >= 0); 138 | return ret; 139 | } 140 | 141 | ssize_t Socket::write(char const* buf, size_t len, int flags) { 142 | // Write asynchronously 143 | if (sd_ == -1) { 144 | throw SocketCloseException(); // Closed locally 145 | } 146 | 147 | ssize_t ret = send(sd_, buf, len, flags); 148 | if (ret < 0) { 149 | if (isSocketCloseError(errno)) { 150 | throw SocketCloseException(); // Closed remotely 151 | } else if (EAGAIN != errno) { 152 | throw SystemError(); 153 | } 154 | } else { 155 | return ret; // Send didn't block 156 | } 157 | 158 | // Send blocked. Set up the kevent, and then try to call send() again 159 | int const kqfd = hub()->handle(); 160 | int const kqflags = EV_ADD|EV_ONESHOT|EV_EOF; 161 | struct kevent ev{0}; 162 | EV_SET(&ev, sd_, EVFILT_WRITE, kqflags, 0, 0, current().get()); 163 | if (kevent(kqfd, &ev, 1, 0, 0, 0) < 0) { 164 | throw SystemError(); 165 | } 166 | current()->block(); 167 | 168 | if (sd_ == -1) { 169 | throw SocketCloseException(); 170 | } 171 | 172 | ret = send(sd_, buf, len, flags); 173 | if (ret < 0) { 174 | if (isSocketCloseError(errno)) { 175 | throw SocketCloseException(); // Closed remotely 176 | } else { 177 | throw SystemError(); 178 | } 179 | } 180 | assert(ret >= 0); 181 | return ret; 182 | } 183 | 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/Socket.win.inl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | namespace coro { 24 | 25 | void Socket::connect(SocketAddr const& addr) { 26 | // Windows asynchronous connect 27 | 28 | // Initialize a bunch of Windows-specific crap needed to load a pointer to 29 | // the ConnectEx function... 30 | 31 | // Get a pointer to the ConnectEx() function. Sigh. Windows. This 32 | // function never blocks, however, so we don't have to worry about I/O 33 | // completion ports. 34 | DWORD code = SIO_GET_EXTENSION_FUNCTION_POINTER; 35 | GUID guid = WSAID_CONNECTEX; 36 | LPFN_CONNECTEX ConnectEx = 0; 37 | DWORD bytes = 0; 38 | DWORD len = sizeof(ConnectEx); 39 | WSAIoctl(sd_, code, &guid, sizeof(guid), &ConnectEx, len, &bytes, 0, 0); 40 | 41 | // To use ConnectEx, bind() must be called first to assign a port number to 42 | // the socket. 43 | struct sockaddr_in sin{0}; 44 | sin.sin_family = AF_INET; 45 | sin.sin_addr.s_addr = htonl(INADDR_ANY); 46 | sin.sin_port = 0; 47 | if (::bind(sd_, (struct sockaddr*)&sin, sizeof(sin)) != 0) { 48 | throw SystemError(); 49 | } 50 | 51 | // Initialize the OVERLAPPED structure that contains the user I/O data used 52 | // to resume the coroutine when ConnectEx completes. 53 | Overlapped op{0}; 54 | OVERLAPPED* evt = &op.overlapped; 55 | op.coroutine = current().get(); 56 | 57 | // Now call ConnectEx to begin connecting the socket. The call will return 58 | // immediately, allowing this function to yield to the I/O manager. 59 | sin = addr.sockaddr(); 60 | if (!ConnectEx(sd_, (struct sockaddr*)&sin, sizeof(sin), 0, 0, 0, evt)) { 61 | if (ERROR_IO_PENDING != GetLastError()) { 62 | throw SystemError(); 63 | } 64 | } 65 | current()->block(); 66 | if (ERROR_SUCCESS != op.error) { 67 | throw SystemError(op.error); 68 | } 69 | 70 | // The following setsockopt() call is needed when calling ConectEx. From 71 | // the MSDN documentation: 72 | // 73 | // When the ConnectEx function returns TRUE, the socket s is in the default 74 | // state for a connected socket. The socket s does not enable previously 75 | // set properties or options until SO_UPDATE_CONNECT_CONTEXT is set on the 76 | // socket. Use the setsockopt function to set the SO_UPDATE_CONNECT_CONTEXT 77 | // option. 78 | if (::setsockopt(sd_, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0)) { 79 | throw SystemError(); 80 | } 81 | } 82 | 83 | SocketHandle Socket::acceptRaw() { 84 | // Waits for a client to connect, then returns a pointer to the established 85 | // connection. The code below is a bit tricky, because Windows expects the 86 | // call to accept() to happen before the I/O event can be triggered. For 87 | // Unix systems, the wait happens first, and then accept() is used to 88 | // receive the incoming socket afterwards. 89 | 90 | // Create a new socket for AcceptEx to use when a peer connects. 91 | SOCKET ls = sd_; 92 | SOCKET sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 93 | if (sd < 0) { 94 | throw SystemError(); 95 | } 96 | 97 | // Get a pointer to the AcceptEx() function. 98 | DWORD code = SIO_GET_EXTENSION_FUNCTION_POINTER; 99 | GUID guid = WSAID_ACCEPTEX; 100 | LPFN_ACCEPTEX AcceptEx = 0; 101 | DWORD bytes = 0; 102 | DWORD len = sizeof(AcceptEx); 103 | WSAIoctl(sd, code, &guid, sizeof(guid), &AcceptEx, len, &bytes, 0, 0); 104 | 105 | // Initialize the OVERLAPPED structure that contains the user I/O data used 106 | // to resume the coroutine when AcceptEx completes. 107 | Overlapped op{0}; 108 | op.coroutine = current().get(); 109 | OVERLAPPED* evt = &op.overlapped; 110 | 111 | // Now call ConnectEx to begin accepting peer connections. The call will 112 | // return immediately, allowing this function to yield to the I/O manager. 113 | char buffer[(sizeof(struct sockaddr_in)+16)*2]; // Windows BS, apparently 114 | DWORD socklen = sizeof(struct sockaddr_in)+16; // Ditto 115 | DWORD read = 0; 116 | if (!AcceptEx(ls, sd, buffer, 0, socklen, socklen, &read, evt)) { 117 | if (ERROR_IO_PENDING != GetLastError()) { 118 | throw SystemError(); 119 | } 120 | } 121 | current()->block(); 122 | if (ERROR_SUCCESS != op.error) { 123 | throw SystemError(op.error); 124 | } 125 | 126 | // The following setsockopt() call is needed when calling AcceptEx. From 127 | // the MSDN documentation: 128 | // 129 | // When the AcceptEx function returns, the socket sAcceptSocket is in the 130 | // default state for a connected socket. The socket sAcceptSocket does not 131 | // inherit the properties of the socket associated with sListenSocket 132 | // parameter until SO_UPDATE_ACCEPT_CONTEXT is set on the socket. Use the 133 | // setsockopt function to set the SO_UPDATE_ACCEPT_CONTEXT option, 134 | // specifying sAcceptSocket as the socket handle and sListenSocket as the 135 | // option value. 136 | char const* opt = (char const*)&ls; 137 | int const optlen = int(sizeof(ls)); 138 | if (::setsockopt(sd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, opt, optlen)) { 139 | throw SystemError(); 140 | } 141 | 142 | return sd; 143 | } 144 | 145 | bool isSocketCloseError(DWORD error) { 146 | // Returns true if the given error code should be converted to a socket close 147 | // exception. 148 | switch (error) { 149 | case ERROR_NETNAME_DELETED: 150 | case ERROR_CONNECTION_ABORTED: 151 | case WSAENETRESET: 152 | case WSAECONNABORTED: 153 | case WSAECONNRESET: 154 | return true; 155 | default: 156 | return false; 157 | } 158 | } 159 | 160 | ssize_t Socket::read(char* buf, size_t len, int flags) { 161 | // Read from the socket asynchronously. Returns the # of bytes read. 162 | WSABUF wsabuf = { ULONG(len), buf }; 163 | Overlapped op{0}; 164 | op.coroutine = current().get(); 165 | OVERLAPPED* evt = &op.overlapped; 166 | DWORD flg = flags; 167 | 168 | if (sd_ == -1) { 169 | throw SocketCloseException(); // Socket closed locally 170 | } 171 | 172 | if(WSARecv(sd_, &wsabuf, 1, NULL, &flg, evt, NULL)) { 173 | if (isSocketCloseError(GetLastError())) { 174 | throw SocketCloseException(); // Socket closed remotely 175 | } else if (ERROR_IO_PENDING != GetLastError()) { 176 | throw SystemError(); 177 | } 178 | } 179 | current()->block(); 180 | if (ERROR_SUCCESS != op.error) { 181 | if (isSocketCloseError(op.error)) { 182 | throw SocketCloseException(); // Socket closed remotely during read 183 | } else { 184 | throw SystemError(op.error); 185 | } 186 | } 187 | assert(op.bytes >= 0); 188 | return op.bytes; 189 | } 190 | 191 | ssize_t Socket::write(char const* buf, size_t len, int flags) { 192 | // Write to the socket asynchronously. Returns the # of bytes written. 193 | WSABUF wsabuf = { ULONG(len), LPSTR(buf) }; 194 | Overlapped op{0}; 195 | op.coroutine = current().get(); 196 | OVERLAPPED* evt = &op.overlapped; 197 | 198 | if (sd_ == -1) { 199 | throw SocketCloseException(); // Socket closed locally 200 | } 201 | 202 | if(WSASend(sd_, &wsabuf, 1, NULL, flags, evt, NULL)) { 203 | if (isSocketCloseError(GetLastError())) { 204 | throw SocketCloseException(); // Socket closed remotely 205 | } else if (ERROR_IO_PENDING != GetLastError()) { 206 | throw SystemError(); 207 | } 208 | } 209 | current()->block(); 210 | if (ERROR_SUCCESS != op.error) { 211 | if (isSocketCloseError(op.error)) { 212 | throw SocketCloseException(); // Socket closed remotely during write 213 | } else { 214 | throw SystemError(op.error); 215 | } 216 | } 217 | assert(op.bytes >= 0); 218 | return op.bytes; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/SslSocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/SslSocket.hpp" 25 | 26 | #ifdef __APPLE__ 27 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 28 | #endif 29 | 30 | namespace coro { 31 | 32 | SslError::SslError() : what_(ERR_error_string(ERR_get_error(), 0)) { 33 | } 34 | 35 | SslSocket::SslSocket(int type, int protocol) : Socket(type, protocol), context_(0), conn_(0), eof_(false) { 36 | // Intitialize the SSL client-side socket 37 | SSL_library_init(); 38 | SSL_load_error_strings(); 39 | ERR_load_BIO_strings(); 40 | } 41 | 42 | SslSocket::SslSocket(SocketHandle sd, SSL_CTX* context) : Socket(sd, ""), context_(0), eof_(false) { 43 | // Initialize SSL server-side socket 44 | SSL_library_init(); 45 | SSL_load_error_strings(); 46 | ERR_load_BIO_strings(); 47 | 48 | conn_ = SSL_new(context); 49 | assert(conn_); 50 | 51 | in_ = BIO_new(BIO_s_mem()); 52 | out_ = BIO_new(BIO_s_mem()); 53 | SSL_set_bio(conn_, in_, out_); 54 | SSL_set_accept_state(conn_); 55 | } 56 | 57 | SslSocket::~SslSocket() { 58 | SSL_free(conn_); // conn_ tracks the associated the SSL_CTX its refcount. 59 | SSL_CTX_free(context_); 60 | } 61 | 62 | Ptr SslSocket::accept() { 63 | return Ptr(new SslSocket(acceptRaw(), context_)); 64 | } 65 | 66 | void SslSocket::listen(int backlog) { 67 | if (!context_) { 68 | context_ = SSL_CTX_new(SSLv23_server_method()); 69 | assert(context_); 70 | } 71 | Socket::listen(backlog); 72 | } 73 | 74 | void SslSocket::connect(SocketAddr const& addr) { 75 | if (!context_) { 76 | context_ = SSL_CTX_new(SSLv23_client_method()); 77 | assert(context_); 78 | } 79 | if (!conn_) { 80 | conn_ = SSL_new(context_); 81 | assert(conn_); 82 | 83 | in_ = BIO_new(BIO_s_mem()); 84 | out_ = BIO_new(BIO_s_mem()); 85 | SSL_set_bio(conn_, in_, out_); 86 | SSL_set_connect_state(conn_); 87 | } 88 | 89 | Socket::connect(addr); 90 | } 91 | 92 | void SslSocket::useCertificateFile(std::string const& path) { 93 | if (!context_) { 94 | assert(!"not initialized yet"); 95 | } 96 | if (SSL_CTX_use_certificate_file(context_, path.c_str(), SSL_FILETYPE_PEM) <= 0) { 97 | throw SslError(); 98 | } 99 | } 100 | 101 | void SslSocket::usePrivateKeyFile(std::string const& path) { 102 | if (!context_) { 103 | assert(!"not initialized yet"); 104 | } 105 | if (SSL_CTX_use_PrivateKey_file(context_, path.c_str(), SSL_FILETYPE_PEM) <= 0) { 106 | throw SslError(); 107 | } 108 | if (!SSL_CTX_check_private_key(context_)) { 109 | throw SslError(); 110 | } 111 | } 112 | 113 | void SslSocket::writeAllRaw(char const* buf, size_t len, int flags) { 114 | // Write all data in 'buf'. Block until all data is written, or the connection 115 | // is closed. Throws a SocketCloseException if the connection was closed by 116 | // the other end. 117 | while(len > 0) { 118 | ssize_t bytes = Socket::write(buf, len, flags); 119 | if (bytes == 0) { 120 | throw SocketCloseException(); 121 | } else if (bytes > 0) { 122 | buf += bytes; 123 | len -= bytes; 124 | } else { 125 | assert(!"unexpected negative byte value"); 126 | } 127 | } 128 | } 129 | 130 | void SslSocket::writeFromBio(int flags) { 131 | char buf[4096]; 132 | ssize_t const pending = BIO_ctrl_pending(out_); 133 | if (!pending) { return; } 134 | ssize_t bytes = BIO_read(out_, buf, sizeof(buf)); 135 | if (bytes > 0) { 136 | writeAllRaw(buf, bytes, flags); 137 | } else if (bytes == -1 || bytes == 0) { 138 | // No data 139 | } else { 140 | assert(!"bio error"); 141 | } 142 | } 143 | 144 | void SslSocket::readToBio(int flags) { 145 | char buf[4096]; 146 | ssize_t const bytes = Socket::read(buf, sizeof(buf), flags); 147 | if (bytes > 0) { 148 | ssize_t const written = BIO_write(in_, buf, int(bytes)); 149 | assert(bytes==written); 150 | } else if (bytes == 0) { 151 | // No data 152 | eof_ = true; 153 | } else { 154 | assert(!"socket read error"); 155 | } 156 | } 157 | 158 | ssize_t SslSocket::write(char const* buf, size_t len, int /* unused */) { 159 | retry: 160 | ssize_t const bytes = SSL_write(conn_, buf, int(len)); 161 | writeFromBio(0); // Write data if available 162 | if (bytes < 0) { 163 | handleReturn(bytes); 164 | goto retry; 165 | } 166 | return bytes; 167 | } 168 | 169 | ssize_t SslSocket::read(char* buf, size_t len, int /* unused */) { 170 | retry: 171 | ssize_t const bytes = SSL_read(conn_, buf, int(len)); 172 | if (bytes < 0) { 173 | handleReturn(bytes); 174 | if (eof_) { 175 | return 0; 176 | } 177 | goto retry; 178 | } 179 | return bytes; 180 | } 181 | 182 | void SslSocket::handleReturn(ssize_t ret) { 183 | int const err = SSL_get_error(conn_, int(ret)); 184 | if (SSL_ERROR_WANT_WRITE == err) { 185 | writeFromBio(0); 186 | } else if (SSL_ERROR_WANT_READ == err) { 187 | readToBio(0); 188 | } else if (SSL_ERROR_SSL == err) { 189 | throw SslError(); 190 | } else { 191 | assert(!"unexpected error"); 192 | } 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/Time.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Time.hpp" 25 | 26 | namespace coro { 27 | 28 | Time Time::now() { 29 | #ifdef _WIN32 30 | LARGE_INTEGER count{0}; 31 | QueryPerformanceCounter(&count); 32 | LARGE_INTEGER freq{0}; 33 | QueryPerformanceFrequency(&freq); 34 | return Time::microsec(1000000 * count.QuadPart / freq.QuadPart); 35 | #else 36 | struct timeval ts{0}; 37 | gettimeofday(&ts, 0); 38 | return Time::sec(ts.tv_sec)+Time::microsec(ts.tv_usec); 39 | #endif 40 | } 41 | 42 | #ifndef _WIN32 43 | struct timespec Time::timespec() const { 44 | // Convert to a timespec struct for use with various system calls 45 | struct timespec out{0}; 46 | out.tv_sec = microsec_/1000000; 47 | out.tv_nsec = (microsec_%1000000)*1000; 48 | return out; 49 | } 50 | #endif 51 | 52 | } 53 | -------------------------------------------------------------------------------- /test/Basic.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "coro/Common.hpp" 24 | #include "coro/Coroutine.hpp" 25 | #include "coro/Hub.hpp" 26 | 27 | void foo() { 28 | try { 29 | std::cout << "hello" << std::endl; 30 | coro::yield(); 31 | } catch (coro::ExitException const&) { 32 | std::cout << "exception" << std::endl; 33 | throw; 34 | } 35 | std::cout << "hello" << std::endl; 36 | 37 | coro::sleep(coro::Time::sec(.4)); 38 | std::cout << "one" << std::endl; 39 | coro::sleep(coro::Time::sec(.4)); 40 | std::cout << "two" << std::endl; 41 | } 42 | 43 | void bar() { 44 | for (auto i = 0; i < 2; ++i) { 45 | coro::sleep(coro::Time::millisec(1000)); 46 | std::cout << "barrrrrr" << std::endl; 47 | } 48 | } 49 | 50 | void baz() { 51 | for (auto i = 0; i < 20; ++i) { 52 | coro::sleep(coro::Time::millisec(100)); 53 | std::cout << "baz" << std::endl; 54 | } 55 | } 56 | 57 | int main() { 58 | auto cbaz = coro::start(baz); 59 | auto cbar = coro::start(bar); 60 | auto cfoo = coro::start(foo); 61 | coro::run(); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /test/Event.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | int main() { 6 | auto event = coro::Event(); 7 | auto trigger = false; 8 | 9 | auto notifier = coro::start([&]() { 10 | printf("notified\n"); 11 | trigger = true; 12 | event.notifyAll(); 13 | }); 14 | 15 | auto waiter = coro::start([&]() { 16 | printf("waiting\n"); 17 | event.wait([&]() { return trigger; }); 18 | printf("done\n"); 19 | }); 20 | 21 | coro::run(); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test/Join.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | int main() { 27 | auto counter = 0; 28 | auto one = coro::start([&]{ 29 | coro::yield(); 30 | assert(counter==0); 31 | counter++; 32 | }); 33 | auto two = coro::start([&]{ 34 | one->join(); 35 | assert(counter==1); 36 | }); 37 | 38 | coro::run(); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/Selector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | #include 25 | #include 26 | 27 | using namespace coro; 28 | 29 | Ptr e1(new Event); 30 | Ptr e2(new Event); 31 | Ptr e3(new Event); 32 | 33 | void publisher() { 34 | e1->notifyAll(); 35 | e2->notifyAll(); 36 | 37 | } 38 | 39 | void consumer() { 40 | int count = 2; 41 | while (count > 0) { 42 | coro::Selector() 43 | .on(e1, [&]() { count--; std::cout << "e1" << std::endl; }) 44 | .on(e2, [&]() { count--; std::cout << "e2" << std::endl; }) 45 | .on(e3, [&]() { std::cout << "e2" << std::endl; }); 46 | } 47 | } 48 | 49 | int main() { 50 | auto b = coro::start(consumer); 51 | auto a = coro::start(publisher); 52 | 53 | coro::run(); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /test/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void server() { 6 | try { 7 | char buf[1024]; 8 | auto ls = std::make_shared(); 9 | ls->setsockopt(SOL_SOCKET, SO_REUSEADDR, 1); 10 | ls->bind(coro::SocketAddr("127.0.0.1", 9090)); 11 | ls->listen(10); 12 | 13 | auto sd = ls->accept(); 14 | ssize_t len = 0; 15 | while ((len = sd->read(buf, sizeof(buf))) > 0) { 16 | printf("%.*s", (int)len, buf); 17 | fflush(stdout); 18 | } 19 | std::cout << "exit" << std::endl; 20 | } catch (coro::SystemError const& ex) { 21 | std::cout << ex.what() << std::endl; 22 | exit(1); 23 | } 24 | } 25 | 26 | void client() { 27 | auto sd = std::make_shared(); 28 | auto msg = "hello world\n"; 29 | try { 30 | sd->connect(coro::SocketAddr("127.0.0.1", 9090)); 31 | for (auto i = 0; i < 1000; ++i) { 32 | sd->writeAll(msg, strlen(msg)); 33 | } 34 | } catch (coro::SystemError const& ex) { 35 | std::cout << ex.what() << std::endl; 36 | } 37 | } 38 | 39 | int main() { 40 | auto cserver = coro::start(server); 41 | auto cclient = coro::start(client); 42 | coro::run(); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /test/SocketDisconnect.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | auto msg = std::string("hello world\n"); 27 | 28 | coro::Ptr newServer() { 29 | auto ls = std::make_shared(); 30 | ls->setsockopt(SOL_SOCKET, SO_REUSEADDR, 1); 31 | ls->bind(coro::SocketAddr("127.0.0.1", 9090)); 32 | ls->listen(10); 33 | 34 | auto sd = ls->accept(); 35 | return sd; 36 | } 37 | 38 | coro::Ptr newClient() { 39 | auto sd = std::make_shared(); 40 | sd->connect(coro::SocketAddr("127.0.0.1", 9090)); 41 | return sd; 42 | } 43 | 44 | void testReadDisconnect() { 45 | // Client disconnects while the server is reading from its socket. 46 | auto server = coro::start([]{ 47 | char buf[1024]; 48 | auto sd = newServer(); 49 | sd->readAll(buf, msg.length()); 50 | try { 51 | sd->readAll(buf, msg.length()); 52 | assert(!"failed"); 53 | } catch (coro::SocketCloseException const&) { 54 | } 55 | }); 56 | 57 | auto client = coro::start([]{ 58 | auto sd = newClient(); 59 | sd->writeAll(msg.c_str(), msg.length()); 60 | }); 61 | coro::run(); 62 | } 63 | 64 | void testWriteDisconnect() { 65 | // Server disconnects while the client is reading from its socket. 66 | auto server = coro::start([]{ 67 | char buf[1024]; 68 | auto sd = newServer(); 69 | sd->readAll(buf, msg.length()); 70 | }); 71 | 72 | auto client = coro::start([]{ 73 | auto sd = newClient(); 74 | sd->writeAll(msg.c_str(), msg.length()); 75 | try { 76 | for (;;) { 77 | sd->writeAll(msg.c_str(), msg.length()); 78 | } 79 | } catch (coro::SocketCloseException const&) { 80 | } 81 | }); 82 | coro::run(); 83 | } 84 | 85 | int main() { 86 | testReadDisconnect(); 87 | testWriteDisconnect(); 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /test/Ssl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Matt Fichman 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, APEXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | void testHttpGet() { 28 | auto sd = std::make_shared(); 29 | sd->connect(coro::SocketAddr("www.google.com", 443)); 30 | char const buf[] = "GET / HTTP/1.1\nHost: www.google.com\nAccept-Encoding:identity\nConnection: close\n\n"; 31 | sd->writeAll(buf, sizeof(buf)-1); 32 | 33 | char buf2[4096]; 34 | std::stringstream ss; 35 | while (ssize_t len = sd->read(buf2, sizeof(buf2))) { 36 | std::cerr << std::string(buf2, len); 37 | } 38 | } 39 | 40 | void testClientServer() { 41 | 42 | auto c = coro::start([&]{ 43 | auto ls = std::make_shared(); 44 | ls->bind(coro::SocketAddr("127.0.0.1", 8000)); 45 | ls->listen(10); 46 | ls->useCertificateFile("test/test.crt"); 47 | ls->usePrivateKeyFile("test/test.key"); 48 | 49 | auto sd = ls->accept(); 50 | 51 | ls.reset(); 52 | char const buf[] = "foobar\n"; 53 | sd->writeAll(buf, sizeof(buf)-1); 54 | 55 | char buf2[4096]; 56 | while (sd->read(buf2, sizeof(buf2))) { 57 | } 58 | }); 59 | 60 | auto s = coro::start([&]{ 61 | auto sd = std::make_shared(); 62 | sd->connect(coro::SocketAddr("127.0.0.1", 8000)); 63 | 64 | char const buf[] = "GET / HTTP/1.1\nHost: 127.0.0.1\nConnection: close\n\n"; 65 | sd->writeAll(buf, sizeof(buf)-1); 66 | sd->shutdown(SHUT_WR); 67 | 68 | char buf2[4096]; 69 | std::stringstream ss; 70 | while (ssize_t len = sd->read(buf2, sizeof(buf2))) { 71 | std::cerr << std::string(buf2, len); 72 | } 73 | }); 74 | 75 | s->join(); 76 | } 77 | 78 | void test() { 79 | 80 | //testHttpGet(); 81 | testClientServer(); 82 | } 83 | 84 | int main() { 85 | 86 | auto c = coro::start(test); 87 | coro::run(); 88 | 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /test/Stack.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | 6 | using namespace coro; 7 | 8 | void recurse(int n) { 9 | char data[10000]; 10 | data[0] = char(n); 11 | if (n == 0) { return; } 12 | else { recurse(n-1); } 13 | } 14 | 15 | 16 | int main() { 17 | // Check that coroutines do not use too much stack mem 18 | std::vector> coros; 19 | 20 | for (auto i = 0; i < 10; ++i) { 21 | for (auto j = 0; j < 1000; ++j) { 22 | coros.push_back(coro::start(std::bind(recurse, 100))); 23 | } 24 | hub()->quiesce(); 25 | coros.clear(); 26 | } 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /test/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICMTCCAZoCCQCrkNLIl7s7RjANBgkqhkiG9w0BAQUFADBdMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMRYwFAYDVQQDDA0xOTIuMTY4LjEuMTU0MB4XDTE0MDMxNjIwMjQy 5 | N1oXDTE1MDMxNjIwMjQyN1owXTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt 6 | U3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEWMBQGA1UE 7 | AwwNMTkyLjE2OC4xLjE1NDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4ozP 8 | TtRmsdABsJjowH3ARQ8fDvc/QcQiO6vkfozNlLoWmk+JwJc6ORQ+r5CKoazPoPb9 9 | I6P91vVTsnn5HbmPsKYuump2yF0J8T9UWkHniip81UPR5VxIaXJ4PN9f+gUie2tb 10 | xJWX/NS3wCJEaaSs1uHTHhOn7r4sYOV5AzdpMUsCAwEAATANBgkqhkiG9w0BAQUF 11 | AAOBgQAH9QmpSIZSRyshftRTAMZ5LKMc0hCF6HLM716UXzdaB6xUeYWZussq45YE 12 | 1PH23Uf1xlTS40PpQYJ9A7U8YI1FH0X3bexj1Y3bkgNdOo+rgn89MOFWcsTOXH24 13 | H2qgf0P2nqbSbxwkukXsw4h8ugPVGUC0L4X4uFZapfw2b+YjRg== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /test/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXwIBAAKBgQDijM9O1Gax0AGwmOjAfcBFDx8O9z9BxCI7q+R+jM2UuhaaT4nA 3 | lzo5FD6vkIqhrM+g9v0jo/3W9VOyefkduY+wpi66anbIXQnxP1RaQeeKKnzVQ9Hl 4 | XEhpcng831/6BSJ7a1vElZf81LfAIkRppKzW4dMeE6fuvixg5XkDN2kxSwIDAQAB 5 | AoGBAM/dqwkFUgSseiBy7HxR5Wx352Nv8JaDQ3XEzi5fOvHPnJWRjS8skrt0m9+y 6 | m8CkMw3aEYH2Ih5tkpIb4Dsp22/byRhaxMlZa6ACcYZr0Dja1EvMhSzkDl805NNJ 7 | Ceg6wuX4EAP4VbFkvrp6uwzFAs9mXd2xNPdUfoQBsCzwTCUBAkEA9TpzNxV+EwUp 8 | CvYzXWKAAKN8peL9j6KPt8zGAxEylbCBr8Ffb2OdiO/FRxZLk4Z6zbeiTzPKgFri 9 | Msh5QGO+6wJBAOyAU2CNvGR1/HD8NprbbQ5b5dqJN9TXj9e0VfPE1kyKAChCDCcz 10 | xNStrE+v+/lptVuOduJLZ8pEcJ0uUozFfyECQQDn1SM5LytqRdWgVFMI9Ob4M5bS 11 | qP53vQJLi96i5a5Bz4U7J//WKYG0BKEnbE90n40XUIS0hczqfYEc62Od2O6TAkEA 12 | hcUL5AgncDdgQ1QVWsAUTlPijhqhLs2CugJxv4EtqXtBHYRGNYJNqfJWDXjAMFH/ 13 | V7scOWFnYnwVqoJCBQfMoQJBAIAB7gixarblW0yxWu3yFpjA5+4u93RSlCSIrDs7 14 | +wsXS1V3agzAHh7SVKBYiD+KpZ5pMd/Kd4XrjO34MAgHSf0= 15 | -----END RSA PRIVATE KEY----- 16 | --------------------------------------------------------------------------------