├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── dub.json ├── examples ├── buffered_tcp_client │ ├── dub.json │ └── source │ │ └── app.d ├── buffered_tcp_listener │ ├── dub.json │ └── source │ │ └── app.d ├── dirwatcher │ ├── .gitignore │ ├── dub.json │ ├── source │ │ └── app.d │ └── temp │ │ └── readme ├── netcat │ ├── dub.json │ └── source │ │ └── app.d ├── netlink │ ├── README │ ├── dub.json │ ├── kmodule │ │ ├── Makefile │ │ └── netlinkKernel.c │ └── source │ │ └── app.d ├── tcp_client │ ├── dub.json │ ├── source │ │ └── app.d │ └── ws2_32.lib ├── tcp_listener │ ├── dub.json │ └── source │ │ └── app.d ├── uds_client │ ├── dub.json │ └── source │ │ └── app.d └── uds_listener │ ├── dub.json │ └── source │ └── app.d ├── logo.png ├── logo.webp ├── source └── libasync │ ├── bufferedtcp.d │ ├── dns.d │ ├── event.d │ ├── events.d │ ├── file.d │ ├── internals │ ├── epoll.d │ ├── freelist.d │ ├── kqueue.d │ ├── logging.d │ ├── path.d │ ├── queue.d │ ├── socket_compat.d │ ├── validator.d │ └── win32.d │ ├── notifier.d │ ├── package.d │ ├── posix.d │ ├── posix2.d │ ├── signal.d │ ├── socket.d │ ├── tcp.d │ ├── test.d │ ├── threads.d │ ├── timer.d │ ├── types.d │ ├── udp.d │ ├── uds.d │ ├── watcher.d │ └── windows.d ├── tests ├── http_request_benchmark │ ├── dub.json │ ├── plot_results.gnu │ └── source │ │ └── app.d ├── tcp_test │ ├── dub.json │ └── source │ │ └── app.d └── transceiver │ ├── dub.json │ └── source │ └── app.d └── ws2_32_ex.lib /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | # allow this workflow to be triggered manually 12 | 13 | jobs: 14 | builder: 15 | name: 'Build and test on ${{ matrix.arch }}-${{ matrix.os }}/${{ matrix.dc }}' 16 | runs-on: ${{ matrix.os }} 17 | continue-on-error: ${{ contains(matrix.dc, 'beta') }} 18 | env: 19 | ARCH: ${{ matrix.arch }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | dc: [ldc-latest, ldc-beta, dmd-latest, dmd-beta] 24 | os: [ubuntu-latest, windows-latest] 25 | arch: [x86, x86_64] 26 | include: 27 | - dc: ldc-latest 28 | os: macos-latest 29 | arch: x86_64 30 | - dc: dmd-latest 31 | os: macos-latest 32 | arch: x86_64 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: dlang-community/setup-dlang@v1 36 | with: 37 | compiler: ${{ matrix.dc }} 38 | - name: Install multi-lib for 32-bit systems 39 | if: matrix.arch == 'x86' && matrix.os == 'ubuntu-latest' 40 | run: sudo apt-get update && sudo apt-get install gcc-multilib --fix-missing 41 | - id: build 42 | name: Test library 43 | run: | 44 | dub test --arch=$ARCH 45 | shell: bash -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .dub 3 | *.pdb 4 | *.exe 5 | *.*~ 6 | dub.selections.json 7 | *.log 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Etienne Cimon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/etcimon/libasync/actions/workflows/ci.yml/badge.svg)](https://github.com/etcimon/libasync/actions/workflows/ci.yml) 2 | 3 | About 4 | ----- 5 | 6 | The libasync asynchronous library is written completely in D, features a cross-platform event loop and enhanced connectivity and concurrency facilities for extremely lightweight asynchronous tasks. It embeds naturally to D projects (DMD >= 2.076.0, LDC >= 1.18.0), allows you to target a wide range of architectures through LDC, compiles statically with your project and has an open source license (MIT). 7 | 8 | A fully functional, tested vibe.d driver is available in [the latest version of vibe.d](https://github.com/rejectedsoftware/vibe.d/), you can enable it by appending `"subConfigurations": { "vibe-d": "libasync"}` in your project's dub.json configuration file. 9 | 10 | ### Features 11 | 12 | The following capabilities have been tested in a production environment: 13 | 14 | (*) _Unit tests confirmed on Mac, Linux, Windows_ - Platforms used were Mac OS X (10.8+, 10.9+), Linux (Fedora 20+) and Windows 7+ 32/64 bit, although it should be compatible to 99% of Desktop OS users. 15 | 16 | (*) _Compiles with DMD & LDC_ (DMD 2.076.0+, LDC 1.18.0+) 17 | 18 | - **Multi-threading** support - EventLoop can be launched and run from an unlimited number of threads! 19 | 20 | - **Asynchronous TCP connection** - handles multiple requests at a time in each individual thread 21 | 22 | - **Buffered TCP connection** - Allows callbacks to be attached to a byte sized future 23 | 24 | - **Asynchronous TCP listener** - delivers a new connection to the delegate of your choice 25 | 26 | - **File Operations** - executes file read/write/append commands in a thread pool, notifies of completion in a handler 27 | 28 | - **DNS resolver** - runs blocking DNS resolve operations in a thread pool, savings are the duration of a ping. 29 | 30 | - **File/Folder Watcher** - watches directories for file changes (CREATE, DELETE, MODIFY, RENAME/MOVE) 31 | 32 | - **UDP connection** - receives or sends packets from/to multiple peers 33 | 34 | - **Timer** - sets a periodic or one-shot/periodic timer with high-precision (μs) to call a select delegate 35 | 36 | - **Signal** - Wakes up the event loop in a foreign thread and passes a message to its delegate 37 | 38 | - **Notifier** - Thread-local and lock-less adaptation of **Signal** which queues a message intended for a local delegate 39 | 40 | ### Limitations 41 | 42 | Some or all of these limitations are possibly being implemented currently and may be available in a future release. 43 | 44 | - **One EventLoop per thread** - There is a hard limit of one event loop per thread 45 | - **Manual error management** - The entire library is `nothrow` and error management must be built on top of it. 46 | - **No embedded HTTP or TLS handlers** - The support fort HTTP, TLS (and other protocols) is only available through vibe.d with Tasks as of yet. 47 | 48 | Installation Instructions 49 | ------------------------- 50 | 51 | - Download and install DMD from [dlang.org](http://dlang.org/download.html) 52 | - Use Git to clone this repository 53 | - Run `dub test` to test the library on your operating system (submit any issue with a log report by uncommenting `enum LOG = true` in `types.d`) 54 | - Add the library to your project by including it in the dependencies, using `import libasync` 55 | - The recommended editor is Visual Studio Code with the Code-D extension 56 | - On another note, you can also try the vibe.d libasync built-in driver by adding `"subConfigurations": { "vibe-d": "libasync" }` to your vibe.d dub.json. 57 | 58 | Tutorial 59 | -------- 60 | 61 | There are many examples available in the `examples/` older. They must be tested by starting the server before the client. 62 | 63 | All other usage examples are available in `source/libasync/test.d`. 64 | 65 | Documentation has been written throughout the code. 66 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # Please use dub to build: code.dlang.org 2 | # This build method is only used to diagnose dmd ICE through gdb... 3 | dmd -lib -m64 -g source/event/internals/epoll.d source/event/internals/kqueue.d source/event/internals/path.d source/event/internals/validator.d source/event/internals/hashmap.d source/event/internals/memory.d source/event/internals/socket_compat.d source/event/internals/win32.d source/event/file.d source/event/tcp.d source/event/timer.d source/event/watcher.d source/event/dns.d source/event/types.d source/event/windows.d source/event/events.d source/event/notifier.d source/event/signal.d source/event/threads.d source/event/udp.d source/event/d.d source/event/posix2.d source/event/posix.d source/event/test.d 4 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libasync", 3 | "description": "A cross-platform event loop library of asynchronous objects.", 4 | "license": "MIT", 5 | "authors": ["Etienne Cimon", "Sönke Ludwig"], 6 | "targetName": "async", 7 | "targetType": "staticLibrary", 8 | "targetPath": "build", 9 | "workingDirectory": "build", 10 | "libs-windows": ["advapi32", "user32", "ws2_32"], 11 | "libs-linux": ["rt", "resolv"], 12 | "dependencies": { 13 | "memutils": { "version": "~>1.0.1" } 14 | }, 15 | "configurations": [ 16 | { 17 | "name": "regular", 18 | "sourceFiles-windows-x86-dmd": ["ws2_32_ex.lib"] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/buffered_tcp_client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffered_tcp_client", 3 | "description": "A simple TCP client", 4 | "dependencies": { 5 | "libasync": {"version": "~master", "path": "../../"}, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/buffered_tcp_client/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | 3 | import std.stdio : writeln; 4 | 5 | void main() 6 | { 7 | auto evl = new EventLoop; 8 | auto tcpClient = new TCPClient(evl, "localhost", 8081); 9 | 10 | while (true) 11 | { 12 | tcpClient.write(cast(ubyte[])("Some message"), (conn) 13 | { 14 | conn.read(10, (conn, msg) 15 | { 16 | auto res = cast(string)msg; 17 | writeln("Received: ", res); 18 | }); 19 | 20 | }); 21 | evl.loop(); 22 | } 23 | /*destroyAsyncThreads();*/ 24 | } 25 | 26 | class TCPClient 27 | { 28 | alias Buffered = BufferedTCPConnection!4092; 29 | 30 | private Buffered conn; 31 | 32 | this(EventLoop evl, string host, size_t port) 33 | { 34 | this.conn = new Buffered(evl); 35 | 36 | if (!conn.host(host, port).run(&conn.handle)) 37 | writeln(conn.status); 38 | } 39 | 40 | void read(in size_t len, in Buffered.OnRead onReadCb) 41 | { 42 | conn.read(len, onReadCb); 43 | } 44 | 45 | void write(in ubyte[] msg, in Buffered.OnEvent cb) 46 | { 47 | conn.write(msg, msg.length, cb); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/buffered_tcp_listener/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffered_tcp_listener", 3 | "description": "A simple TCP listener", 4 | "dependencies": { 5 | "libasync": {"version": "~master", "path": "../../"}, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/buffered_tcp_listener/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | 3 | import std.functional : toDelegate; 4 | import std.stdio : writeln; 5 | 6 | void main() 7 | { 8 | auto evl = new EventLoop; 9 | auto listener = new TCPListener(evl); 10 | listener.connect( 11 | "localhost", 12 | 8081, 13 | (conn) 14 | { 15 | conn.read(12, toDelegate(&onRead)); 16 | }, 17 | (conn) 18 | { 19 | writeln("Connection closed"); 20 | }, 21 | (conn) 22 | { 23 | writeln("Error during TCP Event"); 24 | }); 25 | 26 | while (true) 27 | evl.loop(); 28 | /*destroyAsyncThreads();*/ 29 | } 30 | 31 | alias Buffered = BufferedTCPConnection!4092; 32 | 33 | void onRead(Buffered conn, in ubyte[] msg) 34 | { 35 | auto res = cast(string)msg; 36 | writeln("Received: ", res); 37 | 38 | auto buf = cast(ubyte[])("Some reply"); 39 | conn.write(buf, buf.length, (conn2) 40 | { 41 | conn.read(12, toDelegate(&onRead)); 42 | }); 43 | } 44 | 45 | class TCPListener 46 | { 47 | private 48 | { 49 | AsyncTCPListener listener; 50 | 51 | Buffered.OnEvent onConnectCb; 52 | Buffered.OnEvent onCloseCb; 53 | Buffered.OnEvent onErrorCb; 54 | } 55 | 56 | this(scope EventLoop evl) 57 | { 58 | listener = new AsyncTCPListener(evl); 59 | } 60 | 61 | void connect( 62 | in string host, 63 | in size_t port, 64 | in Buffered.OnEvent onConnectCb, 65 | in Buffered.OnEvent onCloseCb, 66 | in Buffered.OnEvent onErrorCb) 67 | { 68 | this.onConnectCb = onConnectCb; 69 | this.onCloseCb = onCloseCb; 70 | this.onErrorCb = onErrorCb; 71 | 72 | auto ok = listener.host(host, port).run((AsyncTCPConnection conn) 73 | { 74 | auto bufConn = new Buffered( 75 | conn, onConnectCb, onCloseCb, onErrorCb); 76 | return &bufConn.handle; 77 | }); 78 | 79 | if (ok) 80 | writeln("Listening to ", listener.local); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/dirwatcher/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | -------------------------------------------------------------------------------- /examples/dirwatcher/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dirwatcher", 3 | "description": "A directory watcher with libasync", 4 | "copyright": "Copyright © 2015, Etienne", 5 | "authors": ["Etienne"], 6 | "dependencies": { 7 | "libasync": { "path": "../../" } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/dirwatcher/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | import libasync.watcher; 3 | import std.stdio : writeln; 4 | import libasync.threads; 5 | 6 | // You must deinitialize the worker thread manually when the application closes 7 | version(Libasync_Threading) shared static ~this() { destroyAsyncThreads(); } 8 | 9 | // This is our main callback, it gets called when there are changes. 10 | void onDirectoryEvent(DWChangeInfo change) { 11 | writeln("Main Callback got directory event: ", change); 12 | } 13 | 14 | // Program entry point 15 | void main() 16 | { 17 | // Each thread has one event loop. It does not run unless we run it with `loop` in a loop. 18 | auto ev_loop = getThreadEventLoop(); 19 | 20 | // We can initialize an async object by attaching it to this event loop 21 | auto watcher = new AsyncDirectoryWatcher(ev_loop); 22 | 23 | DWChangeInfo[8] change_buf; 24 | 25 | // By `run`ing the async object, we register it in the operating system 26 | watcher.run( 27 | { // This scope is executed at each directory event 28 | writeln("Enter Handler (directory event captured)"); 29 | // We will have to drain the internal event buffer first 30 | DWChangeInfo[] changes = change_buf[]; 31 | uint cnt; 32 | // Capture the directory events up to 8 at a time 33 | do { 34 | cnt = watcher.readChanges(changes); 35 | // Forward them to our custom handler 36 | foreach (i; 0 .. cnt) { 37 | onDirectoryEvent(changes[i]); 38 | } 39 | } while (cnt > 0); 40 | }); 41 | 42 | // This makes our watcher look for file changes in the "temp" sub folder, it must be used after the watcher is registered (aka `run`). It is relative to the current directory which is usually this running executable's folder 43 | watcher.watchDir("temp"); 44 | writeln("Event loop now running. Try saving a file in the temp/ folder!"); 45 | 46 | while (ev_loop.loop()) 47 | continue; 48 | writeln("Event loop exited"); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /examples/dirwatcher/temp/readme: -------------------------------------------------------------------------------- 1 | This folder will be monitored for changes in the example 2 | -------------------------------------------------------------------------------- /examples/netcat/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netcat", 3 | "description": "A rudimentary netcat clone", 4 | "copyright": "Copyright © 2016, Moritz Maxeiner", 5 | "authors": ["Moritz Maxeiner"], 6 | "dependencies": { 7 | "libasync": { "path": "../../" }, 8 | "docopt": "~>0.6.1-b.5" 9 | }, 10 | 11 | "targetName": "ncat", 12 | "buildTypes": { 13 | "release": { 14 | "versions": ["StdLoggerDisableTrace", 15 | "StdLoggerDisableInfo", 16 | "StdLoggerDisableWarning"] 17 | }, 18 | "informative": { 19 | "versions": ["StdLoggerDisableTrace"], 20 | "buildSettings": ["releaseMode", "optimize", "inline"] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /examples/netcat/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | immutable HELP = "Minimalistic netcat-alike 3 | 4 | Usage: 5 | ncat -h | --help 6 | ncat -l [-46uk]
7 | ncat -l [-46uk] 8 | ncat -lU [-uk]
9 | ncat [-46u]
10 | ncat [-46u] 11 | ncat -U [-u]
12 | 13 | Options: 14 | -h --help Show this screen. 15 | -l Start in listen mode, allowing inbound connects 16 | -k Keep listening for another connection after the 17 | current one has been closed. 18 | -4 Operate in the IPv4 address family. 19 | -6 Operate in the IPv6 address family [default]. 20 | -U Operate in the UNIX domain socket address family (Posix platforms only). 21 | -u Use datagram socket (e.g. UDP) 22 | "; 23 | 24 | int main(string[] args) 25 | { 26 | auto arguments = docopt.docopt(HELP, args[1..$], true); 27 | 28 | auto af = getAddressFamily(arguments); 29 | 30 | NetworkAddress address = void; 31 | try address = getAddress(arguments, af); catch (Exception e) { 32 | stderr.writeln("ncat: ", e.msg); 33 | return 1; 34 | } 35 | 36 | auto type = getType(arguments); 37 | auto mode = getMode(arguments); 38 | auto keepListening = keepListening(arguments); 39 | 40 | g_running = true; 41 | g_eventLoop = getThreadEventLoop(); 42 | if (mode == Mode.Listen) { 43 | if (!listen(address, af, type, keepListening)) return 1; 44 | } else { 45 | if (!connect(address, af, type)) return 1; 46 | } 47 | while (g_running) g_eventLoop.loop(-1.seconds); 48 | return g_status; 49 | } 50 | 51 | bool connect(ref NetworkAddress remote, int af, SocketType type) 52 | { 53 | g_client = ThreadMem.alloc!AsyncSocket(g_eventLoop, af, type); 54 | if (g_client.connectionOriented) transceive(g_client, { g_running = false; }); 55 | 56 | if(!g_client.run()) { 57 | stderr.writeln("ncat: ", g_client.error); 58 | ThreadMem.free(g_client); 59 | g_client = null; 60 | return false; 61 | } 62 | 63 | if (!g_client.connect(remote)) { 64 | stderr.writeln("ncat: ", g_client.error); 65 | ThreadMem.free(g_client); 66 | g_client = null; 67 | return false; 68 | } 69 | if (!g_client.connectionOriented) { 70 | transceive(g_client); 71 | version (Posix) if (af == AF_UNIX) { 72 | import std.path: buildPath; 73 | import std.file: tempDir; 74 | import std.conv : to; 75 | import std.random: randomCover; 76 | import std.range: take; 77 | import std.ascii: hexDigits; 78 | import std.array: array; 79 | 80 | auto localName = buildPath(tempDir, "ncat." ~ hexDigits.array.randomCover.take(8).array.to!string); 81 | 82 | NetworkAddress local; 83 | local.sockAddr.sa_family = AF_UNIX; 84 | (cast(sockaddr_un*) local.sockAddr).sun_path[0 .. localName.length] = cast(byte[]) localName[]; 85 | if (!g_client.bind(local)) { 86 | stderr.writeln("ncat: ", g_client.error); 87 | ThreadMem.free(g_client); 88 | g_client = null; 89 | return false; 90 | } 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | bool listen(ref NetworkAddress local, int af, SocketType type, bool keepListening = false) 98 | { 99 | g_listener = ThreadMem.alloc!AsyncSocket(g_eventLoop, af, type); 100 | setupError(g_listener); 101 | 102 | if(!g_listener.run()) { 103 | stderr.writeln("ncat: ", g_listener.error); 104 | ThreadMem.free(g_listener); 105 | g_listener = null; 106 | return false; 107 | } 108 | 109 | try switch (af) { 110 | case AF_INET, AF_INET6: 111 | int yes = 1; 112 | g_listener.setOption(SOL_SOCKET, SO_REUSEADDR, (cast(ubyte*) &yes)[0..yes.sizeof]); 113 | version (Posix) g_listener.setOption(SOL_SOCKET, SO_REUSEPORT, (cast(ubyte*) &yes)[0..yes.sizeof]); 114 | break; 115 | version (Posix) { 116 | case AF_UNIX: 117 | auto path = to!string(cast(const(char)*) local.sockAddrUnix.sun_path.ptr).ifThrown(""); 118 | if (path.exists && !path.isDir) path.remove.collectException; 119 | break; 120 | } 121 | default: assert(false); 122 | } catch (Exception e) { 123 | stderr.writeln("ncat: ", e.msg); 124 | return false; 125 | } 126 | 127 | if (!g_listener.bind(local)) { 128 | stderr.writeln("ncat: ", g_listener.error); 129 | ThreadMem.free(g_listener); 130 | g_listener = null; 131 | return false; 132 | } 133 | 134 | if (!g_listener.connectionOriented) { 135 | NetworkAddress from, to; 136 | mixin StdInTransmitter transmitter; 137 | 138 | transmitter.start(g_listener, to); 139 | g_listener.receive({ 140 | auto buffer = new ubyte[4096]; 141 | g_listener.receiveContinuously = true; 142 | g_listener.receiveFrom(buffer, from, (data) { 143 | if (to == NetworkAddress.init) { 144 | to = from; 145 | transmitter.reader.loop(); 146 | } 147 | if (from == to) { 148 | stdout.rawWrite(data).collectException(); 149 | stdout.flush().collectException(); 150 | } 151 | }); 152 | }); 153 | } else if (!g_listener.listen()) { 154 | stderr.writeln("ncat: ", g_listener.error); 155 | ThreadMem.free(g_listener); 156 | g_listener = null; 157 | return false; 158 | } else { 159 | g_onAccept = (handle, family, type, protocol) { 160 | if (!keepListening) { 161 | g_listener.kill(); 162 | assumeWontThrow(ThreadMem.free(g_listener)); 163 | g_listener = null; 164 | } 165 | g_client = assumeWontThrow(ThreadMem.alloc!AsyncSocket(g_eventLoop, family, type, protocol, handle)); 166 | transceive(g_client, { 167 | assumeWontThrow(ThreadMem.free(g_client)); 168 | g_client = null; 169 | if (g_listener) g_listener.accept(g_onAccept); 170 | else g_running = false; 171 | }); 172 | return g_client; 173 | }; 174 | 175 | g_listener.accept(g_onAccept); 176 | } 177 | 178 | return true; 179 | } 180 | 181 | AsyncAcceptRequest.OnComplete g_onAccept = void; 182 | 183 | void setupError(ref AsyncSocket socket, bool exit = true) nothrow 184 | { 185 | socket.onError = { 186 | stderr.writeln("ncat: ", socket.error).collectException(); 187 | if (exit) { 188 | g_running = false; 189 | g_status = 1; 190 | } 191 | }; 192 | } 193 | 194 | mixin template StdInTransmitter() 195 | { 196 | auto readBuffer = new shared ubyte[4096]; 197 | shared size_t readCount; 198 | 199 | auto onRead = new shared AsyncSignal(g_eventLoop); 200 | auto reader = new StdInReader(readBuffer, readCount, onRead); 201 | 202 | void start(ref AsyncSocket socket) nothrow { 203 | onRead.run({ 204 | if (readCount == 0) { 205 | if (socket.connectionOriented) { 206 | socket.kill(); 207 | assumeWontThrow(ThreadMem.free(socket)); 208 | socket = null; 209 | if (g_listener) g_listener.accept(g_onAccept); 210 | else g_running = false; 211 | } 212 | reader.stop(); 213 | } else { 214 | socket.send(cast(ubyte[]) readBuffer[0..readCount], { reader.loop(); }); 215 | } 216 | }); 217 | reader.start(); 218 | reader.loop(); 219 | } 220 | 221 | void start(ref AsyncSocket socket, ref NetworkAddress to) nothrow { 222 | onRead.run({ 223 | if (readCount == 0) { 224 | to = NetworkAddress.init; 225 | } else { 226 | socket.sendTo(cast(ubyte[]) readBuffer[0..readCount], to, { reader.loop(); }); 227 | } 228 | }); 229 | reader.start(); 230 | } 231 | } 232 | 233 | void transceive(ref AsyncSocket socket, AsyncSocket.OnClose onClose = null) nothrow 234 | { 235 | setupError(socket); 236 | 237 | if (socket.connectionOriented) { 238 | socket.onConnect = { 239 | socket.receive({ 240 | auto buffer = new ubyte[4096]; 241 | socket.receiveContinuously = true; 242 | socket.receive(buffer, (data) { 243 | stdout.rawWrite(data).collectException(); 244 | stdout.flush().collectException(); 245 | }); 246 | }); 247 | 248 | mixin StdInTransmitter transmitter; 249 | transmitter.start(socket); 250 | }; 251 | if (onClose) socket.onClose = onClose; 252 | } else { 253 | socket.receive({ 254 | auto buffer = new ubyte[4096]; 255 | socket.receiveContinuously = true; 256 | socket.receive(buffer, (data) { 257 | stdout.rawWrite(data).collectException(); 258 | stdout.flush().collectException(); 259 | }); 260 | }); 261 | 262 | mixin StdInTransmitter transmitter; 263 | transmitter.start(socket); 264 | } 265 | } 266 | 267 | class StdInReader : Thread 268 | { 269 | private: 270 | import core.sync.semaphore : Semaphore; 271 | 272 | shared ubyte[] m_buffer; 273 | shared size_t* m_readCount; 274 | shared AsyncSignal m_onRead; 275 | shared bool m_running; 276 | 277 | Semaphore m_sem; 278 | 279 | public: 280 | this(shared ubyte[] buffer, shared ref size_t readCount, shared AsyncSignal onRead) nothrow 281 | { 282 | m_buffer = buffer; 283 | m_onRead = onRead; 284 | m_readCount = &readCount; 285 | m_sem = assumeWontThrow(new Semaphore(0)); 286 | m_running = true; 287 | assumeWontThrow(isDaemon = true); 288 | assumeWontThrow(super(&run)); 289 | } 290 | 291 | version (Posix) void run() 292 | { 293 | import core.sys.posix.unistd : STDIN_FILENO, read; 294 | 295 | m_sem.wait(); 296 | while (m_running && g_running) { 297 | *m_readCount = read(STDIN_FILENO, cast(void*) m_buffer.ptr, m_buffer.length); 298 | m_onRead.trigger(); 299 | m_sem.wait(); 300 | } 301 | } 302 | 303 | version (Windows) void run() 304 | { 305 | import libasync.internals.win32 : HANDLE, DWORD, GetStdHandle, ReadFile; 306 | enum STD_INPUT_HANDLE = cast(DWORD) -10; 307 | enum INVALID_HANDLE_VALUE = cast(HANDLE) -1; 308 | 309 | auto stdin = GetStdHandle(STD_INPUT_HANDLE); 310 | assert(stdin != INVALID_HANDLE_VALUE, "ncat: Failed to get standard input handle"); 311 | 312 | m_sem.wait(); 313 | while (m_running && g_running) { 314 | DWORD bytesRead = void; 315 | auto err = ReadFile(stdin, cast(void*) m_buffer.ptr, cast(DWORD) m_buffer.length, &bytesRead, null); 316 | *m_readCount = bytesRead; 317 | m_onRead.trigger(); 318 | m_sem.wait(); 319 | } 320 | } 321 | 322 | void stop() nothrow 323 | { m_running = false; loop(); } 324 | 325 | void loop() nothrow 326 | { assumeWontThrow(m_sem.notify()); } 327 | } 328 | 329 | shared bool g_running; 330 | EventLoop g_eventLoop; 331 | int g_status; 332 | 333 | AsyncSocket g_listener, g_client; 334 | 335 | enum Mode { Connect, Listen } 336 | 337 | auto getAddressFamily(A)(A arguments) 338 | { 339 | if (arguments["-6"].isTrue) return AddressFamily.INET6; 340 | else if (arguments["-4"].isTrue) return AddressFamily.INET; 341 | else if (arguments["-U"].isTrue) return AddressFamily.UNIX; 342 | return AddressFamily.INET6; 343 | } 344 | 345 | auto getAddress(A)(A arguments, int af) 346 | { 347 | auto address = arguments["
"]; 348 | ushort port = void; 349 | 350 | if (!arguments[""].isNull) try { 351 | port = arguments[""].toString.to!ushort; 352 | } catch (Exception) { 353 | throw new Exception("port must be integer and 0 <= port <= %s, not '%s'".format(ushort.max, arguments[""])); 354 | } 355 | 356 | if (address.isNull) switch (af) { 357 | case AF_INET: with (new InternetAddress("0.0.0.0", port)) return NetworkAddress(name, nameLen); 358 | case AF_INET6: with (new Internet6Address("::", port)) return NetworkAddress(name, nameLen); 359 | default: assert(false); 360 | } 361 | 362 | switch (af) { 363 | case AF_INET: with (new InternetAddress(address.toString, port)) return NetworkAddress(name, nameLen); 364 | case AF_INET6: with (new Internet6Address(address.toString, port)) return NetworkAddress(name, nameLen); 365 | version (Posix) case AF_UNIX: with(new UnixAddress(address.toString)) return NetworkAddress(name, nameLen); 366 | default: assert(false); 367 | } 368 | } 369 | 370 | auto getType(A)(A arguments) 371 | { return arguments["-u"].isTrue? SocketType.DGRAM : SocketType.STREAM; } 372 | 373 | auto getMode(A)(A arguments) 374 | { return arguments["-l"].isTrue? Mode.Listen : Mode.Connect; } 375 | 376 | auto keepListening(A)(A arguments) 377 | { return arguments["-k"].isTrue; } 378 | 379 | import core.time; 380 | import core.thread; 381 | import core.atomic; 382 | 383 | import std.stdio; 384 | import std.socket; 385 | import std.conv; 386 | import std.format; 387 | import std.file; 388 | import std.exception; 389 | 390 | import memutils.utils; 391 | 392 | import libasync; 393 | import docopt; -------------------------------------------------------------------------------- /examples/netlink/README: -------------------------------------------------------------------------------- 1 | Original source: https://gist.github.com/arunk-s/c897bb9d75a6c98733d6 2 | 3 | 1. Build kernel module: 4 | $ (cd kmodule ; make) 5 | 2. Load kernel module: 6 | # insmod kmodule/netlinkKernel.ko 7 | 4. Send message to kernel module and see received reply message: 8 | $ dub --build=release 9 | 5. See message the kernel received: 10 | # dmesg 11 | 6. Unload kernel module: 12 | # rmmod netlinkKernel 13 | 7. Clean kernel module build files: 14 | $ (cd kmodule ; make clean) -------------------------------------------------------------------------------- /examples/netlink/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netlink", 3 | "description": "Communicate with Linux kernel using Netlink", 4 | "copyright": "Copyright © 2016, Moritz Maxeiner", 5 | "authors": ["Moritz Maxeiner"], 6 | "dependencies": { 7 | "libasync": { "path": "../../" } 8 | }, 9 | 10 | "targetName": "netlinkUser", 11 | "buildTypes": { 12 | "release": { 13 | "versions": ["StdLoggerDisableTrace", 14 | "StdLoggerDisableInfo", 15 | "StdLoggerDisableWarning"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/netlink/kmodule/Makefile: -------------------------------------------------------------------------------- 1 | KDIR := /lib/modules/$(shell uname -r)/build 2 | 3 | obj-m += netlinkKernel.o 4 | 5 | all: 6 | $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules 7 | 8 | clean: 9 | rm -rf *.o *.ko *.mod.* *.cmd .module* modules* Module* .*.cmd .tmp* 10 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 11 | -------------------------------------------------------------------------------- /examples/netlink/kmodule/netlinkKernel.c: -------------------------------------------------------------------------------- 1 | //Taken from https://stackoverflow.com/questions/15215865/netlink-sockets-in-c-using-the-3-x-linux-kernel?lq=1 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define NETLINK_USER 31 9 | 10 | struct sock *nl_sk = NULL; 11 | 12 | static void hello_nl_recv_msg(struct sk_buff *skb) { 13 | 14 | struct nlmsghdr *nlh; 15 | int pid; 16 | struct sk_buff *skb_out; 17 | int msg_size; 18 | char *msg="Hello from kernel"; 19 | int res; 20 | 21 | printk(KERN_INFO "Entering: %s\n", __FUNCTION__); 22 | 23 | msg_size=strlen(msg); 24 | 25 | nlh=(struct nlmsghdr*)skb->data; 26 | printk(KERN_INFO "Netlink received msg payload:%s\n",(char*)nlmsg_data(nlh)); 27 | pid = nlh->nlmsg_pid; /*pid of sending process */ 28 | 29 | skb_out = nlmsg_new(msg_size,0); 30 | 31 | if(!skb_out) 32 | { 33 | 34 | printk(KERN_ERR "Failed to allocate new skb\n"); 35 | return; 36 | 37 | } 38 | 39 | nlh=nlmsg_put(skb_out,0,0,NLMSG_DONE,msg_size,0); 40 | NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */ 41 | strncpy(nlmsg_data(nlh),msg,msg_size); 42 | 43 | res=nlmsg_unicast(nl_sk,skb_out,pid); 44 | 45 | if(res<0) 46 | printk(KERN_INFO "Error while sending bak to user\n"); 47 | } 48 | 49 | static int __init hello_init(void) { 50 | 51 | printk("Entering: %s\n",__FUNCTION__); 52 | //This is for 3.6 kernels and above. 53 | struct netlink_kernel_cfg cfg = { 54 | .input = hello_nl_recv_msg, 55 | }; 56 | 57 | nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg); 58 | //nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, 0, hello_nl_recv_msg,NULL,THIS_MODULE); 59 | if(!nl_sk) 60 | { 61 | 62 | printk(KERN_ALERT "Error creating socket.\n"); 63 | return -10; 64 | 65 | } 66 | 67 | return 0; 68 | } 69 | 70 | static void __exit hello_exit(void) { 71 | 72 | printk(KERN_INFO "exiting hello module\n"); 73 | netlink_kernel_release(nl_sk); 74 | } 75 | 76 | module_init(hello_init); module_exit(hello_exit); 77 | 78 | MODULE_LICENSE("GPL"); 79 | -------------------------------------------------------------------------------- /examples/netlink/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | enum NETLINK_USER = 31; 3 | enum MAX_PAYLOAD = 1024; 4 | 5 | sockaddr_nl src_addr, dest_addr; 6 | nlmsghdr* nlh; 7 | 8 | int main(string[] args) 9 | { 10 | auto eventLoop = getThreadEventLoop(); 11 | 12 | auto socket = new AsyncSocket(eventLoop, AF_NETLINK, SOCK_RAW, NETLINK_USER); 13 | 14 | socket.onError = { 15 | stderr.writeln("netlink: ", socket.error).collectException(); 16 | g_running = false; 17 | g_status = 1; 18 | }; 19 | 20 | if(!socket.run()) { 21 | stderr.writeln("netlink: ", socket.error); 22 | return 1; 23 | } 24 | 25 | src_addr.nl_family = AF_NETLINK; 26 | src_addr.nl_pid = thisProcessID(); 27 | 28 | if (!socket.bind(cast(sockaddr*) &src_addr, cast(socklen_t) src_addr.sizeof)) { 29 | stderr.writeln("netlink: ", socket.error); 30 | return 1; 31 | } 32 | 33 | dest_addr.nl_family = AF_NETLINK; 34 | dest_addr.nl_pid = 0; // For Linux kernel 35 | dest_addr.nl_groups = 0; // Unicast 36 | 37 | nlh = cast(nlmsghdr*) GC.calloc(NLMSG_SPACE(MAX_PAYLOAD)); 38 | nlh.nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); 39 | nlh.nlmsg_pid = thisProcessID(); 40 | nlh.nlmsg_flags = 0; 41 | 42 | copy("Hello", cast(char[]) NLMSG_DATA(nlh)[0..MAX_PAYLOAD]); 43 | 44 | auto message = NetworkMessage((cast(ubyte*) nlh)[0..nlh.nlmsg_len]); 45 | message.name = cast(sockaddr*) &dest_addr; 46 | message.nameLength = dest_addr.sizeof; 47 | 48 | socket.sendMessage(message, { 49 | socket.receiveMessage(message, (data) { 50 | stdout.writeln((cast(char*) NLMSG_DATA(cast(nlmsghdr*) data)).fromStringz).collectException; 51 | }); 52 | }); 53 | 54 | foreach (i; 1..10) { 55 | if (!g_running) break; 56 | else eventLoop.loop(10.msecs); 57 | } 58 | return g_status; 59 | } 60 | 61 | int g_status; 62 | bool g_running = true; 63 | 64 | import core.time; 65 | import core.sys.posix.sys.types : pid_t; 66 | import core.memory; 67 | 68 | import std.stdio : stdout, stderr; 69 | import std.exception : collectException; 70 | import std.process : thisProcessID; 71 | import std.string : fromStringz; 72 | import std.algorithm.mutation : copy; 73 | 74 | import libasync; 75 | import libasync.internals.socket_compat : sa_family_t, sockaddr, socklen_t; 76 | 77 | enum AF_NETLINK = 16; 78 | 79 | enum NLMSG_ALIGNTO = cast(uint) 4; 80 | auto NLMSG_ALIGN(uint len) { return (len + NLMSG_ALIGNTO - 1) & ~(NLMSG_ALIGNTO - 1); } 81 | enum NLMSG_HDRLEN = cast(uint) NLMSG_ALIGN(nlmsghdr.sizeof); 82 | auto NLMSG_LENGTH(uint len) { return len + NLMSG_HDRLEN; } 83 | auto NLMSG_SPACE(uint len) { return NLMSG_ALIGN(NLMSG_LENGTH(len)); } 84 | auto NLMSG_DATA(nlmsghdr* nlh) { return cast(void*) (cast(ubyte*) nlh) + NLMSG_LENGTH(0); } 85 | auto NLMSG_NEXT(nlmsghdr* nlh, ref uint len) { 86 | len -= NLMSG_ALIGN(nlh.nlmsg_len); 87 | return cast(nlmsghdr*) ((cast(ubyte*) nlh) + NLMSG_ALIGN(nlh.nlmsg_len)); 88 | } 89 | auto NLMSG_OK(nlmsghdr* nlh, uint len) 90 | { 91 | return len >= nlmsghdr.sizeof && 92 | nlh.nlmsg_len >= nlmsghdr.sizeof && 93 | nlh.nlmsg_len <= len; 94 | } 95 | auto NLMSG_PAYLOAD(nlmsghdr* nlh, uint len) 96 | { return nlh.nlmsg_len - NLMSG_SPACE(len); } 97 | 98 | struct nlmsghdr { 99 | uint nlmsg_len; /// Length of message including header 100 | ushort nlmsg_type; /// Type of message content 101 | ushort nlmsg_flags; /// Additional flags 102 | uint nlmsg_seq; /// Sequence number 103 | uint nlmsg_pid; /// Sender port ID 104 | } 105 | 106 | struct nlmsgerr { 107 | int error; /// Negative errno or 0 for acknowledgements 108 | nlmsghdr msg; /// Message header that caused the error 109 | } 110 | 111 | struct sockaddr_nl { 112 | sa_family_t nl_family; /// AF_NETLINK 113 | ushort nl_pad; /// Zero 114 | pid_t nl_pid; /// Port ID 115 | uint nl_groups; /// Multicast groups mask 116 | }; -------------------------------------------------------------------------------- /examples/tcp_client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcp_client", 3 | "description": "A simple TCP client", 4 | "dependencies": { 5 | "libasync": {"version": "~master", "path": "../../"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/tcp_client/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | import std.stdio; 3 | 4 | EventLoop g_evl; 5 | TCPConnection g_tcpConnection; 6 | bool g_closed; 7 | 8 | /// This example creates a connection localhost:8081, sends a message and waits for a reply before closing 9 | void main() { 10 | g_evl = new EventLoop; 11 | g_tcpConnection = new TCPConnection("localhost", 8081); 12 | 13 | while(!g_closed) 14 | g_evl.loop(); 15 | version(Libasync_Threading) destroyAsyncThreads(); 16 | } 17 | 18 | class TCPConnection { 19 | AsyncTCPConnection m_conn; 20 | 21 | this(string host, size_t port) 22 | { 23 | m_conn = new AsyncTCPConnection(g_evl); 24 | 25 | if (!m_conn.host(host, port).run(&handler)) { 26 | writeln(m_conn.status); 27 | } 28 | } 29 | 30 | void onConnect() { 31 | onRead(); 32 | onWrite(); 33 | } 34 | 35 | void onRead() { 36 | static ubyte[] bin = new ubyte[4092]; 37 | while (true) { 38 | uint len = m_conn.recv(bin); 39 | 40 | if (len > 0) { 41 | string res = cast(string)bin[0..len]; 42 | writeln("Received data: ", res); 43 | m_conn.kill(); 44 | } 45 | if (len < bin.length) 46 | break; 47 | } 48 | 49 | 50 | } 51 | 52 | void onWrite() { 53 | m_conn.send(cast(ubyte[])"My Message"); 54 | writeln("Sent: My Message"); 55 | } 56 | 57 | void onClose() { 58 | writeln("Connection closed"); 59 | g_closed = true; 60 | } 61 | 62 | void handler(TCPEvent ev) { 63 | 64 | try final switch (ev) { 65 | case TCPEvent.CONNECT: 66 | onConnect(); 67 | break; 68 | case TCPEvent.READ: 69 | onRead(); 70 | break; 71 | case TCPEvent.WRITE: 72 | onWrite(); 73 | break; 74 | case TCPEvent.CLOSE: 75 | onClose(); 76 | break; 77 | case TCPEvent.ERROR: 78 | assert(false, m_conn.error()); 79 | } catch (Exception e) { 80 | assert(false, e.toString()); 81 | } 82 | return; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /examples/tcp_client/ws2_32.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etcimon/libasync/2a6aa938ea82dc48beaa2847d143598fd3de2af0/examples/tcp_client/ws2_32.lib -------------------------------------------------------------------------------- /examples/tcp_listener/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcp_listener", 3 | "description": "A simple TCP listener", 4 | "dependencies": { 5 | "libasync": {"version": "~master", "path": "../../"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/tcp_listener/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | import std.stdio; 3 | 4 | EventLoop g_evl; 5 | TCPListener g_listener; 6 | bool g_closed; 7 | 8 | /// This example creates a listener on localhost:8081 which accepts connections, sends a reply, 9 | /// and exits the process after the first client has disconnected 10 | void main() { 11 | g_evl = new EventLoop; 12 | g_listener = new TCPListener("localhost", 8081); 13 | 14 | while(!g_closed) 15 | g_evl.loop(); 16 | version(Libasync_Threading) destroyAsyncThreads(); 17 | } 18 | 19 | class TCPListener { 20 | AsyncTCPListener m_listener; 21 | 22 | this(string host, size_t port) { 23 | m_listener = new AsyncTCPListener(g_evl); 24 | if (m_listener.host(host, port).run(&handler)) 25 | writeln("Listening to ", m_listener.local.toString()); 26 | 27 | } 28 | 29 | void delegate(TCPEvent) handler(AsyncTCPConnection conn) { 30 | auto tcpConn = new TCPConnection(conn); 31 | return &tcpConn.handler; 32 | } 33 | 34 | } 35 | 36 | class TCPConnection { 37 | AsyncTCPConnection m_conn; 38 | 39 | this(AsyncTCPConnection conn) 40 | { 41 | this.m_conn = conn; 42 | } 43 | void onConnect() { 44 | onRead(); 45 | onWrite(); 46 | } 47 | 48 | // Note: All buffers must be empty when returning from TCPEvent.READ 49 | void onRead() { 50 | static ubyte[] bin = new ubyte[4092]; 51 | while (true) { 52 | uint len = m_conn.recv(bin); 53 | 54 | if (len > 0) { 55 | auto res = cast(string)bin[0..len]; 56 | writeln("Received data: ", res); 57 | } 58 | if (len < bin.length) 59 | break; 60 | } 61 | } 62 | 63 | void onWrite() { 64 | m_conn.send(cast(ubyte[])"My Reply"); 65 | writeln("Sent: My Reply"); 66 | } 67 | 68 | void onClose() { 69 | writeln("Connection closed"); 70 | g_closed = true; 71 | } 72 | 73 | void handler(TCPEvent ev) { 74 | final switch (ev) { 75 | case TCPEvent.CONNECT: 76 | onConnect(); 77 | break; 78 | case TCPEvent.READ: 79 | onRead(); 80 | break; 81 | case TCPEvent.WRITE: 82 | onWrite(); 83 | break; 84 | case TCPEvent.CLOSE: 85 | onClose(); 86 | break; 87 | case TCPEvent.ERROR: 88 | assert(false, "Error during TCP Event"); 89 | } 90 | return; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /examples/uds_client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uds_client", 3 | "description": "A simple unix domain socket client", 4 | "dependencies": { 5 | "libasync": { "path": "../../" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/uds_client/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | import libasync; 3 | 4 | /// This example creates a client connecting to /tmp/libasync_uds.sock, 5 | /// then forwarding everything it receives to STDOUT, shutting itself 6 | /// down after ten seconds. 7 | void main(string[] args) 8 | { 9 | g_eventLoop = getThreadEventLoop(); 10 | auto client = new UDSClient(new UnixAddress("/tmp/libasync_uds.sock")); 11 | 12 | while(!g_closed) g_eventLoop.loop(-1.seconds); 13 | } 14 | 15 | EventLoop g_eventLoop = void; 16 | bool g_closed; 17 | 18 | class UDSClient { 19 | AsyncUDSConnection m_conn; 20 | AsyncTimer m_killer; 21 | 22 | this(UnixAddress peer) nothrow 23 | { 24 | m_conn = new AsyncUDSConnection(g_eventLoop); 25 | m_conn.peer = peer; 26 | m_conn.run(&handler); 27 | } 28 | 29 | void onConnect() { 30 | writeln("Connected"); 31 | m_killer = new AsyncTimer(g_eventLoop); 32 | m_killer.duration = 10.seconds; 33 | m_killer.periodic = false; 34 | m_killer.run({ 35 | m_killer.kill(); 36 | m_conn.kill(); 37 | g_closed = true; 38 | }); 39 | } 40 | 41 | // NOTE: All buffers must be empty when returning from TCPEvent.READ 42 | void onRead() { 43 | static buffer = new ubyte[4096]; 44 | uint read = void; 45 | do { 46 | read = m_conn.recv(buffer); 47 | writefln("Received %d bytes:", read); 48 | writeln(cast(string) buffer[0 .. read]); 49 | } while (read >= buffer.length); 50 | } 51 | 52 | void onWrite() { 53 | writeln("OS send buffer ready"); 54 | } 55 | 56 | void onClose() { 57 | writeln("Disconnected"); 58 | m_killer.kill(); 59 | m_conn.kill(); 60 | g_closed = true; 61 | } 62 | 63 | void handler(EventCode code) { 64 | final switch (code) with (EventCode) { 65 | case CONNECT: 66 | onConnect(); 67 | break; 68 | case READ: 69 | onRead(); 70 | break; 71 | case WRITE: 72 | onWrite(); 73 | break; 74 | case CLOSE: 75 | onClose(); 76 | break; 77 | case ERROR: 78 | assert(false, "Error during UDS Event"); 79 | } 80 | return; 81 | } 82 | } 83 | 84 | version(Libasync_Threading) shared static ~this() { 85 | destroyAsyncThreads(); 86 | } 87 | 88 | import core.time; 89 | import std.stdio; 90 | -------------------------------------------------------------------------------- /examples/uds_listener/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uds_listener", 3 | "description": "A simple numbers station with unix domain sockets", 4 | "dependencies": { 5 | "libasync": { "path": "../../" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/uds_listener/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | import libasync; 3 | 4 | /// This example creates a listener at /tmp/libasync_uds.sock, 5 | /// continuously accepts connections, and periodically (1 second) sends 6 | /// a pseudo-random number in the interval [0,99] to each connected client 7 | void main(string[] args) 8 | { 9 | g_eventLoop = getThreadEventLoop(); 10 | g_listener = new UDSListener(new UnixAddress("/tmp/libasync_uds.sock")); 11 | 12 | while(!g_closed) g_eventLoop.loop(-1.seconds); 13 | } 14 | 15 | EventLoop g_eventLoop = void; 16 | UDSListener g_listener = void; 17 | bool g_closed; 18 | 19 | class UDSListener { 20 | AsyncUDSListener m_listener; 21 | 22 | this(UnixAddress address) { 23 | m_listener = new AsyncUDSListener(g_eventLoop); 24 | m_listener.local = address; 25 | if (m_listener.run(&handler)) { 26 | writeln("Listening to ", m_listener.local.toString()); 27 | } 28 | } 29 | 30 | void delegate(EventCode) handler(AsyncUDSConnection conn) nothrow { 31 | auto udsConn = new UDSConnection(conn); 32 | return &udsConn.handler; 33 | } 34 | } 35 | 36 | class UDSConnection { 37 | AsyncUDSConnection m_conn; 38 | AsyncTimer m_generator; 39 | 40 | this(AsyncUDSConnection conn) nothrow 41 | { 42 | m_conn = conn; 43 | } 44 | 45 | void onConnect() { 46 | writeln("Client connected"); 47 | m_generator = new AsyncTimer(g_eventLoop); 48 | m_generator.duration = 1.seconds; 49 | m_generator.periodic = true; 50 | m_generator.run({ 51 | m_conn.send(cast(ubyte[]) "%d".format(uniform(0, 100))); 52 | }); 53 | } 54 | 55 | // NOTE: All buffers must be empty when returning from TCPEvent.READ 56 | void onRead() { 57 | static buffer = new ubyte[4096]; 58 | uint read = void; 59 | while ((read = m_conn.recv(buffer)) >= buffer.length) { 60 | writeln("Received data: ", cast(string) buffer[0 .. read]); 61 | } 62 | } 63 | 64 | void onWrite() { 65 | writeln("OS send buffer ready"); 66 | } 67 | 68 | void onClose() { 69 | writeln("Client disconnected"); 70 | m_generator.kill(); 71 | m_conn.kill(); 72 | } 73 | 74 | void handler(EventCode code) { 75 | final switch (code) with (EventCode) { 76 | case CONNECT: 77 | onConnect(); 78 | break; 79 | case READ: 80 | onRead(); 81 | break; 82 | case WRITE: 83 | onWrite(); 84 | break; 85 | case CLOSE: 86 | onClose(); 87 | break; 88 | case ERROR: 89 | assert(false, "Error during UDS Event"); 90 | } 91 | return; 92 | } 93 | } 94 | 95 | version(Libasync_Threading) shared static ~this() { 96 | destroyAsyncThreads(); 97 | } 98 | 99 | import core.time; 100 | import std.stdio; 101 | import std.format; 102 | import std.random; 103 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etcimon/libasync/2a6aa938ea82dc48beaa2847d143598fd3de2af0/logo.png -------------------------------------------------------------------------------- /logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etcimon/libasync/2a6aa938ea82dc48beaa2847d143598fd3de2af0/logo.webp -------------------------------------------------------------------------------- /source/libasync/bufferedtcp.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.bufferedtcp; 3 | 4 | import std.algorithm : copy; 5 | import std.array : array, empty, front, popFront; 6 | import std.range : isInputRange, isOutputRange; 7 | 8 | import memutils.circularbuffer : CircularBuffer; 9 | 10 | import libasync.events; 11 | import libasync.tcp : AsyncTCPConnection, TCPEvent, TCPEventHandler; 12 | import libasync.types : StatusInfo; 13 | 14 | /// 15 | final class BufferedTCPConnection(size_t size = 4092) 16 | { 17 | /// 18 | alias OnEvent = void delegate(BufferedTCPConnection!size conn); 19 | /// 20 | alias OnRead = 21 | void delegate(BufferedTCPConnection!size conn, in ubyte[] msg); 22 | 23 | private 24 | { 25 | AsyncTCPConnection asyncConn; 26 | 27 | OnEvent onConnectCb; 28 | OnEvent onCloseCb; 29 | OnEvent onErrorCb; 30 | OnReadInfo[] onReadCbs; 31 | OnWriteInfo[] onWriteCbs; 32 | 33 | CircularBuffer!(ubyte, size) readBuffer; 34 | CircularBuffer!(ubyte, size) writeBuffer; 35 | ubyte[] workBuffer = new ubyte[size]; 36 | } 37 | 38 | /// 39 | this( 40 | EventLoop evl, 41 | fd_t preInitializedSocket = fd_t.init, 42 | in OnEvent onConnectCb = null, 43 | in OnEvent onCloseCb = null, 44 | in OnEvent onErrorCb = null) 45 | in 46 | { 47 | assert(evl !is null); 48 | } 49 | do 50 | { 51 | asyncConn = new AsyncTCPConnection(evl, preInitializedSocket); 52 | 53 | this.onConnectCb = onConnectCb; 54 | this.onCloseCb = onCloseCb; 55 | this.onErrorCb = onErrorCb; 56 | } 57 | 58 | /// 59 | this( 60 | AsyncTCPConnection conn, 61 | in OnEvent onConnectCb = null, 62 | in OnEvent onCloseCb = null, 63 | in OnEvent onErrorCb = null) 64 | in 65 | { 66 | assert(conn !is null); 67 | } 68 | do 69 | { 70 | asyncConn = conn; 71 | 72 | this.onConnectCb = onConnectCb; 73 | this.onCloseCb = onCloseCb; 74 | this.onErrorCb = onErrorCb; 75 | } 76 | 77 | /// 78 | @property bool hasError() const 79 | { 80 | return asyncConn.hasError; 81 | } 82 | 83 | /** 84 | * The status code is Status.ASYNC if the call is delayed (yield), 85 | * Status.ABORT if an unrecoverable socket/fd error occurs (throw), or 86 | * Status.ERROR if an internal error occured (assert). 87 | */ 88 | @property StatusInfo status() const 89 | { 90 | return asyncConn.status; 91 | } 92 | 93 | /** 94 | * Returns: Human-readable error message from the underlying operating 95 | * system. 96 | */ 97 | @property string error() const 98 | { 99 | return asyncConn.error; 100 | } 101 | 102 | /// 103 | @property bool isConnected() const nothrow 104 | { 105 | return asyncConn.isConnected; 106 | } 107 | 108 | /** 109 | * Returns: true if this connection was accepted by an AsyncTCPListener 110 | * instance. 111 | */ 112 | @property bool inbound() const 113 | { 114 | return asyncConn.inbound; 115 | } 116 | 117 | /// Disables(true)/enables(false) nagle's algorithm (default:enabled). 118 | @property void noDelay(bool b) 119 | { 120 | asyncConn.noDelay(b); 121 | } 122 | 123 | /// Changes the default OS configurations for this underlying TCP Socket. 124 | bool setOption(T)(TCPOption op, in T val) 125 | { 126 | return asyncConn.setOption(op, val); 127 | } 128 | 129 | /// Returns the OS-specific structure of the internet address 130 | /// of the remote network adapter 131 | @property NetworkAddress peer() const 132 | { 133 | return asyncConn.peer; 134 | } 135 | 136 | /// Returns the OS-specific structure of the internet address 137 | /// for the local end of the connection. 138 | @property NetworkAddress local() 139 | { 140 | return asyncConn.local; 141 | } 142 | 143 | /// Sets the remote address as an OS-specific structure (only usable before connecting). 144 | @property void peer(NetworkAddress addr) 145 | { 146 | asyncConn.peer = addr; 147 | } 148 | 149 | /// (Blocking) Resolves the specified host and resets the peer to this address. 150 | /// Use AsyncDNS for a non-blocking resolver. (only usable before connecting). 151 | typeof(this) host(string hostname, size_t port) 152 | { 153 | asyncConn.host(hostname, port); 154 | return this; 155 | } 156 | 157 | /// Sets the peer to the specified IP address and port. (only usable before connecting). 158 | typeof(this) ip(string ip, size_t port) 159 | { 160 | asyncConn.ip(ip, port); 161 | return this; 162 | } 163 | 164 | /// Starts the connection by registering the associated callback handler in the 165 | /// underlying OS event loop. 166 | bool run(void delegate(TCPEvent) del) 167 | { 168 | TCPEventHandler handler; 169 | handler.conn = asyncConn; 170 | handler.del = del; 171 | return run(handler); 172 | } 173 | 174 | private bool run(TCPEventHandler del) 175 | { 176 | return asyncConn.run(del); 177 | } 178 | 179 | /** 180 | * Receive data from the underlying stream. To be used when TCPEvent.READ 181 | * is received by the callback handler. 182 | * IMPORTANT: This must be called until is returns a lower value than the 183 | * buffer! 184 | */ 185 | private uint recv() 186 | in 187 | { 188 | assert(isConnected, "No socket to operate on"); 189 | } 190 | do 191 | { 192 | uint cnt = asyncConn.recv(workBuffer); 193 | if (cnt > 0) 194 | copy(workBuffer[0..cnt], &readBuffer); 195 | return cnt; 196 | } 197 | 198 | /** 199 | * Send data through the underlying stream by moving it into the OS buffer. 200 | */ 201 | private uint send() 202 | in 203 | { 204 | assert(isConnected, "No socket to operate on"); 205 | } 206 | do 207 | { 208 | copy(writeBuffer[], workBuffer); 209 | return asyncConn.send(workBuffer[0..writeBuffer.length].array); 210 | } 211 | 212 | /** 213 | * Removes the connection from the event loop, closing it if necessary, and 214 | * cleans up the underlying resources. 215 | */ 216 | private bool kill(in bool forced = false) 217 | in 218 | { 219 | assert(isConnected); 220 | } 221 | do 222 | { 223 | bool ret = asyncConn.kill(forced); 224 | return ret; 225 | } 226 | 227 | /// 228 | void read(in size_t len, in OnRead onReadCb) 229 | { 230 | onReadCbs ~= OnReadInfo(len, onReadCb); 231 | } 232 | 233 | // Note: All buffers must be empty when returning from TCPEvent.READ 234 | private void onRead() 235 | { 236 | uint read; 237 | do read = recv(); 238 | while (read == readBuffer.capacity); 239 | 240 | if (onReadCbs.empty) 241 | return; 242 | 243 | foreach (ref info; onReadCbs) with (info) 244 | if (readBuffer.length >= len) 245 | { 246 | cb(this, readBuffer[0..len].array); 247 | readBuffer.popFrontN(len); 248 | onReadCbs.popFront(); 249 | } 250 | else 251 | break; 252 | } 253 | 254 | /// 255 | void write(R)(in R msg, in size_t len, in OnEvent cb = null) 256 | if (isInputRange!R) 257 | { 258 | writeBuffer.put(msg[0..len]); 259 | 260 | onWriteCbs ~= OnWriteInfo(len, cb); 261 | onWrite(); 262 | } 263 | 264 | private void onWrite() 265 | { 266 | if (writeBuffer.length == 0) 267 | return; 268 | 269 | uint sent = send(); 270 | writeBuffer.popFrontN(sent); 271 | 272 | foreach (ref info; onWriteCbs) with (info) 273 | if (sent >= len) 274 | { 275 | cb(this); 276 | onWriteCbs.popFront(); 277 | sent -= len; 278 | } 279 | else 280 | break; 281 | } 282 | 283 | private void onConnect() 284 | { 285 | if (onConnectCb !is null) 286 | onConnectCb(this); 287 | } 288 | 289 | private void onError() 290 | { 291 | if (onErrorCb !is null) 292 | onErrorCb(this); 293 | } 294 | 295 | /// 296 | void close() 297 | { 298 | kill(); 299 | onClose(); 300 | } 301 | 302 | private void onClose() 303 | { 304 | if (onCloseCb !is null) 305 | onCloseCb(this); 306 | } 307 | 308 | /// 309 | void handle(TCPEvent ev) 310 | { 311 | final switch (ev) 312 | { 313 | case TCPEvent.CONNECT: 314 | onConnect(); 315 | break; 316 | case TCPEvent.READ: 317 | onRead(); 318 | break; 319 | case TCPEvent.WRITE: 320 | onWrite(); 321 | break; 322 | case TCPEvent.CLOSE: 323 | onClose(); 324 | break; 325 | case TCPEvent.ERROR: 326 | onError(); 327 | break; 328 | } 329 | } 330 | 331 | private struct OnReadInfo 332 | { 333 | const size_t len; 334 | const OnRead cb; 335 | } 336 | 337 | private struct OnWriteInfo 338 | { 339 | const size_t len; 340 | const OnEvent cb; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /source/libasync/dns.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.dns; 3 | 4 | import libasync.types; 5 | import libasync.events; 6 | import core.thread : Thread, ThreadGroup; 7 | import core.sync.mutex; 8 | import core.sync.condition; 9 | import core.atomic; 10 | import libasync.threads; 11 | import libasync.internals.freelist; 12 | import libasync.internals.queue; 13 | import libasync.internals.logging; 14 | 15 | /// 16 | enum DNSCmd { 17 | /// 18 | RESOLVEHOST, 19 | /// 20 | RESOLVEIP 21 | } 22 | 23 | /// Resolves internet addresses and returns the results in a specified callback. 24 | shared final class AsyncDNS 25 | { 26 | nothrow: 27 | package EventLoop m_evLoop; 28 | private: 29 | bool m_busy; 30 | bool m_error; 31 | DNSReadyHandler m_handler; 32 | DNSCmdInfo m_cmdInfo; 33 | StatusInfo m_status; 34 | Thread m_owner; 35 | static if (is_Windows || EPOLL) { 36 | AsyncDNSRequest* m_activeReq; 37 | } 38 | public: 39 | /// 40 | this(EventLoop evl) { 41 | m_evLoop = cast(shared) evl; 42 | try { 43 | m_cmdInfo.ready = new shared AsyncSignal(cast(EventLoop)m_evLoop); 44 | } catch (Throwable) { 45 | assert(false, "Failed to start DNS Signaling"); 46 | } 47 | m_cmdInfo.ready.run(cast(void delegate())&callback); 48 | 49 | m_owner = cast(shared)Thread.getThis(); 50 | try m_cmdInfo.mtx = cast(shared) new Mutex; catch (Exception) {} 51 | } 52 | 53 | /// 54 | synchronized @property StatusInfo status() const 55 | { 56 | return cast(StatusInfo) m_status; 57 | } 58 | 59 | /// 60 | @property string error() const 61 | { 62 | return status.text; 63 | } 64 | 65 | /// Uses the callback for all resolved addresses. 66 | shared(typeof(this)) handler(void delegate(NetworkAddress) del) { 67 | shared DNSReadyHandler handler; 68 | handler.del = cast(shared) del; 69 | handler.ctxt = this; 70 | try synchronized(this) m_handler = handler; 71 | catch (Throwable) assert(false, "Failed to set handler in AsyncDNS"); 72 | return this; 73 | } 74 | 75 | /// Sends a request through a thread pool for the specified host to be resolved. The 76 | /// callback specified in run() will be signaled with the OS-specific NetworkAddress 77 | /// structure. 78 | bool resolveHost(string url, bool ipv6 = false, bool force_async = false) 79 | in { 80 | assert(!m_busy, "Resolver is busy or closed"); 81 | assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on."); 82 | } 83 | do { 84 | static if (LOG) .tracef("Resolving url: %s", url); 85 | static if (is_Windows || EPOLL) { 86 | if (force_async) { 87 | tracef("Resolving async with signal fd: %X", m_cmdInfo.ready.id); 88 | m_cmdInfo.command = DNSCmd.RESOLVEHOST; 89 | m_cmdInfo.ipv6 = ipv6; 90 | m_cmdInfo.url = cast(shared) url; 91 | AsyncDNSRequest* dns_req = AsyncDNSRequest.alloc(this); 92 | m_activeReq = cast(shared)dns_req; 93 | return (cast(EventLoop)m_evLoop).resolve(dns_req, cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no); 94 | } 95 | } 96 | 97 | version(Libasync_Threading) 98 | if (force_async == true) { 99 | synchronized(m_cmdInfo.mtx) { 100 | m_cmdInfo.command = DNSCmd.RESOLVEHOST; 101 | m_cmdInfo.ipv6 = ipv6; 102 | m_cmdInfo.url = cast(shared) url; 103 | } 104 | return doOffThread({ process(this); }); 105 | } 106 | 107 | { 108 | m_cmdInfo.command = DNSCmd.RESOLVEHOST; 109 | m_cmdInfo.ipv6 = ipv6; 110 | m_cmdInfo.url = cast(shared) url; 111 | m_cmdInfo.addr = cast(shared)( (cast(EventLoop)m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no) ); 112 | callback(); 113 | return true; 114 | } 115 | 116 | } 117 | 118 | /// Returns an OS-specific NetworkAddress structure from the specified IP. 119 | NetworkAddress resolveIP(string url, bool ipv6) 120 | in { 121 | assert(!m_busy, "Resolver is busy or closed"); 122 | assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on."); 123 | } 124 | do { 125 | return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no); 126 | } 127 | 128 | /// Cleans up underlying resources. Used as a placeholder for possible future purposes. 129 | bool kill() { 130 | return true; 131 | } 132 | 133 | package: 134 | synchronized @property DNSCmdInfo cmdInfo() { 135 | return m_cmdInfo; 136 | } 137 | 138 | shared(NetworkAddress*) addr() { 139 | try synchronized(m_cmdInfo.mtx) 140 | return cast(shared)&m_cmdInfo.addr; 141 | catch (Exception) {} 142 | return null; 143 | } 144 | 145 | synchronized @property void status(StatusInfo stat) { 146 | m_status = cast(shared) stat; 147 | } 148 | 149 | synchronized @property bool waiting() const { 150 | return cast(bool) m_busy; 151 | } 152 | 153 | synchronized @property void waiting(bool b) { 154 | m_busy = cast(shared) b; 155 | } 156 | 157 | synchronized void callback() { 158 | static if (is_Windows || EPOLL) { 159 | if (m_activeReq) { 160 | import std.exception : assumeWontThrow; 161 | assumeWontThrow(AsyncDNSRequest.free(cast(AsyncDNSRequest*)m_activeReq)); 162 | m_activeReq = null; 163 | } 164 | } 165 | try { 166 | m_handler(cast(NetworkAddress)m_cmdInfo.addr); 167 | } 168 | catch (Throwable e) { 169 | warningf("Failed to send command. %s", e.toString()); 170 | } 171 | } 172 | 173 | } 174 | 175 | package struct AsyncDNSRequest 176 | { 177 | AsyncDNS dns; /// DNS resolver to use 178 | version(Windows) { 179 | import libasync.internals.win32; 180 | PADDRINFOEX infos; 181 | AsyncOverlapped* overlapped; 182 | ~this() { 183 | try { 184 | AsyncOverlapped.free(overlapped); 185 | } catch (Exception e) { 186 | import libasync.internals.logging; 187 | static if (LOG) tracef("Exception freeing in AsyncDNSRequest: %s", e.toString()); 188 | } 189 | } 190 | } 191 | static if (EPOLL) { 192 | import libasync.internals.socket_compat : gaicb, sigevent, addrinfo; 193 | gaicb* host; 194 | sigevent sig; 195 | ~this() { 196 | static if (LOG) tracef("Destroying AsyncDNSRequest"); 197 | try { 198 | ThreadMem.free(cast(addrinfo*)host.ar_request); 199 | ThreadMem.free(cast(gaicb*)host); 200 | } catch (Exception e) { 201 | import libasync.internals.logging; 202 | static if (LOG) tracef("Exception freeing in AsyncDNSRequest: %s", e.toString()); 203 | } 204 | } 205 | } 206 | mixin FreeList!1_000; 207 | } 208 | 209 | 210 | 211 | package shared struct DNSCmdInfo 212 | { 213 | DNSCmd command; 214 | bool ipv6; 215 | string url; 216 | NetworkAddress addr; 217 | AsyncSignal ready; 218 | AsyncDNS dns; 219 | Mutex mtx; // for NetworkAddress writing 220 | } 221 | 222 | package shared struct DNSReadyHandler { 223 | AsyncDNS ctxt; 224 | void delegate(NetworkAddress) del; 225 | 226 | void opCall(NetworkAddress addr) { 227 | assert(ctxt !is null); 228 | del(addr); 229 | return; 230 | } 231 | } 232 | 233 | private void process(shared AsyncDNS ctxt) { 234 | auto evLoop = getThreadEventLoop(); 235 | 236 | DNSCmdInfo cmdInfo = ctxt.cmdInfo(); 237 | auto mutex = cmdInfo.mtx; 238 | DNSCmd cmd; 239 | string url; 240 | cmd = cmdInfo.command; 241 | url = cmdInfo.url; 242 | 243 | try final switch (cmd) 244 | { 245 | case DNSCmd.RESOLVEHOST: 246 | *ctxt.addr = cast(shared) evLoop.resolveHost(url, 0, cmdInfo.ipv6 ? isIPv6.yes : isIPv6.no); 247 | break; 248 | 249 | case DNSCmd.RESOLVEIP: 250 | *ctxt.addr = cast(shared) evLoop.resolveIP(url, 0, cmdInfo.ipv6 ? isIPv6.yes : isIPv6.no); 251 | break; 252 | 253 | } catch (Throwable e) { 254 | auto status = StatusInfo.init; 255 | status.code = Status.ERROR; 256 | try status.text = e.toString(); catch (Throwable) {} 257 | ctxt.status = status; 258 | } 259 | 260 | try cmdInfo.ready.trigger(evLoop); 261 | catch (Throwable e) { 262 | auto status = StatusInfo.init; 263 | status.code = Status.ERROR; 264 | try status.text = e.toString(); catch (Throwable) {} 265 | ctxt.status = status; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /source/libasync/event.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.event; 3 | 4 | import core.thread; 5 | import libasync.types; 6 | import libasync.events; 7 | 8 | /// Takes a raw kernel-emitted file descriptor and registers its events into the event loop for async processing 9 | /// NOTE: If it's a socket, it must be made non-blocking before being passed here. 10 | /// NOTE: By default dispatched events are READ, WRITE, and ERROR; enabling 'stateful' adds CONNECT and CLOSE 11 | class AsyncEvent 12 | { 13 | nothrow: 14 | private: 15 | Thread m_owner; 16 | EventLoop m_evLoop; 17 | fd_t m_evId; 18 | bool m_stateful; 19 | 20 | public: 21 | /// 22 | this(EventLoop evl, fd_t ev_id, bool stateful = false) 23 | in { 24 | assert(evl !is null && ev_id > 0); 25 | } 26 | do { 27 | m_evLoop = evl; 28 | import core.thread : Thread; 29 | m_owner = Thread.getThis(); 30 | m_evId = ev_id; 31 | m_stateful = stateful; 32 | } 33 | 34 | /// 35 | @property bool hasError() const 36 | { 37 | return (cast(EventLoop)m_evLoop).status.code != Status.OK; 38 | } 39 | 40 | /// Used to diagnose errors when run() or kill() returns false 41 | @property StatusInfo status() const { 42 | return (cast(EventLoop)m_evLoop).status; 43 | } 44 | 45 | /// Human-readable string describing the error 46 | @property string error() const { 47 | return (cast(EventLoop)m_evLoop).error; 48 | } 49 | 50 | /// Registers the signal handler in the event loop 51 | bool run(void delegate(EventCode) del) 52 | in { 53 | debug assert(Thread.getThis() is cast(Thread)m_owner); 54 | } 55 | do { 56 | 57 | EventHandler handler; 58 | handler.del = del; 59 | handler.ev = this; 60 | return (cast(EventLoop) m_evLoop).run(this, handler); 61 | } 62 | 63 | /// Returns the Thread that created this object. 64 | synchronized @property Thread owner() const { 65 | return cast(Thread) m_owner; 66 | } 67 | 68 | /// 69 | @property fd_t id() const { 70 | return m_evId; 71 | } 72 | 73 | /// Removes the event from the event loop, closing the file descriptor if necessary, 74 | /// and cleans up the underlying resources. 75 | bool kill(bool forced = false) 76 | do { 77 | scope(exit) m_evId = 0; 78 | return m_evLoop.kill(this, forced); 79 | } 80 | 81 | package: 82 | mixin COSocketMixins; 83 | 84 | @property bool stateful() const { 85 | return m_stateful; 86 | } 87 | 88 | @property void stateful(bool stateful) { 89 | m_stateful = stateful; 90 | } 91 | 92 | @property void id(fd_t id) { 93 | m_evId = id; 94 | } 95 | } 96 | 97 | package struct EventHandler { 98 | AsyncEvent ev; 99 | void delegate(EventCode) del; 100 | void opCall(EventCode code){ 101 | assert(ev !is null); 102 | del(code); 103 | assert(ev !is null); 104 | return; 105 | } 106 | } 107 | 108 | /// 109 | enum EventCode : char { 110 | /// 111 | ERROR = 0, 112 | /// 113 | READ, 114 | /// 115 | WRITE, 116 | /// 117 | CONNECT, 118 | /// 119 | CLOSE 120 | } 121 | -------------------------------------------------------------------------------- /source/libasync/events.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.events; 3 | 4 | import std.stdio; 5 | 6 | import core.thread; 7 | import memutils.vector : Array; 8 | import std.datetime : Duration; 9 | import std.typecons : Flag; 10 | 11 | public import libasync.types; 12 | public import libasync.bufferedtcp; 13 | public import libasync.tcp; 14 | public import libasync.udp; 15 | public import libasync.uds; 16 | public import libasync.notifier; 17 | public import libasync.dns; 18 | public import libasync.timer; 19 | public import libasync.signal; 20 | public import libasync.watcher; 21 | public import libasync.file; 22 | public import libasync.threads; 23 | public import libasync.event; 24 | public import libasync.socket; 25 | 26 | version(Windows) { 27 | public import libasync.windows; 28 | } 29 | 30 | version(Posix) { 31 | public import libasync.posix; 32 | } 33 | 34 | private static EventLoop g_evLoop; 35 | /// 36 | EventLoop getThreadEventLoop() nothrow { 37 | if (!g_evLoop) { 38 | g_evLoop = new EventLoop(); 39 | } 40 | return g_evLoop; 41 | } 42 | 43 | /// Event handlers can be registered to the event loop by being run(), all events 44 | /// associated with them will trigger the OS to resume the underlying thread which 45 | /// enables the existence of all the asynchroneous event objects in this library. 46 | final class EventLoop 47 | { 48 | 49 | package: 50 | EventLoopImpl m_evLoop; 51 | bool m_asyncThreadsStarted = false; 52 | 53 | nothrow: 54 | public: 55 | /// 56 | this() { 57 | if (m_evLoop.started || !m_evLoop.init(this)) 58 | assert(false, "Event loop initialization failure"); 59 | } 60 | 61 | /// Call this to cleanup underlying OS resources. The implementation is currently incomplete 62 | /// and requires the process to be shut down for the resources to be collected automatically. 63 | /// Used as a placeholder in the meantime. 64 | void exit() { 65 | if (this is g_evLoop) 66 | g_evLoop = null; 67 | m_evLoop.exit(); 68 | } 69 | 70 | /// 71 | NetworkAddress resolveIP(in string ip, ushort port = 0, isIPv6 ipv6 = isIPv6.no, isTCP tcp = isTCP.yes, isForced force = isForced.yes) 72 | { 73 | if (!force) 74 | return m_evLoop.getAddressFromIP(ip, port, ipv6, tcp); 75 | NetworkAddress addr = m_evLoop.getAddressFromIP(ip, port, ipv6, tcp); 76 | if (status.code != Status.OK) 77 | addr = m_evLoop.getAddressFromIP(ip, port, !ipv6, tcp); 78 | return addr; 79 | } 80 | 81 | /** Blocks until the hostname is resolved, unless it's invalid. */ 82 | NetworkAddress resolveHost(in string host, ushort port = 0, isIPv6 ipv6 = isIPv6.no, isTCP tcp = isTCP.yes, isForced force = isForced.yes) 83 | { 84 | if (!force) 85 | return m_evLoop.getAddressFromDNS(host, port, ipv6, tcp); 86 | NetworkAddress addr = m_evLoop.getAddressFromDNS(host, port, ipv6, tcp); 87 | if (status.code != Status.OK) 88 | addr = m_evLoop.getAddressFromDNS(host, port, !ipv6, tcp); 89 | return addr; 90 | } 91 | 92 | package: 93 | 94 | @property StatusInfo status() const 95 | { 96 | return m_evLoop.status; 97 | } 98 | 99 | @property string error() const 100 | { 101 | return m_evLoop.error; 102 | } 103 | 104 | /** Sends a request to underlying async DNS resolver. Non-blocking */ 105 | bool resolve(AsyncDNSRequest* req, in string host, ushort port = 0, isIPv6 ipv6 = isIPv6.no, isTCP tcp = isTCP.yes) 106 | { 107 | return m_evLoop.resolve(req, host, port, ipv6, tcp); 108 | } 109 | 110 | uint recvFrom(in fd_t fd, ubyte[] data, ref NetworkAddress addr) { 111 | return m_evLoop.recvFrom(fd, data, addr); 112 | } 113 | 114 | uint sendTo(in fd_t fd, in ubyte[] data, in NetworkAddress addr) { 115 | return m_evLoop.sendTo(fd, data, addr); 116 | } 117 | 118 | uint recv(in fd_t fd, ubyte[] data) 119 | { 120 | return m_evLoop.recv(fd, data); 121 | } 122 | 123 | pragma(inline, true) 124 | uint send(in fd_t fd, in ubyte[] data) 125 | { 126 | return m_evLoop.send(fd, data); 127 | } 128 | 129 | pragma(inline, true) 130 | uint read(in fd_t fd, ref ubyte[] data) 131 | { 132 | return m_evLoop.read(fd, data); 133 | } 134 | 135 | uint readChanges(in fd_t fd, ref DWChangeInfo[] dst) { 136 | return m_evLoop.readChanges(fd, dst); 137 | } 138 | 139 | uint write(in fd_t fd, in ubyte[] data) 140 | { 141 | return m_evLoop.write(fd, data); 142 | } 143 | 144 | uint watch(in fd_t fd, in WatchInfo info) { 145 | return m_evLoop.watch(fd, info); 146 | } 147 | 148 | bool unwatch(in fd_t fd, in fd_t wd) { 149 | return m_evLoop.unwatch(fd, wd); 150 | } 151 | 152 | bool broadcast(in fd_t fd, bool b) 153 | { 154 | return m_evLoop.broadcast(fd, b); 155 | } 156 | 157 | NetworkAddress localAddr(in fd_t fd, bool ipv6 = false) { 158 | return m_evLoop.localAddr(fd, ipv6); 159 | } 160 | 161 | bool notify(T)(in fd_t fd, T payload) 162 | if (is(T == shared AsyncSignal) || is(T == AsyncNotifier)) 163 | { 164 | return m_evLoop.notify(fd, payload); 165 | } 166 | 167 | bool setOption(T)(in fd_t fd, TCPOption option, in T val) { 168 | return m_evLoop.setOption(fd, option, val); 169 | } 170 | 171 | /*uint send(in ubyte[] data, in fd_t fd, in NetworkAddress dst) 172 | { 173 | return m_evLoop.send(data, fd, dst); 174 | }*/ 175 | 176 | bool closeSocket(fd_t fd, bool connected, bool listener = false) 177 | { 178 | return m_evLoop.closeSocket(fd, connected, listener); 179 | } 180 | 181 | bool run(AsyncEvent ctxt, EventHandler del) { 182 | return m_evLoop.run(ctxt, del); 183 | } 184 | 185 | fd_t run(AsyncTCPConnection ctxt, TCPEventHandler del) { 186 | return m_evLoop.run(ctxt, del); 187 | } 188 | 189 | fd_t run(AsyncTCPListener ctxt, TCPAcceptHandler del) { 190 | return m_evLoop.run(ctxt, del); 191 | } 192 | 193 | version (Posix) 194 | fd_t run(AsyncUDSConnection ctxt) { 195 | return m_evLoop.run(ctxt); 196 | } 197 | 198 | version (Posix) 199 | fd_t run(AsyncUDSListener ctxt) { 200 | return m_evLoop.run(ctxt); 201 | } 202 | 203 | fd_t run(AsyncSocket ctxt) { 204 | return m_evLoop.run(ctxt); 205 | } 206 | 207 | void submitRequest(AsyncAcceptRequest* ctxt) { 208 | m_evLoop.submitRequest(ctxt); 209 | } 210 | 211 | void submitRequest(AsyncReceiveRequest* ctxt) { 212 | m_evLoop.submitRequest(ctxt); 213 | } 214 | 215 | void submitRequest(AsyncSendRequest* ctxt) { 216 | m_evLoop.submitRequest(ctxt); 217 | } 218 | 219 | import libasync.internals.socket_compat : sockaddr, socklen_t; 220 | bool bind(AsyncSocket ctxt, sockaddr* addr, socklen_t addrlen) 221 | { 222 | return m_evLoop.bind(ctxt, addr, addrlen); 223 | } 224 | 225 | bool connect(AsyncSocket ctxt, sockaddr* addr, socklen_t addrlen) 226 | { 227 | return m_evLoop.connect(ctxt, addr, addrlen); 228 | } 229 | 230 | bool listen(AsyncSocket ctxt, int backlog) 231 | { 232 | return m_evLoop.listen(ctxt, backlog); 233 | } 234 | 235 | fd_t run(shared AsyncSignal ctxt) { 236 | return m_evLoop.run(ctxt); 237 | } 238 | 239 | fd_t run(AsyncNotifier ctxt) { 240 | return m_evLoop.run(ctxt); 241 | } 242 | 243 | fd_t run(AsyncTimer ctxt, TimerHandler del, Duration timeout) { 244 | return m_evLoop.run(ctxt, del, timeout); 245 | } 246 | 247 | fd_t run(AsyncUDPSocket ctxt, UDPHandler del) { 248 | return m_evLoop.run(ctxt, del); 249 | } 250 | 251 | fd_t run(AsyncDirectoryWatcher ctxt, DWHandler del) { 252 | return m_evLoop.run(ctxt, del); 253 | } 254 | 255 | version (Posix) 256 | AsyncUDSConnection accept(AsyncUDSListener ctxt) { 257 | return m_evLoop.accept(ctxt); 258 | } 259 | 260 | bool kill(AsyncEvent obj, bool forced = true) { 261 | return m_evLoop.kill(obj, forced); 262 | } 263 | 264 | bool kill(AsyncDirectoryWatcher obj) { 265 | return m_evLoop.kill(obj); 266 | } 267 | 268 | bool kill(AsyncSocket obj, bool forced = false) { 269 | return m_evLoop.kill(obj, forced); 270 | } 271 | 272 | bool kill(AsyncTCPConnection obj, bool forced = false) { 273 | return m_evLoop.kill(obj, forced); 274 | } 275 | 276 | bool kill(AsyncTCPListener obj) { 277 | return m_evLoop.kill(obj); 278 | } 279 | 280 | bool kill(shared AsyncSignal obj) { 281 | return m_evLoop.kill(obj); 282 | } 283 | 284 | bool kill(AsyncNotifier obj) { 285 | return m_evLoop.kill(obj); 286 | } 287 | 288 | bool kill(AsyncTimer obj) { 289 | return m_evLoop.kill(obj); 290 | } 291 | 292 | bool kill(AsyncUDPSocket obj) { 293 | return m_evLoop.kill(obj); 294 | } 295 | 296 | /** 297 | Runs the event loop once and returns false if a an unrecoverable error occured 298 | Using a value of 0 will return immediately, while a value of -1.seconds will block indefinitely 299 | */ 300 | public bool loop(Duration max_timeout = 100.msecs) 301 | { 302 | version(Libasync_Threading) 303 | if(!m_asyncThreadsStarted) { 304 | if(!spawnAsyncThreads()) { 305 | return false; 306 | } 307 | m_asyncThreadsStarted = true; 308 | } 309 | 310 | if (!m_evLoop.loop(max_timeout) && m_evLoop.status.code == Status.EVLOOP_FAILURE) { 311 | return false; 312 | } 313 | 314 | return true; 315 | } 316 | 317 | } 318 | -------------------------------------------------------------------------------- /source/libasync/file.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.file; 3 | import libasync.types; 4 | import libasync.events; 5 | import core.thread : Thread, ThreadGroup; 6 | import core.sync.mutex; 7 | import core.sync.condition; 8 | import std.stdio; 9 | import core.atomic; 10 | import libasync.internals.path; 11 | import libasync.threads; 12 | import std.file; 13 | import std.conv : to; 14 | import libasync.internals.logging; 15 | 16 | /// Runs all blocking file I/O commands in a thread pool and calls the handler 17 | /// upon completion. 18 | shared final class AsyncFile 19 | { 20 | nothrow: 21 | private: 22 | EventLoop m_evLoop; 23 | bool m_busy; 24 | bool m_error; 25 | FileReadyHandler m_handler; 26 | FileCmdInfo m_cmdInfo; 27 | StatusInfo m_status; 28 | ulong m_cursorOffset; 29 | Thread m_owner; 30 | File* m_file; 31 | 32 | public: 33 | /// 34 | this(EventLoop evl) { 35 | m_evLoop = cast(shared) evl; 36 | m_cmdInfo.ready = new shared AsyncSignal(cast(EventLoop)m_evLoop); 37 | m_cmdInfo.ready.run(cast(void delegate())&handler); 38 | m_owner = cast(shared)Thread.getThis(); 39 | m_file = cast(shared)new File; 40 | try m_cmdInfo.mtx = cast(shared) new Mutex; catch (Exception) {} 41 | } 42 | 43 | /// Cleans up the underlying resources. todo: make this dispose? 44 | bool kill() { 45 | scope(failure) assert(false); 46 | if (file.isOpen) 47 | (cast()*m_file).close(); 48 | (cast()*m_file).__dtor(); 49 | m_cmdInfo.ready.kill(); 50 | m_cmdInfo = typeof(m_cmdInfo).init; 51 | m_handler = typeof(m_handler).init; 52 | return true; 53 | } 54 | 55 | /// 56 | synchronized @property StatusInfo status() const 57 | { 58 | return cast(StatusInfo) m_status; 59 | } 60 | 61 | /// 62 | @property string error() const 63 | { 64 | return status.text; 65 | } 66 | 67 | /// Retrieve the buffer from the last command. Must be called upon completion. 68 | shared(ubyte[]) buffer() { 69 | try synchronized(m_cmdInfo.mtx) 70 | return m_cmdInfo.buffer; 71 | catch (Exception) {} 72 | return null; 73 | } 74 | 75 | /// The current offset updated after the command execution 76 | synchronized @property ulong offset() const { 77 | return m_cursorOffset; 78 | } 79 | 80 | /// Sets the handler called by the owner thread's event loop after the command is completed. 81 | shared(typeof(this)) onReady(void delegate() del) { 82 | shared FileReadyHandler handler; 83 | handler.del = del; 84 | handler.ctxt = this; 85 | try synchronized(this) m_handler = handler; catch (Exception) {} 86 | return this; 87 | } 88 | 89 | /// Creates a new buffer with the specified length and uses it to read the 90 | /// file data at the specified path starting at the specified offset byte. 91 | bool read(string file_path, size_t len = 128, ulong off = -1, bool create_if_not_exists = true, bool truncate_if_exists = false) { 92 | return read(file_path, new shared ubyte[len], off, create_if_not_exists, truncate_if_exists); 93 | } 94 | 95 | /// Reads the file into the buffer starting at offset byte position. 96 | bool read(string file_path, shared ubyte[] buffer, ulong off = -1, bool create_if_not_exists = true, bool truncate_if_exists = false) 97 | in { 98 | assert(!m_busy, "File is busy or closed"); 99 | assert(m_handler.ctxt !is null, "AsyncFile must be run before being operated on."); 100 | } 101 | do { 102 | if (buffer.length == 0) { 103 | try m_handler(); catch (Exception) { } 104 | return true; 105 | } 106 | try { 107 | static string last_path; 108 | static string last_native_path; 109 | if (last_path == file_path) 110 | file_path = last_native_path; 111 | else { 112 | last_path = file_path; 113 | file_path = Path(file_path).toNativeString(); 114 | last_native_path = file_path; 115 | } 116 | 117 | bool flag; 118 | if (create_if_not_exists && !m_file && !exists(file_path)) 119 | flag = true; 120 | else if (truncate_if_exists && (m_file || exists(file_path))) 121 | flag = true; 122 | if (flag) // touch 123 | { 124 | if (file.isOpen) 125 | file.close(); 126 | import core.stdc.stdio; 127 | import std.string : toStringz; 128 | FILE * f = fopen(file_path.toStringz, "w\0".ptr); 129 | fclose(f); 130 | } 131 | 132 | if (!file.isOpen || m_cmdInfo.command != FileCmd.READ) { 133 | auto tmp = File(file_path, "rb"); 134 | file = tmp; 135 | m_cmdInfo.command = FileCmd.READ; 136 | } 137 | 138 | version(Libasync_Threading) 139 | if (buffer.length > 65_536) { 140 | synchronized(m_cmdInfo.mtx) { 141 | m_cmdInfo.buffer = buffer; 142 | m_cmdInfo.command = FileCmd.READ; 143 | filePath = Path(file_path); 144 | } 145 | offset = off; 146 | 147 | return doOffThread({ process(this); }); 148 | } 149 | 150 | { 151 | m_cmdInfo.buffer = cast(shared(ubyte[])) buffer; 152 | 153 | if (off != -1) 154 | file.seek(cast(long)off); 155 | ubyte[] res; 156 | res = file.rawRead(cast(ubyte[])buffer); 157 | if (res) 158 | m_cursorOffset = cast(shared(ulong)) (off + res.length); 159 | m_handler(); 160 | return true; 161 | } 162 | } catch (Exception e) { 163 | auto status = StatusInfo.init; 164 | status.code = Status.ERROR; 165 | try status.text = "Error in read, " ~ e.toString(); catch (Exception) {} 166 | m_status = cast(shared) status; 167 | try m_handler(); catch (Exception) { } 168 | return false; 169 | } 170 | } 171 | 172 | /// Writes the data from the buffer into the file at the specified path starting at the 173 | /// given offset byte position. 174 | bool write(string file_path, shared const(ubyte)[] buffer, ulong off = -1, bool create_if_not_exists = true, bool truncate_if_exists = false) 175 | in { 176 | assert(!m_busy, "File is busy or closed"); 177 | assert(m_handler.ctxt !is null, "AsyncFile must be run before being operated on."); 178 | } 179 | do { 180 | if (buffer.length == 0) { 181 | try m_handler(); catch (Exception) { return false; } 182 | return true; 183 | } 184 | try { 185 | 186 | static string last_path; 187 | static string last_native_path; 188 | if (last_path == file_path) 189 | file_path = last_native_path; 190 | else { 191 | last_path = file_path; 192 | file_path = Path(file_path).toNativeString(); 193 | last_native_path = file_path; 194 | } 195 | 196 | bool flag; 197 | if (create_if_not_exists && !m_file && !exists(file_path)) 198 | flag = true; 199 | else if (truncate_if_exists && (m_file || exists(file_path))) 200 | flag = true; 201 | if (flag) // touch 202 | { 203 | if (file.isOpen) 204 | file.close(); 205 | import core.stdc.stdio; 206 | import std.string : toStringz; 207 | FILE * f = fopen(file_path.toStringz, "w\0".ptr); 208 | fclose(f); 209 | } 210 | 211 | if (!file.isOpen || m_cmdInfo.command != FileCmd.WRITE) { 212 | auto tmp = File(file_path, "r+b"); 213 | file = tmp; 214 | m_cmdInfo.command = FileCmd.WRITE; 215 | } 216 | 217 | version(Libasync_Threading) 218 | if (buffer.length > 65_536) { 219 | synchronized(m_cmdInfo.mtx) { 220 | m_cmdInfo.buffer = cast(shared(ubyte[])) buffer; 221 | m_cmdInfo.command = FileCmd.WRITE; 222 | filePath = Path(file_path); 223 | } 224 | offset = off; 225 | 226 | return doOffThread({ process(this); }); 227 | } 228 | 229 | m_cmdInfo.buffer = cast(shared(ubyte[])) buffer; 230 | if (off != -1) 231 | file.seek(cast(long)off); 232 | file.rawWrite(cast(ubyte[])buffer); 233 | file.flush(); 234 | m_cursorOffset = cast(shared(ulong)) (off + buffer.length); 235 | m_handler(); 236 | return true; 237 | } catch (Exception e) { 238 | auto status = StatusInfo.init; 239 | status.code = Status.ERROR; 240 | try status.text = "Error in write, " ~ e.toString(); catch (Exception) {} 241 | m_status = cast(shared) status; 242 | return false; 243 | } 244 | } 245 | 246 | /// Appends the data from the buffer into a file at the specified path. 247 | bool append(string file_path, shared ubyte[] buffer, bool create_if_not_exists = true, bool truncate_if_exists = false) 248 | in { 249 | assert(!m_busy, "File is busy or closed"); 250 | assert(m_handler.ctxt !is null, "AsyncFile must be run before being operated on."); 251 | } 252 | do { 253 | if (buffer.length == 0) { 254 | try m_handler(); catch (Exception) { return false; } 255 | return true; 256 | } 257 | try { 258 | static string last_path; 259 | static string last_native_path; 260 | if (last_path == file_path) 261 | file_path = last_native_path; 262 | else { 263 | last_path = file_path; 264 | file_path = Path(file_path).toNativeString(); 265 | last_native_path = file_path; 266 | } 267 | 268 | bool flag; 269 | if (create_if_not_exists && !m_file && !exists(file_path)) 270 | flag = true; 271 | else if (truncate_if_exists && (m_file || exists(file_path))) 272 | flag = true; 273 | if (flag) // touch 274 | { 275 | if (file.isOpen) 276 | file.close(); 277 | import core.stdc.stdio; 278 | import std.string : toStringz; 279 | FILE * f = fopen(file_path.toStringz, "w\0".ptr); 280 | fclose(f); 281 | } 282 | 283 | if (!file.isOpen || m_cmdInfo.command != FileCmd.APPEND) { 284 | auto tmp = File(file_path, "a+"); 285 | file = tmp; 286 | m_cmdInfo.command = FileCmd.APPEND; 287 | } 288 | 289 | version(Libasync_Threading) 290 | if (buffer.length > 65_536) { 291 | synchronized(m_cmdInfo.mtx) { 292 | m_cmdInfo.buffer = cast(shared(ubyte[])) buffer; 293 | m_cmdInfo.command = FileCmd.APPEND; 294 | filePath = Path(file_path); 295 | } 296 | 297 | return doOffThread({ process(this); }); 298 | } 299 | 300 | m_cmdInfo.buffer = cast(shared(ubyte[])) buffer; 301 | file.rawWrite(cast(ubyte[]) buffer); 302 | m_cursorOffset = cast(shared(ulong)) file.size(); 303 | file.flush(); 304 | m_handler(); 305 | return true; 306 | } catch (Exception e) { 307 | auto status = StatusInfo.init; 308 | status.code = Status.ERROR; 309 | try status.text = "Error in append, " ~ e.toString(); catch (Exception) {} 310 | m_status = cast(shared) status; 311 | return false; 312 | } 313 | } 314 | 315 | package: 316 | 317 | synchronized @property FileCmdInfo cmdInfo() { 318 | return m_cmdInfo; 319 | } 320 | 321 | synchronized @property Path filePath() { 322 | return cast(Path) m_cmdInfo.filePath; 323 | } 324 | 325 | synchronized @property bool waiting() const { 326 | return cast(bool) m_busy; 327 | } 328 | 329 | synchronized @property void filePath(Path file_path) { 330 | m_cmdInfo.filePath = cast(shared) file_path; 331 | } 332 | 333 | synchronized @property File file() { 334 | scope(failure) assert(false); 335 | return cast()*m_file; 336 | } 337 | 338 | synchronized @property void file(ref File f) { 339 | try (cast()*m_file).opAssign(f); 340 | catch (Exception e) { 341 | trace(e.msg); 342 | } 343 | } 344 | 345 | synchronized @property void status(StatusInfo stat) { 346 | m_status = cast(shared) stat; 347 | } 348 | 349 | synchronized @property void offset(ulong val) { 350 | m_cursorOffset = cast(shared) val; 351 | } 352 | 353 | synchronized @property void waiting(bool b) { 354 | m_busy = cast(shared) b; 355 | } 356 | 357 | void handler() { 358 | try m_handler(); 359 | catch (Throwable e) { 360 | trace("Failed to send command. ", e.toString()); 361 | } 362 | } 363 | } 364 | 365 | package enum FileCmd { 366 | READ, 367 | WRITE, 368 | APPEND 369 | } 370 | 371 | package shared struct FileCmdInfo 372 | { 373 | FileCmd command; 374 | Path filePath; 375 | ubyte[] buffer; 376 | AsyncSignal ready; 377 | AsyncFile file; 378 | Mutex mtx; // for buffer writing 379 | } 380 | 381 | package shared struct FileReadyHandler { 382 | AsyncFile ctxt; 383 | void delegate() del; 384 | 385 | void opCall() { 386 | assert(ctxt !is null); 387 | ctxt.waiting = false; 388 | del(); 389 | return; 390 | } 391 | } 392 | 393 | private void process(shared AsyncFile ctxt) { 394 | auto cmdInfo = ctxt.cmdInfo; 395 | auto mutex = cmdInfo.mtx; 396 | FileCmd cmd; 397 | cmd = cmdInfo.command; 398 | 399 | try final switch (cmd) 400 | { 401 | case FileCmd.READ: 402 | File file = ctxt.file; 403 | if (ctxt.offset != -1) 404 | file.seek(cast(long) ctxt.offset); 405 | ubyte[] res; 406 | synchronized(mutex) res = file.rawRead(cast(ubyte[])ctxt.buffer); 407 | if (res) 408 | ctxt.offset = cast(ulong) (ctxt.offset + res.length); 409 | 410 | break; 411 | 412 | case FileCmd.WRITE: 413 | 414 | File file = cast(File) ctxt.file; 415 | if (ctxt.offset != -1) 416 | file.seek(cast(long) ctxt.offset); 417 | synchronized(mutex) file.rawWrite(cast(ubyte[]) ctxt.buffer); 418 | file.flush(); 419 | ctxt.offset = cast(ulong) (ctxt.offset + ctxt.buffer.length); 420 | break; 421 | 422 | case FileCmd.APPEND: 423 | File file = cast(File) ctxt.file; 424 | synchronized(mutex) file.rawWrite(cast(ubyte[]) ctxt.buffer); 425 | ctxt.offset = cast(ulong) file.size(); 426 | file.flush(); 427 | break; 428 | } catch (Throwable e) { 429 | auto status = StatusInfo.init; 430 | status.code = Status.ERROR; 431 | try status.text = "Error in " ~ cmd.to!string ~ ", " ~ e.toString(); catch (Exception) {} 432 | ctxt.status = status; 433 | } 434 | 435 | try cmdInfo.ready.trigger(getThreadEventLoop()); 436 | catch (Throwable e) { 437 | trace("AsyncFile Thread Error: ", e.toString()); 438 | auto status = StatusInfo.init; 439 | status.code = Status.ERROR; 440 | try status.text = e.toString(); catch (Exception) {} 441 | ctxt.status = status; 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /source/libasync/internals/epoll.d: -------------------------------------------------------------------------------- 1 | /** 2 | * D header file to interface with the Linux epoll API (http://man7.org/linux/man-pages/man7/epoll.7.html). 3 | * Available since Linux 2.6 4 | * 5 | * Copyright: Copyright Adil Baig 2012. 6 | * License : $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 | * Authors : Adil Baig (github.com/adilbaig) 8 | */ 9 | module libasync.internals.epoll; 10 | 11 | public import core.sys.linux.epoll; 12 | 13 | version (linux): 14 | 15 | extern (C): 16 | @system: 17 | nothrow: 18 | 19 | // Backport fix from druntime commit c94547af8d55e1490b51aac358171b761da6a657 20 | static if (__VERSION__ < 2071) 21 | { 22 | version (X86) immutable PackedEvent = true; 23 | else version (X86_64) immutable PackedEvent = true; 24 | else version (ARM) immutable PackedEvent = false; 25 | else version (AArch64) immutable PackedEvent = false; 26 | else version (PPC) immutable PackedEvent = false; 27 | else version (PPC64) immutable PackedEvent = false; 28 | else version (MIPS64) immutable PackedEvent = false; 29 | else version (SystemZ) immutable PackedEvent = false; 30 | else static assert(false, "Platform not supported"); 31 | 32 | static if (PackedEvent) 33 | { 34 | align(1) struct epoll_event 35 | { 36 | align(1): 37 | uint events; 38 | epoll_data_t data; 39 | } 40 | } 41 | else 42 | { 43 | struct epoll_event 44 | { 45 | uint events; 46 | epoll_data_t data; 47 | } 48 | } 49 | 50 | int epoll_ctl (int epfd, int op, int fd, epoll_event *event); 51 | int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout); 52 | } 53 | 54 | enum EFD_NONBLOCK = 0x800; 55 | enum EPOLL_CLOEXEC = 0x80000; 56 | 57 | int eventfd (uint initval, int flags); 58 | 59 | // Available as of druntime commit d4ef137ffd1a92e003b45d5a53958322d317271c 60 | static if (__VERSION__ >= 2070) 61 | { 62 | public import core.sys.linux.timerfd : timerfd_create, timerfd_settime; 63 | } 64 | else 65 | { 66 | int timerfd_create (int clockid, int flags); 67 | int timerfd_settime (int fd, int flags, itimerspec* new_value, itimerspec* old_value); 68 | } 69 | 70 | import core.sys.posix.time : itimerspec; 71 | 72 | import core.sys.posix.pthread : pthread_t; 73 | import core.sys.posix.signal : sigval; 74 | int pthread_sigqueue(pthread_t, int sig, in sigval); 75 | -------------------------------------------------------------------------------- /source/libasync/internals/freelist.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.freelist; 2 | 3 | import std.traits : isIntegral; 4 | 5 | mixin template FreeList(alias Limit) 6 | if (is(typeof(Limit) == typeof(null)) || isIntegral!(typeof(Limit))) 7 | { 8 | static if (!is(typeof(this) == struct)) static assert(false, "FreeList only works on structs"); 9 | 10 | alias T = typeof(this); 11 | 12 | public: 13 | import memutils.utils : ThreadMem; 14 | import std.exception : assumeWontThrow; 15 | static T* alloc(Args...)(auto ref Args args) @trusted 16 | { 17 | return assumeWontThrow(ThreadMem.alloc!T(args)); 18 | } 19 | 20 | static void free(T* obj) @trusted 21 | { 22 | assumeWontThrow(ThreadMem.free(obj)); 23 | } 24 | } 25 | mixin template UnlimitedFreeList() { mixin FreeList!null; } -------------------------------------------------------------------------------- /source/libasync/internals/kqueue.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.kqueue; 2 | import libasync.types; 3 | nothrow: 4 | import core.stdc.stdint : intptr_t, uintptr_t; 5 | version(Posix) 6 | public import core.sys.posix.signal : timespec; 7 | 8 | version (FreeBSD) { 9 | public import core.sys.freebsd.sys.event; 10 | immutable HasKqueue = true; 11 | } 12 | version (DragonFlyBSD) { 13 | public import core.sys.dragonflybsd.sys.event; 14 | immutable HasKqueue = true; 15 | } 16 | static if (is_OSX || is_iOS) { 17 | static if (__VERSION__ >= 2090) 18 | public import core.sys.darwin.sys.event; 19 | immutable HasKqueue = true; 20 | } 21 | else immutable HasKqueue = false; 22 | 23 | extern(C): 24 | @nogc: 25 | 26 | enum O_EVTONLY = 0x8000; 27 | static if (__VERSION__ < 2090): 28 | /* While these are in druntime, they are - as of 2.071 - not marked as @nogc. 29 | * So declare them here as such for platforms with kqueue. */ 30 | static if (HasKqueue) { 31 | int kqueue(); 32 | int kevent(int kq, const kevent_t *changelist, int nchanges, 33 | kevent_t *eventlist, int nevents, 34 | const timespec *timeout); 35 | } 36 | 37 | version(OSX): 38 | enum : short 39 | { 40 | EVFILT_READ = -1, 41 | EVFILT_WRITE = -2, 42 | EVFILT_AIO = -3, /* attached to aio requests */ 43 | EVFILT_VNODE = -4, /* attached to vnodes */ 44 | EVFILT_PROC = -5, /* attached to struct proc */ 45 | EVFILT_SIGNAL = -6, /* attached to struct proc */ 46 | EVFILT_TIMER = -7, /* timers */ 47 | EVFILT_MACHPORT = -8, /* Mach portsets */ 48 | EVFILT_FS = -9, /* filesystem events */ 49 | EVFILT_USER = -10, /* User events */ 50 | EVFILT_VM = -12, /* virtual memory events */ 51 | EVFILT_SYSCOUNT = 11 52 | } 53 | 54 | extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) 55 | { 56 | *kevp = kevent_t(args); 57 | } 58 | 59 | struct kevent_t 60 | { 61 | uintptr_t ident; /* identifier for this event */ 62 | short filter; /* filter for event */ 63 | ushort flags; 64 | uint fflags; 65 | intptr_t data; 66 | void *udata; /* opaque user data identifier */ 67 | } 68 | 69 | enum : ushort 70 | { 71 | /* actions */ 72 | EV_ADD = 0x0001, /* add event to kq (implies enable) */ 73 | EV_DELETE = 0x0002, /* delete event from kq */ 74 | EV_ENABLE = 0x0004, /* enable event */ 75 | EV_DISABLE = 0x0008, /* disable event (not reported) */ 76 | 77 | /* flags */ 78 | EV_ONESHOT = 0x0010, /* only report one occurrence */ 79 | EV_CLEAR = 0x0020, /* clear event state after reporting */ 80 | EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */ 81 | EV_DISPATCH = 0x0080, /* disable event after reporting */ 82 | 83 | EV_SYSFLAGS = 0xF000, /* reserved by system */ 84 | EV_FLAG1 = 0x2000, /* filter-specific flag */ 85 | 86 | /* returned values */ 87 | EV_EOF = 0x8000, /* EOF detected */ 88 | EV_ERROR = 0x4000, /* error, data contains errno */ 89 | } 90 | 91 | enum : uint 92 | { 93 | /* 94 | * data/hint flags/masks for EVFILT_USER, shared with userspace 95 | * 96 | * On input, the top two bits of fflags specifies how the lower twenty four 97 | * bits should be applied to the stored value of fflags. 98 | * 99 | * On output, the top two bits will always be set to NOTE_FFNOP and the 100 | * remaining twenty four bits will contain the stored fflags value. 101 | */ 102 | NOTE_FFNOP = 0x00000000, /* ignore input fflags */ 103 | NOTE_FFAND = 0x40000000, /* AND fflags */ 104 | NOTE_FFOR = 0x80000000, /* OR fflags */ 105 | NOTE_FFCOPY = 0xc0000000, /* copy fflags */ 106 | NOTE_FFCTRLMASK = 0xc0000000, /* masks for operations */ 107 | NOTE_FFLAGSMASK = 0x00ffffff, 108 | 109 | NOTE_TRIGGER = 0x01000000, /* Cause the event to be 110 | triggered for output. */ 111 | 112 | /* 113 | * data/hint flags for EVFILT_{READ|WRITE}, shared with userspace 114 | */ 115 | NOTE_LOWAT = 0x0001, /* low water mark */ 116 | 117 | /* 118 | * data/hint flags for EVFILT_VNODE, shared with userspace 119 | */ 120 | NOTE_DELETE = 0x0001, /* vnode was removed */ 121 | NOTE_WRITE = 0x0002, /* data contents changed */ 122 | NOTE_EXTEND = 0x0004, /* size increased */ 123 | NOTE_ATTRIB = 0x0008, /* attributes changed */ 124 | NOTE_LINK = 0x0010, /* link count changed */ 125 | NOTE_RENAME = 0x0020, /* vnode was renamed */ 126 | NOTE_REVOKE = 0x0040, /* vnode access was revoked */ 127 | 128 | /* 129 | * data/hint flags for EVFILT_PROC, shared with userspace 130 | */ 131 | NOTE_EXIT = 0x80000000, /* process exited */ 132 | NOTE_FORK = 0x40000000, /* process forked */ 133 | NOTE_EXEC = 0x20000000, /* process exec'd */ 134 | NOTE_PCTRLMASK = 0xf0000000, /* mask for hint bits */ 135 | NOTE_PDATAMASK = 0x000fffff, /* mask for pid */ 136 | 137 | /* additional flags for EVFILT_PROC */ 138 | NOTE_TRACK = 0x00000001, /* follow across forks */ 139 | NOTE_TRACKERR = 0x00000002, /* could not track child */ 140 | NOTE_CHILD = 0x00000004, /* am a child process */ 141 | } 142 | -------------------------------------------------------------------------------- /source/libasync/internals/logging.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.logging; 2 | 3 | import libasync.types; 4 | static if (__VERSION__ < 2103){ 5 | import std.stdio : stdout; 6 | import std.experimental.logger; 7 | } 8 | else { 9 | import std.stdio : stdout; 10 | import std.logger; 11 | } 12 | static __gshared FileLogger sharedLog; 13 | static this() { 14 | sharedLog = new FileLogger(stdout, LOGLEVEL); 15 | } 16 | 17 | nothrow: 18 | // The below is adapted for nothrow from 19 | // https://github.com/dlang/phobos/blob/master/std/experimental/logger/core.d 20 | // and is thus under Boost license 21 | 22 | template defaultLogFunction(LogLevel ll) 23 | { 24 | void defaultLogFunction(int line = __LINE__, string file = __FILE__, 25 | string funcName = __FUNCTION__, 26 | string prettyFuncName = __PRETTY_FUNCTION__, 27 | string moduleName = __MODULE__, A...)(lazy A args) @trusted 28 | if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0) 29 | { 30 | static if (ll >= LOGLEVEL) 31 | { 32 | try sharedLog.log!(line, file, funcName, prettyFuncName, moduleName)("", args); 33 | catch (Throwable e) {} 34 | } 35 | } 36 | 37 | void defaultLogFunction(int line = __LINE__, string file = __FILE__, 38 | string funcName = __FUNCTION__, 39 | string prettyFuncName = __PRETTY_FUNCTION__, 40 | string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) @trusted 41 | { 42 | static if (ll >= LOGLEVEL) 43 | { 44 | try sharedLog.log!(line, file, funcName, prettyFuncName, moduleName)(condition, args); 45 | catch (Throwable e) {} 46 | } 47 | } 48 | } 49 | 50 | alias trace = defaultLogFunction!(LogLevel.trace); 51 | alias info = defaultLogFunction!(LogLevel.info); 52 | alias warning = defaultLogFunction!(LogLevel.warning); 53 | alias error = defaultLogFunction!(LogLevel.error); 54 | alias critical = defaultLogFunction!(LogLevel.critical); 55 | 56 | template defaultLogFunctionf(LogLevel ll) 57 | { 58 | void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, 59 | string funcName = __FUNCTION__, 60 | string prettyFuncName = __PRETTY_FUNCTION__, 61 | string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) @trusted 62 | { 63 | static if (ll >= LOGLEVEL) 64 | { 65 | try sharedLog.logf!(line, file, funcName, prettyFuncName, moduleName)(msg, args); 66 | catch (Throwable e) {} 67 | } 68 | } 69 | 70 | void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, 71 | string funcName = __FUNCTION__, 72 | string prettyFuncName = __PRETTY_FUNCTION__, 73 | string moduleName = __MODULE__, A...)(lazy bool condition, lazy string msg, lazy A args) @trusted 74 | { 75 | static if (ll >= LOGLEVEL) 76 | { 77 | try sharedLog.logf!(line, file, funcName, prettyFuncName, moduleName)(condition, msg, args); 78 | catch (Throwable e) {} 79 | } 80 | } 81 | } 82 | 83 | alias tracef = defaultLogFunctionf!(LogLevel.trace); 84 | alias infof = defaultLogFunctionf!(LogLevel.info); 85 | alias warningf = defaultLogFunctionf!(LogLevel.warning); 86 | alias errorf = defaultLogFunctionf!(LogLevel.error); 87 | alias criticalf = defaultLogFunctionf!(LogLevel.critical); 88 | -------------------------------------------------------------------------------- /source/libasync/internals/path.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.path; 2 | /** 3 | Contains routines for high level path handling. 4 | Copyright: © 2012 RejectedSoftware e.K. 5 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 | Authors: Sönke Ludwig 7 | */ 8 | import std.algorithm : canFind, min; 9 | import std.array; 10 | import std.conv; 11 | import std.exception; 12 | import std.string; 13 | /** 14 | Represents an absolute or relative file system path. 15 | This struct allows to do safe operations on paths, such as concatenation and sub paths. Checks 16 | are done to disallow invalid operations such as concatenating two absolute paths. It also 17 | validates path strings and allows for easy checking of malicious relative paths. 18 | */ 19 | struct Path { 20 | private { 21 | immutable(PathEntry)[] m_nodes; 22 | bool m_absolute = false; 23 | bool m_endsWithSlash = false; 24 | } 25 | /// Constructs a Path object by parsing a path string. 26 | this(string pathstr) 27 | { 28 | m_nodes = cast(immutable)splitPath(pathstr); 29 | m_absolute = (pathstr.startsWith("/") || m_nodes.length > 0 && (m_nodes[0].toString().canFind(':') || m_nodes[0] == "\\")); 30 | m_endsWithSlash = pathstr.endsWith("/"); 31 | } 32 | /// Constructs a path object from a list of PathEntry objects. 33 | this(immutable(PathEntry)[] nodes, bool absolute) 34 | { 35 | m_nodes = nodes; 36 | m_absolute = absolute; 37 | } 38 | /// Constructs a relative path with one path entry. 39 | this(PathEntry entry){ 40 | m_nodes = [entry]; 41 | m_absolute = false; 42 | } 43 | /// Determines if the path is absolute. 44 | @property bool absolute() const { return m_absolute; } 45 | /// Resolves all '.' and '..' path entries as far as possible. 46 | void normalize() 47 | { 48 | immutable(PathEntry)[] newnodes; 49 | foreach( n; m_nodes ){ 50 | switch(n.toString()){ 51 | default: 52 | newnodes ~= n; 53 | break; 54 | case "", ".": break; 55 | case "..": 56 | enforce(!m_absolute || newnodes.length > 0, "Path goes below root node."); 57 | if( newnodes.length > 0 && newnodes[$-1] != ".." ) newnodes = newnodes[0 .. $-1]; 58 | else newnodes ~= n; 59 | break; 60 | } 61 | } 62 | m_nodes = newnodes; 63 | } 64 | /// Converts the Path back to a string representation using slashes. 65 | string toString() 66 | const { 67 | if( m_nodes.empty ) return absolute ? "/" : ""; 68 | Appender!string ret; 69 | // for absolute paths start with / 70 | if( absolute ) ret.put('/'); 71 | foreach( i, f; m_nodes ){ 72 | if( i > 0 ) ret.put('/'); 73 | ret.put(f.toString()); 74 | } 75 | if( m_nodes.length > 0 && m_endsWithSlash ) 76 | ret.put('/'); 77 | if (ret.data.length > 1 && ret.data[$-1] == '.') 78 | return ret.data[0 .. $-1]; 79 | return ret.data; 80 | } 81 | /// Converts the Path object to a native path string (backslash as path separator on Windows). 82 | string toNativeString() 83 | const { 84 | Appender!string ret; 85 | // for absolute unix paths start with / 86 | version(Posix) { if(absolute) ret.put('/'); } 87 | foreach( i, f; m_nodes ){ 88 | version(Windows) { if( i > 0 ) ret.put('\\'); } 89 | version(Posix) { if( i > 0 ) ret.put('/'); } 90 | else { enforce("Unsupported OS"); } 91 | ret.put(f.toString()); 92 | } 93 | if( m_nodes.length > 0 && m_endsWithSlash ){ 94 | version(Windows) { ret.put('\\'); } 95 | version(Posix) { ret.put('/'); } 96 | } 97 | if (ret.data.length > 1 && ret.data[$-1] == '.') 98 | return ret.data[0 .. $-1]; 99 | return ret.data; 100 | } 101 | /// Tests if `rhs` is an anchestor or the same as this path. 102 | bool startsWith(const Path rhs) const { 103 | if( rhs.m_nodes.length > m_nodes.length ) return false; 104 | foreach( i; 0 .. rhs.m_nodes.length ) 105 | if( m_nodes[i] != rhs.m_nodes[i] ) 106 | return false; 107 | return true; 108 | } 109 | /// Computes the relative path from `parentPath` to this path. 110 | Path relativeTo(const Path parentPath) const { 111 | assert(this.absolute && parentPath.absolute); 112 | version(Windows){ 113 | // a path such as ..\C:\windows is not valid, so force the path to stay absolute in this case 114 | if( this.absolute && !this.empty && 115 | (m_nodes[0].toString().endsWith(":") && !parentPath.startsWith(this[0 .. 1]) || 116 | m_nodes[0] == "\\" && !parentPath.startsWith(this[0 .. min(2, $)]))) 117 | { 118 | return this; 119 | } 120 | } 121 | int nup = 0; 122 | while( parentPath.length > nup && !startsWith(parentPath[0 .. parentPath.length-nup]) ){ 123 | nup++; 124 | } 125 | Path ret = Path(null, false); 126 | ret.m_endsWithSlash = true; 127 | foreach( i; 0 .. nup ) ret ~= ".."; 128 | ret ~= Path(m_nodes[parentPath.length-nup .. $], false); 129 | ret.m_endsWithSlash = this.m_endsWithSlash; 130 | return ret; 131 | } 132 | /// The last entry of the path 133 | @property ref immutable(PathEntry) head() const { enforce(m_nodes.length > 0); return m_nodes[$-1]; } 134 | /// The parent path 135 | @property Path parentPath() const { return this[0 .. length-1]; } 136 | /// The ist of path entries of which this path is composed 137 | @property immutable(PathEntry)[] nodes() const { return m_nodes; } 138 | /// The number of path entries of which this path is composed 139 | @property size_t length() const { return m_nodes.length; } 140 | /// True if the path contains no entries 141 | @property bool empty() const { return m_nodes.length == 0; } 142 | /// Determines if the path ends with a slash (i.e. is a directory) 143 | @property bool endsWithSlash() const { return m_endsWithSlash; } 144 | /// ditto 145 | @property void endsWithSlash(bool v) { m_endsWithSlash = v; } 146 | /// Determines if this path goes outside of its base path (i.e. begins with '..'). 147 | @property bool external() const { return !m_absolute && m_nodes.length > 0 && m_nodes[0].m_name == ".."; } 148 | ref immutable(PathEntry) opIndex(size_t idx) const { return m_nodes[idx]; } 149 | Path opSlice(size_t start, size_t end) const { 150 | auto ret = Path(m_nodes[start .. end], start == 0 ? absolute : false); 151 | ret.m_endsWithSlash = end == m_nodes.length ? m_endsWithSlash : true; 152 | return ret; 153 | } 154 | size_t opDollar(int dim)() const if(dim == 0) { return m_nodes.length; } 155 | Path opBinary(string OP)(const Path rhs) const if( OP == "~" ) 156 | { 157 | assert(!rhs.absolute, "Trying to append absolute path."); 158 | if (!rhs.length) return this; 159 | Path ret; 160 | ret.m_nodes = m_nodes; 161 | ret.m_absolute = m_absolute; 162 | ret.m_endsWithSlash = rhs.m_endsWithSlash; 163 | ret.normalize(); // needed to avoid "."~".." become "" instead of ".." 164 | foreach (folder; rhs.m_nodes) { 165 | switch (folder.toString()) { 166 | default: ret.m_nodes = ret.m_nodes ~ folder; break; 167 | case "", ".": break; 168 | case "..": 169 | enforce(!ret.absolute || ret.m_nodes.length > 0, "Relative path goes below root node!"); 170 | if( ret.m_nodes.length > 0 && ret.m_nodes[$-1].toString() != ".." ) 171 | ret.m_nodes = ret.m_nodes[0 .. $-1]; 172 | else ret.m_nodes = ret.m_nodes ~ folder; 173 | break; 174 | } 175 | } 176 | return ret; 177 | } 178 | Path opBinary(string OP)(string rhs) const if( OP == "~" ) { return opBinary!"~"(Path(rhs)); } 179 | Path opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { return opBinary!"~"(Path(rhs)); } 180 | void opOpAssign(string OP)(string rhs) if( OP == "~" ) { opOpAssign!"~"(Path(rhs)); } 181 | void opOpAssign(string OP)(PathEntry rhs) if( OP == "~" ) { opOpAssign!"~"(Path(rhs)); } 182 | void opOpAssign(string OP)(immutable(PathEntry)[] rhs) if( OP == "~" ) { opOpAssign!"~"(Path(rhs, false)); } 183 | void opOpAssign(string OP)(Path rhs) if( OP == "~" ) 184 | { 185 | assert(!rhs.absolute, "Trying to append absolute path."); 186 | if (!rhs.length) return; 187 | auto p = this ~ rhs; 188 | m_nodes = p.m_nodes; 189 | m_endsWithSlash = rhs.m_endsWithSlash; 190 | } 191 | /// Tests two paths for equality using '=='. 192 | bool opEquals(ref const Path rhs) const { 193 | if( m_absolute != rhs.m_absolute ) return false; 194 | if( m_endsWithSlash != rhs.m_endsWithSlash ) return false; 195 | if( m_nodes.length != rhs.length ) return false; 196 | foreach( i; 0 .. m_nodes.length ) 197 | if( m_nodes[i] != rhs.m_nodes[i] ) 198 | return false; 199 | return true; 200 | } 201 | /// ditto 202 | bool opEquals(const Path other) const { return opEquals(other); } 203 | int opCmp(ref const Path rhs) const { 204 | if( m_absolute != rhs.m_absolute ) return cast(int)m_absolute - cast(int)rhs.m_absolute; 205 | foreach( i; 0 .. min(m_nodes.length, rhs.m_nodes.length) ) 206 | if( m_nodes[i] != rhs.m_nodes[i] ) 207 | return m_nodes[i].opCmp(rhs.m_nodes[i]); 208 | if( m_nodes.length > rhs.m_nodes.length ) return 1; 209 | if( m_nodes.length < rhs.m_nodes.length ) return -1; 210 | return 0; 211 | } 212 | hash_t toHash() 213 | const nothrow @trusted { 214 | hash_t ret; 215 | auto strhash = &typeid(string).getHash; 216 | try foreach (n; nodes) ret ^= strhash(&n.m_name); 217 | catch (Throwable) assert(false); 218 | if (m_absolute) ret ^= 0xfe3c1738; 219 | if (m_endsWithSlash) ret ^= 0x6aa4352d; 220 | return ret; 221 | } 222 | } 223 | unittest 224 | { 225 | { 226 | auto unc = "\\\\server\\share\\path"; 227 | auto uncp = Path(unc); 228 | uncp.normalize(); 229 | version(Windows) assert(uncp.toNativeString() == unc); 230 | assert(uncp.absolute); 231 | assert(!uncp.endsWithSlash); 232 | } 233 | { 234 | auto abspath = "/test/path/"; 235 | auto abspathp = Path(abspath); 236 | assert(abspathp.toString() == abspath); 237 | version(Windows) {} else assert(abspathp.toNativeString() == abspath); 238 | assert(abspathp.absolute); 239 | assert(abspathp.endsWithSlash); 240 | assert(abspathp.length == 2); 241 | assert(abspathp[0] == "test"); 242 | assert(abspathp[1] == "path"); 243 | } 244 | { 245 | auto relpath = "test/path/"; 246 | auto relpathp = Path(relpath); 247 | assert(relpathp.toString() == relpath); 248 | version(Windows) assert(relpathp.toNativeString() == "test\\path\\"); 249 | else assert(relpathp.toNativeString() == relpath); 250 | assert(!relpathp.absolute); 251 | assert(relpathp.endsWithSlash); 252 | assert(relpathp.length == 2); 253 | assert(relpathp[0] == "test"); 254 | assert(relpathp[1] == "path"); 255 | } 256 | { 257 | auto winpath = "C:\\windows\\test"; 258 | auto winpathp = Path(winpath); 259 | assert(winpathp.toString() == "/C:/windows/test"); 260 | version(Windows) assert(winpathp.toNativeString() == winpath); 261 | else assert(winpathp.toNativeString() == "/C:/windows/test"); 262 | assert(winpathp.absolute); 263 | assert(!winpathp.endsWithSlash); 264 | assert(winpathp.length == 3); 265 | assert(winpathp[0] == "C:"); 266 | assert(winpathp[1] == "windows"); 267 | assert(winpathp[2] == "test"); 268 | } 269 | { 270 | auto dotpath = "/test/../test2/././x/y"; 271 | auto dotpathp = Path(dotpath); 272 | assert(dotpathp.toString() == "/test/../test2/././x/y"); 273 | dotpathp.normalize(); 274 | assert(dotpathp.toString() == "/test2/x/y"); 275 | } 276 | { 277 | auto dotpath = "/test/..////test2//./x/y"; 278 | auto dotpathp = Path(dotpath); 279 | assert(dotpathp.toString() == "/test/..////test2//./x/y"); 280 | dotpathp.normalize(); 281 | assert(dotpathp.toString() == "/test2/x/y"); 282 | } 283 | { 284 | auto parentpath = "/path/to/parent"; 285 | auto parentpathp = Path(parentpath); 286 | auto subpath = "/path/to/parent/sub/"; 287 | auto subpathp = Path(subpath); 288 | auto subpath_rel = "sub/"; 289 | assert(subpathp.relativeTo(parentpathp).toString() == subpath_rel); 290 | auto subfile = "/path/to/parent/child"; 291 | auto subfilep = Path(subfile); 292 | auto subfile_rel = "child"; 293 | assert(subfilep.relativeTo(parentpathp).toString() == subfile_rel); 294 | } 295 | { // relative paths across Windows devices are not allowed 296 | version (Windows) { 297 | auto p1 = Path("\\\\server\\share"); assert(p1.absolute); 298 | auto p2 = Path("\\\\server\\othershare"); assert(p2.absolute); 299 | auto p3 = Path("\\\\otherserver\\share"); assert(p3.absolute); 300 | auto p4 = Path("C:\\somepath"); assert(p4.absolute); 301 | auto p5 = Path("C:\\someotherpath"); assert(p5.absolute); 302 | auto p6 = Path("D:\\somepath"); assert(p6.absolute); 303 | assert(p4.relativeTo(p5) == Path("../somepath")); 304 | assert(p4.relativeTo(p6) == Path("C:\\somepath")); 305 | assert(p4.relativeTo(p1) == Path("C:\\somepath")); 306 | assert(p1.relativeTo(p2) == Path("../share")); 307 | assert(p1.relativeTo(p3) == Path("\\\\server\\share")); 308 | assert(p1.relativeTo(p4) == Path("\\\\server\\share")); 309 | } 310 | } 311 | } 312 | struct PathEntry { 313 | private { 314 | string m_name; 315 | } 316 | this(string str) 317 | { 318 | assert(!str.canFind('/') && (!str.canFind('\\') || str.length == 1), "Invalid path entry: " ~ str); 319 | m_name = str; 320 | } 321 | string toString() const { return m_name; } 322 | Path opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { return Path(cast(immutable)[this, rhs], false); } 323 | bool opEquals(ref const PathEntry rhs) const { return m_name == rhs.m_name; } 324 | bool opEquals(PathEntry rhs) const { return m_name == rhs.m_name; } 325 | bool opEquals(string rhs) const { return m_name == rhs; } 326 | int opCmp(ref const PathEntry rhs) const { return m_name.cmp(rhs.m_name); } 327 | int opCmp(string rhs) const { return m_name.cmp(rhs); } 328 | } 329 | private bool isValidFilename(string str) 330 | { 331 | foreach( ch; str ) 332 | if( ch == '/' || /*ch == ':' ||*/ ch == '\\' ) return false; 333 | return true; 334 | } 335 | /// Joins two path strings. subpath must be relative. 336 | string joinPath(string basepath, string subpath) 337 | { 338 | Path p1 = Path(basepath); 339 | Path p2 = Path(subpath); 340 | return (p1 ~ p2).toString(); 341 | } 342 | /// Splits up a path string into its elements/folders 343 | PathEntry[] splitPath(string path) 344 | { 345 | if( path.startsWith("/") || path.startsWith("\\") ) path = path[1 .. $]; 346 | if( path.empty ) return null; 347 | if( path.endsWith("/") || path.endsWith("\\") ) path = path[0 .. $-1]; 348 | // count the number of path nodes 349 | size_t nelements = 0; 350 | foreach( i, char ch; path ) 351 | if( ch == '\\' || ch == '/' ) 352 | nelements++; 353 | nelements++; 354 | // reserve space for the elements 355 | PathEntry[] storage; 356 | /*if (alloc) { 357 | auto mem = alloc.alloc(nelements * PathEntry.sizeof); 358 | mem[] = 0; 359 | storage = cast(PathEntry[])mem; 360 | } else*/ storage = new PathEntry[nelements]; 361 | size_t startidx = 0; 362 | size_t eidx = 0; 363 | // detect UNC path 364 | if(path.startsWith("\\")) 365 | { 366 | storage[eidx++] = PathEntry(path[0 .. 1]); 367 | path = path[1 .. $]; 368 | } 369 | // read and return the elements 370 | foreach( i, char ch; path ) 371 | if( ch == '\\' || ch == '/' ){ 372 | storage[eidx++] = PathEntry(path[startidx .. i]); 373 | startidx = i+1; 374 | } 375 | storage[eidx++] = PathEntry(path[startidx .. $]); 376 | assert(eidx == nelements); 377 | return storage; 378 | } 379 | -------------------------------------------------------------------------------- /source/libasync/internals/queue.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.queue; 2 | 3 | mixin template Queue() 4 | { 5 | static if (!is(typeof(this) == struct)) static assert(false, "Queue only works on structs"); 6 | 7 | private: 8 | alias T = typeof(this); 9 | 10 | /** 11 | * Iterates over the elements in the queue at the time of the range's creation. 12 | * The range is safe from being invalidated by the following queue mutating operations: 13 | * - any number of insertFront 14 | * - any number of insertBack 15 | * - a single removeFront beyond the current element of the range 16 | * Removing more than one element from the queue the range is iterating over beyond 17 | * the current element of the range results in undefined behaviour. 18 | */ 19 | struct QueueRange 20 | { 21 | private: 22 | T* head, tail, next; 23 | 24 | this(T* head) @safe pure @nogc nothrow 25 | { 26 | if (head) { 27 | assert(head.queue.tail, T.stringof ~ ".QueueRange.this: Not head of a queue"); 28 | this.head = head; 29 | tail = this.head.queue.tail; 30 | next = this.head.queue.next; 31 | } 32 | } 33 | 34 | public: 35 | @property bool empty() const @safe pure @nogc nothrow 36 | { return !head; } 37 | 38 | @property T* front() @safe pure @nogc nothrow 39 | { 40 | assert(!empty, T.stringof ~ ".QueueRange.front: Range is empty"); 41 | return head; 42 | } 43 | 44 | void popFront() @safe pure @nogc nothrow 45 | { 46 | assert(!empty, T.stringof ~ ".QueueRange.popFront: Range is empty"); 47 | head = next; 48 | if (!empty && head != tail) next = head.queue.next; 49 | else next = null; 50 | } 51 | 52 | @property typeof(this) save() @safe pure @nogc nothrow 53 | { return this; } 54 | } 55 | 56 | struct QueueInfo 57 | { 58 | T* next, tail; 59 | } 60 | 61 | QueueInfo queue; 62 | 63 | public: 64 | struct Queue 65 | { 66 | private: 67 | T* head; 68 | 69 | public: 70 | @property bool empty() const @safe pure @nogc nothrow 71 | { return !head; } 72 | 73 | @property T* front() @safe pure @nogc nothrow 74 | { 75 | assert(!empty, T.stringof ~ ".Queue.front: Queue is empty"); 76 | return head; 77 | } 78 | 79 | void insertFront(T* obj) @safe pure /+@nogc+/ nothrow 80 | { 81 | assert(obj, T.stringof ~ ".Queue.insertFront: Null elements are forbidden"); 82 | assert(!obj.queue.next, T.stringof ~ ".Queue.insertFront: Already in a queue"); 83 | assert(head != obj, T.stringof ~ ".Queue.insertBack: Already head of this queue"); 84 | assert(!obj.queue.tail, T.stringof ~ ".Queue.insertFront: Already head of a queue"); 85 | if (empty) obj.queue.tail = obj; 86 | else { 87 | obj.queue.tail = head.queue.tail; 88 | head.queue.tail = null; 89 | obj.queue.next = head; 90 | } 91 | head = obj; 92 | //(impure) static if (LOG) .tracef(T.stringof ~ ".Queue.insertFront: Inserted %s", obj); 93 | } 94 | 95 | void removeFront() @safe pure /+@nogc+/ nothrow 96 | { 97 | assert(!empty, T.stringof ~ ".Queue.removeFront: Queue is empty"); 98 | //(impure) static if (LOG) .tracef(T.stringof ~ ".Queue.removeFront: Removing %s", head); 99 | auto newHead = head.queue.next; 100 | if (newHead) newHead.queue.tail = head.queue.tail; 101 | head.queue.next = null; 102 | head.queue.tail = null; 103 | head = newHead; 104 | } 105 | 106 | void insertBack(T* obj) @safe pure /+@nogc+/ nothrow 107 | { 108 | assert(obj, T.stringof ~ ".Queue.insertBack: Null elements are forbidden"); 109 | assert(!obj.queue.next, T.stringof ~ ".Queue.insertBack: Already in a queue"); 110 | assert(!obj.queue.tail, T.stringof ~ ".Queue.insertBack: Already head of a queue"); 111 | assert(head != obj, T.stringof ~ ".Queue.insertBack: Duplicate elements are forbidden"); 112 | if (empty) { 113 | obj.queue.tail = obj; 114 | head = obj; 115 | } else { 116 | head.queue.tail.queue.next = obj; 117 | head.queue.tail = obj; 118 | } 119 | //(impure) static if (LOG) .tracef(T.stringof ~ ".Queue.insertBack: Inserted %s", obj); 120 | } 121 | 122 | auto opSlice() @safe pure @nogc nothrow 123 | { return QueueRange(head); } 124 | } 125 | } -------------------------------------------------------------------------------- /source/libasync/internals/socket_compat.d: -------------------------------------------------------------------------------- 1 | /** 2 | * D header file for POSIX. 3 | * 4 | * Copyright: Copyright Sean Kelly 2005 - 2009. 5 | * License: Boost License 1.0. 6 | * Authors: Sean Kelly, Alex Rønne Petersen 7 | * Standards: The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition 8 | */ 9 | 10 | /* Copyright Sean Kelly 2005 - 2009. 11 | * Distributed under the Boost Software License, Version 1.0. 12 | * (See accompanying file LICENSE or copy at 13 | * http://www.boost.org/LICENSE_1_0.txt) 14 | */ 15 | module libasync.internals.socket_compat; 16 | import libasync.types; 17 | version (Windows) 18 | { 19 | public import libasync.internals.win32 : 20 | sockaddr, sockaddr_storage, sockaddr_in, sockaddr_in6, 21 | AF_UNSPEC, AF_INET, AF_INET6, 22 | socklen_t, 23 | getsockname, getpeername, 24 | SOL_SOCKET, SO_TYPE, SO_ERROR, SO_REUSEADDR, 25 | getsockopt, setsockopt, 26 | bind, connect, listen, SOMAXCONN, 27 | SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM, SOCK_RAW, SOCK_RDM; 28 | } else: 29 | 30 | public import core.sys.posix.sys.un; 31 | 32 | static if (is_OSX || is_iOS) { 33 | public import core.sys.posix.arpa.inet; 34 | public import core.sys.posix.netdb; 35 | public import core.sys.posix.netinet.tcp; 36 | public import core.sys.posix.netinet.in_; 37 | public import core.sys.posix.sys.select; 38 | public import core.sys.posix.sys.socket; 39 | private import core.sys.posix.config; 40 | public import core.sys.posix.sys.types; // for ssize_t, size_t 41 | public import core.sys.posix.sys.uio; // for iovec 42 | 43 | 44 | enum: int 45 | { 46 | TCP_MAX_SACK = 3, 47 | TCP_MSS = 512, 48 | TCP_MINMSS = 216, 49 | TCP_MINMSSOVERLOAD = 1000, 50 | TCP_MAXWIN = 65535L, 51 | TCP_MAX_WINSHIFT = 14, 52 | TCP_MAXBURST = 4, 53 | TCP_MAXHLEN = 60, 54 | TCP_MAXOLEN = 40, 55 | TCP_NODELAY = 1, 56 | TCP_MAXSEG = 2, 57 | TCP_NOPUSH = 4, 58 | TCP_NOOPT = 8, 59 | TCP_KEEPALIVE = 16, 60 | TCP_QUICKACK = -1, 61 | TCP_KEEPCNT = -1, 62 | TCP_KEEPINTVL = -1, 63 | TCP_KEEPIDLE = -1, 64 | TCP_CONGESTION = -1, 65 | TCP_CORK = -1, 66 | TCP_DEFER_ACCEPT = -1 67 | } 68 | } 69 | 70 | else version (linux) { 71 | private import core.sys.posix.config; 72 | public import core.sys.posix.sys.types; // for ssize_t, size_t 73 | public import core.sys.posix.sys.uio; // for iovec 74 | public import core.sys.posix.arpa.inet; 75 | public import core.sys.posix.netdb; 76 | public import core.sys.posix.netinet.tcp; 77 | public import core.sys.posix.netinet.in_; 78 | public import core.sys.posix.sys.select; 79 | public import core.sys.posix.sys.socket; 80 | 81 | enum: int 82 | { 83 | TCP_MAXSEG = 2, 84 | TCP_CORK = 3, 85 | TCP_KEEPIDLE = 4, 86 | TCP_KEEPINTVL = 5, 87 | TCP_KEEPCNT = 6, 88 | TCP_SYNCNT = 7, 89 | TCP_LINGER2 = 8, 90 | TCP_DEFER_ACCEPT = 9, 91 | TCP_WINDOW_CLAMP = 10, 92 | TCP_INFO = 11, 93 | TCP_QUICKACK = 12, 94 | TCP_CONGESTION = 13, 95 | TCP_MD5SIG = 14 96 | } 97 | extern(C) nothrow @nogc struct gaicb { 98 | const(char)* ar_name; 99 | const(char)* ar_service; 100 | const(addrinfo)* ar_request; 101 | addrinfo* ar_result; 102 | } 103 | enum : int { 104 | GAI_WAIT = 0, 105 | GAI_NOWAIT = 1, 106 | EAI_INPROGRESS = -100, 107 | EAI_SYSTEM = -10 108 | } 109 | 110 | extern(C) static union sigval_t { /* Data passed with notification */ 111 | int sival_int; /* Integer value */ 112 | void *sival_ptr; /* Pointer value */ 113 | } 114 | 115 | extern(C) static struct sigevent_t { 116 | int sigev_notify; /* Notification method */ 117 | int sigev_signo; /* Notification signal */ 118 | sigval_t sigev_value; /* Data passed with 119 | notification */ 120 | void function(sigval_t) sigev_notify_function; 121 | /* Function used for thread 122 | notification (SIGEV_THREAD) */ 123 | void *sigev_notify_attributes; 124 | /* Attributes for notification thread 125 | (SIGEV_THREAD) */ 126 | pid_t sigev_notify_thread_id; 127 | /* ID of thread to signal (SIGEV_THREAD_ID) */ 128 | } 129 | extern(C) nothrow @nogc int getaddrinfo_a(int mode, gaicb **list, int nitems, sigevent *sevp); 130 | 131 | } 132 | extern (C) nothrow @nogc: 133 | 134 | // 135 | // Required 136 | // 137 | /* 138 | socklen_t 139 | sa_family_t 140 | 141 | struct sockaddr 142 | { 143 | sa_family_t sa_family; 144 | char sa_data[]; 145 | } 146 | 147 | struct sockaddr_storage 148 | { 149 | sa_family_t ss_family; 150 | } 151 | 152 | struct msghdr 153 | { 154 | void* msg_name; 155 | socklen_t msg_namelen; 156 | struct iovec* msg_iov; 157 | int msg_iovlen; 158 | void* msg_control; 159 | socklen_t msg_controllen; 160 | int msg_flags; 161 | } 162 | 163 | struct iovec {} // from core.sys.posix.sys.uio 164 | 165 | struct cmsghdr 166 | { 167 | socklen_t cmsg_len; 168 | int cmsg_level; 169 | int cmsg_type; 170 | } 171 | 172 | SCM_RIGHTS 173 | 174 | CMSG_DATA(cmsg) 175 | CMSG_NXTHDR(mhdr,cmsg) 176 | CMSG_FIRSTHDR(mhdr) 177 | 178 | struct linger 179 | { 180 | int l_onoff; 181 | int l_linger; 182 | } 183 | 184 | SOCK_DGRAM 185 | SOCK_SEQPACKET 186 | SOCK_STREAM 187 | 188 | SOL_SOCKET 189 | 190 | SO_ACCEPTCONN 191 | SO_BROADCAST 192 | SO_DEBUG 193 | SO_DONTROUTE 194 | SO_ERROR 195 | SO_KEEPALIVE 196 | SO_LINGER 197 | SO_OOBINLINE 198 | SO_RCVBUF 199 | SO_RCVLOWAT 200 | SO_RCVTIMEO 201 | SO_REUSEADDR 202 | SO_SNDBUF 203 | SO_SNDLOWAT 204 | SO_SNDTIMEO 205 | SO_TYPE 206 | 207 | SOMAXCONN 208 | 209 | MSG_CTRUNC 210 | MSG_DONTROUTE 211 | MSG_EOR 212 | MSG_OOB 213 | MSG_PEEK 214 | MSG_TRUNC 215 | MSG_WAITALL 216 | 217 | AF_INET 218 | AF_UNIX 219 | AF_UNSPEC 220 | 221 | SHUT_RD 222 | SHUT_RDWR 223 | SHUT_WR 224 | 225 | int accept(int, sockaddr*, socklen_t*); 226 | int accept4(int, sockaddr*, socklen_t*, int flags); 227 | int bind(int, in sockaddr*, socklen_t); 228 | int connect(int, in sockaddr*, socklen_t); 229 | int getpeername(int, sockaddr*, socklen_t*); 230 | int getsockname(int, sockaddr*, socklen_t*); 231 | int getsockopt(int, int, int, void*, socklen_t*); 232 | int listen(int, int); 233 | ssize_t recv(int, void*, size_t, int); 234 | ssize_t recvfrom(int, void*, size_t, int, sockaddr*, socklen_t*); 235 | ssize_t recvmsg(int, msghdr*, int); 236 | ssize_t send(int, in void*, size_t, int); 237 | ssize_t sendmsg(int, in msghdr*, int); 238 | ssize_t sendto(int, in void*, size_t, int, in sockaddr*, socklen_t); 239 | int setsockopt(int, int, int, in void*, socklen_t); 240 | int shutdown(int, int); 241 | int socket(int, int, int); 242 | int sockatmark(int); 243 | int socketpair(int, int, int, ref int[2]); 244 | */ 245 | 246 | version( linux ) 247 | { 248 | version (X86) 249 | { 250 | enum SO_REUSEPORT = 15; 251 | } 252 | else version (X86_64) 253 | { 254 | enum SO_REUSEPORT = 15; 255 | } 256 | else version (MIPS32) 257 | { 258 | enum SO_REUSEPORT = 0x0200; 259 | } 260 | else version (MIPS64) 261 | { 262 | enum SO_REUSEPORT = 0x0200; 263 | } 264 | else version (PPC) 265 | { 266 | enum SO_REUSEPORT = 15; 267 | } 268 | else version (PPC64) 269 | { 270 | enum SO_REUSEPORT = 15; 271 | } 272 | else version (ARM) 273 | { 274 | enum SO_REUSEPORT = 15; 275 | } 276 | else version (AArch64) 277 | { 278 | enum SO_REUSEPORT = 15; 279 | } 280 | else 281 | static assert(0, "unimplemented"); 282 | 283 | int accept4(int, sockaddr*, socklen_t*, int flags); 284 | } 285 | else static if (is_OSX || is_iOS) 286 | { 287 | enum SO_REUSEPORT = 0x0200; 288 | 289 | int accept4(int, sockaddr*, socklen_t*, int flags); 290 | } 291 | else version( FreeBSD ) 292 | { 293 | enum SO_REUSEPORT = 0x0200; 294 | 295 | int accept4(int, sockaddr*, socklen_t*, int flags); 296 | } 297 | else version( DragonFlyBSD ) 298 | { 299 | enum SO_REUSEPORT = 0x0200; 300 | 301 | int accept4(int, sockaddr*, socklen_t*, int flags); 302 | } 303 | else version (Solaris) 304 | { 305 | enum SO_REUSEPORT = 0x0200; 306 | 307 | int accept4(int, sockaddr*, socklen_t*, int flags); 308 | } 309 | else version( Android ) 310 | { 311 | enum SO_REUSEPORT = 15; 312 | // constants needed for std.socket 313 | enum IPPROTO_IGMP = 2; 314 | enum IPPROTO_PUP = 12; 315 | enum IPPROTO_IDP = 22; 316 | enum INADDR_NONE = 0xFFFFFFFF; 317 | 318 | int accept4(int, sockaddr*, socklen_t*, int flags); 319 | } 320 | else 321 | { 322 | static assert(false, "Unsupported platform"); 323 | } 324 | -------------------------------------------------------------------------------- /source/libasync/internals/validator.d: -------------------------------------------------------------------------------- 1 | module libasync.internals.validator; 2 | 3 | import std.regex : regex, Regex, matchAll; 4 | 5 | deprecated bool validateIPv4(string str) 6 | { 7 | static auto IPv4Regex = regex(`^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s*$`, ``); 8 | auto cap = matchAll(str, IPv4Regex); 9 | return !cap.empty; 10 | } 11 | 12 | deprecated bool validateIPv6(string str) 13 | { 14 | static auto IPv6Regex = regex(`^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$`, ``); 15 | auto cap = matchAll(str, IPv6Regex); 16 | return !cap.empty; 17 | } 18 | 19 | bool validateHost(string str) 20 | { 21 | static auto HostRegex = regex(`^\s*((?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*\.?)\s*$`, ``); 22 | auto cap = matchAll(str, HostRegex); 23 | return !cap.empty; 24 | } 25 | -------------------------------------------------------------------------------- /source/libasync/internals/win32.d: -------------------------------------------------------------------------------- 1 | /// [internal] 2 | module libasync.internals.win32; 3 | 4 | version(Windows): 5 | 6 | public import core.sys.windows.windows; 7 | public import core.sys.windows.winsock2; 8 | public import core.sys.windows.com : GUID; 9 | 10 | extern(System) nothrow 11 | { 12 | enum HWND HWND_MESSAGE = cast(HWND)-3; 13 | enum { 14 | GWLP_WNDPROC = -4, 15 | GWLP_HINSTANCE = -6, 16 | GWLP_HWNDPARENT = -8, 17 | GWLP_USERDATA = -21, 18 | GWLP_ID = -12, 19 | } 20 | 21 | version(Win32){ // avoiding linking errors with out-of-the-box dmd 22 | alias SetWindowLongA SetWindowLongPtrA; 23 | alias GetWindowLongA GetWindowLongPtrA; 24 | } else { 25 | LONG_PTR SetWindowLongPtrA(HWND hWnd, int nIndex, LONG_PTR dwNewLong); 26 | LONG_PTR GetWindowLongPtrA(HWND hWnd, int nIndex); 27 | } 28 | LONG_PTR SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong); 29 | LONG_PTR GetWindowLongPtrW(HWND hWnd, int nIndex); 30 | LONG_PTR SetWindowLongA(HWND hWnd, int nIndex, LONG dwNewLong); 31 | LONG_PTR GetWindowLongA(HWND hWnd, int nIndex); 32 | 33 | alias void function(DWORD, DWORD, OVERLAPPED*) LPOVERLAPPED_COMPLETION_ROUTINE; 34 | 35 | HANDLE CreateEventW(SECURITY_ATTRIBUTES* lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName); 36 | BOOL PostThreadMessageW(DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam); 37 | DWORD MsgWaitForMultipleObjectsEx(DWORD nCount, const(HANDLE) *pHandles, DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags); 38 | static if (!is(typeof(&CreateFileW))) BOOL CloseHandle(HANDLE hObject); 39 | static if (!is(typeof(&CreateFileW))) HANDLE CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, 40 | DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 41 | BOOL WriteFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, OVERLAPPED* lpOverlapped, 42 | LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 43 | BOOL ReadFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, OVERLAPPED* lpOverlapped, 44 | LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 45 | BOOL GetFileSizeEx(HANDLE hFile, long *lpFileSize); 46 | BOOL SetEndOfFile(HANDLE hFile); 47 | BOOL GetOverlappedResult(HANDLE hFile, OVERLAPPED* lpOverlapped, DWORD* lpNumberOfBytesTransferred, BOOL bWait); 48 | BOOL WSAGetOverlappedResult(SOCKET s, OVERLAPPED* lpOverlapped, DWORD* lpcbTransfer, BOOL fWait, DWORD* lpdwFlags); 49 | BOOL PostMessageW(HWND hwnd, UINT msg, WPARAM wPara, LPARAM lParam); 50 | BOOL PostThreadMessageA(HWND hwnd, UINT msg, WPARAM wPara, LPARAM lParam); 51 | 52 | HANDLE GetStdHandle(DWORD nStdHandle); 53 | bool ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesRead, DWORD* lpNumberOfBytesRead, OVERLAPPED* lpOverlapped); 54 | 55 | static if (__VERSION__ < 2065) { 56 | BOOL PeekMessageW(MSG *lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); 57 | LONG DispatchMessageW(MSG *lpMsg); 58 | 59 | enum { 60 | ERROR_ALREADY_EXISTS = 183, 61 | ERROR_IO_PENDING = 997 62 | } 63 | } 64 | 65 | struct FILE_NOTIFY_INFORMATION { 66 | DWORD NextEntryOffset; 67 | DWORD Action; 68 | DWORD FileNameLength; 69 | WCHAR[1] FileName; 70 | } 71 | 72 | BOOL ReadDirectoryChangesW(HANDLE hDirectory, void* lpBuffer, DWORD nBufferLength, BOOL bWatchSubtree, DWORD dwNotifyFilter, LPDWORD lpBytesReturned, void* lpOverlapped, void* lpCompletionRoutine); 73 | HANDLE FindFirstChangeNotificationW(LPCWSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter); 74 | HANDLE FindNextChangeNotification(HANDLE hChangeHandle); 75 | 76 | enum{ 77 | WSAPROTOCOL_LEN = 255, 78 | MAX_PROTOCOL_CHAIN = 7, 79 | }; 80 | 81 | enum WSA_IO_INCOMPLETE = 996; 82 | enum WSA_IO_PENDING = 997; 83 | 84 | struct WSAPROTOCOL_INFOW { 85 | DWORD dwServiceFlags1; 86 | DWORD dwServiceFlags2; 87 | DWORD dwServiceFlags3; 88 | DWORD dwServiceFlags4; 89 | DWORD dwProviderFlags; 90 | GUID ProviderId; 91 | DWORD dwCatalogEntryId; 92 | WSAPROTOCOLCHAIN ProtocolChain; 93 | int iVersion; 94 | int iAddressFamily; 95 | int iMaxSockAddr; 96 | int iMinSockAddr; 97 | int iSocketType; 98 | int iProtocol; 99 | int iProtocolMaxOffset; 100 | int iNetworkByteOrder; 101 | int iSecurityScheme; 102 | DWORD dwMessageSize; 103 | DWORD dwProviderReserved; 104 | WCHAR[WSAPROTOCOL_LEN+1] szProtocol; 105 | }; 106 | 107 | struct WSAPROTOCOLCHAIN { 108 | int ChainLen; 109 | DWORD[MAX_PROTOCOL_CHAIN] ChainEntries; 110 | }; 111 | 112 | struct WSABUF { 113 | size_t len; 114 | ubyte *buf; 115 | } 116 | 117 | struct WSAMSG { 118 | sockaddr* name; 119 | int namelen; 120 | WSABUF* lpBuffers; 121 | DWORD dwBufferCount; 122 | WSABUF Control; 123 | DWORD dwFlags; 124 | } 125 | 126 | struct WSAOVERLAPPEDX { 127 | ULONG_PTR Internal; 128 | ULONG_PTR InternalHigh; 129 | union { 130 | struct { 131 | DWORD Offset; 132 | DWORD OffsetHigh; 133 | } 134 | PVOID Pointer; 135 | } 136 | HANDLE hEvent; 137 | } 138 | 139 | enum { 140 | WSA_FLAG_OVERLAPPED = 0x01 141 | } 142 | 143 | enum { 144 | FD_READ = 0x0001, 145 | FD_WRITE = 0x0002, 146 | FD_OOB = 0x0004, 147 | FD_ACCEPT = 0x0008, 148 | FD_CONNECT = 0x0010, 149 | FD_CLOSE = 0x0020, 150 | FD_QOS = 0x0040, 151 | FD_GROUP_QOS = 0x0080, 152 | FD_ROUTING_INTERFACE_CHANGE = 0x0100, 153 | FD_ADDRESS_LIST_CHANGE = 0x0200 154 | } 155 | 156 | struct ADDRINFOEXW { 157 | int ai_flags; 158 | int ai_family; 159 | int ai_socktype; 160 | int ai_protocol; 161 | size_t ai_addrlen; 162 | LPCWSTR ai_canonname; 163 | sockaddr* ai_addr; 164 | void* ai_blob; 165 | size_t ai_bloblen; 166 | GUID* ai_provider; 167 | ADDRINFOEXW* ai_next; 168 | } 169 | 170 | alias PADDRINFOEX = ADDRINFOEXW*; 171 | 172 | struct ADDRINFOA { 173 | int ai_flags; 174 | int ai_family; 175 | int ai_socktype; 176 | int ai_protocol; 177 | size_t ai_addrlen; 178 | LPSTR ai_canonname; 179 | sockaddr* ai_addr; 180 | ADDRINFOA* ai_next; 181 | } 182 | 183 | struct ADDRINFOW { 184 | int ai_flags; 185 | int ai_family; 186 | int ai_socktype; 187 | int ai_protocol; 188 | size_t ai_addrlen; 189 | LPWSTR ai_canonname; 190 | sockaddr* ai_addr; 191 | ADDRINFOW* ai_next; 192 | } 193 | 194 | enum { 195 | NS_ALL = 0, 196 | NS_DNS = 12 197 | } 198 | 199 | struct WSAPROTOCOL_INFO { 200 | DWORD dwServiceFlags1; 201 | DWORD dwServiceFlags2; 202 | DWORD dwServiceFlags3; 203 | DWORD dwServiceFlags4; 204 | DWORD dwProviderFlags; 205 | GUID ProviderId; 206 | DWORD dwCatalogEntryId; 207 | WSAPROTOCOLCHAIN ProtocolChain; 208 | int iVersion; 209 | int iAddressFamily; 210 | int iMaxSockAddr; 211 | int iMinSockAddr; 212 | int iSocketType; 213 | int iProtocol; 214 | int iProtocolMaxOffset; 215 | int iNetworkByteOrder; 216 | int iSecurityScheme; 217 | DWORD dwMessageSize; 218 | DWORD dwProviderReserved; 219 | CHAR[WSAPROTOCOL_LEN+1] szProtocol; 220 | } 221 | alias sockaddr SOCKADDR; 222 | 223 | enum SOMAXCONN = 0x7fffffff; 224 | auto SOMAXCONN_HINT(int b) { return -b; } 225 | 226 | enum _SS_MAXSIZE = 128; // Maximum size 227 | enum _SS_ALIGNSIZE = long.sizeof; // Desired alignment 228 | 229 | enum _SS_PAD1SIZE = _SS_ALIGNSIZE - ushort.sizeof; 230 | enum _SS_PAD2SIZE = _SS_MAXSIZE - (ushort.sizeof + _SS_PAD1SIZE + _SS_ALIGNSIZE); 231 | 232 | struct SOCKADDR_STORAGE { 233 | ushort ss_family; 234 | byte[_SS_PAD1SIZE] __ss_pad1; 235 | long __ss_align; 236 | byte[_SS_PAD2SIZE] __ss_pad2; 237 | } 238 | alias SOCKADDR_STORAGE* PSOCKADDR_STORAGE; 239 | alias SOCKADDR_STORAGE sockaddr_storage; 240 | 241 | alias void function(DWORD, DWORD, WSAOVERLAPPEDX*, DWORD) LPWSAOVERLAPPED_COMPLETION_ROUTINEX; 242 | alias void function(DWORD, DWORD, WSAOVERLAPPEDX*) LPLOOKUPSERVICE_COMPLETION_ROUTINE; 243 | alias void* LPCONDITIONPROC; 244 | alias void* LPTRANSMIT_FILE_BUFFERS; 245 | 246 | alias BOOL function(SOCKET sListenSocket, 247 | SOCKET sAcceptSocket, 248 | void* lpOutputBuffer, 249 | DWORD dwReceiveDataLength, 250 | DWORD dwLocalAddressLength, 251 | DWORD dwRemoteAddressLength, 252 | DWORD* lpdwBytesReceived, 253 | OVERLAPPED* lpOverlapped) LPFN_ACCEPTEX; 254 | auto WSAID_ACCEPTEX = GUID(0xb5367df1, 0xcbac, 0x11cf, [ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 ]); 255 | 256 | alias void function(void* lpOutputBuffer, 257 | DWORD dwReceiveDataLength, 258 | DWORD dwLocalAddressLength, 259 | DWORD dwRemoteAddressLength, 260 | sockaddr** LocalSockaddr, 261 | int* LocalSockaddrLength, 262 | sockaddr** RemoteSockaddr, 263 | int* RemoteSockaddrLength) LPFN_GETACCEPTEXSOCKADDRS; 264 | auto WSAID_GETACCEPTEXSOCKADDRS = GUID(0xb5367df2, 0xcbac, 0x11cf, [ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 ]); 265 | 266 | alias BOOL function(SOCKET s, sockaddr* name, int namelen, void* lpSendBuffer, DWORD dwSendDataLength, DWORD* lpdwBytesSent, OVERLAPPED* lpOverlapped) LPFN_CONNECTEX; 267 | auto WSAID_CONNECTEX = GUID(0x25a207b9, 0xddf3, 0x4660, [ 0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e ]); 268 | 269 | alias BOOL function(SOCKET s, OVERLAPPED* lpOverlapped, DWORD dwFlags, DWORD dwReserved) LPFN_DISCONNECTEX; 270 | auto WSAID_DISCONNECTEX = GUID(0x7fda2e11, 0x8630, 0x436f, [ 0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57 ]); 271 | 272 | int WSAIoctl(SOCKET s, DWORD dwIoControlCode, void* lpvInBuffer, DWORD cbInBuffer, void* lpvOutBuffer, DWORD cbOutBuffer, DWORD* lpcbBytesReturned, WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); 273 | 274 | enum IOCPARM_MASK = 0x7f; 275 | enum uint IOC_VOID = 0x20000000; 276 | enum uint IOC_OUT = 0x40000000; 277 | enum uint IOC_IN = 0x80000000; 278 | enum uint IOC_INOUT = (IOC_IN|IOC_OUT); 279 | 280 | enum uint IOC_UNIX = 0x00000000; 281 | enum uint IOC_WS2 = 0x08000000; 282 | enum uint IOC_PROTOCOL = 0x10000000; 283 | enum uint IOC_VENDOR = 0x18000000; 284 | 285 | auto _WSAIO(uint x,uint y) { return (IOC_VOID|(x)|(y)); } 286 | auto _WSAIOR(uint x,uint y) { return (IOC_OUT|(x)|(y)); } 287 | auto _WSAIOW(uint x,uint y) { return (IOC_IN|(x)|(y)); } 288 | auto _WSAIORW(uint x,uint y) { return (IOC_INOUT|(x)|(y)); } 289 | 290 | enum SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW(IOC_WS2,6); 291 | 292 | LPFN_ACCEPTEX AcceptEx; 293 | LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockaddrs; 294 | LPFN_CONNECTEX ConnectEx; 295 | LPFN_DISCONNECTEX DisconnectEx; 296 | 297 | enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B; 298 | enum SO_UPDATE_CONNECT_CONTEXT = 0x7010; 299 | 300 | bool CancelIo(HANDLE hFile); 301 | bool CancelIoEx(HANDLE hFile, OVERLAPPED* lpOverlapped); 302 | 303 | SOCKET WSAAccept(SOCKET s, sockaddr *addr, INT* addrlen, LPCONDITIONPROC lpfnCondition, DWORD_PTR dwCallbackData); 304 | int WSAAsyncSelect(SOCKET s, HWND hWnd, uint wMsg, sizediff_t lEvent); 305 | SOCKET WSASocketW(int af, int type, int protocol, WSAPROTOCOL_INFOW *lpProtocolInfo, uint g, DWORD dwFlags); 306 | int WSARecv(SOCKET s, WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesRecvd, DWORD* lpFlags, const WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); 307 | int WSASend(SOCKET s, const WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesSent, DWORD dwFlags, const WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); 308 | int WSARecvFrom(SOCKET s, WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesRecvd, DWORD* lpFlags, sockaddr* lpFrom, INT* lpFromlen, const WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); 309 | int WSASendTo(SOCKET s, const WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesSent, DWORD dwFlags, sockaddr* lpTo, int iToLen, const WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); 310 | int WSASendDisconnect(SOCKET s, WSABUF* lpOutboundDisconnectData); 311 | INT WSAStringToAddressA(const LPTSTR AddressString, INT AddressFamily, const WSAPROTOCOL_INFO* lpProtocolInfo, SOCKADDR* lpAddress, INT* lpAddressLength); 312 | INT WSAStringToAddressW(const LPWSTR AddressString, INT AddressFamily, const WSAPROTOCOL_INFOW* lpProtocolInfo, SOCKADDR* lpAddress, INT* lpAddressLength); 313 | INT WSAAddressToStringW(const SOCKADDR* lpsaAddress, DWORD dwAddressLength, const WSAPROTOCOL_INFO* lpProtocolInfo, LPWSTR lpszAddressString, DWORD* lpdwAddressStringLength); 314 | int GetAddrInfoExW(LPCWSTR pName, LPCWSTR pServiceName, DWORD dwNameSpace, GUID* lpNspId, const ADDRINFOEXW *pHints, ADDRINFOEXW **ppResult, timeval *timeout, WSAOVERLAPPEDX* lpOverlapped, LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, HANDLE* lpNameHandle); 315 | int GetAddrInfoW(LPCWSTR pName, LPCWSTR pServiceName, const ADDRINFOW *pHints, ADDRINFOW **ppResult); 316 | int getaddrinfo(LPCSTR pName, LPCSTR pServiceName, const ADDRINFOA *pHints, ADDRINFOA **ppResult); 317 | void FreeAddrInfoW(ADDRINFOW* pAddrInfo); 318 | void FreeAddrInfoExW(ADDRINFOEXW* pAddrInfo); 319 | void freeaddrinfo(ADDRINFOA* ai); 320 | BOOL TransmitFile(SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, OVERLAPPED* lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags); 321 | 322 | enum WM_USER = 0x0400; 323 | 324 | enum { 325 | QS_ALLPOSTMESSAGE = 0x0100, 326 | QS_HOTKEY = 0x0080, 327 | QS_KEY = 0x0001, 328 | QS_MOUSEBUTTON = 0x0004, 329 | QS_MOUSEMOVE = 0x0002, 330 | QS_PAINT = 0x0020, 331 | QS_POSTMESSAGE = 0x0008, 332 | QS_RAWINPUT = 0x0400, 333 | QS_SENDMESSAGE = 0x0040, 334 | QS_TIMER = 0x0010, 335 | 336 | QS_MOUSE = (QS_MOUSEMOVE | QS_MOUSEBUTTON), 337 | QS_INPUT = (QS_MOUSE | QS_KEY | QS_RAWINPUT), 338 | QS_ALLEVENTS = (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY), 339 | QS_ALLINPUT = (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE), 340 | }; 341 | 342 | enum { 343 | MWMO_ALERTABLE = 0x0002, 344 | MWMO_INPUTAVAILABLE = 0x0004, 345 | MWMO_WAITALL = 0x0001, 346 | }; 347 | } 348 | -------------------------------------------------------------------------------- /source/libasync/notifier.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.notifier; 3 | 4 | import libasync.types; 5 | import libasync.events; 6 | 7 | /// Thread-local event dispatcher/handler, used to wake up the associated 8 | /// callback in a new call stack originating from the event loop. 9 | final class AsyncNotifier 10 | { 11 | /// 12 | void delegate() m_evh; 13 | nothrow: 14 | private: 15 | EventLoop m_evLoop; 16 | fd_t m_evId; 17 | version(Posix) static if (EPOLL) shared ushort m_owner; 18 | 19 | public: 20 | /// 21 | this(EventLoop evl) 22 | in { 23 | assert(evl !is null); 24 | } 25 | do { 26 | m_evLoop = evl; 27 | } 28 | 29 | mixin DefStatus; 30 | 31 | /// Starts the notifier with the associated delegate (handler) 32 | bool run(void delegate() del) { 33 | m_evh = cast(void delegate()) del; 34 | m_evId = m_evLoop.run(this); 35 | if (m_evId != fd_t.init) 36 | return true; 37 | else 38 | return false; 39 | } 40 | 41 | /// Cleans up associated resources. 42 | bool kill() 43 | { 44 | return m_evLoop.kill(this); 45 | } 46 | 47 | /// Enqueues a call to the handler originating from the thread-local event loop. 48 | bool trigger() 49 | { 50 | return m_evLoop.notify(m_evId, this); 51 | } 52 | 53 | /// 54 | @property fd_t id() const { 55 | return m_evId; 56 | } 57 | 58 | package: 59 | version(Posix) mixin EvInfoMixins; 60 | 61 | void handler() { 62 | try m_evh(); 63 | catch (Exception) {} 64 | return; 65 | } 66 | } 67 | 68 | package struct NotifierHandler { 69 | AsyncNotifier ctxt; 70 | void function(AsyncNotifier) fct; 71 | 72 | void opCall() { 73 | assert(ctxt !is null); 74 | fct(ctxt); 75 | return; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /source/libasync/package.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync; 3 | 4 | public import libasync.events; 5 | -------------------------------------------------------------------------------- /source/libasync/signal.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.signal; 3 | import std.traits; 4 | 5 | import libasync.types; 6 | import libasync.events; 7 | import core.thread; 8 | import core.sync.mutex : Mutex; 9 | import std.exception : assumeWontThrow; 10 | 11 | /// Enqueues a signal in the event loop of the AsyncSignal owner's thread, 12 | /// which allows a foreign thread to trigger the callback handler safely. 13 | shared final class AsyncSignal 14 | { 15 | private void delegate() m_sgh; 16 | nothrow: 17 | private: 18 | Thread m_owner; 19 | EventLoop m_evLoop; 20 | fd_t m_evId; 21 | shared Mutex m_mutex; 22 | 23 | void lock() @trusted const nothrow 24 | { assumeWontThrow((cast()this).m_mutex.lock()); } 25 | 26 | void unlock() @trusted const nothrow 27 | { assumeWontThrow((cast()this).m_mutex.unlock()); } 28 | 29 | public: 30 | 31 | /// 32 | this(EventLoop evl) 33 | in { 34 | assert(evl !is null); 35 | } 36 | do { 37 | m_evLoop = cast(shared) evl; 38 | import core.thread : Thread; 39 | m_owner = cast(shared) Thread.getThis(); 40 | m_mutex = new shared Mutex; 41 | 42 | version(Posix) { 43 | static if (EPOLL) { 44 | import core.sys.posix.pthread : pthread_self; 45 | m_pthreadId = cast(shared)pthread_self(); 46 | } else /* if KQUEUE */ { 47 | m_owner_id = cast(shared) g_threadId; 48 | } 49 | } 50 | } 51 | 52 | /// 53 | @property bool hasError() const 54 | { 55 | return (cast(EventLoop)m_evLoop).status.code != Status.OK; 56 | } 57 | 58 | /// Used to diagnose errors when run() or kill() returns false 59 | @property StatusInfo status() const { 60 | return (cast(EventLoop)m_evLoop).status; 61 | } 62 | 63 | /// Human-readable string describing the error 64 | @property string error() const { 65 | return (cast(EventLoop)m_evLoop).error; 66 | } 67 | 68 | /// Registers the signal handler in the event loop 69 | bool run(void delegate() del) 70 | in { 71 | debug assert(Thread.getThis() is cast(Thread)m_owner); 72 | } 73 | do { 74 | lock(); 75 | scope (exit) unlock(); 76 | 77 | m_sgh = cast(void delegate()) del; 78 | 79 | m_evId = (cast(EventLoop) m_evLoop).run(this); 80 | if (m_evId != fd_t.init) 81 | return true; 82 | else 83 | return false; 84 | } 85 | 86 | /// Cleans up underlying resources. This object must be run() again afterwards to be valid 87 | bool kill() 88 | in { 89 | debug assert(Thread.getThis() is cast(Thread)m_owner); 90 | } 91 | do { 92 | return (cast(EventLoop)m_evLoop).kill(cast(shared AsyncSignal) this); 93 | } 94 | 95 | /// Triggers the handler in its local thread 96 | bool trigger(EventLoop evl) { 97 | lock(); 98 | scope (exit) unlock(); 99 | return evl.notify(m_evId, this); 100 | } 101 | 102 | /// ditto 103 | bool trigger() { 104 | lock(); 105 | scope (exit) unlock(); 106 | return (cast(EventLoop)m_evLoop).notify(m_evId, this); 107 | } 108 | 109 | /// Returns the Thread that created this object. 110 | @property Thread owner() const { 111 | lock(); 112 | scope (exit) unlock(); 113 | return cast(Thread) m_owner; 114 | } 115 | 116 | /// 117 | @property fd_t id() const { 118 | return m_evId; 119 | } 120 | 121 | package: 122 | version(Posix) mixin EvInfoMixinsShared; 123 | 124 | void handler() { 125 | try m_sgh(); 126 | catch (Throwable) {} 127 | return; 128 | } 129 | } 130 | 131 | package shared struct SignalHandler { 132 | AsyncSignal ctxt; 133 | void function(shared AsyncSignal) fct; 134 | 135 | void opCall(shared AsyncSignal ctxt) { 136 | assert(ctxt !is null); 137 | fct(ctxt); 138 | return; 139 | } 140 | } 141 | 142 | 143 | /** 144 | Determines if the given list of types has any non-immutable and unshared aliasing outside of their object tree. 145 | 146 | The types in particular may only contain plain data, pointers or arrays to immutable or shared data, or references 147 | encapsulated in stdx.typecons.Isolated. Values that do not have unshared and unisolated aliasing are safe to be passed 148 | between threads. 149 | */ 150 | template isWeaklyIsolated(T...) 151 | { 152 | import std.typecons : Rebindable; 153 | static if (T.length == 0) enum bool isWeaklyIsolated = true; 154 | else static if (T.length > 1) enum bool isWeaklyIsolated = isWeaklyIsolated!(T[0 .. $/2]) && isWeaklyIsolated!(T[$/2 .. $]); 155 | else { 156 | static if(is(T[0] == immutable)) enum bool isWeaklyIsolated = true; 157 | else static if (is(T[0] == shared)) enum bool isWeaklyIsolated = true; 158 | else static if (isInstanceOf!(Rebindable, T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(typeof(T[0].get())); 159 | else static if (is(T[0] : Throwable)) enum bool isWeaklyIsolated = true; // WARNING: this is unsafe, but needed for send/receive! 160 | else static if (is(typeof(T[0].__isIsolatedType))) enum bool isWeaklyIsolated = true; 161 | else static if (is(typeof(T[0].__isWeakIsolatedType))) enum bool isWeaklyIsolated = true; 162 | else static if (is(T[0] == class)) enum bool isWeaklyIsolated = false; 163 | else static if (is(T[0] == interface)) enum bool isWeaklyIsolated = false; // can't know if the implementation is isolated 164 | else static if (is(T[0] == delegate)) enum bool isWeaklyIsolated = T[0].stringof.endsWith(" shared"); // can't know to what a delegate points - FIXME: use something better than a string comparison 165 | else static if (isDynamicArray!(T[0])) enum bool isWeaklyIsolated = is(typeof(T[0].init[0]) == immutable); 166 | else static if (isAssociativeArray!(T[0])) enum bool isWeaklyIsolated = false; // TODO: be less strict here 167 | else static if (isSomeFunction!(T[0])) enum bool isWeaklyIsolated = true; // functions are immutable 168 | else static if (isPointer!(T[0])) enum bool isWeaklyIsolated = is(typeof(*T[0].init) == immutable) || is(typeof(*T[0].init) == shared); 169 | else static if (isAggregateType!(T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(FieldTypeTuple!(T[0])); 170 | else enum bool isWeaklyIsolated = true; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /source/libasync/tcp.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.tcp; 3 | import std.traits : isPointer; 4 | import libasync.types; 5 | import libasync.events; 6 | import std.typecons : Tuple; 7 | import libasync.internals.logging; 8 | /// Wraps a TCP stream between 2 network adapters, using a custom handler to 9 | /// signal related events. Many of these objects can be active concurrently 10 | /// in a thread if the event loop is running and the handlers do not block. 11 | final class AsyncTCPConnection 12 | { 13 | package: 14 | EventLoop m_evLoop; 15 | 16 | private: 17 | NetworkAddress m_peer; 18 | 19 | nothrow: 20 | fd_t m_socket; 21 | fd_t m_preInitializedSocket; 22 | bool m_noDelay; 23 | bool m_inbound; 24 | version(Posix) { 25 | ~this() { 26 | if (isConnected) { 27 | import core.sys.posix.unistd : close; 28 | close(m_socket); 29 | m_socket = fd_t.init; 30 | } 31 | if (evInfo) { 32 | static if (LOG) tracef("Calling AsyncTCPConnection.~this with evInfo: %X", cast(void*)evInfo); 33 | import memutils.utils : ThreadMem; 34 | import std.exception : assumeWontThrow; 35 | assumeWontThrow(ThreadMem.free(evInfo)); 36 | evInfo = null; 37 | } 38 | } 39 | } 40 | 41 | public: 42 | /// 43 | this(EventLoop evl, fd_t preInitializedSocket = fd_t.init) 44 | in { assert(evl !is null); } 45 | do { 46 | m_evLoop = evl; 47 | m_preInitializedSocket = preInitializedSocket; 48 | } 49 | 50 | mixin DefStatus; 51 | 52 | /// Returns false if the connection has gone. 53 | @property bool isConnected() const { 54 | return m_socket != fd_t.init; 55 | } 56 | 57 | /// Returns true if this connection was accepted by an AsyncTCPListener instance. 58 | @property bool inbound() const { 59 | return m_inbound; 60 | } 61 | 62 | /// Disables(true)/enables(false) nagle's algorithm (default:enabled). 63 | @property void noDelay(bool b) 64 | { 65 | if (m_socket == fd_t.init) 66 | m_noDelay = b; 67 | else 68 | setOption(TCPOption.NODELAY, true); 69 | } 70 | 71 | /// Changes the default OS configurations for this underlying TCP Socket. 72 | bool setOption(T)(TCPOption op, in T val) 73 | in { assert(isConnected, "No socket to operate on"); } 74 | do { 75 | return m_evLoop.setOption(m_socket, op, val); 76 | } 77 | 78 | /// Returns the OS-specific structure of the internet address 79 | /// of the remote network adapter 80 | @property NetworkAddress peer() const 81 | { 82 | return m_peer; 83 | } 84 | 85 | /// Returns the OS-specific structure of the internet address 86 | /// for the local end of the connection. 87 | @property NetworkAddress local() 88 | in { 89 | assert(isConnected && m_peer != NetworkAddress.init, "Cannot get local address from a non-connected socket"); 90 | } 91 | do { 92 | return m_evLoop.localAddr(m_socket, m_peer.ipv6); 93 | } 94 | 95 | /// Sets the remote address as an OS-specific structure (only usable before connecting). 96 | @property void peer(NetworkAddress addr) 97 | in { 98 | assert(!isConnected, "Cannot change remote address on a connected socket"); 99 | assert(addr != NetworkAddress.init); 100 | } 101 | do { 102 | m_peer = addr; 103 | } 104 | 105 | /// (Blocking) Resolves the specified host and resets the peer to this address. 106 | /// Use AsyncDNS for a non-blocking resolver. (only usable before connecting). 107 | typeof(this) host(string hostname, size_t port) 108 | in { 109 | assert(!isConnected, "Cannot change remote address on a connected socket"); 110 | } 111 | do { 112 | m_peer = m_evLoop.resolveHost(hostname, cast(ushort) port); 113 | return this; 114 | } 115 | 116 | /// Sets the peer to the specified IP address and port. (only usable before connecting). 117 | typeof(this) ip(string ip, size_t port) 118 | in { 119 | assert(!isConnected, "Cannot change remote address on a connected socket"); 120 | } 121 | do { 122 | m_peer = m_evLoop.resolveIP(ip, cast(ushort) port); 123 | return this; 124 | } 125 | 126 | /// Starts the connection by registering the associated callback handler in the 127 | /// underlying OS event loop. 128 | bool run(void delegate(TCPEvent) del) { 129 | TCPEventHandler handler; 130 | handler.del = del; 131 | handler.conn = this; 132 | return run(handler); 133 | } 134 | 135 | /// 136 | bool run(TCPEventHandler del) 137 | in { assert(!isConnected); } 138 | do { 139 | m_socket = m_evLoop.run(this, del); 140 | if (m_socket == 0) 141 | return false; 142 | else 143 | return true; 144 | } 145 | 146 | /// Receive data from the underlying stream. To be used when TCPEvent.READ is received by the 147 | /// callback handler. IMPORTANT: This must be called until is returns a lower value than the buffer! 148 | final pragma(inline, true) 149 | uint recv(ref ubyte[] ub) 150 | //in { assert(isConnected, "No socket to operate on"); } 151 | //do 152 | { 153 | return m_evLoop.recv(m_socket, ub); 154 | } 155 | 156 | /// Send data through the underlying stream by moving it into the OS buffer. 157 | final pragma(inline, true) 158 | uint send(in ubyte[] ub) 159 | //in { assert(isConnected, "No socket to operate on"); } 160 | //do 161 | { 162 | uint ret = m_evLoop.send(m_socket, ub); 163 | version(Posix) 164 | if (m_evLoop.status.code == Status.ASYNC) 165 | this.writeBlocked = true; 166 | return ret; 167 | } 168 | 169 | /// Removes the connection from the event loop, closing it if necessary, and 170 | /// cleans up the underlying resources. 171 | bool kill(bool forced = false) 172 | in { assert(isConnected); } 173 | do { 174 | bool ret = m_evLoop.kill(this, forced); 175 | scope(exit) m_socket = 0; 176 | return ret; 177 | } 178 | 179 | @property fd_t socket() const { 180 | return m_socket; 181 | } 182 | 183 | package: 184 | mixin COSocketMixins; 185 | 186 | @property void inbound(bool b) { 187 | m_inbound = b; 188 | } 189 | 190 | @property bool noDelay() const 191 | { 192 | return m_noDelay; 193 | } 194 | 195 | @property void socket(fd_t sock) { 196 | m_socket = sock; 197 | } 198 | 199 | @property fd_t preInitializedSocket() const { 200 | return m_preInitializedSocket; 201 | } 202 | } 203 | 204 | /// Accepts connections on a single IP:PORT tuple by sending a new inbound AsyncTCPConnection 205 | /// object to the handler for every newly completed handshake. 206 | /// 207 | /// Note: If multiple threads are listening to the same IP:PORT tuple, the connections will 208 | /// be distributed evenly between them. However, this behavior on Windows is not implemented yet. 209 | final class AsyncTCPListener 210 | { 211 | private: 212 | nothrow: 213 | EventLoop m_evLoop; 214 | fd_t m_socket; 215 | NetworkAddress m_local; 216 | bool m_noDelay; 217 | bool m_started; 218 | 219 | public: 220 | 221 | /// 222 | this(EventLoop evl, fd_t sock = fd_t.init) { m_evLoop = evl; m_socket = sock; } 223 | 224 | mixin DefStatus; 225 | 226 | /// Sets the default value for nagle's algorithm on new connections. 227 | @property void noDelay(bool b) 228 | in { assert(!m_started, "Cannot set noDelay on a running object."); } 229 | do { 230 | m_noDelay = b; 231 | } 232 | 233 | /// Returns the local internet address as an OS-specific structure. 234 | @property NetworkAddress local() const 235 | { 236 | return m_local; 237 | } 238 | 239 | /// Sets the local internet address as an OS-specific structure. 240 | @property void local(NetworkAddress addr) 241 | in { assert(!m_started, "Cannot rebind a listening socket"); } 242 | do { 243 | m_local = addr; 244 | } 245 | 246 | /// Sets the local listening interface to the specified hostname/port. 247 | typeof(this) host(string hostname, size_t port) 248 | in { assert(!m_started, "Cannot rebind a listening socket"); } 249 | do { 250 | m_local = m_evLoop.resolveHost(hostname, cast(ushort) port); 251 | return this; 252 | } 253 | 254 | /// Sets the local listening interface to the specified ip/port. 255 | typeof(this) ip(string ip, size_t port) 256 | in { assert(!m_started, "Cannot rebind a listening socket"); } 257 | do { 258 | m_local = m_evLoop.resolveIP(ip, cast(ushort) port); 259 | return this; 260 | } 261 | 262 | /// Starts accepting connections by registering the given handler with the underlying OS event. 263 | bool run(void delegate(TCPEvent) delegate(AsyncTCPConnection) del) { 264 | TCPAcceptHandler handler; 265 | handler.ctxt = this; 266 | handler.del = del; 267 | return run(handler); 268 | } 269 | 270 | private bool run(TCPAcceptHandler del) 271 | in { 272 | assert(m_local != NetworkAddress.init, "Cannot bind without an address. Please run .host() or .ip()"); 273 | } 274 | do { 275 | m_socket = m_evLoop.run(this, del); 276 | if (m_socket == fd_t.init) 277 | return false; 278 | else { 279 | if (m_local.port == 0) 280 | m_local = m_evLoop.localAddr(m_socket, m_local.ipv6); 281 | m_started = true; 282 | return true; 283 | } 284 | } 285 | 286 | /// Use to implement distributed servicing of connections 287 | @property fd_t socket() const { 288 | return m_socket; 289 | } 290 | 291 | /// Stops accepting connections and cleans up the underlying OS resources. 292 | bool kill() 293 | in { assert(m_socket != 0); } 294 | do { 295 | bool ret = m_evLoop.kill(this); 296 | if (ret) 297 | m_started = false; 298 | return ret; 299 | } 300 | 301 | package: 302 | version(Posix) mixin EvInfoMixins; 303 | version(Distributed) version(Windows) mixin TCPListenerDistMixins; 304 | @property bool noDelay() const 305 | { 306 | return m_noDelay; 307 | } 308 | } 309 | 310 | package struct TCPEventHandler { 311 | AsyncTCPConnection conn; 312 | 313 | /// Use getContext/setContext to persist the context in each activity. Using AsyncTCPConnection in args 314 | /// allows the EventLoop implementation to create and pass a new object, which is necessary for listeners. 315 | void delegate(TCPEvent) del; 316 | 317 | void opCall(TCPEvent ev){ 318 | if (conn is null || !conn.isConnected) return; //, "Connection was disposed before shutdown could be completed"); 319 | del(ev); 320 | return; 321 | } 322 | } 323 | 324 | package struct TCPAcceptHandler { 325 | AsyncTCPListener ctxt; 326 | void delegate(TCPEvent) delegate(AsyncTCPConnection) del; 327 | 328 | TCPEventHandler opCall(AsyncTCPConnection conn){ // conn is null = error! 329 | assert(ctxt !is null); 330 | 331 | void delegate(TCPEvent) ev_handler = del(conn); 332 | TCPEventHandler handler; 333 | handler.del = ev_handler; 334 | handler.conn = conn; 335 | return handler; 336 | } 337 | } 338 | 339 | /// 340 | enum TCPEvent : char { 341 | ERROR = 0, /// The connection will be forcefully closed, this is debugging information 342 | CONNECT, /// indicates write will not block, although recv may or may not have data 343 | READ, /// called once when new bytes are in the buffer 344 | WRITE, /// only called when send returned Status.ASYNC 345 | CLOSE /// The connection is being shutdown 346 | } 347 | 348 | /// 349 | enum TCPOption : char { 350 | NODELAY = 0, /// Don't delay send to coalesce packets 351 | REUSEADDR = 1, /// 352 | REUSEPORT, /// 353 | CORK, /// 354 | LINGER, /// 355 | BUFFER_RECV, /// 356 | BUFFER_SEND, /// 357 | TIMEOUT_RECV, /// 358 | TIMEOUT_SEND, /// 359 | TIMEOUT_HALFOPEN, /// 360 | KEEPALIVE_ENABLE, /// 361 | KEEPALIVE_DEFER, /// Start keeplives after this period 362 | KEEPALIVE_COUNT, /// Number of keepalives before death 363 | KEEPALIVE_INTERVAL, /// Interval between keepalives 364 | DEFER_ACCEPT, /// 365 | QUICK_ACK, /// Bock/reenable quick ACKs. 366 | CONGESTION /// 367 | } 368 | -------------------------------------------------------------------------------- /source/libasync/test.d: -------------------------------------------------------------------------------- 1 | module libasync.test; 2 | version(unittest): 3 | import libasync.events; 4 | import std.stdio; 5 | import std.datetime; 6 | import libasync.file; 7 | import std.conv : to; 8 | import std.datetime.stopwatch : StopWatch; 9 | import core.stdc.stdlib : getenv; 10 | import std.string : fromStringz, toStringz; 11 | AsyncDirectoryWatcher g_watcher; 12 | shared AsyncDNS g_dns; 13 | import libasync.internals.logging; 14 | string cache_path; 15 | 16 | 17 | unittest { 18 | cache_path = "."; 19 | version(iOS) cache_path = getenv("HOME".toStringz).fromStringz.to!string ~ "/Library/Caches"; 20 | 21 | version(Libasync_Threading) spawnAsyncThreads(); 22 | scope(exit) 23 | destroyAsyncThreads(); 24 | //writeln("Unit test started"); 25 | g_cbCheck = new shared bool[19]; 26 | g_lastTimer = Clock.currTime(); 27 | gs_start = Clock.currTime(); 28 | g_evl = getThreadEventLoop(); 29 | scope(exit) g_evl.destroy(); 30 | g_evl.loop(1.msecs); 31 | //writeln("Loading objects..."); 32 | testDirectoryWatcher(); 33 | testDNS(); 34 | testOneshotTimer(); 35 | testMultiTimer(); 36 | gs_tlsEvent = new shared AsyncSignal(g_evl); 37 | testSignal(); 38 | testEvents(); 39 | testTCPListen("localhost", 8081); 40 | testHTTPConnect(); 41 | //writeln("Loaded. Running event loop..."); 42 | testFile(); 43 | testTCPConnect("localhost", 8081); 44 | while(Clock.currTime() - gs_start < 7.seconds) 45 | g_evl.loop(100.msecs); 46 | 47 | int i; 48 | g_listnr.kill(); 49 | g_watcher.kill(); 50 | g_timerOneShot.kill(); 51 | g_timerMulti.kill(); 52 | g_notifier.kill(); 53 | g_evl.exit(); 54 | 55 | g_watcher.destroy(); 56 | g_notifier.destroy(); 57 | g_listnr.destroy(); 58 | g_dns.destroy(); 59 | g_tcpConnect.destroy(); 60 | g_conn.destroy(); 61 | foreach (bool b; g_cbCheck) { 62 | assert(b, "Callback not triggered: g_cbCheck[" ~ i.to!string ~ "]"); 63 | i++; 64 | } 65 | writeln("Callback triggers were successful, run time: ", Clock.currTime - gs_start); 66 | 67 | assert(g_cbTimerCnt >= 3, "Multitimer expired only " ~ g_cbTimerCnt.to!string ~ " times"); // MultiTimer expired 3-4 times 68 | 69 | 70 | } 71 | 72 | StopWatch g_swDns; 73 | void testDNS() { 74 | g_dns = new shared AsyncDNS(g_evl); 75 | g_swDns.start(); 76 | g_dns.handler((NetworkAddress addr) { 77 | g_cbCheck[17] = true; 78 | writeln("Resolved to: ", addr.toString(), ", it took: ", g_swDns.peek().total!"usecs", " usecs"); 79 | }).resolveHost("httpbin.org", false, true); 80 | } 81 | 82 | void testDirectoryWatcher() { 83 | import std.file : mkdir, rmdir, exists; 84 | if (exists(cache_path ~ "/hey/tmp.tmp")) 85 | remove((cache_path ~ "/hey/tmp.tmp").toStringz); 86 | if (exists(cache_path ~ "/hey")) 87 | rmdir(cache_path ~ "/hey"); 88 | g_watcher = new AsyncDirectoryWatcher(g_evl); 89 | g_watcher.run({ 90 | DWChangeInfo[1] change; 91 | DWChangeInfo[] changeRef = change.ptr[0..1]; 92 | bool done; 93 | while(g_watcher.readChanges(changeRef)) 94 | { 95 | g_cbCheck[18] = true; 96 | writeln(change); 97 | if (change[0].event == DWFileEvent.DELETED) 98 | done = true; 99 | } 100 | }); 101 | g_watcher.watchDir(cache_path); 102 | 103 | AsyncTimer tm = new AsyncTimer(g_evl); 104 | tm.duration(1.seconds).run({ 105 | writeln("Creating directory ./hey"); 106 | mkdir(cache_path ~ "/hey"); 107 | assert(g_watcher.watchDir(cache_path ~ "/hey/")); 108 | tm.duration(1.seconds).run({ 109 | static import std.file; 110 | writeln("Writing to ./hey/tmp.tmp for the first time"); 111 | std.file.write(cache_path ~ "/hey/tmp.tmp", "some string"); 112 | tm.duration(100.msecs).run({ 113 | writeln("Removing ./hey/tmp.tmp"); 114 | remove((cache_path ~ "/hey/tmp.tmp").toStringz); 115 | tm.kill(); 116 | }); 117 | }); 118 | }); 119 | } 120 | 121 | void testFile() { 122 | gs_file = new shared AsyncFile(g_evl); 123 | 124 | { 125 | File file = File(cache_path ~ "/test.txt", "w+"); 126 | file.rawWrite("This is the file content."); 127 | file.close(); 128 | //writeln("Wrote to file"); 129 | } 130 | gs_file.onReady({ 131 | //writeln("Created and wrote to test.txt through AsyncFile"); 132 | auto file = gs_file; 133 | //if (file.status.code == Status.ERROR) 134 | //writeln("ERROR: ", file.status.text); 135 | 136 | import std.string : startsWith; 137 | if ((cast(string)file.buffer).startsWith("This is the file content.")) { 138 | g_cbCheck[7] = true; 139 | } 140 | else { 141 | writeln("ERROR: ", cast(string)file.buffer); 142 | assert(false); 143 | } 144 | import std.file : remove; 145 | gs_file.kill(); 146 | remove(cache_path ~ "/test.txt"); 147 | //writeln("Removed test.txt .. "); 148 | }).read(cache_path ~ "/test.txt"); 149 | 150 | } 151 | 152 | 153 | void testSignal() { 154 | g_notifier = new AsyncNotifier(g_evl); 155 | auto title = "This is my title"; 156 | 157 | void delegate() del = { 158 | import std.stdio; 159 | assert(title == "This is my title"); 160 | g_cbCheck[0] = true; 161 | //writeln("Got signal title"); 162 | 163 | return; 164 | }; 165 | 166 | g_notifier.run(del); 167 | g_notifier.trigger(); // will be completed in the event loop 168 | } 169 | 170 | void testEvents() { 171 | 172 | gs_tlsEvent.run({ 173 | assert(g_message == "Some message here"); 174 | g_cbCheck[1] = true; 175 | //writeln("Got valid TLS Event"); 176 | }); 177 | 178 | gs_shrEvent = new shared AsyncSignal(g_evl); 179 | 180 | gs_shrEvent.run({ 181 | assert(gs_hshr.message == "Hello from shared!"); 182 | g_cbCheck[2] = true; 183 | //writeln("Got valid shared event!"); 184 | }); 185 | 186 | testTLSEvent(); 187 | 188 | import std.concurrency; 189 | Tid t2 = spawn(&testSharedEvent); 190 | import core.thread : Thread; 191 | while (!gs_shrEvent2 || gs_shrEvent2.id == 0) 192 | Thread.sleep(100.msecs); 193 | 194 | gs_shrEvent2.trigger(g_evl); 195 | } 196 | 197 | void testTLSEvent() { 198 | gs_tlsEvent.trigger(); 199 | } 200 | 201 | void testSharedEvent() { 202 | EventLoop evl2 = new EventLoop; 203 | 204 | gs_shrEvent2 = new shared AsyncSignal(evl2); 205 | gs_shrEvent2.run({ 206 | g_cbCheck[3] = true; 207 | return; 208 | }); 209 | 210 | gs_shrEvent.trigger(evl2); 211 | 212 | while(Clock.currTime() - gs_start < 1.seconds) 213 | evl2.loop(); 214 | 215 | gs_shrEvent.trigger(evl2); 216 | 217 | while(Clock.currTime() - gs_start < 4.seconds) 218 | evl2.loop(); 219 | 220 | evl2.exit(); 221 | } 222 | 223 | void testOneshotTimer() { 224 | g_timerOneShot = new AsyncTimer(g_evl); 225 | g_timerOneShot.duration(1.seconds).run({ 226 | assert(!g_cbCheck[4] && Clock.currTime() - gs_start > 900.msecs && Clock.currTime() - gs_start < 2500.msecs, "Timer completed in " ~ (Clock.currTime() - gs_start).total!"msecs".to!string ~ "ms" ); 227 | assert(g_timerOneShot.id != 0); 228 | //writeln("Got timer callback!"); 229 | g_cbCheck[4] = true; 230 | 231 | }); 232 | } 233 | 234 | void testMultiTimer() { 235 | g_timerMulti = new AsyncTimer(g_evl); 236 | g_timerMulti.periodic().duration(1.seconds).run({ 237 | assert(g_lastTimer !is SysTime.init && Clock.currTime() - g_lastTimer > 900.msecs && Clock.currTime() - g_lastTimer < 2500.msecs, "Timer completed in " ~ (Clock.currTime() - gs_start).total!"msecs".to!string ~ "ms" ); 238 | assert(g_timerMulti.id > 0); 239 | assert(!g_timerMulti.oneShot); 240 | g_lastTimer = Clock.currTime(); 241 | g_cbTimerCnt++; 242 | //writeln("Got timer callback #", g_cbTimerCnt.to!string); 243 | g_cbCheck[5] = true; 244 | }); 245 | 246 | } 247 | 248 | 249 | void trafficHandler(TCPEvent ev){ 250 | //writeln("##TrafficHandler!"); 251 | void doRead() { 252 | static ubyte[] bin = new ubyte[4092]; 253 | while (true) { 254 | uint len = g_conn.recv(bin); 255 | //writeln("!!Server Received " ~ len.to!string ~ " bytes"); 256 | // import std.file; 257 | if (len > 0) { 258 | auto res = cast(string)bin[0..len]; 259 | //writeln(res); 260 | import std.algorithm : canFind; 261 | if (res.canFind("Client Hello")) 262 | g_cbCheck[8] = true; 263 | 264 | if (res.canFind("Client WRITE")) 265 | g_cbCheck[8] = true; 266 | 267 | if (res.canFind("Client READ")) 268 | g_cbCheck[9] = true; 269 | 270 | if (res.canFind("Client KILL")) 271 | g_cbCheck[10] = true; 272 | } 273 | if (len < bin.length) 274 | break; 275 | } 276 | } 277 | 278 | final switch (ev) { 279 | case TCPEvent.CONNECT: 280 | //writeln("!!Server Connected"); 281 | doRead(); 282 | if (g_conn.socket != 0) 283 | g_conn.send(cast(ubyte[])"Server Connect"); 284 | break; 285 | case TCPEvent.READ: 286 | //writeln("!!Server Read is ready"); 287 | g_cbCheck[11] = true; 288 | if (g_conn.socket != 0) 289 | g_conn.send(cast(ubyte[])"Server READ"); 290 | doRead(); 291 | break; 292 | case TCPEvent.WRITE: 293 | //writeln("!!Server Write is ready"); 294 | if (g_conn.socket != 0) 295 | g_conn.send(cast(ubyte[])"Server WRITE"); 296 | break; 297 | case TCPEvent.CLOSE: 298 | doRead(); 299 | //writeln("!!Server Disconnect!"); 300 | g_cbCheck[12] = true; 301 | break; 302 | case TCPEvent.ERROR: 303 | //writeln("!!Server Error!"); 304 | break; 305 | } 306 | 307 | return; 308 | } 309 | 310 | void testTCPListen(string ip, ushort port) { 311 | 312 | g_listnr = new AsyncTCPListener(g_evl); 313 | 314 | void delegate(TCPEvent) handler(AsyncTCPConnection conn) { 315 | g_conn = conn; 316 | g_cbCheck[6] = true; 317 | import std.functional : toDelegate; 318 | //writeln("Got handler TCPListen"); 319 | return toDelegate(&trafficHandler); 320 | } 321 | 322 | auto success = g_listnr.host(ip, port).run(&handler); 323 | assert(success, g_listnr.error); 324 | } 325 | 326 | void testTCPConnect(string ip, ushort port) { 327 | g_tcpConnect = new AsyncTCPConnection(g_evl); 328 | g_tcpConnect.peer = g_evl.resolveHost(ip, port); 329 | 330 | void delegate(TCPEvent) connHandler = (TCPEvent ev){ 331 | void doRead() { 332 | static ubyte[] bin = new ubyte[4092]; 333 | while (true) { 334 | assert(g_tcpConnect.socket > 0); 335 | uint len = g_tcpConnect.recv(bin); 336 | //writeln("!!Client Received " ~ len.to!string ~ " bytes"); 337 | //if (len > 0) 338 | // writeln(cast(string)bin[0..len]); 339 | if (len < bin.length) 340 | break; 341 | } 342 | } 343 | final switch (ev) { 344 | case TCPEvent.CONNECT: 345 | // writeln("!!Client Connected"); 346 | g_tcpConnect.setOption(TCPOption.QUICK_ACK, true); 347 | g_tcpConnect.setOption(TCPOption.NODELAY, true); 348 | g_cbCheck[14] = true; 349 | if (g_tcpConnect.socket != 0) 350 | g_tcpConnect.send(cast(ubyte[])"Client Hello"); 351 | assert(g_tcpConnect.socket > 0); 352 | break; 353 | case TCPEvent.READ: 354 | //writeln("!!Client Read is ready at writes: ", g_writes); 355 | doRead(); 356 | 357 | // respond 358 | g_writes += 1; 359 | if (g_writes > 3) { 360 | if (g_tcpConnect.socket != 0) 361 | g_tcpConnect.send(cast(ubyte[])"Client KILL"); 362 | g_tcpConnect.kill(); 363 | 364 | g_cbCheck[13] = true; 365 | } 366 | else 367 | if (g_tcpConnect.socket != 0) 368 | g_tcpConnect.send(cast(ubyte[])"Client READ"); 369 | 370 | break; 371 | case TCPEvent.WRITE: 372 | g_writes += 1; 373 | static if (LOG) tracef("!!Client Write is ready"); 374 | if (g_tcpConnect.socket != 0) 375 | g_tcpConnect.send(cast(ubyte[])"Client WRITE"); 376 | break; 377 | case TCPEvent.CLOSE: 378 | static if (LOG) tracef("!!Client Disconnected"); 379 | break; 380 | case TCPEvent.ERROR: 381 | static if (LOG) tracef("!!Client Error!"); 382 | break; 383 | } 384 | return; 385 | }; 386 | 387 | auto success = g_tcpConnect.run(connHandler); 388 | assert(success); 389 | 390 | } 391 | 392 | void testHTTPConnect() { 393 | auto conn = new AsyncTCPConnection(g_evl); 394 | conn.peer = g_evl.resolveHost("httpbin.org", 80); 395 | 396 | auto del = (TCPEvent ev){ 397 | final switch (ev) { 398 | case TCPEvent.CONNECT: 399 | //writeln("!!Connected"); 400 | static ubyte[] abin = new ubyte[4092]; 401 | while (true) { 402 | uint len = conn.recv(abin); 403 | if (len < abin.length) 404 | break; 405 | } 406 | g_cbCheck[15] = true; 407 | //writeln(conn.local.toString()); 408 | //writeln(conn.peer.toString()); 409 | conn.send(cast(ubyte[])"GET /ip\nHost: httpbin.org\nConnection: close\n\n"); 410 | break; 411 | case TCPEvent.READ: 412 | static ubyte[] bin = new ubyte[4092]; 413 | while (true) { 414 | uint len = conn.recv(bin); 415 | g_cbCheck[16] = true; 416 | if (len > 0) writeln("HTTP Response: ", cast(string)bin.ptr[0 .. len]); 417 | //writeln("!!Received " ~ len.to!string ~ " bytes"); 418 | if (len < bin.length) 419 | break; 420 | } 421 | break; 422 | case TCPEvent.WRITE: 423 | //writeln("!!Write is ready"); 424 | break; 425 | case TCPEvent.CLOSE: 426 | //writeln("!!Disconnected"); 427 | break; 428 | case TCPEvent.ERROR: 429 | //writeln("!!Error!"); 430 | break; 431 | } 432 | return; 433 | }; 434 | 435 | 436 | conn.run(del); 437 | } 438 | 439 | EventLoop g_evl; 440 | AsyncTimer g_timerOneShot; 441 | AsyncTimer g_timerMulti; 442 | AsyncTCPConnection g_tcpConnect; 443 | AsyncTCPConnection g_httpConnect; 444 | AsyncTCPConnection g_conn; // incoming 445 | AsyncNotifier g_notifier; 446 | AsyncTCPListener g_listnr; 447 | shared AsyncSignal gs_tlsEvent; 448 | shared AsyncSignal gs_shrEvent; 449 | shared AsyncSignal gs_shrEvent2; 450 | shared AsyncFile gs_file; 451 | __gshared SysTime gs_start; 452 | string g_message = "Some message here"; 453 | shared Msg* gs_hshr = new shared Msg("Hello from shared!"); 454 | shared bool[] g_cbCheck; 455 | int g_cbTimerCnt; 456 | int g_writes; 457 | SysTime g_lastTimer; 458 | 459 | shared struct Msg { 460 | string message; 461 | } 462 | -------------------------------------------------------------------------------- /source/libasync/threads.d: -------------------------------------------------------------------------------- 1 |  2 | module libasync.threads; 3 | 4 | import std.parallelism; 5 | 6 | import libasync.internals.logging; 7 | 8 | bool spawnAsyncThreads(uint threadCount = totalCPUs > 1 ? totalCPUs - 1 : 1) nothrow 9 | in { 10 | assert(threadCount >= 1, "Need at least one worker thread"); 11 | } do { 12 | try defaultPoolThreads(threadCount); 13 | catch (Exception e) { 14 | critical("Failed to spawn worker threads: ", e.toString()); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | nothrow 21 | bool doOffThread(void delegate() work) { 22 | try taskPool.put(task(work)); 23 | catch (Exception e) { 24 | critical("Failed to dispatch task to thread pool: ", e.toString()); 25 | return false; 26 | } 27 | return true; 28 | } 29 | 30 | nothrow 31 | void destroyAsyncThreads(bool wait = true) { 32 | try taskPool.finish(wait); 33 | catch (Exception e) { 34 | critical("Failed to terminate worker threads: ", e.toString()); 35 | assert(false); 36 | } 37 | } -------------------------------------------------------------------------------- /source/libasync/timer.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.timer; 3 | 4 | import libasync.types; 5 | import libasync.events; 6 | import std.datetime; 7 | 8 | /// 9 | final class AsyncTimer 10 | { 11 | 12 | nothrow: 13 | private: 14 | bool m_oneshot = true; 15 | fd_t m_timerId; 16 | EventLoop m_evLoop; 17 | TimerHandler m_evh; 18 | Duration m_timeout; 19 | bool m_shooting = false; 20 | bool m_rearmed = false; 21 | 22 | public: 23 | /// 24 | this(EventLoop evl) 25 | in { assert(evl !is null); } 26 | do { m_evLoop = evl; } 27 | 28 | mixin DefStatus; 29 | 30 | /// Returns the Duration that the timer will wait before calling the handler 31 | /// after it is run. 32 | @property Duration timeout() const { 33 | return m_timeout; 34 | } 35 | 36 | /// Returns whether the timer is set to rearm itself (oneShot=false) or 37 | /// if it will have to be rearmed (oneShot=true). 38 | @property bool oneShot() const { 39 | return m_oneshot; 40 | } 41 | 42 | /// Sets the timer to become periodic. For a running timer, 43 | /// this setting will take effect after the timer is expired (oneShot) or 44 | /// after it is killed (periodic). 45 | typeof(this) periodic(bool b = true) 46 | in { assert(m_timerId == 0 || m_oneshot); } 47 | do 48 | { 49 | m_oneshot = !b; 50 | return this; 51 | } 52 | 53 | /// Sets or changes the duration on the timer. For a running timer, 54 | /// this setting will take effect after the timer is expired (oneShot) or 55 | /// after it is killed (periodic). 56 | typeof(this) duration(Duration dur) { 57 | m_timeout = dur; 58 | return this; 59 | } 60 | 61 | /// Runs a non-periodic, oneshot timer once using the specified Duration as 62 | /// a timeout. The handler from the last call to run() will be reused. 63 | bool rearm(Duration dur) 64 | in { 65 | assert(m_timeout > 0.seconds); 66 | // assert(m_shooting); 67 | assert(m_oneshot, "Cannot rearm a periodic timer, it must fist be killed."); 68 | } 69 | do { 70 | m_rearmed = true; 71 | 72 | m_timerId = m_evLoop.run(this, m_evh, dur); 73 | m_timeout = dur; 74 | 75 | if (m_timerId == 0) 76 | return false; 77 | else 78 | return true; 79 | } 80 | 81 | /// Starts the timer using the delegate as an expiration callback. 82 | bool run(void delegate() del) 83 | in { 84 | assert(m_timeout > 0.seconds); 85 | assert(m_oneshot || !m_timerId, "Cannot rearm a periodic timer, it must fist be killed."); 86 | } 87 | do { 88 | TimerHandler handler; 89 | handler.del = del; 90 | handler.ctxt = this; 91 | 92 | return run(handler); 93 | } 94 | 95 | private bool run(TimerHandler cb) { 96 | m_evh = cb; 97 | 98 | if (m_timerId) 99 | m_rearmed = true; 100 | else 101 | m_rearmed = false; 102 | m_timerId = m_evLoop.run(this, cb, m_timeout); 103 | // try writeln("Timer starting", m_timerId); catch (Throwable e) {} 104 | if (m_timerId == 0) 105 | return false; 106 | else 107 | return true; 108 | } 109 | 110 | /// Cleans up underlying OS resources. This is required to change the 111 | /// timer from periodic to oneshot, or before disposing of this object. 112 | bool kill() { 113 | return m_evLoop.kill(this); 114 | } 115 | 116 | /// 117 | @property fd_t id() { 118 | return m_timerId; 119 | } 120 | 121 | package: 122 | version(Posix) mixin EvInfoMixins; 123 | 124 | @property void id(fd_t fd) { 125 | m_timerId = fd; 126 | } 127 | 128 | @property void rearmed(bool b) { 129 | m_rearmed = b; 130 | } 131 | 132 | @property bool rearmed() { 133 | return m_rearmed; 134 | } 135 | 136 | /*void handler() { 137 | try m_evh(); 138 | catch (Throwable e) {} 139 | return; 140 | }*/ 141 | } 142 | 143 | package struct TimerHandler { 144 | AsyncTimer ctxt; 145 | void delegate() del; 146 | void opCall() { 147 | assert(ctxt !is null); 148 | ctxt.m_rearmed = false; 149 | del(); 150 | return; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /source/libasync/udp.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.udp; 3 | 4 | import libasync.types; 5 | 6 | import libasync.events; 7 | 8 | /// Wrapper for a UDP Stream which must be bound to a socket. 9 | final nothrow class AsyncUDPSocket 10 | { 11 | nothrow: 12 | private: 13 | EventLoop m_evLoop; 14 | fd_t m_socket; 15 | fd_t m_preInitializedSocket; 16 | NetworkAddress m_local; 17 | 18 | public: 19 | /// 20 | this(EventLoop evl, fd_t preInitializedSocket = fd_t.init) 21 | in { assert(evl !is null); } 22 | do { 23 | m_evLoop = evl; 24 | m_preInitializedSocket = preInitializedSocket; 25 | } 26 | 27 | mixin DefStatus; 28 | 29 | 30 | /// Returns the locally bound address as an OS-specific structure. 31 | @property NetworkAddress local() const 32 | { 33 | return m_local; 34 | } 35 | 36 | /// Grants broadcast permissions to the socket (must be set before run). 37 | bool broadcast(bool b) 38 | in { assert(m_socket != fd_t.init, "Cannot change state on unbound UDP socket"); } 39 | do { 40 | return m_evLoop.broadcast(m_socket, b); 41 | } 42 | 43 | /// Sets the hostname and port to which the UDP socket must be bound locally. 44 | typeof(this) host(string hostname, size_t port) 45 | in { assert(m_socket == fd_t.init, "Cannot rebind an UDP socket"); } 46 | do 47 | { 48 | m_local = m_evLoop.resolveHost(hostname, cast(ushort) port); 49 | return this; 50 | } 51 | 52 | /// Sets the IP and port to which the UDP socket will be bound locally. 53 | typeof(this) ip(string ip, size_t port) 54 | in { assert(m_socket == fd_t.init, "Cannot rebind an UDP socket"); } 55 | do { 56 | m_local = m_evLoop.resolveIP(ip, cast(ushort) port); 57 | return this; 58 | } 59 | 60 | /// Sets the local network address to which this UDP Socket will be bound. 61 | @property void local(NetworkAddress l) 62 | in { 63 | assert(l != NetworkAddress.init, "The local address is empty"); 64 | assert(m_socket == fd_t.init, "Cannot rebind an UDP socket"); 65 | } 66 | do { 67 | m_local = l; 68 | } 69 | 70 | /// Registers the UDP socket in the underlying OS event loop, forwards 71 | /// all related events to the specified delegate. 72 | bool run(void delegate(UDPEvent) del) 73 | { 74 | UDPHandler handler; 75 | handler.del = del; 76 | handler.conn = this; 77 | return run(handler); 78 | } 79 | 80 | private bool run(UDPHandler del) 81 | in { assert(m_local != NetworkAddress.init && m_socket == fd_t.init, "Cannot rebind an UDP socket"); } 82 | do { 83 | m_socket = m_evLoop.run(this, del); 84 | if (m_socket == fd_t.init) 85 | return false; 86 | else { 87 | if (m_local.port == 0) 88 | m_local = m_evLoop.localAddr(m_socket, m_local.ipv6); 89 | return true; 90 | } 91 | } 92 | 93 | /// Receives data from one peer and copies its address to the 94 | /// associated OS-specific address structure. 95 | uint recvFrom(ref ubyte[] data, ref NetworkAddress addr) { 96 | return m_evLoop.recvFrom(m_socket, data, addr); 97 | } 98 | 99 | /// Sends data to the internet address specified by the associated 100 | /// OS-specific structure. 101 | uint sendTo(in ubyte[] data, in NetworkAddress addr) { 102 | return m_evLoop.sendTo(m_socket, data, addr); 103 | } 104 | 105 | /// Cleans up the resources associated with this object in the underlying OS. 106 | bool kill() 107 | in { assert(m_socket != fd_t.init); } 108 | do { 109 | return m_evLoop.kill(this); 110 | } 111 | 112 | @property fd_t socket() const { 113 | return m_socket; 114 | } 115 | 116 | package: 117 | version(Posix) mixin EvInfoMixins; 118 | 119 | @property void socket(fd_t val) { 120 | m_socket = val; 121 | } 122 | 123 | @property fd_t preInitializedSocket() const { 124 | return m_preInitializedSocket; 125 | } 126 | } 127 | 128 | package struct UDPHandler { 129 | AsyncUDPSocket conn; 130 | void delegate(UDPEvent) del; 131 | void opCall(UDPEvent code){ 132 | if(conn is null) 133 | return; 134 | del(code); 135 | assert(conn !is null); 136 | return; 137 | } 138 | } 139 | 140 | /// 141 | enum UDPEvent : char { 142 | ERROR = 0, /// 143 | READ, /// 144 | WRITE /// 145 | } 146 | -------------------------------------------------------------------------------- /source/libasync/uds.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.uds; 3 | 4 | version (Posix): 5 | 6 | public import std.socket : UnixAddress; 7 | 8 | import libasync.types; 9 | import libasync.events; 10 | import libasync.event; 11 | 12 | import core.sys.posix.sys.socket; 13 | 14 | /// 15 | final class AsyncUDSConnection 16 | { 17 | package: 18 | EventLoop m_evLoop; 19 | AsyncEvent m_event; 20 | 21 | private: 22 | UnixAddress m_peer; 23 | fd_t m_socket, m_preInitializedSocket; 24 | bool m_inbound; 25 | 26 | nothrow: 27 | 28 | package: 29 | @property fd_t socket() const { 30 | return m_socket; 31 | } 32 | 33 | @property fd_t preInitializedSocket() const { 34 | return m_preInitializedSocket; 35 | } 36 | 37 | @property void inbound(bool inbound) { 38 | m_inbound = inbound; 39 | } 40 | 41 | public: 42 | /// 43 | this(EventLoop evl, fd_t preInitializedSocket = fd_t.init) 44 | in { assert(evl !is null); } 45 | do { 46 | m_evLoop = evl; 47 | m_preInitializedSocket = preInitializedSocket; 48 | } 49 | 50 | mixin DefStatus; 51 | 52 | /// Returns false if the connection has gone. 53 | @property bool isConnected() const { 54 | return m_socket != fd_t.init; 55 | } 56 | 57 | /// Returns true if this connection was accepted by an AsyncUDSListener instance. 58 | @property bool inbound() const { 59 | return m_inbound; 60 | } 61 | 62 | /// 63 | @property UnixAddress peer() const 64 | { 65 | return cast(UnixAddress) m_peer; 66 | } 67 | 68 | /// 69 | @property void peer(UnixAddress addr) 70 | in { 71 | assert(!isConnected, "Cannot change remote address on a connected socket"); 72 | assert(addr !is UnixAddress.init); 73 | } 74 | do { 75 | m_peer = addr; 76 | } 77 | 78 | /// 79 | bool run(void delegate(EventCode) del) 80 | in { assert(!isConnected); } 81 | do { 82 | m_socket = m_evLoop.run(this); 83 | if (m_socket == 0) return false; 84 | 85 | m_event = new AsyncEvent(m_evLoop, m_socket, true); 86 | return m_event.run(del); 87 | } 88 | 89 | /// Receive data from the underlying stream. To be used when EventCode.READ is received by the 90 | /// callback handler. IMPORTANT: This must be called until is returns a lower value than the buffer! 91 | final pragma(inline, true) 92 | uint recv(ref ubyte[] ub) 93 | { 94 | return m_evLoop.recv(m_socket, ub); 95 | } 96 | 97 | /// Send data through the underlying stream by moving it into the OS buffer. 98 | final pragma(inline, true) 99 | uint send(in ubyte[] ub) 100 | { 101 | uint ret = m_evLoop.send(m_socket, ub); 102 | if (m_evLoop.status.code == Status.ASYNC) 103 | m_event.writeBlocked = true; 104 | return ret; 105 | } 106 | 107 | /// Removes the connection from the event loop, closing it if necessary, and 108 | /// cleans up the underlying resources. 109 | bool kill(bool forced = false) 110 | in { assert(isConnected); } 111 | do { 112 | scope(exit) m_socket = 0; 113 | return m_event.kill(forced); 114 | } 115 | } 116 | 117 | /// 118 | final class AsyncUDSListener 119 | { 120 | package: 121 | EventLoop m_evLoop; 122 | 123 | private: 124 | AsyncEvent m_event; 125 | 126 | UnixAddress m_local; 127 | fd_t m_socket; 128 | bool m_started, m_unlinkFirst; 129 | void delegate(EventCode) delegate(AsyncUDSConnection) nothrow m_del; 130 | 131 | nothrow: 132 | 133 | private: 134 | void handler(EventCode code) 135 | { 136 | switch (code) { 137 | case EventCode.READ: 138 | AsyncUDSConnection conn = void; 139 | while ((conn = m_evLoop.accept(this)) !is null) { 140 | conn.run(m_del(conn)); 141 | } 142 | break; 143 | default: 144 | break; 145 | } 146 | } 147 | 148 | package: 149 | @property bool unlinkFirst() const { 150 | return m_unlinkFirst; 151 | } 152 | 153 | @property fd_t socket() const { 154 | return m_socket; 155 | } 156 | 157 | public: 158 | /// 159 | this(EventLoop evl, bool unlinkFirst = true) 160 | in { assert(evl !is null); } 161 | do { 162 | m_evLoop = evl; 163 | m_unlinkFirst = unlinkFirst; 164 | } 165 | 166 | mixin DefStatus; 167 | 168 | /// Returns the unix domain socket address as an OS-specific structure. 169 | @property UnixAddress local() const 170 | { 171 | return cast(UnixAddress) m_local; 172 | } 173 | 174 | /// Sets the local internet address as an OS-specific structure. 175 | @property void local(UnixAddress addr) 176 | in { assert(!m_started, "Cannot rebind a listening socket"); } 177 | do { 178 | m_local = addr; 179 | } 180 | 181 | /// Starts accepting connections by registering the given handler with the underlying OS event. 182 | bool run(void delegate(EventCode) delegate(AsyncUDSConnection) nothrow del) 183 | in { 184 | assert(m_local !is UnixAddress.init, "Cannot bind without an address. Please set .local"); 185 | } 186 | do { 187 | m_del = del; 188 | m_socket = m_evLoop.run(this); 189 | if (m_socket == 0) return false; 190 | 191 | m_event = new AsyncEvent(m_evLoop, m_socket, false); 192 | m_started = m_event.run(&handler); 193 | return m_started; 194 | } 195 | 196 | /// Stops accepting connections and cleans up the underlying OS resources. 197 | /// NOTE: MUST be called to clean up the domain socket path 198 | bool kill() 199 | in { assert(m_socket != 0); } 200 | do { 201 | import core.sys.posix.unistd : unlink; 202 | import core.sys.posix.sys.un : sockaddr_un; 203 | 204 | bool ret = m_evLoop.kill(m_event); 205 | if (ret) m_started = false; 206 | unlink(cast(char*) (cast(sockaddr_un*) m_local.name).sun_path); 207 | return ret; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /source/libasync/watcher.d: -------------------------------------------------------------------------------- 1 | /// 2 | module libasync.watcher; 3 | 4 | import libasync.types; 5 | 6 | import libasync.events; 7 | import libasync.internals.path; 8 | import memutils.vector : Array; 9 | import std.file; 10 | 11 | /// Watches one or more directories in the local filesystem for the specified events 12 | /// by calling a custom event handler asynchroneously when they occur. 13 | /// 14 | /// Usage: run() the object, start watching directories, receive an event in your handler, 15 | /// read the changes by draining the buffer. 16 | final nothrow class AsyncDirectoryWatcher 17 | { 18 | nothrow: 19 | private: 20 | EventLoop m_evLoop; 21 | Array!WatchInfo m_directories; 22 | fd_t m_fd; 23 | debug bool m_dataRemaining; 24 | public: 25 | /// 26 | this(EventLoop evl) 27 | in { assert(evl !is null); } 28 | do { m_evLoop = evl; } 29 | 30 | mixin DefStatus; 31 | 32 | /// Fills the buffer with file/folder events and returns the number 33 | /// of events consumed. Returns 0 when the buffer is drained. 34 | uint readChanges(ref DWChangeInfo[] dst) { 35 | uint cnt = m_evLoop.readChanges(m_fd, dst); 36 | 37 | debug { 38 | if (cnt < dst.length) 39 | m_dataRemaining = false; 40 | } 41 | return cnt; 42 | } 43 | 44 | /// Registers the object in the underlying event loop and sends notifications 45 | /// related to buffer activity by calling the specified handler. 46 | bool run(void delegate() del) { 47 | DWHandler handler; 48 | handler.del = del; 49 | handler.ctxt = this; 50 | 51 | m_fd = m_evLoop.run(this, handler); 52 | // import std.stdio; 53 | // try writeln("Running with FD: ", m_fd); catch (Throwable e) {} 54 | 55 | if (m_fd == fd_t.init) 56 | return false; 57 | return true; 58 | } 59 | 60 | /// Starts watching for file events in the specified directory, 61 | /// recursing into subdirectories will add those and its files 62 | /// to the watch list as well. 63 | bool watchDir(string path, DWFileEvent ev = DWFileEvent.ALL, bool recursive = false) { 64 | 65 | try 66 | { 67 | path = Path(path).toNativeString(); 68 | //import std.stdio; 69 | //writeln("Watching ", path); 70 | bool addWatch() { 71 | WatchInfo info; 72 | info.events = ev; 73 | try info.path = Path(path); catch (Exception) {} 74 | info.recursive = recursive; 75 | 76 | //writeln("Watch: ", info.path.toNativeString()); 77 | uint wd = m_evLoop.watch(m_fd, info); 78 | //writeln("Watching WD: ", wd); 79 | if (wd == 0 && m_evLoop.status.code != Status.OK) 80 | return false; 81 | info.wd = wd; 82 | try m_directories.insert(info); catch (Exception) {} 83 | return true; 84 | } 85 | 86 | if (!addWatch()) 87 | return false; 88 | 89 | } 90 | catch (Exception e) { 91 | static if (DEBUG) { 92 | import std.stdio; 93 | try writeln("Could not add directory: " ~ e.toString()); catch (Throwable e) {} 94 | } 95 | return false; 96 | } 97 | 98 | return true; 99 | } 100 | 101 | /// Removes the directory and its files from the event watch list. 102 | /// Recursive will remove all subdirectories in the watch list. 103 | bool unwatchDir(string path, bool recursive) { 104 | import std.algorithm : countUntil; 105 | 106 | try { 107 | path = Path(path).toString(); 108 | 109 | bool removeWatch(string path) { 110 | auto idx = m_directories[].countUntil!((a,b) => a.path == b)(Path(path)); 111 | if (idx < 0) 112 | return true; 113 | 114 | if (!m_evLoop.unwatch(m_fd, m_directories[idx].wd)) 115 | return false; 116 | typeof(m_directories) dirList; 117 | dirList.reserve(m_directories.length - 1); 118 | dirList[] = m_directories[0 .. idx]; 119 | dirList ~= m_directories[idx+1 .. $]; 120 | m_directories = dirList; 121 | return true; 122 | } 123 | removeWatch(path); 124 | if (recursive && path.isDir) { 125 | foreach (de; path.dirEntries(SpanMode.shallow)) { 126 | if (de.isDir){ 127 | if (!removeWatch(path)) 128 | return false; 129 | } 130 | } 131 | } 132 | } catch (Exception) {} 133 | return true; 134 | } 135 | 136 | /// Cleans up underlying resources. 137 | bool kill() 138 | in { assert(m_fd != fd_t.init); } 139 | do { 140 | return m_evLoop.kill(this); 141 | } 142 | 143 | /// 144 | @property fd_t fd() const { 145 | return m_fd; 146 | } 147 | 148 | package: 149 | version(Posix) mixin EvInfoMixins; 150 | 151 | @property void fd(fd_t val) { 152 | m_fd = val; 153 | } 154 | 155 | } 156 | 157 | /// Represents one event on one file in a watched directory. 158 | struct DWChangeInfo { 159 | /// The event triggered by the file/folder 160 | DWFileEvent event; 161 | /// The os-independent address of the file/folder 162 | private Path m_path; 163 | 164 | /// 165 | @property string path() { 166 | return m_path.toNativeString(); 167 | } 168 | 169 | /// 170 | @property void path(Path p) { 171 | m_path = p; 172 | } 173 | 174 | } 175 | 176 | /// List of events that can be watched for. They must be 'Or'ed together 177 | /// to combined them when calling watch(). OS-triggerd events are exclusive. 178 | enum DWFileEvent : uint { 179 | ERROR = 0, /// 180 | MODIFIED = 0x00000002, /// 181 | MOVED_FROM = 0x00000040, /// 182 | MOVED_TO = 0x00000080, /// 183 | CREATED = 0x00000100, /// 184 | DELETED = 0x00000200, /// 185 | ALL = MODIFIED | MOVED_FROM | MOVED_TO | CREATED | DELETED /// 186 | } 187 | 188 | 189 | package struct DWHandler { 190 | AsyncDirectoryWatcher ctxt; 191 | void delegate() del; 192 | void opCall(){ 193 | assert(ctxt !is null); 194 | debug ctxt.m_dataRemaining = true; 195 | del(); 196 | debug { 197 | assert(!ctxt.m_dataRemaining, "You must read all changes when you receive a notification for directory changes"); 198 | } 199 | assert(ctxt !is null); 200 | return; 201 | } 202 | } 203 | 204 | package struct WatchInfo { 205 | DWFileEvent events; 206 | Path path; 207 | bool recursive; 208 | uint wd; // watch descriptor 209 | } 210 | -------------------------------------------------------------------------------- /tests/http_request_benchmark/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http_request_benchmark", 3 | "description": "Benchmark simple HTTP request send and receive.", 4 | "copyright": "Copyright © 2016, Moritz Maxeiner", 5 | "authors": ["Moritz Maxeiner"], 6 | "dependencies": { 7 | "libasync": { "path": "../../" } 8 | }, 9 | "buildTypes": { 10 | "release": { 11 | "versions": ["StdLoggerDisableLogging"] 12 | }, 13 | }, 14 | "targetPath": "bin" 15 | } 16 | -------------------------------------------------------------------------------- /tests/http_request_benchmark/plot_results.gnu: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env gnuplot 2 | 3 | set terminal svg 4 | 5 | set title 'Runtime comparison for sending series of single HTTP requests' 6 | 7 | set object 1 rect from screen 0, 0, 0 to screen 1, 1, 0 behind 8 | set object 1 rect fc rgb "white" fillstyle solid 1.0 9 | 10 | set style fill solid 0.25 border -1 11 | set style boxplot nooutliers pointtype 7 12 | set boxwidth 0.5 13 | set pointsize 0.5 14 | 15 | unset key 16 | set border 2 17 | set xtics ("AsyncSocket" 1, "AsyncTCPConnection" 2) scale 0.0 18 | set ytics nomirror 19 | set ylabel 'Time required for completion in nanoseconds' 20 | 21 | cd 'results' 22 | results = system('ls */*.dat') 23 | do for [result in results] { 24 | set output sprintf('%s.svg', result[:strlen(result)-4]) 25 | 26 | stats result using 2 27 | range = 1.5 # (this is the default value of the `set style boxplot range` value) 28 | lower_limit = STATS_lo_quartile - range*(STATS_up_quartile - STATS_lo_quartile) 29 | upper_limit = STATS_up_quartile + range*(STATS_up_quartile - STATS_lo_quartile) 30 | 31 | set table '/tmp/gnuplot.dat' 32 | plot result index 0 using 2:($2 > upper_limit || $2 < lower_limit ? 1 : 0) smooth frequency 33 | plot result index 1 using 2:($2 > upper_limit || $2 < lower_limit ? 1 : 0) smooth frequency 34 | unset table 35 | 36 | plot result index 0 using (1):2 with boxplot,\ 37 | result index 1 using (2):2 with boxplot,\ 38 | '/tmp/gnuplot.dat' index 0 using (1):($2 > 0 ? $1 : 1/0) with points pt 6 lc rgb "grey",\ 39 | '/tmp/gnuplot.dat' index 1 using (2):($2 > 0 ? $1 : 1/0) with points pt 6 lc rgb "grey" 40 | } -------------------------------------------------------------------------------- /tests/http_request_benchmark/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | // Use these to avoid reaching maximum amount of open sockets: 3 | // # echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 4 | // # echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse 5 | // # echo 2 > /proc/sys/net/ipv4/tcp_fin_timeout 6 | 7 | import core.time; 8 | 9 | import std.datetime : Clock; 10 | import std.string : format; 11 | import std.stdio : stderr, File; 12 | import std.file : mkdirRecurse; 13 | import std.exception : enforce, collectException; 14 | import std.traits : isInstanceOf; 15 | 16 | import memutils.circularbuffer : CircularBuffer; 17 | 18 | import libasync; 19 | 20 | immutable HTTPRequest = "GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nAccept: */*\r\nConnection: Close\r\n\r\n"; 21 | 22 | immutable getServerAddress = { 23 | import std.socket : InternetAddress; 24 | return NetworkAddress(new InternetAddress("127.0.0.1", 8080)); 25 | }; 26 | 27 | immutable bufferSize = 64 * 1024; 28 | immutable repetitions = 1 << 12; 29 | immutable averageWindowSize = 1000; 30 | 31 | version (Windows) immutable dataPath = "results/windows"; 32 | else version (linux) immutable dataPath = "results/linux"; 33 | else version (OSX) immutable dataPath = "results/osx"; 34 | else version (FreeBSD) immutable dataPath = "results/freebsd"; 35 | else version (DragonFlyBSD) immutable dataPath = "results/dragonflybsd"; 36 | else immutable dataPath = "results/unknown"; 37 | 38 | immutable dataFilenameFormat = dataPath ~ "/runtimes_%s.dat"; 39 | 40 | EventLoop g_eventLoop = void; 41 | bool g_running = void; 42 | 43 | void main() 44 | { 45 | mkdirRecurse(dataPath); 46 | 47 | auto serverAddress = getServerAddress(); 48 | g_eventLoop = getThreadEventLoop(); 49 | 50 | auto dataFile = File(dataFilenameFormat.format(Clock.currTime.toISOString()), "w"); 51 | 52 | foreach (i; 0..repetitions) { 53 | auto client = doSetup_AsyncSocket(serverAddress); 54 | g_running = true; 55 | dataFile.writefln("%s %s", i, measure!loopUntilDone.total!"nsecs"); 56 | } 57 | 58 | dataFile.writeln(); 59 | dataFile.writeln(); 60 | 61 | foreach (i; 0..repetitions) { 62 | auto client = doSetup_AsyncTCPConnection(serverAddress); 63 | g_running = true; 64 | dataFile.writefln("%s %s", i, measure!loopUntilDone.total!"nsecs"); 65 | } 66 | } 67 | 68 | auto measure(alias fun, Args...)(Args args) 69 | { 70 | auto before = MonoTime.currTime; 71 | fun(args); 72 | auto after = MonoTime.currTime; 73 | return after - before; 74 | } 75 | 76 | pragma(inline, true) 77 | void loopUntilDone() 78 | { while (g_running) g_eventLoop.loop(-1.seconds); } 79 | 80 | auto doSetup_AsyncSocket(NetworkAddress to) 81 | { 82 | import libasync.internals.socket_compat : AF_INET; 83 | 84 | auto client = new AsyncSocket(g_eventLoop, AF_INET, SocketType.STREAM); 85 | 86 | static recvBuf = new ubyte[bufferSize]; 87 | 88 | client.onConnect = { 89 | client.receiveContinuously = true; // Start continuous receive mode with next receive call 90 | client.receive(recvBuf, (data) {}); // Ignore the HTTP Response 91 | client.send(cast(ubyte[]) HTTPRequest, {}); // Send the HTTP Request, do nothing once it was sent 92 | }; 93 | 94 | client.onClose = { g_running = false; }; 95 | 96 | client.onError = { 97 | stderr.writeln("Socket operation failed: ", client.error).collectException(); 98 | g_running = false; 99 | }; 100 | 101 | enforce(client.run(), "Failed to create socket: " ~ client.error); 102 | enforce(client.connect(to), "Failed to start connecting: " ~ client.error); 103 | 104 | return client; 105 | } 106 | 107 | auto doSetup_AsyncTCPConnection(NetworkAddress to) 108 | { 109 | auto client = new AsyncTCPConnection(g_eventLoop); 110 | client.peer = to; 111 | 112 | void onRead() { 113 | static recvBuf = new ubyte[bufferSize]; 114 | uint recvCount = void; 115 | 116 | do { 117 | recvCount = client.recv(recvBuf); 118 | } while (recvCount > 0); 119 | } 120 | 121 | void onWrite() { 122 | client.send(cast(ubyte[]) HTTPRequest); 123 | } 124 | 125 | void onConnect() { 126 | onRead(); 127 | onWrite(); 128 | } 129 | 130 | void onClose() { g_running = false; } 131 | 132 | void handler(TCPEvent ev) { 133 | 134 | try final switch (ev) { 135 | case TCPEvent.CONNECT: 136 | onConnect(); 137 | break; 138 | case TCPEvent.READ: 139 | onRead(); 140 | break; 141 | case TCPEvent.WRITE: 142 | //onWrite(); 143 | break; 144 | case TCPEvent.CLOSE: 145 | onClose(); 146 | break; 147 | case TCPEvent.ERROR: 148 | assert(false, client.error()); 149 | } catch (Exception e) { 150 | assert(false, e.toString()); 151 | } 152 | return; 153 | } 154 | 155 | enforce(client.run(&handler), "Failed to create connection: " ~ client.error); 156 | 157 | return client; 158 | } 159 | -------------------------------------------------------------------------------- /tests/tcp_test/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcp_test", 3 | "description": "A simple TCP test", 4 | "dependencies": { 5 | "libasync": {"version": "~master", "path": "../../"}, 6 | "memutils": {"version": "~>0.3.5"} 7 | }, 8 | "versions": ["EnableDebugger"] 9 | } 10 | -------------------------------------------------------------------------------- /tests/tcp_test/source/app.d: -------------------------------------------------------------------------------- 1 | import libasync; 2 | import std.socket; 3 | import std.stdio; 4 | import core.memory; 5 | import memutils.utils; 6 | 7 | void main() { 8 | void handler(TCPEvent ev) { 9 | writeln("Event ", ev); 10 | } 11 | 12 | auto evl = ThreadMem.alloc!EventLoop(); 13 | auto count = 1000; 14 | foreach(_;0 .. count) { 15 | auto conn = ThreadMem.alloc!AsyncTCPConnection(evl); 16 | conn.host("1.1.1.1", 9999).run(&handler); 17 | auto timer = ThreadMem.alloc!AsyncTimer(evl); 18 | timer.duration = 10.msecs; 19 | timer.run({ 20 | writeln("timeout"); 21 | try conn.kill(true); catch (Throwable e) { } 22 | timer.kill(); 23 | }); 24 | evl.loop(100.seconds); 25 | writeln("loop done"); 26 | ThreadMem.free(conn); 27 | ThreadMem.free(timer); 28 | GC.collect(); 29 | GC.minimize(); 30 | } 31 | writeln("done"); 32 | version(Libasync_Threading) destroyAsyncThreads(); 33 | } 34 | -------------------------------------------------------------------------------- /tests/transceiver/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transceiver", 3 | "description": "Receive/Send data continuously", 4 | "copyright": "Copyright © 2016, Moritz Maxeiner", 5 | "authors": ["Moritz Maxeiner"], 6 | "dependencies": { 7 | "libasync": { "path": "../../" }, 8 | "docopt": "~>0.6.1-b.5" 9 | }, 10 | 11 | "targetName": "transceiver", 12 | "buildTypes": { 13 | "release": { 14 | "versions": ["StdLoggerDisableTrace", 15 | "StdLoggerDisableInfo", 16 | "StdLoggerDisableWarning"] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/transceiver/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | immutable HELP = "Receive/Send data continuously 3 | 4 | Usage: 5 | transceiver -h | --help 6 | transceiver -l [-46]
7 | transceiver -l [-46] 8 | transceiver [-46]
9 | transceiver [-46] 10 | 11 | Options: 12 | -h --help Show this screen. 13 | -l Start in listen mode, allowing inbound connects 14 | -4 Operate in the IPv4 address family. 15 | -6 Operate in the IPv6 address family [default]. 16 | "; 17 | 18 | int main(string[] args) 19 | { 20 | auto arguments = docopt.docopt(HELP, args[1..$], true); 21 | 22 | auto af = getAddressFamily(arguments); 23 | 24 | NetworkAddress address = void; 25 | try address = getAddress(arguments, af); catch (Exception e) { 26 | stderr.writeln("transceiver: ", e.msg); 27 | return 1; 28 | } 29 | 30 | auto mode = getMode(arguments); 31 | 32 | g_running = true; 33 | g_eventLoop = getThreadEventLoop(); 34 | g_receiveBuffer = new ubyte[4096]; 35 | if (mode == Mode.Listen) { 36 | if (!listen(address, af, SOCK_STREAM)) return 1; 37 | } else { 38 | if (!connect(address, af, SOCK_STREAM)) return 1; 39 | } 40 | while (g_running) g_eventLoop.loop(-1.seconds); 41 | return g_status; 42 | } 43 | 44 | bool connect(ref NetworkAddress remote, int af, int type) 45 | { 46 | auto socket = new AsyncSocket(g_eventLoop, af, type); 47 | transceive(socket); 48 | 49 | if(!socket.run()) { 50 | stderr.writeln("transceiver: ", socket.error); 51 | return false; 52 | } 53 | 54 | if (!socket.connect(remote)) { 55 | stderr.writeln("transceiver: ", socket.error); 56 | return false; 57 | } 58 | 59 | return true; 60 | } 61 | 62 | bool listen(ref NetworkAddress local, int af, int type) 63 | { 64 | auto listener = new AsyncSocket(g_eventLoop, af, type); 65 | 66 | listener.onError = { 67 | stderr.writeln("transceiver: ", listener.error).collectException(); 68 | g_status = 1; 69 | g_running = false; 70 | }; 71 | 72 | if(!listener.run()) { 73 | stderr.writeln("transceiver: ", listener.error); 74 | return false; 75 | } 76 | 77 | try { 78 | int yes = 1; 79 | listener.setOption(SOL_SOCKET, SO_REUSEADDR, (cast(ubyte*) &yes)[0..yes.sizeof]); 80 | version (Posix) listener.setOption(SOL_SOCKET, SO_REUSEPORT, (cast(ubyte*) &yes)[0..yes.sizeof]); 81 | } catch (Exception e) { 82 | stderr.writeln("transceiver: ", e.msg); 83 | return false; 84 | } 85 | 86 | if (!listener.bind(local)) { 87 | stderr.writeln("transceiver: ", listener.error); 88 | return false; 89 | } 90 | 91 | if (!listener.listen()) { 92 | stderr.writeln("transceiver: ", listener.error); 93 | return false; 94 | } 95 | 96 | AsyncAcceptRequest.OnComplete onAccept = void; 97 | onAccept = (handle, family, type, protocol) { 98 | scope (exit) if (listener.alive) listener.accept(onAccept); 99 | auto client = new AsyncSocket(g_eventLoop, family, type, protocol, handle); 100 | transceive(client, true, false); 101 | return client; 102 | }; 103 | 104 | listener.accept(onAccept); 105 | 106 | return true; 107 | } 108 | 109 | void transceive(AsyncSocket socket, bool send = true, bool exitOnClose = true) nothrow 110 | { 111 | import std.random : randomCover, randomSample, uniform; 112 | import std.ascii : letters; 113 | 114 | void delegate() nothrow delegate(size_t, size_t, void delegate() nothrow) nothrow createSender = (i, n, onComplete) { 115 | static dataGenerator = generate!(() => cast(ubyte) letters[uniform(0, letters.length)]); 116 | 117 | auto index = assumeWontThrow(i.to!string); 118 | auto dataOffset = 5 + index.length + 2; 119 | auto data = new ubyte[dataOffset + n + 1]; 120 | 121 | data[0..5] = cast(ubyte[]) "send_"; 122 | data[5..5+index.length] = cast(ubyte[]) index; 123 | data[dataOffset-2..dataOffset] = cast(ubyte[]) ": "; 124 | data[$-1] = '\n'; 125 | 126 | return delegate void() nothrow { 127 | if (socket.alive) { 128 | assumeWontThrow(data[dataOffset..$-1] = dataGenerator.take(n).array); 129 | socket.send(data, onComplete); 130 | } 131 | }; 132 | }; 133 | 134 | auto senders = new void delegate() nothrow[1]; 135 | auto onSent = { assumeWontThrow(senders.randomSample(1).front())(); }; 136 | 137 | socket.onConnect = delegate void() nothrow { 138 | foreach (i, ref sender; senders) { 139 | sender = createSender(i, 2 << (3+i), { onSent(); }); 140 | } 141 | 142 | if (send && senders.length > 0) assumeWontThrow(senders.front())(); 143 | socket.receiveContinuously = true; 144 | socket.receive(g_receiveBuffer, (data) { 145 | try { 146 | stdout.rawWrite(data); 147 | stdout.flush(); 148 | } catch (Exception e) {} 149 | }); 150 | }; 151 | socket.onClose = { if (exitOnClose) g_running = false; }; 152 | socket.onError = { 153 | stderr.writeln("transceiver: ", socket.error).collectException(); 154 | g_running = false; 155 | g_status = 1; 156 | }; 157 | } 158 | 159 | bool g_running; 160 | EventLoop g_eventLoop; 161 | int g_status; 162 | ubyte[] g_receiveBuffer; 163 | 164 | enum Mode { Connect, Listen } 165 | 166 | auto getAddressFamily(A)(A arguments) 167 | { return arguments["-4"].isTrue? AF_INET : AF_INET6; } 168 | 169 | auto getAddress(A)(A arguments, int af) 170 | { 171 | auto address = arguments["
"]; 172 | ushort port = void; 173 | 174 | if (!arguments[""].isNull) try { 175 | port = arguments[""].toString.to!ushort; 176 | } catch (Exception) { 177 | throw new Exception("port must be integer and 0 <= port <= %s, not '%s'".format(ushort.max, arguments[""])); 178 | } 179 | 180 | if (address.isNull) switch (af) { 181 | case AF_INET: with (new InternetAddress("0.0.0.0", port)) return NetworkAddress(name, nameLen); 182 | case AF_INET6: with (new Internet6Address("::", port)) return NetworkAddress(name, nameLen); 183 | default: assert(false); 184 | } 185 | 186 | switch (af) { 187 | case AF_INET: with (new InternetAddress(address.toString, port)) return NetworkAddress(name, nameLen); 188 | case AF_INET6: with (new Internet6Address(address.toString, port)) return NetworkAddress(name, nameLen); 189 | default: assert(false); 190 | } 191 | } 192 | 193 | auto getMode(A)(A arguments) 194 | { return arguments["-l"].isTrue? Mode.Listen : Mode.Connect; } 195 | 196 | import core.time; 197 | 198 | import std.stdio; 199 | import std.socket; 200 | import std.conv; 201 | import std.format; 202 | import std.file; 203 | import std.exception; 204 | import std.range; 205 | 206 | import libasync; 207 | import libasync.internals.socket_compat; 208 | 209 | import docopt; -------------------------------------------------------------------------------- /ws2_32_ex.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etcimon/libasync/2a6aa938ea82dc48beaa2847d143598fd3de2af0/ws2_32_ex.lib --------------------------------------------------------------------------------