├── .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 | [](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
--------------------------------------------------------------------------------