├── .codecov.yml ├── .editorconfig ├── .github └── workflows │ ├── musl.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── LICENSE_DE.txt ├── README.md ├── dub.sdl ├── examples ├── bench-dummy-http-server │ ├── dub.sdl │ └── source │ │ └── app.d └── echo-server │ ├── dub.sdl │ └── source │ └── app.d ├── run-ci.sh ├── source └── vibe │ ├── appmain.d │ ├── core │ ├── args.d │ ├── channel.d │ ├── concurrency.d │ ├── connectionpool.d │ ├── core.d │ ├── file.d │ ├── internal │ │ ├── release.d │ │ └── threadlocalwaiter.d │ ├── log.d │ ├── net.d │ ├── package.d │ ├── parallelism.d │ ├── path.d │ ├── process.d │ ├── stream.d │ ├── sync.d │ ├── task.d │ └── taskpool.d │ └── internal │ ├── allocator.d │ ├── array.d │ ├── async.d │ ├── freelistref.d │ ├── interfaceproxy.d │ ├── list.d │ ├── string.d │ ├── traits.d │ └── typetuple.d └── tests ├── 0-tcp.d ├── 0-tcpproxy.d ├── args.d ├── args.sh ├── dirwatcher.d ├── issue-104-unreferenced-periodic-timer.d ├── issue-110-close-while-waitForData.d ├── issue-115-connect-fail-leak.d ├── issue-157-consume-closed-channel.d ├── issue-161-multiple-joiners.d ├── issue-267-pipe.d ├── issue-331-tcp-connect-timeout.d ├── issue-58-task-already-scheduled.d ├── issue-66-yield-eventloop-exit.d ├── pull-218-resolvehost-dns-address-family.d ├── std.concurrency.d ├── tcp-read-timeout.d ├── vibe.core.concurrency.1408.d ├── vibe.core.core.1590.d ├── vibe.core.core.1742.d ├── vibe.core.core.refcount.d ├── vibe.core.file.d ├── vibe.core.file.gcleak.d ├── vibe.core.net.1376.d ├── vibe.core.net.1429.d ├── vibe.core.net.1441.d ├── vibe.core.net.1452.d ├── vibe.core.net.1726.d ├── vibe.core.process.d └── vibe.core.stream.pipe.d /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Documentation: https://github.com/codecov/support/wiki/codecov.yml 2 | 3 | coverage: 4 | precision: 3 5 | round: down 6 | range: 80...100 7 | 8 | status: 9 | # Learn more at https://codecov.io/docs#yaml_default_commit_status 10 | project: off 11 | patch: 12 | default: 13 | informational: true 14 | changes: off 15 | 16 | 17 | comment: false 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,d,di,dd,json}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.{d,di}] 12 | dfmt_brace_style = knr 13 | dfmt_keep_line_breaks = true 14 | dfmt_single_template_constraint_indent = true 15 | dfmt_single_indent = true 16 | dfmt_template_constraint_style = conditional_newline_indent 17 | dfmt_compact_labeled_statements = false 18 | dfmt_space_after_cast = false 19 | dfmt_align_switch_statements = false 20 | -------------------------------------------------------------------------------- /.github/workflows/musl.yml: -------------------------------------------------------------------------------- 1 | name: Musl tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | - github_actions 11 | 12 | jobs: 13 | main: 14 | name: Musl tests 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | dc: [ dmd, ldc ] 19 | include: 20 | - { dc: dmd, dcname: dmd } 21 | - { dc: ldc, dcname: ldc2 } 22 | runs-on: ubuntu-latest 23 | container: alpine 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Install dependencies 28 | run: apk add --no-cache build-base dub ${{ matrix.dc }} 29 | - name: Run tests 30 | run: dub test --compiler=${{ matrix.dcname }} 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run all D Tests 2 | 3 | # Only triggers on pushes/PRs to master 4 | on: 5 | pull_request: 6 | branches: 7 | - master 8 | push: 9 | branches: 10 | - master 11 | - github_actions 12 | 13 | jobs: 14 | test: 15 | name: Dub Tests 16 | timeout-minutes: 30 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macOS-13] 21 | dc: [dmd-latest, ldc-latest] 22 | arch: [x86_64] 23 | include: 24 | - { os: ubuntu-latest, dc: dmd-2.096.1, arch: x86_64 } 25 | - { os: windows-latest, dc: dmd-2.092.0, arch: x86_64 } 26 | - { os: windows-latest, dc: dmd-2.092.0, arch: x86_mscoff } 27 | - { os: windows-latest, dc: dmd-2.091.1, arch: x86_64 } 28 | - { os: windows-latest, dc: ldc-1.20.1, arch: x86_64 } 29 | 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Install D compiler 35 | uses: dlang-community/setup-dlang@v2 36 | with: 37 | compiler: ${{ matrix.dc }} 38 | 39 | - name: Run tests 40 | env: 41 | DC: ${{matrix.dc}} 42 | ARCH: ${{matrix.arch}} 43 | run: ./run-ci.sh 44 | shell: bash 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | .vs 3 | libvibe_core.a 4 | vibe-core-test-* 5 | *.exe 6 | *.lst 7 | *.obj 8 | *.sln 9 | dub.selections.json 10 | vibe_core.lib 11 | tests/tests 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2018 RejectedSoftware e.K. 2 | Copyright (c) 2016-2024 Sönke Ludwig 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE_DE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vibe-d/vibe-core/5077e8a4b8778727226e38f71fccd930517c8062/LICENSE_DE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![vibe.d](https://vibed.org/images/title-new.png)](https://vibed.org) 2 | 3 | vibe.d core package 4 | =================== 5 | 6 | The core package provides the low level I/O and concurrency primitives that are used to implement the higher level systems: 7 | 8 | - Event loop management 9 | - Fiber based lightweight tasks, including task local storage and `std.concurrency` integration 10 | - Files, sockets, timers 11 | - Stream type definitions (used for files, sockets and higher level stream types) 12 | - Synchronization primitives (mutexes, condition variables, semaphores, cross task/cross thread events) 13 | - Logging facilities 14 | - Command line argument parsing 15 | - Various smaller utilities 16 | 17 | The fundamental building block is the fiber based task concept, together with the event based asynchronous I/O model. This enables developing highly scalable I/O concurrent applications without running into the complexities and design implications that asynchronous I/O programming models usually impose. See the [features page](https://vibed.org/features) for a more detailed explanation. 18 | 19 | [![DUB Package](https://img.shields.io/dub/v/vibe-core.svg)](https://code.dlang.org/packages/vibe-core) 20 | [![Posix and Windows Build Status](https://github.com/vibe-d/vibe-core/actions/workflows/test.yml/badge.svg)](https://github.com/vibe-d/vibe-core/actions/workflows/test.yml) 21 | [![Musl Build Status](https://github.com/vibe-d/vibe-core/actions/workflows/musl.yml/badge.svg)](https://github.com/vibe-d/vibe-core/actions/workflows/musl.yml) 22 | 23 | Supported compilers 24 | ------------------- 25 | 26 | The following compilers are tested and supported: 27 | 28 | - DMD 2.101.2 29 | - DMD 2.092.0 30 | - DMD 2.091.1 31 | - LDC 1.30.0 32 | - LDC 1.20.1 33 | 34 | Supported up to 1.22.5: 35 | - DMD 2.085.1 36 | - DMD 2.079.0 37 | - LDC 1.15.0 38 | - LDC 1.14.0 39 | - LDC 1.9.0 40 | 41 | Supported up to 1.6.2: 42 | - DMD 2.078.3 43 | - LDC 1.8.0 44 | 45 | Supported up to 1.4.7: 46 | 47 | - DMD 2.077.1 48 | - DMD 2.076.1 49 | - LDC 1.7.0 50 | - LDC 1.6.0 51 | 52 | Supported up to 1.4.3: 53 | 54 | - DMD 2.075.1 55 | - DMD 2.074.1 56 | - DMD 2.073.2 57 | - DMD 2.072.2 58 | - LDC 1.5.0 59 | - LDC 1.4.0 60 | - LDC 1.3.0 61 | - LDC 1.2.0 62 | 63 | Supported up to 1.3.0: 64 | 65 | - DMD 2.071.2 66 | - LDC 1.1.0 67 | 68 | Supported up to 1.1.1: 69 | 70 | - DMD 2.070.2 71 | - LDC 1.0.0 72 | 73 | 74 | Separation of the former `vibe-d:core` package 75 | ---------------------------------------------- 76 | 77 | This is the successor of the `vibe-d:core` sub package of [vibe.d 0.7.x](https://github.com/rejectedsoftware/vibe.d.git). The API is mostly compatible from a library user point of view, but the whole library has received some heavy lifting under the surface, close to a rewrite. Most classes have been replaced by reference counting structs and `@safe nothrow` attributes are now used throughout the library, whenever possible. Adding `@nogc` on the other hand could only be done in a very limited context due to its viral nature and the lack of an `@trusted` equivalent. 78 | 79 | Another major design change is that instead of the previous driver model, there is now a separate, lower-level event loop abstraction ([eventcore](https://github.com/vibe-d/eventcore.git)) which follows a callback based Proactor pattern. The logic to schedule fibers based on events has been pulled out of this abstraction and is now maintained as a single function, leading to a huge improvment in terms of robustness (most issues in the previous implementation have probably never surfaced in practice, but there turned out to be lots of them). 80 | 81 | Finally, the stream design has received two big changes. Streams can now either be implemented as classes, as usual, or they can be implemented as structs in a duck typing/DbC fashion. This, coupled with templated wrapper stream types, allows to eliminate the overhead of virtual function calls, enables reference counting instead of GC allocations, and allows the compiler to inline across stream boundaries. The second change to streams is the added support for an [`IOMode`](https://github.com/vibe-d/eventcore/blob/c242fdae16470ae4dc4e7e6578d582c1d3ba57ec/source/eventcore/driver.d#L533) parameter that enables I/O patterns as they are possible when using OS sockets directly. The `leastSize` and `dataAvailableForRead` properties will in turn be deprecated. 82 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "vibe-core" 2 | description "The I/O core library of vibe.d." 3 | authors "Sönke Ludwig" 4 | copyright "Copyright © 2016-2020, Sönke Ludwig" 5 | license "MIT" 6 | 7 | dependency "eventcore" version="~>0.9.27" 8 | dependency "vibe-container" version=">=1.3.1 <2.0.0-0" 9 | 10 | targetName "vibe_core" 11 | 12 | mainSourceFile "source/vibe/appmain.d" 13 | 14 | configuration "winapi" { 15 | subConfiguration "eventcore" "winapi" 16 | versions "Windows7" 17 | } 18 | configuration "epoll" { 19 | subConfiguration "eventcore" "epoll" 20 | } 21 | configuration "cfrunloop" { 22 | subConfiguration "eventcore" "cfrunloop" 23 | } 24 | configuration "kqueue" { 25 | subConfiguration "eventcore" "kqueue" 26 | } 27 | configuration "select" { 28 | subConfiguration "eventcore" "select" 29 | versions "Windows7" platform="windows" 30 | } 31 | //configuration "libasync" { 32 | // subConfiguration "eventcore" "libasync" 33 | //} 34 | 35 | buildType "unittest" { 36 | buildOptions "unittests" "debugMode" "debugInfo" 37 | debugVersions "VibeMutexLog" "VibeAsyncLog" 38 | } 39 | -------------------------------------------------------------------------------- /examples/bench-dummy-http-server/dub.sdl: -------------------------------------------------------------------------------- 1 | name "bench-http-server" 2 | description "Minimal partial HTTP server implementation for baseline performance testing" 3 | dependency "vibe-core" path="../../" 4 | -------------------------------------------------------------------------------- /examples/bench-dummy-http-server/source/app.d: -------------------------------------------------------------------------------- 1 | import vibe.core.core; 2 | import vibe.core.log; 3 | import vibe.core.net; 4 | import vibe.core.stream; 5 | 6 | import std.exception : enforce; 7 | import std.functional : toDelegate; 8 | import std.range.primitives : isOutputRange; 9 | 10 | 11 | void main() 12 | { 13 | void staticAnswer(TCPConnection conn) 14 | nothrow @safe { 15 | try { 16 | while (!conn.empty) { 17 | while (true) { 18 | CountingRange r; 19 | conn.readLine(r); 20 | if (!r.count) break; 21 | } 22 | conn.write(cast(const(ubyte)[])"HTTP/1.1 200 OK\r\nContent-Length: 13\r\nContent-Type: text/plain\r\nConnection: keep-alive\r\nKeep-Alive: timeout=10\r\n\r\nHello, World!"); 23 | conn.flush(); 24 | } 25 | } catch (Exception e) { 26 | scope (failure) assert(false); 27 | logError("Error processing request: %s", e.msg); 28 | } 29 | } 30 | 31 | auto listener = listenTCP(8080, &staticAnswer, "127.0.0.1"); 32 | logInfo("Listening to HTTP requests on http://127.0.0.1:8080/"); 33 | scope (exit) listener.stopListening(); 34 | 35 | runApplication(); 36 | } 37 | 38 | struct CountingRange { 39 | @safe nothrow @nogc: 40 | ulong count = 0; 41 | void put(ubyte) { count++; } 42 | void put(in ubyte[] arr) { count += arr.length; } 43 | } 44 | 45 | void readLine(R, InputStream)(InputStream stream, ref R dst, size_t max_bytes = size_t.max) 46 | if (isInputStream!InputStream && isOutputRange!(R, ubyte)) 47 | { 48 | import std.algorithm.comparison : min, max; 49 | import std.algorithm.searching : countUntil; 50 | 51 | enum end_marker = "\r\n"; 52 | enum nmarker = end_marker.length; 53 | 54 | size_t nmatched = 0; 55 | 56 | while (true) { 57 | enforce(!stream.empty, "Reached EOF while searching for end marker."); 58 | enforce(max_bytes > 0, "Reached maximum number of bytes while searching for end marker."); 59 | auto max_peek = max(max_bytes, max_bytes+nmarker); // account for integer overflow 60 | auto pm = stream.peek()[0 .. min($, max_bytes)]; 61 | if (!pm.length) { // no peek support - inefficient route 62 | ubyte[2] buf = void; 63 | auto l = nmarker - nmatched; 64 | stream.read(buf[0 .. l]); 65 | foreach (i; 0 .. l) { 66 | if (buf[i] == end_marker[nmatched]) { 67 | nmatched++; 68 | } else if (buf[i] == end_marker[0]) { 69 | foreach (j; 0 .. nmatched) dst.put(end_marker[j]); 70 | nmatched = 1; 71 | } else { 72 | foreach (j; 0 .. nmatched) dst.put(end_marker[j]); 73 | nmatched = 0; 74 | dst.put(buf[i]); 75 | } 76 | if (nmatched == nmarker) return; 77 | } 78 | } else { 79 | assert(nmatched == 0); 80 | 81 | auto idx = pm.countUntil(end_marker[0]); 82 | if (idx < 0) { 83 | dst.put(pm); 84 | max_bytes -= pm.length; 85 | stream.skip(pm.length); 86 | } else { 87 | dst.put(pm[0 .. idx]); 88 | if (idx+1 < pm.length && pm[idx+1] == end_marker[1]) { 89 | assert(nmarker == 2); 90 | stream.skip(idx+2); 91 | return; 92 | } else { 93 | nmatched++; 94 | stream.skip(idx+1); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/echo-server/dub.sdl: -------------------------------------------------------------------------------- 1 | name "echo-server" 2 | description "A simple echo protocol server implementation" 3 | dependency "vibe-core" path="../../" 4 | targetType "executable" 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/echo-server/source/app.d: -------------------------------------------------------------------------------- 1 | import vibe.core.core : runApplication; 2 | import vibe.core.log; 3 | import vibe.core.net; 4 | import vibe.core.stream : pipe; 5 | 6 | 7 | void main() 8 | { 9 | auto listeners = listenTCP(7000, (conn) @safe nothrow { 10 | try pipe(conn, conn); 11 | catch (Exception e) 12 | logError("Error: %s", e.msg); 13 | }); 14 | 15 | // closes the listening sockets 16 | scope (exit) 17 | foreach (l; listeners) 18 | l.stopListening(); 19 | 20 | runApplication(); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /run-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x -o pipefail 4 | 5 | DUB_FLAGS=${DUB_FLAGS:-} 6 | DCVER=${DC#*-} 7 | DC=${DC%-*} 8 | if [ "$DC" == "ldc" ]; then DC="ldc2"; fi 9 | 10 | # Check for trailing whitespace" 11 | grep -nrI --include='*.d' '\s$' . && (echo "Trailing whitespace found"; exit 1) 12 | 13 | # test for successful release build 14 | dub build -b release --compiler=$DC $DUB_FLAGS 15 | 16 | # test for successful 32-bit build 17 | if [ "$DC" == "dmd" ]; then 18 | dub build --arch=x86 $DUB_FLAGS 19 | fi 20 | 21 | dub test --compiler=$DC $DUB_FLAGS 22 | 23 | if [ ${BUILD_EXAMPLE=1} -eq 1 ]; then 24 | for ex in $(\ls -1 examples/); do 25 | echo "[INFO] Building example $ex" 26 | (cd examples/$ex && dub build --compiler=$DC && dub clean) 27 | done 28 | fi 29 | 30 | if [ ${RUN_TEST=1} -eq 1 ]; then 31 | for ex in `\ls -1 tests/*.d`; do 32 | script="${ex%.d}.sh" 33 | if [ -e "$script" ]; then 34 | echo "[INFO] Running test scipt $script" 35 | (cd tests && "./${script:6}") 36 | else 37 | echo "[INFO] Running test $ex" 38 | dub --temp-build --compiler=$DC --single $ex 39 | fi 40 | done 41 | fi 42 | -------------------------------------------------------------------------------- /source/vibe/appmain.d: -------------------------------------------------------------------------------- 1 | /** 2 | Provides vibe based applications with a central program entry point. 3 | 4 | This module is included automatically through the import 'vibe.d'. It will provide a default 5 | application entry point which parses command line arguments, reads the global vibe configuration 6 | file, and starts the event loop. 7 | 8 | The application itself then just has to initialize itself from within a 'static this' module 9 | constructor and perform the appropriate calls to listen for connections or other operations. 10 | 11 | If you want to provide your own main function, you have to import vibe.vibe instead of 12 | vibe.d and define a -version=VibeCustomMain. Be sure to call vibe.core.core.runEventLoop 13 | at the end of your main function in this case. Also beware that you have to make appropriate 14 | calls to vibe.core.args.finalizeCommandLineOptions and vibe.core.core.lowerPrivileges to get the 15 | same behavior. 16 | 17 | Copyright: © 2012-2016 Sönke Ludwig 18 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 19 | Authors: Sönke Ludwig 20 | */ 21 | module vibe.appmain; 22 | 23 | version (VibeDefaultMain): 24 | 25 | version (VibeCustomMain) { 26 | static assert(false, "Both, VibeCustomMain and VibeDefaultMain are defined. " 27 | ~ "Either define only VibeDefaultMain, or nothing at all (VibeCustomMain " 28 | ~ "has no effect since 0.7.26)."); 29 | } 30 | 31 | /** 32 | The predefined vibe.d application entry point. 33 | 34 | This function will automatically be executed if you import the module vibe.d in your code. It 35 | will perform default command line parsing and starts the event loop. 36 | */ 37 | int main() 38 | { 39 | import vibe.core.core : runApplication; 40 | 41 | version (unittest) { 42 | return 0; 43 | } else { 44 | return runApplication(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/vibe/core/args.d: -------------------------------------------------------------------------------- 1 | /** 2 | Parses and allows querying the command line arguments and configuration 3 | file. 4 | 5 | The optional configuration file (vibe.conf) is a JSON file, containing an 6 | object with the keys corresponding to option names, and values corresponding 7 | to their values. It is searched for in the local directory, user's home 8 | directory, or /etc/vibe/ (POSIX only), whichever is found first. 9 | 10 | Copyright: © 2012-2016 Sönke Ludwig 11 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 12 | Authors: Sönke Ludwig, Vladimir Panteleev 13 | */ 14 | module vibe.core.args; 15 | 16 | import vibe.core.log; 17 | import std.json; 18 | 19 | import std.algorithm : any, map, sort; 20 | import std.array : array, join, replicate, split; 21 | import std.exception; 22 | import std.file; 23 | import std.getopt; 24 | import std.path : buildPath; 25 | import std.string : format, stripRight, wrap; 26 | 27 | import core.runtime; 28 | 29 | 30 | /** 31 | Finds and reads an option from the configuration file or command line. 32 | 33 | Command line options take precedence over configuration file entries. 34 | 35 | Params: 36 | names = Option names. Separate multiple name variants with "|", 37 | as for $(D std.getopt). 38 | pvalue = Pointer to store the value. Unchanged if value was not found. 39 | help_text = Text to be displayed when the application is run with 40 | --help. 41 | 42 | Returns: 43 | $(D true) if the value was found, $(D false) otherwise. 44 | 45 | See_Also: readRequiredOption 46 | */ 47 | bool readOption(T)(string names, T* pvalue, string help_text) 48 | if (isOptionValue!T || is(T : E[], E) && isOptionValue!E) 49 | { 50 | // May happen due to http://d.puremagic.com/issues/show_bug.cgi?id=9881 51 | if (g_args is null) init(); 52 | 53 | OptionInfo info; 54 | info.names = names.split("|").sort!((a, b) => a.length < b.length)().array(); 55 | info.hasValue = !is(T == bool); 56 | info.helpText = help_text; 57 | assert(!g_options.any!(o => o.names == info.names)(), "readOption() may only be called once per option name."); 58 | g_options ~= info; 59 | 60 | immutable olen = g_args.length; 61 | getopt(g_args, getoptConfig, names, pvalue); 62 | if (g_args.length < olen) return true; 63 | 64 | if (g_haveConfig) { 65 | foreach (name; info.names) 66 | if (auto pv = name in g_config) { 67 | static if (isOptionValue!T) { 68 | *pvalue = fromValue!T(*pv); 69 | } else { 70 | *pvalue = (*pv).array.map!(j => fromValue!(typeof(T.init[0]))(j)).array; 71 | } 72 | return true; 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | 79 | unittest { 80 | bool had_json = g_haveConfig; 81 | JSONValue json = g_config; 82 | 83 | scope (exit) { 84 | g_haveConfig = had_json; 85 | g_config = json; 86 | } 87 | 88 | g_haveConfig = true; 89 | g_config = parseJSON(`{ 90 | "a": true, 91 | "b": 16000, 92 | "c": 2000000000, 93 | "d": 8000000000, 94 | "e": 1.0, 95 | "f": 2.0, 96 | "g": "bar", 97 | "h": [false, true], 98 | "i": [-16000, 16000], 99 | "j": [-2000000000, 2000000000], 100 | "k": [-8000000000, 8000000000], 101 | "l": [-1.0, 1.0], 102 | "m": [-2.0, 2.0], 103 | "n": ["bar", "baz"] 104 | }`); 105 | 106 | bool b; readOption("a", &b, ""); assert(b == true); 107 | short s; readOption("b", &s, ""); assert(s == 16_000); 108 | int i; readOption("c", &i, ""); assert(i == 2_000_000_000); 109 | long l; readOption("d", &l, ""); assert(l == 8_000_000_000); 110 | float f; readOption("e", &f, ""); assert(f == cast(float)1.0); 111 | double d; readOption("f", &d, ""); assert(d == 2.0); 112 | string st; readOption("g", &st, ""); assert(st == "bar"); 113 | bool[] ba; readOption("h", &ba, ""); assert(ba == [false, true]); 114 | short[] sa; readOption("i", &sa, ""); assert(sa == [-16000, 16000]); 115 | int[] ia; readOption("j", &ia, ""); assert(ia == [-2_000_000_000, 2_000_000_000]); 116 | long[] la; readOption("k", &la, ""); assert(la == [-8_000_000_000, 8_000_000_000]); 117 | float[] fa; readOption("l", &fa, ""); assert(fa == [cast(float)-1.0, cast(float)1.0]); 118 | double[] da; readOption("m", &da, ""); assert(da == [-2.0, 2.0]); 119 | string[] sta; readOption("n", &sta, ""); assert(sta == ["bar", "baz"]); 120 | } 121 | 122 | 123 | /** 124 | The same as readOption, but throws an exception if the given option is missing. 125 | 126 | See_Also: readOption 127 | */ 128 | T readRequiredOption(T)(string names, string help_text) 129 | { 130 | string formattedNames() { 131 | return names.split("|").map!(s => s.length == 1 ? "-" ~ s : "--" ~ s).join("/"); 132 | } 133 | T ret; 134 | enforce(readOption(names, &ret, help_text) || g_help, 135 | format("Missing mandatory option %s.", formattedNames())); 136 | return ret; 137 | } 138 | 139 | 140 | /** 141 | Prints a help screen consisting of all options encountered in getOption calls. 142 | */ 143 | void printCommandLineHelp() 144 | { 145 | enum dcolumn = 20; 146 | enum ncolumns = 80; 147 | 148 | logInfo("Usage: %s \n", g_args[0]); 149 | foreach (opt; g_options) { 150 | string shortopt; 151 | string[] longopts; 152 | if (opt.names[0].length == 1 && !opt.hasValue) { 153 | shortopt = "-"~opt.names[0]; 154 | longopts = opt.names[1 .. $]; 155 | } else { 156 | shortopt = " "; 157 | longopts = opt.names; 158 | } 159 | 160 | string optionString(string name) 161 | { 162 | if (name.length == 1) return "-"~name~(opt.hasValue ? " " : ""); 163 | else return "--"~name~(opt.hasValue ? "=" : ""); 164 | } 165 | 166 | string[] lopts; foreach(lo; longopts) lopts ~= optionString(lo); 167 | auto optstr = format(" %s %s", shortopt, lopts.join(", ")); 168 | if (optstr.length < dcolumn) optstr ~= replicate(" ", dcolumn - optstr.length); 169 | 170 | auto indent = replicate(" ", dcolumn+1); 171 | auto desc = wrap(opt.helpText, ncolumns - dcolumn - 2, optstr.length > dcolumn ? indent : "", indent).stripRight(); 172 | 173 | if (optstr.length > dcolumn) 174 | logInfo("%s\n%s", optstr, desc); 175 | else logInfo("%s %s", optstr, desc); 176 | } 177 | } 178 | 179 | 180 | /** 181 | Checks for unrecognized command line options and display a help screen. 182 | 183 | This function is called automatically from `vibe.appmain` and from 184 | `vibe.core.core.runApplication` to check for correct command line usage. 185 | It will print a help screen in case of unrecognized options. 186 | 187 | Params: 188 | args_out = Optional parameter for storing any arguments not handled 189 | by any readOption call. If this is left to null, an error 190 | will be triggered whenever unhandled arguments exist. 191 | 192 | Returns: 193 | If "--help" was passed, the function returns false. In all other 194 | cases either true is returned or an exception is thrown. 195 | */ 196 | bool finalizeCommandLineOptions(string[]* args_out = null) 197 | { 198 | scope(exit) g_args = null; 199 | 200 | if (args_out) { 201 | *args_out = g_args; 202 | } else if (g_args.length > 1) { 203 | logError("Unrecognized command line option: %s\n", g_args[1]); 204 | printCommandLineHelp(); 205 | throw new Exception("Unrecognized command line option."); 206 | } 207 | 208 | if (g_help) { 209 | printCommandLineHelp(); 210 | return false; 211 | } 212 | 213 | return true; 214 | } 215 | 216 | /** Tests if a given type is supported by `readOption`. 217 | 218 | Allowed types are Booleans, integers, floating point values and strings. 219 | In addition to plain values, arrays of values are also supported. 220 | */ 221 | enum isOptionValue(T) = is(T == bool) || is(T : long) || is(T : double) || is(T == string); 222 | 223 | /** 224 | This functions allows the usage of a custom command line argument parser 225 | with vibe.d. 226 | 227 | $(OL 228 | $(LI build executable with version(VibeDisableCommandLineParsing)) 229 | $(LI parse main function arguments with a custom command line parser) 230 | $(LI pass vibe.d arguments to `setCommandLineArgs`) 231 | $(LI use vibe.d command line parsing utilities) 232 | ) 233 | 234 | Params: 235 | args = The arguments that should be handled by vibe.d 236 | */ 237 | void setCommandLineArgs(string[] args) 238 | { 239 | g_args = args; 240 | } 241 | 242 | /// 243 | unittest { 244 | import std.format : format; 245 | string[] args = ["--foo", "10"]; 246 | setCommandLineArgs(args); 247 | } 248 | 249 | private struct OptionInfo { 250 | string[] names; 251 | bool hasValue; 252 | string helpText; 253 | } 254 | 255 | private { 256 | __gshared string[] g_args; 257 | __gshared bool g_haveConfig; 258 | __gshared JSONValue g_config; 259 | __gshared OptionInfo[] g_options; 260 | __gshared bool g_help; 261 | } 262 | 263 | private string[] getConfigPaths() 264 | { 265 | string[] result = [""]; 266 | import std.process : environment; 267 | version (Windows) 268 | result ~= environment.get("USERPROFILE"); 269 | else 270 | result ~= [environment.get("HOME"), "/etc/vibe/"]; 271 | return result; 272 | } 273 | 274 | // this is invoked by the first readOption call (at least vibe.core will perform one) 275 | private void init() 276 | { 277 | version (VibeDisableCommandLineParsing) {} 278 | else g_args = Runtime.args; 279 | 280 | if (!g_args.length) g_args = ["dummy"]; 281 | 282 | // TODO: let different config files override individual fields 283 | auto searchpaths = getConfigPaths(); 284 | foreach (spath; searchpaths) { 285 | auto cpath = buildPath(spath, configName); 286 | if (cpath.exists) { 287 | scope(failure) logError("Failed to parse config file %s.", cpath); 288 | auto text = cpath.readText(); 289 | g_config = text.parseJSON(); 290 | g_haveConfig = true; 291 | break; 292 | } 293 | } 294 | 295 | if (!g_haveConfig) 296 | logDiagnostic("No config file found in %s", searchpaths); 297 | 298 | readOption("h|help", &g_help, "Prints this help screen."); 299 | } 300 | 301 | private T fromValue(T)(in JSONValue val) 302 | { 303 | import std.conv : to; 304 | static if (is(T == bool)) { 305 | // JSONType.TRUE has been deprecated in v2.087.0 306 | static if (is(typeof(JSONType.true_))) 307 | return val.type == JSONType.true_; 308 | else 309 | return val.type == JSON_TYPE.TRUE; 310 | } 311 | else static if (is(T : long)) return val.integer.to!T; 312 | else static if (is(T : double)) return val.floating.to!T; 313 | else static if (is(T == string)) return val.str; 314 | else static assert(false); 315 | 316 | } 317 | 318 | private enum configName = "vibe.conf"; 319 | 320 | private template ValueTuple(T...) { alias ValueTuple = T; } 321 | 322 | private alias getoptConfig = ValueTuple!(std.getopt.config.passThrough, std.getopt.config.bundling); 323 | -------------------------------------------------------------------------------- /source/vibe/core/channel.d: -------------------------------------------------------------------------------- 1 | /** Implements a thread-safe, typed producer-consumer queue. 2 | 3 | Copyright: © 2017-2019 Sönke Ludwig 4 | Authors: Sönke Ludwig 5 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 | */ 7 | module vibe.core.channel; 8 | 9 | import vibe.container.ringbuffer : RingBuffer; 10 | import vibe.core.sync : TaskCondition; 11 | import vibe.internal.array : FixedRingBuffer; 12 | 13 | import std.algorithm.mutation : move, swap; 14 | import std.exception : enforce; 15 | import core.sync.mutex; 16 | 17 | // multiple producers allowed, multiple consumers allowed - Q: should this be restricted to allow higher performance? maybe configurable? 18 | // currently always buffered - TODO: implement blocking non-buffered mode 19 | // TODO: implement a multi-channel wait, e.g. 20 | // TaggedAlgebraic!(...) consumeAny(ch1, ch2, ch3); - requires a waitOnMultipleConditions function 21 | 22 | // NOTE: not using synchronized (m_mutex) because it is not nothrow 23 | 24 | 25 | /** Creates a new channel suitable for cross-task and cross-thread communication. 26 | */ 27 | Channel!(T, buffer_size) createChannel(T, size_t buffer_size = 100)(ChannelConfig config = ChannelConfig.init) 28 | { 29 | Channel!(T, buffer_size) ret; 30 | ret.m_impl = new shared ChannelImpl!(T, buffer_size)(config); 31 | return ret; 32 | } 33 | 34 | struct ChannelConfig { 35 | ChannelPriority priority = ChannelPriority.latency; 36 | } 37 | 38 | enum ChannelPriority { 39 | /** Minimize latency 40 | 41 | Triggers readers immediately once data is available and triggers writers 42 | as soon as the queue has space. 43 | */ 44 | latency, 45 | 46 | /** Minimize overhead. 47 | 48 | Triggers readers once the queue is full and triggers writers once the 49 | queue is empty in order to maximize batch sizes and minimize 50 | synchronization overhead. 51 | 52 | Note that in this mode it is necessary to close the channel to ensure 53 | that the buffered data is fully processed. 54 | */ 55 | overhead 56 | } 57 | 58 | /** Thread-safe typed data channel implementation. 59 | 60 | The implementation supports multiple-reader-multiple-writer operation across 61 | multiple tasks in multiple threads. 62 | */ 63 | struct Channel(T, size_t buffer_size = 100) { 64 | enum bufferSize = buffer_size; 65 | 66 | private shared(ChannelImpl!(T, buffer_size)) m_impl; 67 | 68 | this(this) scope @safe { if (m_impl) m_impl.addRef(); } 69 | ~this() scope @safe { if (m_impl) m_impl.releaseRef(); } 70 | 71 | /** Determines whether there is more data to read in a single-reader scenario. 72 | 73 | This property is empty $(I iff) no more elements are in the internal 74 | buffer and `close()` has been called. Once the channel is empty, 75 | subsequent calls to `consumeOne` or `consumeAll` will throw an 76 | exception. 77 | 78 | Note that relying on the return value to determine whether another 79 | element can be read is only safe in a single-reader scenario. It is 80 | generally recommended to use `tryConsumeOne` instead. 81 | */ 82 | deprecated("Use `tryConsumeOne` instead.") 83 | @property bool empty() { return m_impl.empty; } 84 | /// ditto 85 | deprecated("Use `tryConsumeOne` instead.") 86 | @property bool empty() shared { return m_impl.empty; } 87 | 88 | /** Returns the current count of items in the buffer. 89 | 90 | This function is useful for diagnostic purposes. 91 | */ 92 | @property size_t bufferFill() { return m_impl.bufferFill; } 93 | /// ditto 94 | @property size_t bufferFill() shared { return m_impl.bufferFill; } 95 | 96 | /** Closes the channel. 97 | 98 | A closed channel does not accept any new items enqueued using `put` and 99 | causes `empty` to return `fals` as soon as all preceeding elements have 100 | been consumed. 101 | */ 102 | void close() { m_impl.close(); } 103 | /// ditto 104 | void close() shared { m_impl.close(); } 105 | 106 | /** Consumes a single element off the queue. 107 | 108 | This function will block if no elements are available. If the `empty` 109 | property is `true`, an exception will be thrown. 110 | 111 | Note that it is recommended to use `tryConsumeOne` instead of a 112 | combination of `empty` and `consumeOne` due to being more efficient and 113 | also being reliable in a multiple-reader scenario. 114 | */ 115 | deprecated("Use `tryConsumeOne` instead.") 116 | T consumeOne() { return m_impl.consumeOne(); } 117 | /// ditto 118 | deprecated("Use `tryConsumeOne` instead.") 119 | T consumeOne() shared { return m_impl.consumeOne(); } 120 | 121 | /** Attempts to consume a single element. 122 | 123 | If no more elements are available and the channel has been closed, 124 | `false` is returned and `dst` is left untouched. 125 | */ 126 | bool tryConsumeOne(ref T dst) { return m_impl.tryConsumeOne(dst); } 127 | /// ditto 128 | bool tryConsumeOne(ref T dst) shared { return m_impl.tryConsumeOne(dst); } 129 | 130 | /** Attempts to consume all elements currently in the queue. 131 | 132 | This function will block if no elements are available. Once at least one 133 | element is available, the contents of `dst` will be replaced with all 134 | available elements. 135 | 136 | If the `empty` property is or becomes `true` before data becomes 137 | avaiable, `dst` will be left untouched and `false` is returned. 138 | */ 139 | bool consumeAll(ref RingBuffer!(T, buffer_size) dst) 140 | in { assert(dst.empty); } 141 | do { return m_impl.consumeAll(dst); } 142 | /// ditto 143 | bool consumeAll(ref RingBuffer!(T, buffer_size) dst) shared 144 | in { assert(dst.empty); } 145 | do { return m_impl.consumeAll(dst); } 146 | /// ditto 147 | deprecated("Pass a reference to `vibe.container.ringbuffer.RingBuffer` instead.") 148 | bool consumeAll(ref FixedRingBuffer!(T, buffer_size) dst) 149 | in { assert(dst.empty); } 150 | do { return m_impl.consumeAll(dst); } 151 | /// ditto 152 | deprecated("Pass a reference to `vibe.container.ringbuffer.RingBuffer` instead.") 153 | bool consumeAll(ref FixedRingBuffer!(T, buffer_size) dst) shared 154 | in { assert(dst.empty); } 155 | do { return m_impl.consumeAll(dst); } 156 | 157 | /** Enqueues an element. 158 | 159 | This function may block the the event that the internal buffer is full. 160 | */ 161 | void put(T item) { m_impl.put(item.move); } 162 | /// ditto 163 | void put(T item) shared { m_impl.put(item.move); } 164 | } 165 | 166 | 167 | private final class ChannelImpl(T, size_t buffer_size) { 168 | import vibe.core.concurrency : isWeaklyIsolated; 169 | import vibe.container.internal.utilallocator : Mallocator, makeGCSafe, disposeGCSafe; 170 | static assert(isWeaklyIsolated!T, "Channel data type "~T.stringof~" is not safe to pass between threads."); 171 | 172 | private { 173 | Mutex m_mutex; 174 | TaskCondition m_condition; 175 | RingBuffer!(T, buffer_size) m_items; 176 | bool m_closed = false; 177 | ChannelConfig m_config; 178 | int m_refCount = 1; 179 | } 180 | 181 | this(ChannelConfig config) 182 | shared @trusted nothrow { 183 | m_mutex = cast(shared)Mallocator.instance.makeGCSafe!Mutex(); 184 | m_condition = cast(shared)Mallocator.instance.makeGCSafe!TaskCondition(cast()m_mutex); 185 | m_config = config; 186 | } 187 | 188 | private void addRef() 189 | @safe nothrow shared { 190 | m_mutex.lock_nothrow(); 191 | scope (exit) m_mutex.unlock_nothrow(); 192 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 193 | thisus.m_refCount++; 194 | } 195 | 196 | private void releaseRef() 197 | @safe nothrow shared { 198 | bool destroy = false; 199 | { 200 | m_mutex.lock_nothrow(); 201 | scope (exit) m_mutex.unlock_nothrow(); 202 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 203 | if (--thisus.m_refCount == 0) 204 | destroy = true; 205 | } 206 | 207 | if (destroy) { 208 | try () @trusted { 209 | Mallocator.instance.disposeGCSafe(m_condition); 210 | Mallocator.instance.disposeGCSafe(m_mutex); 211 | } (); 212 | catch (Exception e) assert(false); 213 | } 214 | } 215 | 216 | @property bool empty() 217 | shared nothrow { 218 | { 219 | m_mutex.lock_nothrow(); 220 | scope (exit) m_mutex.unlock_nothrow(); 221 | 222 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 223 | 224 | // ensure that in a single-reader scenario !empty guarantees a 225 | // successful call to consumeOne 226 | while (!thisus.m_closed && thisus.m_items.empty) 227 | thisus.m_condition.wait(); 228 | 229 | return thisus.m_closed && thisus.m_items.empty; 230 | } 231 | } 232 | 233 | @property size_t bufferFill() 234 | shared nothrow { 235 | { 236 | m_mutex.lock_nothrow(); 237 | scope (exit) m_mutex.unlock_nothrow(); 238 | 239 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 240 | return thisus.m_items.length; 241 | } 242 | } 243 | 244 | void close() 245 | shared nothrow { 246 | { 247 | m_mutex.lock_nothrow(); 248 | scope (exit) m_mutex.unlock_nothrow(); 249 | 250 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 251 | thisus.m_closed = true; 252 | thisus.m_condition.notifyAll(); 253 | } 254 | } 255 | 256 | bool tryConsumeOne(ref T dst) 257 | shared nothrow { 258 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 259 | bool need_notify = false; 260 | 261 | { 262 | m_mutex.lock_nothrow(); 263 | scope (exit) m_mutex.unlock_nothrow(); 264 | 265 | while (thisus.m_items.empty) { 266 | if (m_closed) return false; 267 | thisus.m_condition.wait(); 268 | } 269 | 270 | if (m_config.priority == ChannelPriority.latency) 271 | need_notify = thisus.m_items.full; 272 | 273 | move(thisus.m_items.front, dst); 274 | thisus.m_items.removeFront(); 275 | 276 | if (m_config.priority == ChannelPriority.overhead) 277 | need_notify = thisus.m_items.empty; 278 | } 279 | 280 | if (need_notify) { 281 | if (m_config.priority == ChannelPriority.overhead) 282 | thisus.m_condition.notifyAll(); 283 | else 284 | thisus.m_condition.notify(); 285 | } 286 | 287 | return true; 288 | } 289 | 290 | T consumeOne() 291 | shared { 292 | T ret; 293 | if (!tryConsumeOne(ret)) 294 | throw new Exception("Attempt to consume from an empty channel."); 295 | return ret; 296 | } 297 | 298 | deprecated 299 | bool consumeAll(ref FixedRingBuffer!(T, buffer_size) dst) 300 | shared nothrow { 301 | RingBuffer!(T, buffer_size) tmp; 302 | if (!consumeAll(tmp)) 303 | return false; 304 | dst.clear(); 305 | foreach (ref el; tmp[]) 306 | dst.put(el.move); 307 | return true; 308 | } 309 | 310 | bool consumeAll(ref RingBuffer!(T, buffer_size) dst) 311 | shared nothrow { 312 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 313 | bool need_notify = false; 314 | 315 | { 316 | m_mutex.lock_nothrow(); 317 | scope (exit) m_mutex.unlock_nothrow(); 318 | 319 | while (thisus.m_items.empty) { 320 | if (m_closed) return false; 321 | thisus.m_condition.wait(); 322 | } 323 | 324 | if (m_config.priority == ChannelPriority.latency) 325 | need_notify = thisus.m_items.full; 326 | 327 | swap(thisus.m_items, dst); 328 | 329 | if (m_config.priority == ChannelPriority.overhead) 330 | need_notify = true; 331 | } 332 | 333 | if (need_notify) { 334 | if (m_config.priority == ChannelPriority.overhead) 335 | thisus.m_condition.notifyAll(); 336 | else thisus.m_condition.notify(); 337 | } 338 | 339 | return true; 340 | } 341 | 342 | void put(T item) 343 | shared { 344 | auto thisus = () @trusted { return cast(ChannelImpl)this; } (); 345 | bool need_notify = false; 346 | 347 | { 348 | m_mutex.lock_nothrow(); 349 | scope (exit) m_mutex.unlock_nothrow(); 350 | 351 | enforce(!m_closed, "Sending on closed channel."); 352 | while (thisus.m_items.full) 353 | thisus.m_condition.wait(); 354 | if (m_config.priority == ChannelPriority.latency) 355 | need_notify = thisus.m_items.empty; 356 | thisus.m_items.put(item.move); 357 | if (m_config.priority == ChannelPriority.overhead) 358 | need_notify = thisus.m_items.full; 359 | } 360 | 361 | if (need_notify) { 362 | if (m_config.priority == ChannelPriority.overhead) 363 | thisus.m_condition.notifyAll(); 364 | else thisus.m_condition.notify(); 365 | } 366 | } 367 | } 368 | 369 | deprecated @safe unittest { // test basic operation and non-copyable struct compatiblity 370 | import std.exception : assertThrown; 371 | 372 | static struct S { 373 | int i; 374 | @disable this(this); 375 | } 376 | 377 | auto ch = createChannel!S; 378 | ch.put(S(1)); 379 | assert(ch.consumeOne().i == 1); 380 | ch.put(S(4)); 381 | ch.put(S(5)); 382 | ch.close(); 383 | assert(!ch.empty); 384 | assert(ch.consumeOne() == S(4)); 385 | assert(!ch.empty); 386 | assert(ch.consumeOne() == S(5)); 387 | assert(ch.empty); 388 | assertThrown(ch.consumeOne()); 389 | } 390 | 391 | @safe unittest { // test basic operation and non-copyable struct compatiblity 392 | static struct S { 393 | int i; 394 | @disable this(this); 395 | } 396 | 397 | auto ch = createChannel!S; 398 | S v; 399 | ch.put(S(1)); 400 | assert(ch.tryConsumeOne(v) && v == S(1)); 401 | ch.put(S(4)); 402 | ch.put(S(5)); 403 | { 404 | RingBuffer!(S, 100) buf; 405 | ch.consumeAll(buf); 406 | assert(buf.length == 2); 407 | assert(buf[0].i == 4); 408 | assert(buf[1].i == 5); 409 | } 410 | ch.put(S(2)); 411 | ch.close(); 412 | assert(ch.tryConsumeOne(v) && v.i == 2); 413 | assert(!ch.tryConsumeOne(v)); 414 | } 415 | 416 | deprecated @safe unittest { // test basic operation and non-copyable struct compatiblity 417 | static struct S { 418 | int i; 419 | @disable this(this); 420 | } 421 | 422 | auto ch = createChannel!S; 423 | S v; 424 | ch.put(S(1)); 425 | assert(ch.tryConsumeOne(v) && v == S(1)); 426 | ch.put(S(4)); 427 | ch.put(S(5)); 428 | { 429 | FixedRingBuffer!(S, 100) buf; 430 | ch.consumeAll(buf); 431 | assert(buf.length == 2); 432 | assert(buf[0].i == 4); 433 | assert(buf[1].i == 5); 434 | } 435 | ch.put(S(2)); 436 | ch.close(); 437 | assert(ch.tryConsumeOne(v) && v.i == 2); 438 | assert(!ch.tryConsumeOne(v)); 439 | } 440 | 441 | deprecated @safe unittest { // make sure shared(Channel!T) can also be used 442 | shared ch = createChannel!int; 443 | ch.put(1); 444 | assert(!ch.empty); 445 | assert(ch.consumeOne == 1); 446 | ch.close(); 447 | assert(ch.empty); 448 | } 449 | 450 | @safe unittest { // make sure shared(Channel!T) can also be used 451 | shared ch = createChannel!int; 452 | ch.put(1); 453 | int v; 454 | assert(ch.tryConsumeOne(v) && v == 1); 455 | ch.close(); 456 | assert(!ch.tryConsumeOne(v)); 457 | } 458 | 459 | @safe unittest { // ensure nothrow'ness for throwing struct 460 | static struct S { 461 | this(this) { throw new Exception("meh!"); } 462 | } 463 | auto ch = createChannel!S; 464 | ch.put(S.init); 465 | ch.put(S.init); 466 | 467 | S s; 468 | RingBuffer!(S, 100, true) sb; 469 | 470 | () nothrow { 471 | assert(ch.tryConsumeOne(s)); 472 | assert(ch.consumeAll(sb)); 473 | assert(sb.length == 1); 474 | ch.close(); 475 | assert(!ch.tryConsumeOne(s)); 476 | } (); 477 | } 478 | 479 | deprecated @safe unittest { // ensure nothrow'ness for throwing struct 480 | static struct S { 481 | this(this) { throw new Exception("meh!"); } 482 | } 483 | auto ch = createChannel!S; 484 | ch.put(S.init); 485 | ch.put(S.init); 486 | 487 | S s; 488 | FixedRingBuffer!(S, 100, true) sb; 489 | 490 | () nothrow { 491 | assert(ch.tryConsumeOne(s)); 492 | assert(ch.consumeAll(sb)); 493 | assert(sb.length == 1); 494 | ch.close(); 495 | assert(!ch.tryConsumeOne(s)); 496 | } (); 497 | } 498 | 499 | unittest { 500 | import std.traits : EnumMembers; 501 | import vibe.core.core : runTask; 502 | 503 | void test(ChannelPriority prio) 504 | { 505 | auto ch = createChannel!int(ChannelConfig(prio)); 506 | runTask(() nothrow { 507 | try { 508 | ch.put(1); 509 | ch.put(2); 510 | ch.put(3); 511 | } catch (Exception e) assert(false, e.msg); 512 | ch.close(); 513 | }); 514 | 515 | int i; 516 | assert(ch.tryConsumeOne(i) && i == 1); 517 | assert(ch.tryConsumeOne(i) && i == 2); 518 | assert(ch.tryConsumeOne(i) && i == 3); 519 | assert(!ch.tryConsumeOne(i)); 520 | } 521 | 522 | foreach (m; EnumMembers!ChannelPriority) 523 | test(m); 524 | } 525 | -------------------------------------------------------------------------------- /source/vibe/core/connectionpool.d: -------------------------------------------------------------------------------- 1 | /** 2 | Generic connection pool for reusing persistent connections across fibers. 3 | 4 | Copyright: © 2012-2016 Sönke Ludwig 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 | module vibe.core.connectionpool; 9 | 10 | import vibe.core.log; 11 | 12 | import core.thread; 13 | import vibe.core.sync; 14 | import vibe.internal.freelistref; 15 | 16 | /** 17 | Generic connection pool class. 18 | 19 | The connection pool is creating connections using the supplied factory 20 | function as needed whenever `lockConnection` is called. Connections are 21 | associated to the calling fiber, as long as any copy of the returned 22 | `LockedConnection` object still exists. Connections that are not associated 23 | to any fiber will be kept in a pool of open connections for later reuse. 24 | 25 | Note that, after retrieving a connection with `lockConnection`, the caller 26 | has to make sure that the connection is actually physically open and to 27 | reopen it if necessary. The `ConnectionPool` class has no knowledge of the 28 | internals of the connection objects. 29 | */ 30 | final class ConnectionPool(Connection) 31 | { 32 | private { 33 | Connection delegate() @safe m_connectionFactory; 34 | Connection[] m_connections; 35 | int[const(Connection)] m_lockCount; 36 | FreeListRef!LocalTaskSemaphore m_sem; 37 | debug Thread m_thread; 38 | } 39 | 40 | this(Connection delegate() @safe connection_factory, uint max_concurrent = uint.max) 41 | { 42 | m_connectionFactory = connection_factory; 43 | () @trusted { m_sem = FreeListRef!LocalTaskSemaphore(max_concurrent); } (); 44 | debug m_thread = () @trusted { return Thread.getThis(); } (); 45 | } 46 | 47 | deprecated("Use an @safe callback instead") 48 | this(Connection delegate() connection_factory, uint max_concurrent = uint.max) 49 | @system { 50 | this(cast(Connection delegate() @safe)connection_factory, max_concurrent); 51 | } 52 | 53 | /** Determines the maximum number of concurrently open connections. 54 | 55 | Attempting to lock more connections that this number will cause the 56 | calling fiber to be blocked until one of the locked connections 57 | becomes available for reuse. 58 | */ 59 | @property void maxConcurrency(uint max_concurrent) { 60 | m_sem.maxLocks = max_concurrent; 61 | } 62 | /// ditto 63 | @property uint maxConcurrency() nothrow { 64 | return m_sem.maxLocks; 65 | } 66 | 67 | /** Retrieves a connection to temporarily associate with the calling fiber. 68 | 69 | The returned `LockedConnection` object uses RAII and reference counting 70 | to determine when to unlock the connection. 71 | */ 72 | LockedConnection!Connection lockConnection() 73 | @safe { 74 | debug assert(m_thread is () @trusted { return Thread.getThis(); } (), "ConnectionPool was called from a foreign thread!"); 75 | 76 | () @trusted { m_sem.lock(); } (); 77 | scope (failure) () @trusted { m_sem.unlock(); } (); 78 | 79 | size_t cidx = size_t.max; 80 | foreach( i, c; m_connections ){ 81 | auto plc = c in m_lockCount; 82 | if( !plc || *plc == 0 ){ 83 | cidx = i; 84 | break; 85 | } 86 | } 87 | 88 | Connection conn; 89 | if( cidx != size_t.max ){ 90 | logTrace("returning %s connection %d of %d", Connection.stringof, cidx, m_connections.length); 91 | conn = m_connections[cidx]; 92 | } else { 93 | logDebug("creating new %s connection, all %d are in use", Connection.stringof, m_connections.length); 94 | conn = m_connectionFactory(); // NOTE: may block 95 | static if (is(typeof(cast(void*)conn))) 96 | logDebug(" ... %s", () @trusted { return cast(void*)conn; } ()); 97 | } 98 | m_lockCount[conn] = 1; 99 | if( cidx == size_t.max ){ 100 | m_connections ~= conn; 101 | logDebug("Now got %d connections", m_connections.length); 102 | } 103 | auto ret = LockedConnection!Connection(this, conn); 104 | return ret; 105 | } 106 | 107 | /** Removes all currently unlocked connections from the pool. 108 | 109 | Params: 110 | disconnect_callback = Gets called for every removed connection to 111 | allow closing connections and freeing associated resources. 112 | */ 113 | void removeUnused(scope void delegate(Connection conn) @safe nothrow disconnect_callback) 114 | { 115 | Connection[] remaining_conns, removed_conns; 116 | foreach (c; m_connections) { 117 | if (m_lockCount.get(c, 0) > 0) 118 | remaining_conns ~= c; 119 | else 120 | removed_conns ~= c; 121 | } 122 | 123 | m_connections = remaining_conns; 124 | 125 | foreach (c; removed_conns) 126 | disconnect_callback(c); 127 | } 128 | 129 | /** Removes an existing connection from the pool 130 | It can be called with a locked connection, same connection 131 | can be added back to the pool anytime. Any fibers that hold 132 | a lock on this connection will keep behaving as expected. 133 | 134 | Params: 135 | conn = connection to remove from the pool 136 | */ 137 | void remove(Connection conn) @safe 138 | { 139 | foreach (idx, c; m_connections) 140 | if (c is conn) 141 | { 142 | m_connections = m_connections[0 .. idx] ~ m_connections[idx + 1 .. $]; 143 | auto plc = conn in m_lockCount; 144 | assert(plc !is null); 145 | assert(*plc >= 0); 146 | if (*plc > 0) 147 | *plc *= -1; // invert the plc to signal LockedConnection that this connection is no longer in the pool 148 | else 149 | m_lockCount.remove(conn); 150 | return; 151 | } 152 | assert(0, "Removing non existing conn"); 153 | } 154 | 155 | /** Add a connection to the pool explicitly 156 | 157 | Params: 158 | conn = new connection to add to the pool 159 | 160 | Returns: 161 | success/failure 162 | */ 163 | bool add(Connection conn) @safe nothrow 164 | { 165 | if (m_connections.length < this.maxConcurrency) 166 | { 167 | auto plc = conn in m_lockCount; 168 | if (plc is null) 169 | m_lockCount[conn] = 0; 170 | else if (*plc < 0) 171 | *plc *= -1; // invert the plc back to positive 172 | m_connections ~= conn; 173 | return true; 174 | } 175 | return false; 176 | } 177 | } 178 | 179 | /// 180 | unittest { 181 | class Connection { 182 | void write() {} 183 | } 184 | 185 | auto pool = new ConnectionPool!Connection({ 186 | return new Connection; // perform the connection here 187 | }); 188 | 189 | // create and lock a first connection 190 | auto c1 = pool.lockConnection(); 191 | c1.write(); 192 | 193 | // create and lock a second connection 194 | auto c2 = pool.lockConnection(); 195 | c2.write(); 196 | 197 | // writing to c1 will still write to the first connection 198 | c1.write(); 199 | 200 | // free up the reference to the first connection, so that it can be reused 201 | destroy(c1); 202 | 203 | // locking a new connection will reuse the first connection now instead of creating a new one 204 | auto c3 = pool.lockConnection(); 205 | c3.write(); 206 | } 207 | 208 | unittest { // issue vibe-d#2109 209 | import vibe.core.net : TCPConnection, connectTCP; 210 | new ConnectionPool!TCPConnection({ return connectTCP("127.0.0.1", 8080); }); 211 | } 212 | 213 | unittest { // removeUnused 214 | class Connection {} 215 | 216 | auto pool = new ConnectionPool!Connection({ 217 | return new Connection; // perform the connection here 218 | }); 219 | 220 | auto c1 = pool.lockConnection(); 221 | auto c1i = c1.__conn; 222 | 223 | auto c2 = pool.lockConnection(); 224 | auto c2i = c2.__conn; 225 | 226 | 227 | assert(pool.m_connections == [c1i, c2i]); 228 | 229 | c2 = LockedConnection!Connection.init; 230 | pool.removeUnused((c) { assert(c is c2i); }); 231 | assert(pool.m_connections == [c1i]); 232 | 233 | c1 = LockedConnection!Connection.init; 234 | pool.removeUnused((c) { assert(c is c1i); }); 235 | assert(pool.m_connections == []); 236 | } 237 | 238 | 239 | struct LockedConnection(Connection) { 240 | import vibe.core.task : Task; 241 | 242 | private { 243 | ConnectionPool!Connection m_pool; 244 | Connection m_conn; 245 | debug uint m_magic = 0xB1345AC2; 246 | } 247 | 248 | @safe: 249 | 250 | private this(ConnectionPool!Connection pool, Connection conn) 251 | { 252 | assert(!!conn); 253 | m_pool = pool; 254 | m_conn = conn; 255 | } 256 | 257 | this(this) 258 | { 259 | debug assert(m_magic == 0xB1345AC2, "LockedConnection value corrupted."); 260 | if (!!m_conn) { 261 | m_pool.m_lockCount[m_conn]++; 262 | static if (is(typeof(cast(void*)conn))) 263 | logTrace("conn %s copy %d", () @trusted { return cast(void*)m_conn; } (), m_pool.m_lockCount[m_conn]); 264 | } 265 | } 266 | 267 | ~this() 268 | { 269 | debug assert(m_magic == 0xB1345AC2, "LockedConnection value corrupted."); 270 | if (!!m_conn) { 271 | auto plc = m_conn in m_pool.m_lockCount; 272 | assert(plc !is null); 273 | assert(*plc != 0); 274 | //logTrace("conn %s destroy %d", cast(void*)m_conn, *plc-1); 275 | if( *plc > 0 && --*plc == 0 ){ 276 | () @trusted { m_pool.m_sem.unlock(); } (); 277 | //logTrace("conn %s release", cast(void*)m_conn); 278 | } 279 | else if (*plc < 0 && ++*plc == 0) // connection was removed from the pool and no lock remains on it 280 | { 281 | m_pool.m_lockCount.remove(m_conn); 282 | } 283 | m_conn = Connection.init; 284 | } 285 | } 286 | 287 | 288 | @property int __refCount() const { return m_pool.m_lockCount.get(m_conn, 0); } 289 | @property inout(Connection) __conn() inout { return m_conn; } 290 | 291 | alias __conn this; 292 | } 293 | 294 | /// 295 | unittest { 296 | int id = 0; 297 | class Connection { 298 | public int id; 299 | } 300 | 301 | auto pool = new ConnectionPool!Connection({ 302 | auto conn = new Connection(); // perform the connection here 303 | conn.id = id++; 304 | return conn; 305 | }); 306 | 307 | // create and lock a first connection 308 | auto c1 = pool.lockConnection(); 309 | assert(c1.id == 0); 310 | pool.remove(c1); 311 | destroy(c1); 312 | 313 | auto c2 = pool.lockConnection(); 314 | assert(c2.id == 1); // assert that we got a new connection 315 | pool.remove(c2); 316 | pool.add(c2); 317 | destroy(c2); 318 | 319 | auto c3 = pool.lockConnection(); 320 | assert(c3.id == 1); // should get the same connection back 321 | } 322 | -------------------------------------------------------------------------------- /source/vibe/core/internal/release.d: -------------------------------------------------------------------------------- 1 | module vibe.core.internal.release; 2 | 3 | import eventcore.core; 4 | import std.stdint : intptr_t; 5 | 6 | /// Release a handle in a thread-safe way 7 | void releaseHandle(string subsys, H)(H handle, shared(NativeEventDriver) drv) 8 | { 9 | if (drv is (() @trusted => cast(shared)eventDriver)()) { 10 | __traits(getMember, eventDriver, subsys).releaseRef(handle); 11 | } else { 12 | // if the owner driver has already been disposed, there is nothing we 13 | // can do anymore 14 | if (!drv.core) { 15 | import vibe.core.log : logWarn; 16 | logWarn("Leaking %s handle %s, because owner thread/driver has already terminated", 17 | H.name, handle.value); 18 | return; 19 | } 20 | 21 | // in case the destructor was called from a foreign thread, 22 | // perform the release in the owner thread 23 | drv.core.runInOwnerThread((H handle) { 24 | __traits(getMember, eventDriver, subsys).releaseRef(handle); 25 | }, handle); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/vibe/core/internal/threadlocalwaiter.d: -------------------------------------------------------------------------------- 1 | /** Implementation of the wait/notify base logic used for most concurrency 2 | primitives 3 | */ 4 | module vibe.core.internal.threadlocalwaiter; 5 | 6 | import vibe.container.internal.utilallocator : Mallocator, disposeGCSafe, makeGCSafe; 7 | import vibe.core.log; 8 | import vibe.internal.list : StackSList; 9 | 10 | import eventcore.core : NativeEventDriver, eventDriver; 11 | import eventcore.driver : EventCallback, EventID; 12 | 13 | 14 | package(vibe.core): 15 | 16 | static StackSList!(ThreadLocalWaiter!true) s_free; // free-list of reusable waiter structs for the calling thread 17 | 18 | ThreadLocalWaiter!false allocPlainThreadLocalWaiter() 19 | @safe nothrow { 20 | return () @trusted { return Mallocator.instance.makeGCSafe!(ThreadLocalWaiter!false); } (); 21 | } 22 | 23 | ThreadLocalWaiter!true allocEventThreadLocalWaiter() 24 | @safe nothrow { 25 | auto drv = eventDriver; 26 | 27 | ThreadLocalWaiter!true w; 28 | if (!s_free.empty) { 29 | w = s_free.first; 30 | s_free.remove(w); 31 | assert(w.m_refCount == 0); 32 | assert(w.m_driver is drv); 33 | w.addRef(); 34 | } else { 35 | w = new ThreadLocalWaiter!true; 36 | } 37 | assert(w.m_refCount == 1); 38 | return w; 39 | } 40 | 41 | void freeThreadResources() 42 | @safe nothrow { 43 | s_free.filter((w) @trusted { 44 | try destroy(w); 45 | catch (Exception e) assert(false, e.msg); 46 | return false; 47 | }); 48 | } 49 | 50 | 51 | /** An object able to wait in a single thread 52 | */ 53 | final class ThreadLocalWaiter(bool EVENT_TRIGGERED) { 54 | import vibe.internal.list : CircularDList; 55 | import core.time : Duration; 56 | 57 | private { 58 | static struct TaskWaiter { 59 | TaskWaiter* prev, next; 60 | void delegate() @safe nothrow notifier; 61 | 62 | void wait(void delegate() @safe nothrow del) @safe nothrow { 63 | assert(notifier is null, "Local waiter is used twice!"); 64 | notifier = del; 65 | } 66 | void cancel() @safe nothrow { notifier = null; } 67 | void emit() @safe nothrow { auto n = notifier; notifier = null; n(); } 68 | } 69 | 70 | static if (EVENT_TRIGGERED) { 71 | package(vibe) ThreadLocalWaiter next; // queue of other waiters in the active/free list of the manual event 72 | NativeEventDriver m_driver; 73 | EventID m_event = EventID.invalid; 74 | } else { 75 | version (unittest) package(vibe.core) int m_emitCount = 0; 76 | else int m_emitCount = 0; 77 | } 78 | int m_refCount = 1; 79 | TaskWaiter m_pivot; 80 | TaskWaiter m_emitPivot; 81 | CircularDList!(TaskWaiter*) m_waiters; 82 | } 83 | 84 | this() 85 | { 86 | m_waiters = CircularDList!(TaskWaiter*)(() @trusted { return &m_pivot; } ()); 87 | static if (EVENT_TRIGGERED) { 88 | m_driver = eventDriver; 89 | m_event = m_driver.events.create(); 90 | assert(m_event != EventID.invalid, "Failed to create event!"); 91 | } 92 | } 93 | 94 | static if (EVENT_TRIGGERED) { 95 | ~this() 96 | { 97 | import vibe.core.internal.release : releaseHandle; 98 | import core.memory : GC; 99 | import core.stdc.stdlib : abort; 100 | 101 | if (m_event != EventID.invalid) { 102 | if (m_driver !is eventDriver) { 103 | logError("ThreadWaiter destroyed in foreign thread"); 104 | // handle GC finalization at process exit gracefully, as 105 | // this can happen when tasks/threads have not been properly 106 | // shut down before application exit 107 | if (!GC.inFinalizer()) abort(); 108 | } else m_driver.events.releaseRef(m_event); 109 | m_event = EventID.invalid; 110 | } 111 | } 112 | } 113 | 114 | @property bool unused() const @safe nothrow { return m_waiters.empty; } 115 | @property bool unique() const @safe nothrow { return m_refCount == 1; } 116 | 117 | static if (!EVENT_TRIGGERED) { 118 | @property int emitCount() const @safe nothrow { return m_emitCount; } 119 | } else { 120 | @property NativeEventDriver driver() @safe nothrow { return m_driver; } 121 | } 122 | 123 | void addRef() @safe nothrow { assert(m_refCount >= 0); m_refCount++; } 124 | bool releaseRef() 125 | @safe nothrow { 126 | assert(m_refCount > 0); 127 | if (--m_refCount == 0) { 128 | static if (EVENT_TRIGGERED) { 129 | s_free.add(this); 130 | // TODO: cap size of m_freeWaiters 131 | } else { 132 | static if (__VERSION__ < 2087) scope (failure) assert(false); 133 | () @trusted { Mallocator.instance.disposeGCSafe(this); } (); 134 | } 135 | return false; 136 | } 137 | return true; 138 | } 139 | 140 | bool wait(bool interruptible)(Duration timeout, scope bool delegate() @safe nothrow exit_condition = null) 141 | @safe { 142 | import core.time : MonoTime; 143 | import vibe.internal.async : Waitable, asyncAwaitAny; 144 | 145 | TaskWaiter waiter_store; 146 | TaskWaiter* waiter = () @trusted { return &waiter_store; } (); 147 | 148 | m_waiters.insertBack(waiter); 149 | assert(waiter.next !is null); 150 | scope (exit) 151 | if (waiter.next !is null) { 152 | m_waiters.remove(waiter); 153 | assert(!waiter.next); 154 | } 155 | 156 | MonoTime target_timeout, now; 157 | if (timeout != Duration.max) { 158 | try now = MonoTime.currTime(); 159 | catch (Exception e) { assert(false, e.msg); } 160 | target_timeout = now + timeout; 161 | } 162 | 163 | bool cancelled; 164 | 165 | alias waitable = Waitable!(typeof(TaskWaiter.notifier), 166 | (cb) { waiter.wait(cb); }, 167 | (cb) { cancelled = true; waiter.cancel(); }, 168 | () {} 169 | ); 170 | 171 | static if (EVENT_TRIGGERED) { 172 | alias ewaitable = Waitable!(EventCallback, 173 | (cb) { 174 | eventDriver.events.wait(m_event, cb); 175 | // check for exit condition *after* starting to wait for the event 176 | // to avoid a race condition 177 | if (exit_condition()) { 178 | eventDriver.events.cancelWait(m_event, cb); 179 | cb(m_event); 180 | } 181 | }, 182 | (cb) { eventDriver.events.cancelWait(m_event, cb); }, 183 | (EventID) {} 184 | ); 185 | 186 | assert(m_event != EventID.invalid); 187 | asyncAwaitAny!(interruptible, waitable, ewaitable)(timeout); 188 | } else { 189 | asyncAwaitAny!(interruptible, waitable)(timeout); 190 | } 191 | 192 | if (cancelled) { 193 | assert(waiter.next !is null, "Cancelled waiter not in queue anymore!?"); 194 | return false; 195 | } else { 196 | assert(waiter.next is null, "Triggered waiter still in queue!?"); 197 | return true; 198 | } 199 | } 200 | 201 | void emit() 202 | @safe nothrow { 203 | import std.algorithm.mutation : swap; 204 | import vibe.core.core : yield; 205 | 206 | static if (!EVENT_TRIGGERED) 207 | m_emitCount++; 208 | 209 | if (m_waiters.empty) return; 210 | 211 | TaskWaiter* pivot = () @trusted { return &m_emitPivot; } (); 212 | 213 | if (pivot.next) { // another emit in progress? 214 | // shift pivot to the end, so that the other emit call will process all pending waiters 215 | if (pivot !is m_waiters.back) { 216 | m_waiters.remove(pivot); 217 | m_waiters.insertBack(pivot); 218 | } 219 | return; 220 | } 221 | 222 | m_waiters.insertBack(pivot); 223 | scope (exit) m_waiters.remove(pivot); 224 | 225 | foreach (w; m_waiters) { 226 | if (w is pivot) break; 227 | emitWaiter(w); 228 | } 229 | } 230 | 231 | bool emitSingle() 232 | @safe nothrow { 233 | static if (!EVENT_TRIGGERED) 234 | m_emitCount++; 235 | 236 | if (m_waiters.empty) return false; 237 | 238 | TaskWaiter* pivot = () @trusted { return &m_emitPivot; } (); 239 | 240 | if (pivot.next) { // another emit in progress? 241 | // shift pivot to the right, so that the other emit call will process another waiter 242 | if (pivot !is m_waiters.back) { 243 | auto n = pivot.next; 244 | m_waiters.remove(pivot); 245 | m_waiters.insertAfter(pivot, n); 246 | } 247 | return true; 248 | } 249 | 250 | emitWaiter(m_waiters.front); 251 | return true; 252 | } 253 | 254 | static if (EVENT_TRIGGERED) { 255 | void triggerEvent() 256 | { 257 | try { 258 | assert(m_event != EventID.invalid); 259 | () @trusted { return cast(shared)m_driver; } ().events.trigger(m_event, true); 260 | } catch (Exception e) assert(false, e.msg); 261 | } 262 | } 263 | 264 | private void emitWaiter(TaskWaiter* w) 265 | @safe nothrow { 266 | m_waiters.remove(w); 267 | 268 | if (w.notifier !is null) { 269 | logTrace("notify task %s %s %s", cast(void*)w, () @trusted { return cast(void*)w.notifier.funcptr; } (), w.notifier.ptr); 270 | w.emit(); 271 | } else logTrace("notify callback is null"); 272 | } 273 | } 274 | 275 | -------------------------------------------------------------------------------- /source/vibe/core/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Makes the complete vibe-core API available with a single import 3 | 4 | Copyright: © 2025 Sönke Ludwig 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 | module vibe.core; 9 | 10 | public import vibe.core.args; 11 | public import vibe.core.channel; 12 | public import vibe.core.concurrency; 13 | public import vibe.core.connectionpool; 14 | public import vibe.core.core; 15 | public import vibe.core.file; 16 | public import vibe.core.log; 17 | public import vibe.core.net; 18 | public import vibe.core.parallelism; 19 | public import vibe.core.path; 20 | public import vibe.core.process; 21 | public import vibe.core.stream; 22 | public import vibe.core.sync; 23 | public import vibe.core.task; 24 | public import vibe.core.taskpool; 25 | -------------------------------------------------------------------------------- /source/vibe/core/parallelism.d: -------------------------------------------------------------------------------- 1 | /** 2 | Contains parallel computation primitives. 3 | 4 | Copyright: © 2021 Sönke Ludwig 5 | Authors: Sönke Ludwig 6 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 7 | */ 8 | module vibe.core.parallelism; 9 | 10 | public import vibe.core.taskpool; 11 | 12 | import vibe.core.channel; 13 | import vibe.core.concurrency : isWeaklyIsolated; 14 | import vibe.core.log; 15 | import std.range : ElementType, isInputRange; 16 | import std.traits : hasMember; 17 | 18 | 19 | /** Processes a range of items in worker tasks and returns them as an unordered 20 | range. 21 | 22 | The order of the result stream can deviate from the order of the input 23 | items, but the approach is more efficient that an ordered map.# 24 | 25 | See_also: `parallelMap` 26 | */ 27 | auto parallelUnorderedMap(alias fun, R)(R items, shared(TaskPool) task_pool, ChannelConfig channel_config = ChannelConfig.init) 28 | if (isInputRange!R && isWeaklyIsolated!(ElementType!R) && isWeaklyIsolated!(typeof(fun(ElementType!R.init)))) 29 | { 30 | import vibe.core.core : runTask; 31 | import core.atomic : atomicOp, atomicStore; 32 | 33 | alias I = ElementType!R; 34 | alias O = typeof(fun(I.init)); 35 | 36 | ChannelConfig inconfig; 37 | inconfig.priority = ChannelPriority.overhead; 38 | auto chin = createChannel!I(inconfig); 39 | auto chout = createChannel!O(channel_config); 40 | 41 | // TODO: discard all operations if the result range is not referenced anymore 42 | 43 | static void senderFun(R items, Channel!I chin) 44 | nothrow { 45 | foreach (itm; items) { 46 | try chin.put(itm); 47 | catch (Exception e) { 48 | logException(e, "Failed to send parallel mapped input"); 49 | break; 50 | } 51 | } 52 | chin.close(); 53 | } 54 | 55 | static void workerFun(Channel!I chin, Channel!O chout, shared(int)* rc) 56 | nothrow { 57 | I item; 58 | while (chin.tryConsumeOne(item)) { 59 | try chout.put(fun(item)); 60 | catch (Exception e) { 61 | logException(e, "Failed to send back parallel mapped result"); 62 | break; 63 | } 64 | } 65 | if (!atomicOp!"-="(*rc, 1)) 66 | chout.close(); 67 | } 68 | 69 | static if (hasMember!(R, "length")) 70 | auto length = items.length; 71 | 72 | runTask(&senderFun, items, chin); 73 | 74 | auto rc = new shared int; 75 | atomicStore(*rc, cast(int)task_pool.threadCount); 76 | 77 | task_pool.runTaskDist(&workerFun, chin, chout, rc); 78 | 79 | static struct Result { 80 | private { 81 | Channel!O m_channel; 82 | O m_front; 83 | bool m_gotFront = false; 84 | static if (hasMember!(R, "length")) typeof(items.length) m_length; 85 | } 86 | 87 | static if (hasMember!(R, "length")) 88 | this(Channel!O chout, typeof(items.length) length) 89 | { 90 | m_channel = chout; 91 | m_length = length; 92 | } 93 | else 94 | this(Channel!O chout) 95 | { 96 | m_channel = chout; 97 | } 98 | 99 | @property bool empty() 100 | { 101 | fetchFront(); 102 | return !m_gotFront; 103 | } 104 | 105 | @property ref O front() 106 | { 107 | fetchFront(); 108 | assert(m_gotFront, "Accessing empty prallelMap range."); 109 | return m_front; 110 | } 111 | 112 | void popFront() 113 | { 114 | fetchFront(); 115 | m_gotFront = false; 116 | static if (hasMember!(R, "length")) m_length--; 117 | } 118 | 119 | static if (hasMember!(R, "length")) 120 | auto length() const { return m_length; } 121 | 122 | private void fetchFront() 123 | { 124 | if (m_gotFront) return; 125 | m_gotFront = m_channel.tryConsumeOne(m_front); 126 | } 127 | } 128 | 129 | static if (hasMember!(R, "length")) 130 | return Result(chout, length); 131 | else 132 | return Result(chout); 133 | } 134 | 135 | /// ditto 136 | auto parallelUnorderedMap(alias fun, R)(R items, ChannelConfig channel_config = ChannelConfig.init) 137 | if (isInputRange!R && isWeaklyIsolated!(ElementType!R) && isWeaklyIsolated!(typeof(fun(ElementType!R.init)))) 138 | { 139 | import vibe.core.core : workerTaskPool; 140 | return parallelUnorderedMap!(fun, R)(items, workerTaskPool, channel_config); 141 | } 142 | 143 | /// 144 | unittest { 145 | import std.algorithm : isPermutation, map; 146 | import std.array : array; 147 | import std.range : iota; 148 | 149 | auto res = iota(100) 150 | .parallelMap!(i => 2 * i) 151 | .array; 152 | assert(res.isPermutation(iota(100).map!(i => 2 * i).array)); 153 | } 154 | 155 | unittest { 156 | import std.range : iota; 157 | 158 | auto res = iota(100) 159 | .parallelUnorderedMap!(i => 2 * i); 160 | assert(res.length == 100); 161 | res.popFront(); 162 | assert(res.length == 99); 163 | } 164 | 165 | 166 | /** Processes a range of items in worker tasks and returns them as an ordered 167 | range. 168 | 169 | The items of the returned stream are in the same order as input. Note that 170 | this may require dynamic buffering of results, so it is recommended to 171 | use unordered mapping if possible. 172 | 173 | See_also: `parallelUnorderedMap` 174 | */ 175 | auto parallelMap(alias fun, R)(R items, shared(TaskPool) task_pool, ChannelConfig channel_config) 176 | if (isInputRange!R && isWeaklyIsolated!(ElementType!R) && isWeaklyIsolated!(typeof(fun(ElementType!R.init)))) 177 | { 178 | import std.algorithm : canFind, countUntil, move, remove; 179 | import std.container.array : Array; 180 | import std.range : enumerate; 181 | import std.typecons : RefCounted, Tuple; 182 | 183 | alias I = ElementType!R; 184 | alias O = typeof(fun(I.init)); 185 | static struct SR { size_t index; O value; } 186 | 187 | auto resunord = items 188 | .enumerate 189 | .parallelUnorderedMap!(itm => SR(itm.index, fun(itm.value)))(task_pool); 190 | 191 | static struct State { 192 | typeof(resunord) m_source; 193 | size_t m_index = 0, m_minIndex = -1; 194 | Array!SR m_buffer; 195 | int m_refCount = 0; 196 | static if (hasMember!(R, "length")) typeof(items.length) m_length; 197 | 198 | this(typeof(resunord) source) 199 | { 200 | m_source = source.move; 201 | static if (hasMember!(R, "length")) m_length = m_source.length; 202 | } 203 | 204 | @property bool empty() 205 | { 206 | return m_source.empty && m_buffer.length == 0; 207 | } 208 | @property ref O front() 209 | { 210 | fetchFront(); 211 | auto idx = m_buffer[].countUntil!(sr => sr.index == m_index); 212 | if (idx < 0) { 213 | assert(m_source.front.index == m_index); 214 | return m_source.front.value; 215 | } 216 | return m_buffer[idx].value; 217 | } 218 | void popFront() 219 | { 220 | m_index++; 221 | 222 | auto idx = m_buffer[].countUntil!(sr => sr.index == m_index-1); 223 | if (idx < 0) { 224 | assert(m_source.front.index == m_index-1); 225 | m_source.popFront(); 226 | } else { 227 | if (idx < m_buffer.length-1) 228 | m_buffer[idx] = m_buffer[$-1]; 229 | m_buffer.removeBack(); 230 | } 231 | } 232 | 233 | private void fetchFront() 234 | { 235 | if (m_buffer[].canFind!(sr => sr.index == m_index)) 236 | return; 237 | 238 | while (m_source.front.index != m_index) { 239 | m_buffer ~= m_source.front; 240 | m_source.popFront(); 241 | } 242 | } 243 | } 244 | 245 | static struct Result { 246 | private RefCounted!State state; 247 | 248 | @property bool empty() { return state.empty; } 249 | @property ref O front() { return state.front; } 250 | void popFront() { state.popFront; } 251 | 252 | static if (hasMember!(R, "length")) auto length() const { return state.m_length - state.m_index; } 253 | } 254 | 255 | return Result(RefCounted!State(resunord.move)); 256 | } 257 | 258 | /// ditto 259 | auto parallelMap(alias fun, R)(R items, ChannelConfig channel_config = ChannelConfig.init) 260 | if (isInputRange!R && isWeaklyIsolated!(ElementType!R) && isWeaklyIsolated!(typeof(fun(ElementType!R.init)))) 261 | { 262 | import vibe.core.core : workerTaskPool; 263 | return parallelMap!(fun, R)(items, workerTaskPool, channel_config); 264 | } 265 | 266 | /// 267 | unittest { 268 | import std.algorithm : map; 269 | import std.array : array; 270 | import std.range : iota; 271 | 272 | auto res = iota(100) 273 | .parallelMap!(i => 2 * i) 274 | .array; 275 | assert(res == iota(100).map!(i => 2 * i).array); 276 | } 277 | 278 | /// 279 | unittest { 280 | import std.algorithm : isPermutation, map; 281 | import std.array : array; 282 | import std.random : uniform; 283 | import std.range : iota; 284 | import core.time : msecs; 285 | import vibe.core.core : sleep; 286 | 287 | // forcing a random computation result order still results in the same 288 | // output order 289 | auto res = iota(100) 290 | .parallelMap!((i) { 291 | sleep(uniform(0, 100).msecs); 292 | return 2 * i; 293 | }) 294 | .array; 295 | assert(res == iota(100).map!(i => 2 * i).array); 296 | } 297 | 298 | unittest { 299 | import std.range : iota; 300 | 301 | auto res = iota(100) 302 | .parallelMap!(i => 2 * i); 303 | assert(res.length == 100); 304 | assert(res.front == 0); 305 | res.popFront(); 306 | assert(res.length == 99); 307 | assert(res.front == 2); 308 | } 309 | -------------------------------------------------------------------------------- /source/vibe/core/taskpool.d: -------------------------------------------------------------------------------- 1 | /** 2 | Multi-threaded task pool implementation. 3 | 4 | Copyright: © 2012-2020 Sönke Ludwig 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 | module vibe.core.taskpool; 9 | 10 | import vibe.core.concurrency : isWeaklyIsolated; 11 | import vibe.core.core : exitEventLoop, isCallable, isMethod, isNothrowCallable, 12 | isNothrowMethod, logicalProcessorCount, runEventLoop, runTask, runTask_internal; 13 | import vibe.core.log; 14 | import vibe.core.sync : ManualEvent, VibeSyncMonitor = Monitor, createSharedManualEvent, createMonitor; 15 | import vibe.core.task : Task, TaskFuncInfo, TaskSettings, callWithMove; 16 | import core.sync.mutex : Mutex; 17 | import core.thread : Thread; 18 | import std.traits : isFunctionPointer; 19 | 20 | 21 | /** Implements a shared, multi-threaded task pool. 22 | */ 23 | shared final class TaskPool { 24 | private { 25 | struct State { 26 | WorkerThread[] threads; 27 | TaskQueue queue; 28 | bool term; 29 | } 30 | VibeSyncMonitor!(State, shared(Mutex)) m_state; 31 | shared(ManualEvent) m_signal; 32 | immutable size_t m_threadCount; 33 | } 34 | 35 | /** Creates a new task pool with the specified number of threads. 36 | 37 | Params: 38 | thread_count = The number of worker threads to create 39 | thread_name_prefix = Optional prefix to use for thread names 40 | */ 41 | this(size_t thread_count = logicalProcessorCount(), string thread_name_prefix = "vibe") 42 | @safe nothrow { 43 | import std.format : format; 44 | 45 | m_threadCount = thread_count; 46 | m_signal = createSharedManualEvent(); 47 | m_state = createMonitor!State(new shared Mutex); 48 | 49 | with (m_state.lock) { 50 | queue.setup(); 51 | threads.length = thread_count; 52 | foreach (i; 0 .. thread_count) { 53 | WorkerThread thr; 54 | () @trusted nothrow { 55 | thr = new WorkerThread(this); 56 | try thr.name = format("%s-%s", thread_name_prefix, i); 57 | catch (Exception e) logException(e, "Failed to set worker thread name"); 58 | thr.start(); 59 | } (); 60 | threads[i] = thr; 61 | } 62 | } 63 | } 64 | 65 | /** Returns the number of worker threads. 66 | */ 67 | @property size_t threadCount() const shared nothrow { return m_threadCount; } 68 | 69 | /** Instructs all worker threads to terminate and waits until all have 70 | finished. 71 | */ 72 | void terminate() 73 | @safe nothrow { 74 | m_state.lock.term = true; 75 | m_signal.emit(); 76 | 77 | while (true) { 78 | WorkerThread th; 79 | with (m_state.lock) 80 | if (threads.length) { 81 | th = threads[0]; 82 | threads = threads[1 .. $]; 83 | } 84 | if (!th) break; 85 | 86 | if (th is Thread.getThis()) 87 | continue; 88 | 89 | () @trusted { 90 | try th.join(); 91 | catch (Exception e) { 92 | logWarn("Failed to wait for worker thread exit: %s", e.msg); 93 | } 94 | } (); 95 | } 96 | 97 | size_t cnt = m_state.lock.queue.length; 98 | if (cnt > 0) logWarn("There were still %d worker tasks pending at exit.", cnt); 99 | 100 | destroy(m_signal); 101 | } 102 | 103 | /** Instructs all worker threads to terminate as soon as all tasks have 104 | been processed and waits for them to finish. 105 | */ 106 | void join() 107 | @safe nothrow { 108 | assert(false, "TODO!"); 109 | } 110 | 111 | /** Runs a new asynchronous task in a worker thread. 112 | 113 | Only function pointers with weakly isolated arguments are allowed to be 114 | able to guarantee thread-safety. 115 | */ 116 | void runTask(FT, ARGS...)(FT func, auto ref ARGS args) 117 | if (isFunctionPointer!FT) 118 | { 119 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 120 | runTask_unsafe(TaskSettings.init, func, args); 121 | } 122 | /// ditto 123 | void runTask(alias method, T, ARGS...)(shared(T) object, auto ref ARGS args) 124 | if (is(typeof(__traits(getMember, object, __traits(identifier, method))))) 125 | { 126 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 127 | auto func = &__traits(getMember, object, __traits(identifier, method)); 128 | runTask_unsafe(TaskSettings.init, func, args); 129 | } 130 | /// ditto 131 | void runTask(FT, ARGS...)(TaskSettings settings, FT func, auto ref ARGS args) 132 | if (isFunctionPointer!FT) 133 | { 134 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 135 | runTask_unsafe(settings, func, args); 136 | } 137 | /// ditto 138 | void runTask(alias method, T, ARGS...)(TaskSettings settings, shared(T) object, auto ref ARGS args) 139 | if (is(typeof(__traits(getMember, object, __traits(identifier, method))))) 140 | { 141 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 142 | auto func = &__traits(getMember, object, __traits(identifier, method)); 143 | runTask_unsafe(settings, func, args); 144 | } 145 | 146 | /** Runs a new asynchronous task in a worker thread, returning the task handle. 147 | 148 | This function will yield and wait for the new task to be created and started 149 | in the worker thread, then resume and return it. 150 | 151 | Only function pointers with weakly isolated arguments are allowed to be 152 | able to guarantee thread-safety. 153 | */ 154 | Task runTaskH(FT, ARGS...)(FT func, auto ref ARGS args) 155 | if (isFunctionPointer!FT && isNothrowCallable!(FT, ARGS)) 156 | { 157 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 158 | 159 | return doRunTaskH(TaskSettings.init, func, args); 160 | } 161 | /// ditto 162 | Task runTaskH(alias method, T, ARGS...)(shared(T) object, auto ref ARGS args) 163 | if (isNothrowMethod!(shared(T), method, ARGS)) 164 | { 165 | static void wrapper()(shared(T) object, ref ARGS args) { 166 | __traits(getMember, object, __traits(identifier, method))(args); 167 | } 168 | return runTaskH(&wrapper!(), object, args); 169 | } 170 | /// ditto 171 | Task runTaskH(FT, ARGS...)(TaskSettings settings, FT func, auto ref ARGS args) 172 | if (isFunctionPointer!FT && isNothrowCallable!(FT, ARGS)) 173 | { 174 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 175 | 176 | return doRunTaskH(settings, func, args); 177 | } 178 | /// ditto 179 | Task runTaskH(alias method, T, ARGS...)(TaskSettings settings, shared(T) object, auto ref ARGS args) 180 | if (isNothrowMethod!(shared(T), method, ARGS)) 181 | { 182 | static void wrapper()(shared(T) object, ref ARGS args) { 183 | __traits(getMember, object, __traits(identifier, method))(args); 184 | } 185 | return runTaskH(settings, &wrapper!(), object, args); 186 | } 187 | 188 | // NOTE: needs to be a separate function to avoid recursion for the 189 | // workaround above, which breaks @safe inference 190 | private Task doRunTaskH(FT, ARGS...)(TaskSettings settings, FT func, ref ARGS args) 191 | if (isFunctionPointer!FT) 192 | { 193 | import std.typecons : Typedef; 194 | import vibe.core.channel : Channel, createChannel; 195 | import vibe.core.task : needsMove; 196 | 197 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 198 | 199 | alias PrivateTask = Typedef!(Task, Task.init, __PRETTY_FUNCTION__); 200 | 201 | auto ch = createChannel!Task(); 202 | 203 | static string argdefs() 204 | { 205 | string ret; 206 | foreach (i, A; ARGS) { 207 | if (i > 0) ret ~= ", "; 208 | if (!needsMove!A) ret ~= "ref "; 209 | ret ~= "ARGS["~i.stringof~"] arg_"~i.stringof; 210 | } 211 | return ret; 212 | } 213 | 214 | static string argvals() 215 | { 216 | string ret; 217 | foreach (i, A; ARGS) { 218 | if (i > 0) ret ~= ", "; 219 | ret ~= "arg_"~i.stringof; 220 | if (needsMove!A) ret ~= ".move"; 221 | } 222 | return ret; 223 | } 224 | 225 | mixin("static void taskFun(Channel!Task ch, FT func, " ~ argdefs() ~ ") {" 226 | ~ " try ch.put(Task.getThis());" 227 | ~ " catch (Exception e) assert(false, e.msg);" 228 | ~ " ch = Channel!Task.init;" 229 | ~ " func("~argvals()~");" 230 | ~ "}"); 231 | runTask_unsafe(settings, &taskFun, ch, func, args); 232 | 233 | Task ret; 234 | if (!ch.tryConsumeOne(ret)) 235 | assert(false, "Channel closed without passing a task handle!?"); 236 | ch.close(); 237 | return ret; 238 | } 239 | 240 | 241 | /** Runs a new asynchronous task in all worker threads concurrently. 242 | 243 | This function is mainly useful for long-living tasks that distribute their 244 | work across all CPU cores. Only function pointers with weakly isolated 245 | arguments are allowed to be able to guarantee thread-safety. 246 | 247 | The number of tasks started is guaranteed to be equal to 248 | `threadCount`. 249 | */ 250 | void runTaskDist(FT, ARGS...)(FT func, auto ref ARGS args) 251 | if (isFunctionPointer!FT && isNothrowCallable!(FT, ARGS)) 252 | { 253 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 254 | runTaskDist_unsafe(TaskSettings.init, func, args); 255 | } 256 | /// ditto 257 | void runTaskDist(alias method, T, ARGS...)(shared(T) object, auto ref ARGS args) 258 | if (isNothrowMethod!(shared(T), method, ARGS)) 259 | { 260 | auto func = &__traits(getMember, object, __traits(identifier, method)); 261 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 262 | 263 | runTaskDist_unsafe(TaskSettings.init, func, args); 264 | } 265 | /// ditto 266 | void runTaskDist(FT, ARGS...)(TaskSettings settings, FT func, auto ref ARGS args) 267 | if (isFunctionPointer!FT && isNothrowCallable!(FT, ARGS)) 268 | { 269 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 270 | runTaskDist_unsafe(settings, func, args); 271 | } 272 | /// ditto 273 | void runTaskDist(alias method, T, ARGS...)(TaskSettings settings, shared(T) object, auto ref ARGS args) 274 | if (isNothrowMethod!(shared(T), method, ARGS)) 275 | { 276 | auto func = &__traits(getMember, object, __traits(identifier, method)); 277 | foreach (T; ARGS) static assert(isWeaklyIsolated!T, "Argument type "~T.stringof~" is not safe to pass between threads."); 278 | 279 | runTaskDist_unsafe(settings, func, args); 280 | } 281 | 282 | /** Runs a new asynchronous task in all worker threads and returns the handles. 283 | 284 | `on_handle` is an alias to a callble that takes a `Task` as its only 285 | argument and is called for every task instance that gets created. 286 | 287 | See_also: `runTaskDist` 288 | */ 289 | void runTaskDistH(HCB, FT, ARGS...)(scope HCB on_handle, FT func, auto ref ARGS args) 290 | if (!is(HCB == TaskSettings)) 291 | { 292 | runTaskDistH(TaskSettings.init, on_handle, func, args); 293 | } 294 | /// ditto 295 | void runTaskDistH(HCB, FT, ARGS...)(TaskSettings settings, scope HCB on_handle, FT func, auto ref ARGS args) 296 | { 297 | import vibe.core.channel : Channel, createChannel; 298 | 299 | // TODO: support non-copyable argument types using .move 300 | auto ch = createChannel!Task; 301 | 302 | static void call(Channel!Task ch, FT func, ARGS args) { 303 | try ch.put(Task.getThis()); 304 | catch (Exception e) assert(false, e.msg); 305 | func(args); 306 | } 307 | runTaskDist(settings, &call, ch, func, args); 308 | 309 | foreach (i; 0 .. this.threadCount) { 310 | Task t; 311 | if (!ch.tryConsumeOne(t)) 312 | assert(false, "Worker thread failed to report task handle"); 313 | on_handle(t); 314 | } 315 | 316 | ch.close(); 317 | } 318 | 319 | private void runTask_unsafe(CALLABLE, ARGS...)(TaskSettings settings, CALLABLE callable, ref ARGS args) 320 | { 321 | import std.traits : ParameterTypeTuple; 322 | import vibe.internal.traits : areConvertibleTo; 323 | import vibe.internal.typetuple; 324 | 325 | alias FARGS = ParameterTypeTuple!CALLABLE; 326 | static assert(areConvertibleTo!(Group!ARGS, Group!FARGS), 327 | "Cannot convert arguments '"~ARGS.stringof~"' to function arguments '"~FARGS.stringof~"'."); 328 | 329 | m_state.lock.queue.put(settings, callable, args); 330 | // NOTE: we need to notify all threads here in order to guarantee proper distribution of 331 | // tasks, as currently the eventcore event implementation does not result in a fair 332 | // scheduling 333 | m_signal.emit(); 334 | } 335 | 336 | private void runTaskDist_unsafe(CALLABLE, ARGS...)(TaskSettings settings, ref CALLABLE callable, ARGS args) // NOTE: no ref for args, to disallow non-copyable types! 337 | { 338 | import std.traits : ParameterTypeTuple; 339 | import vibe.internal.traits : areConvertibleTo; 340 | import vibe.internal.typetuple; 341 | 342 | alias FARGS = ParameterTypeTuple!CALLABLE; 343 | static assert(areConvertibleTo!(Group!ARGS, Group!FARGS), 344 | "Cannot convert arguments '"~ARGS.stringof~"' to function arguments '"~FARGS.stringof~"'."); 345 | 346 | { 347 | auto st = m_state.lock; 348 | foreach (thr; st.threads) { 349 | // create one TFI per thread to properly account for elaborate assignment operators/postblit 350 | thr.m_queue.put(settings, callable, args); 351 | } 352 | } 353 | m_signal.emit(); 354 | } 355 | } 356 | 357 | private final class WorkerThread : Thread { 358 | private { 359 | shared(TaskPool) m_pool; 360 | TaskQueue m_queue; 361 | } 362 | 363 | this(shared(TaskPool) pool) 364 | nothrow { 365 | m_pool = pool; 366 | m_queue.setup(); 367 | super(&main); 368 | } 369 | 370 | private void main() 371 | nothrow { 372 | import core.stdc.stdlib : abort; 373 | import core.exception : InvalidMemoryOperationError; 374 | import std.encoding : sanitize; 375 | 376 | try { 377 | if (m_pool.m_state.lock.term) return; 378 | logDebug("entering worker thread"); 379 | 380 | // There is an issue where a task that periodically calls yield() 381 | // but otherwise only performs a CPU computation will cause a 382 | // call to runEventLoopOnce() or yield() called from the global 383 | // thread context to not return before the task is finished. For 384 | // this reason we start a task here, which in turn is scheduled 385 | // properly together with such a task, and also is schduled 386 | // according to the task priorities. 387 | runTask(&handleWorkerTasks).joinUninterruptible(); 388 | 389 | logDebug("Worker thread exit."); 390 | } catch (Throwable th) { 391 | th.logException!(LogLevel.fatal)("Worker thread terminated due to uncaught error"); 392 | abort(); 393 | } 394 | } 395 | 396 | private void handleWorkerTasks() 397 | nothrow @safe { 398 | import std.algorithm.iteration : filter; 399 | import std.algorithm.mutation : swap; 400 | import std.algorithm.searching : count; 401 | import std.array : array; 402 | 403 | logTrace("worker thread enter"); 404 | TaskFuncInfo taskfunc; 405 | auto emit_count = m_pool.m_signal.emitCount; 406 | while(true) { 407 | with (m_pool.m_state.lock) { 408 | logTrace("worker thread check"); 409 | 410 | if (term) break; 411 | 412 | if (m_queue.consume(taskfunc)) { 413 | logTrace("worker thread got specific task"); 414 | } else if (queue.consume(taskfunc)) { 415 | logTrace("worker thread got unspecific task"); 416 | } 417 | } 418 | 419 | if (taskfunc.func !is null) 420 | .runTask_internal!((ref tfi) { swap(tfi, taskfunc); }); 421 | else emit_count = m_pool.m_signal.waitUninterruptible(emit_count); 422 | } 423 | 424 | logTrace("worker thread exit"); 425 | 426 | if (!m_queue.empty) 427 | logWarn("Worker thread shuts down with specific worker tasks left in its queue."); 428 | 429 | with (m_pool.m_state.lock) { 430 | threads = threads.filter!(t => t !is this).array; 431 | if (threads.length > 0 && !queue.empty) 432 | logWarn("Worker threads shut down with worker tasks still left in the queue."); 433 | } 434 | } 435 | } 436 | 437 | private struct TaskQueue { 438 | nothrow @safe: 439 | // TODO: avoid use of GC 440 | 441 | import vibe.container.ringbuffer : RingBuffer; 442 | RingBuffer!TaskFuncInfo* m_queue; 443 | 444 | void setup() 445 | { 446 | m_queue = new RingBuffer!TaskFuncInfo; 447 | } 448 | 449 | @property bool empty() const { return m_queue.empty; } 450 | 451 | @property size_t length() const { return m_queue.length; } 452 | 453 | void put(CALLABLE, ARGS...)(TaskSettings settings, ref CALLABLE c, ref ARGS args) 454 | { 455 | import std.algorithm.comparison : max; 456 | if (m_queue.full) m_queue.capacity = max(16, m_queue.capacity * 3 / 2); 457 | assert(!m_queue.full); 458 | 459 | m_queue.peekDst[0].settings = settings; 460 | m_queue.peekDst[0].set(c, args); 461 | m_queue.putN(1); 462 | } 463 | 464 | bool consume(ref TaskFuncInfo tfi) 465 | { 466 | import std.algorithm.mutation : swap; 467 | 468 | if (m_queue.empty) return false; 469 | swap(tfi, m_queue.front); 470 | m_queue.removeFront(); 471 | return true; 472 | } 473 | } 474 | 475 | 476 | unittest { // #138 - immutable arguments 477 | auto tp = new shared TaskPool; 478 | struct S { int[] x; } 479 | auto s = immutable(S)([1, 2]); 480 | tp.runTaskH((immutable S s) { assert(s.x == [1, 2]); }, s) 481 | .joinUninterruptible(); 482 | tp.terminate(); 483 | } 484 | -------------------------------------------------------------------------------- /source/vibe/internal/allocator.d: -------------------------------------------------------------------------------- 1 | deprecated("Import `vibe.container.internal.utilallocator` instead.") 2 | module vibe.internal.allocator; 3 | 4 | public import vibe.container.internal.utilallocator; 5 | -------------------------------------------------------------------------------- /source/vibe/internal/async.d: -------------------------------------------------------------------------------- 1 | module vibe.internal.async; 2 | 3 | import std.traits : ParameterTypeTuple, ReturnType; 4 | import std.typecons : tuple; 5 | import vibe.core.core : hibernate, switchToTask; 6 | import vibe.core.task : InterruptException, Task, TaskSwitchPriority; 7 | import vibe.core.log; 8 | import core.time : Duration, seconds; 9 | 10 | 11 | auto asyncAwait(Callback, alias action, alias cancel)(string func = __FUNCTION__) 12 | if (!is(Object == Duration)) { 13 | ParameterTypeTuple!Callback results; 14 | alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; }); 15 | asyncAwaitAny!(true, waitable)(func); 16 | return tuple(results); 17 | } 18 | 19 | auto asyncAwait(Callback, alias action, alias cancel)(Duration timeout, string func = __FUNCTION__) 20 | { 21 | static struct R { 22 | bool completed = true; 23 | ParameterTypeTuple!Callback results; 24 | } 25 | R ret; 26 | static if (is(ReturnType!action == void)) { 27 | alias waitable = Waitable!(Callback, 28 | action, 29 | (cb) { ret.completed = false; cancel(cb); }, 30 | (ParameterTypeTuple!Callback r) { ret.results = r; } 31 | ); 32 | } 33 | else { 34 | alias waitable = Waitable!(Callback, 35 | action, 36 | (cb, waitres) { ret.completed = false; cancel(cb, waitres); }, 37 | (ParameterTypeTuple!Callback r) { ret.results = r; } 38 | ); 39 | } 40 | asyncAwaitAny!(true, waitable)(timeout, func); 41 | return ret; 42 | } 43 | 44 | auto asyncAwaitUninterruptible(Callback, alias action)(string func = __FUNCTION__) 45 | nothrow { 46 | static if (is(typeof(action(Callback.init)) == void)) void cancel(Callback) { assert(false, "Action cannot be cancelled."); } 47 | else void cancel(Callback, typeof(action(Callback.init))) @safe @nogc nothrow { assert(false, "Action cannot be cancelled."); } 48 | ParameterTypeTuple!Callback results; 49 | alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; }); 50 | asyncAwaitAny!(false, waitable)(func); 51 | return tuple(results); 52 | } 53 | 54 | auto asyncAwaitUninterruptible(Callback, alias action, alias cancel)(Duration timeout, string func = __FUNCTION__) 55 | nothrow { 56 | ParameterTypeTuple!Callback results; 57 | alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; }); 58 | asyncAwaitAny!(false, waitable)(timeout, func); 59 | return tuple(results); 60 | } 61 | 62 | template Waitable(CB, alias WAIT, alias CANCEL, alias DONE) 63 | { 64 | import std.traits : ReturnType; 65 | 66 | static assert(is(typeof(WAIT(CB.init))), "WAIT must be callable with a parameter of type "~CB.stringof); 67 | static if (is(typeof(WAIT(CB.init)) == void)) 68 | static assert(is(typeof(CANCEL(CB.init))), 69 | "CANCEL must be callable with a parameter of type "~CB.stringof); 70 | else 71 | static assert(is(typeof(CANCEL(CB.init, typeof(WAIT(CB.init)).init))), 72 | "CANCEL must be callable with parameters "~CB.stringof~ 73 | " and "~typeof(WAIT(CB.init)).stringof); 74 | static assert(is(typeof(DONE(ParameterTypeTuple!CB.init))), 75 | "DONE must be callable with types "~ParameterTypeTuple!CB.stringof); 76 | 77 | alias Callback = CB; 78 | alias wait = WAIT; 79 | alias cancel = CANCEL; 80 | alias done = DONE; 81 | } 82 | 83 | void asyncAwaitAny(bool interruptible, Waitables...)(Duration timeout, string func = __FUNCTION__) 84 | { 85 | if (timeout == Duration.max) asyncAwaitAny!(interruptible, Waitables)(func); 86 | else { 87 | import eventcore.core; 88 | 89 | auto tm = eventDriver.timers.create(); 90 | eventDriver.timers.set(tm, timeout, 0.seconds); 91 | scope (exit) eventDriver.timers.releaseRef(tm); 92 | alias timerwaitable = Waitable!(TimerCallback, 93 | cb => eventDriver.timers.wait(tm, cb), 94 | cb => eventDriver.timers.cancelWait(tm), 95 | (tid) {} 96 | ); 97 | asyncAwaitAny!(interruptible, timerwaitable, Waitables)(func); 98 | } 99 | } 100 | 101 | void asyncAwaitAny(bool interruptible, Waitables...)(string func = __FUNCTION__) 102 | if (Waitables.length >= 1) 103 | { 104 | import std.meta : staticMap; 105 | import std.algorithm.searching : any; 106 | import std.meta : AliasSeq; 107 | import std.traits : ReturnType; 108 | 109 | bool[Waitables.length] fired; 110 | bool any_fired = false; 111 | Task t; 112 | 113 | bool still_inside = true; 114 | scope (exit) still_inside = false; 115 | 116 | debug(VibeAsyncLog) logDebugV("Performing %s async operations in %s", Waitables.length, func); 117 | 118 | static foreach (i, W; Waitables) { 119 | mixin("alias PT"~i.stringof~" = ParameterTypeTuple!(Waitables["~i.stringof~"].Callback);\n" 120 | ~ "scope callback_"~i.stringof~" = ("~generateParamDecls!(CBDel!W)("PT"~i.stringof)~") @safe nothrow {\n" 121 | ~ " // NOTE: this triggers DigitalMars/optlink#18\n" 122 | ~ " //() @trusted { logDebugV(\"siw %%x\", &still_inside); } ();\n" 123 | ~ " debug(VibeAsyncLog) logDebugV(\"Waitable %%s in %%s fired (istask=%%s).\", "~i.stringof~", func, t != Task.init);\n" 124 | ~ " assert(still_inside, \"Notification fired after asyncAwait had already returned!\");\n" 125 | ~ " fired["~i.stringof~"] = true;\n" 126 | ~ " any_fired = true;\n" 127 | ~ " Waitables["~i.stringof~"].done("~generateParamNames!(CBDel!W)~");\n" 128 | ~ " if (t != Task.init) {\n" 129 | ~ " version (VibeHighEventPriority) switchToTask(t);\n" 130 | ~ " else switchToTask(t, TaskSwitchPriority.normal);\n" 131 | ~ " }\n" 132 | ~ "};\n" 133 | ~ "debug(VibeAsyncLog) logDebugV(\"Starting operation %%s\", "~i.stringof~");\n" 134 | ~ "alias WR"~i.stringof~" = typeof(Waitables["~i.stringof~"].wait(() @trusted { return callback_"~i.stringof~"; } ()));\n" 135 | ~ "static if (is(WR"~i.stringof~" == void)) Waitables["~i.stringof~"].wait(() @trusted { return callback_"~i.stringof~"; } ());\n" 136 | ~ "else auto wr"~i.stringof~" = Waitables["~i.stringof~"].wait(() @trusted { return callback_"~i.stringof~"; } ());\n" 137 | ~ "scope (exit) {\n" 138 | ~ " if (!fired["~i.stringof~"]) {\n" 139 | ~ " debug(VibeAsyncLog) logDebugV(\"Cancelling operation %%s\", "~i.stringof~");\n" 140 | ~ " any_fired = true;\n" 141 | ~ " fired["~i.stringof~"] = true;\n" 142 | ~ " static if (is(WR"~i.stringof~" == void)) Waitables["~i.stringof~"].cancel(() @trusted { return callback_"~i.stringof~"; } ());\n" 143 | ~ " else Waitables["~i.stringof~"].cancel(() @trusted { return callback_"~i.stringof~"; } (), wr"~i.stringof~");\n" 144 | ~ " }\n" 145 | ~ "}\n" 146 | ~ "if (any_fired) {\n" 147 | ~ " debug(VibeAsyncLog) logDebugV(\"Returning to %%s without waiting.\", func);\n" 148 | ~ " return;\n" 149 | ~ "}\n" 150 | ); 151 | } 152 | 153 | debug(VibeAsyncLog) logDebugV("Need to wait in %s (%s)...", func, interruptible ? "interruptible" : "uninterruptible"); 154 | 155 | t = Task.getThis(); 156 | 157 | debug (VibeAsyncLog) scope (failure) logDebugV("Aborting wait due to exception"); 158 | 159 | do { 160 | static if (interruptible) { 161 | bool interrupted = false; 162 | hibernate(() @safe nothrow { 163 | debug(VibeAsyncLog) logDebugV("Got interrupted in %s.", func); 164 | interrupted = true; 165 | }); 166 | debug(VibeAsyncLog) logDebugV("Task resumed (fired=%s, interrupted=%s)", fired, interrupted); 167 | if (interrupted) 168 | throw new InterruptException; 169 | } else { 170 | hibernate(); 171 | debug(VibeAsyncLog) logDebugV("Task resumed (fired=%s)", fired); 172 | } 173 | } while (!any_fired); 174 | 175 | debug(VibeAsyncLog) logDebugV("Return result for %s.", func); 176 | } 177 | 178 | private alias CBDel(alias Waitable) = Waitable.Callback; 179 | 180 | @safe nothrow /*@nogc*/ unittest { 181 | int cnt = 0; 182 | auto ret = asyncAwaitUninterruptible!(void delegate(int) @safe nothrow, (cb) { cnt++; cb(42); }); 183 | assert(ret[0] == 42); 184 | assert(cnt == 1); 185 | } 186 | 187 | @safe nothrow /*@nogc*/ unittest { 188 | int a, b, c; 189 | int w1r, w2r; 190 | alias w1 = Waitable!( 191 | void delegate(int) @safe nothrow, 192 | (cb) { a++; cb(42); }, 193 | (cb) { assert(false); }, 194 | (i) { w1r = i; } 195 | ); 196 | alias w2 = Waitable!( 197 | void delegate(int) @safe nothrow, 198 | (cb) { b++; }, 199 | (cb) { c++; }, 200 | (i) { w2r = i; } 201 | ); 202 | alias w3 = Waitable!( 203 | void delegate(int) @safe nothrow, 204 | (cb) { c++; cb(42); }, 205 | (cb) { assert(false); }, 206 | (int n) { assert(n == 42); } 207 | ); 208 | 209 | asyncAwaitAny!(false, w1, w2); 210 | assert(w1r == 42 && w2r == 0); 211 | assert(a == 1 && b == 0 && c == 0); 212 | 213 | asyncAwaitAny!(false, w2, w1); 214 | assert(w1r == 42 && w2r == 0); 215 | assert(a == 2 && b == 1 && c == 1); 216 | 217 | asyncAwaitAny!(false, w3); 218 | assert(c == 2); 219 | } 220 | 221 | private string generateParamDecls(Fun)(string ptypes_name = "PTypes") 222 | { 223 | import std.traits : ParameterTypeTuple, ParameterStorageClass, ParameterStorageClassTuple; 224 | 225 | if (!__ctfe) assert(false); 226 | 227 | alias Types = ParameterTypeTuple!Fun; 228 | alias SClasses = ParameterStorageClassTuple!Fun; 229 | string ret; 230 | foreach (i, T; Types) { 231 | static if (i > 0) ret ~= ", "; 232 | static if (SClasses[i] & ParameterStorageClass.lazy_) ret ~= "lazy "; 233 | static if (SClasses[i] & ParameterStorageClass.scope_) ret ~= "scope "; 234 | static if (SClasses[i] & ParameterStorageClass.out_) ret ~= "out "; 235 | static if (SClasses[i] & ParameterStorageClass.ref_) ret ~= "ref "; 236 | ret ~= ptypes_name~"["~i.stringof~"] param_"~i.stringof; 237 | } 238 | return ret; 239 | } 240 | 241 | private string generateParamNames(Fun)() 242 | { 243 | if (!__ctfe) assert(false); 244 | 245 | string ret; 246 | foreach (i, T; ParameterTypeTuple!Fun) { 247 | static if (i > 0) ret ~= ", "; 248 | ret ~= "param_"~i.stringof; 249 | } 250 | return ret; 251 | } 252 | 253 | private template hasAnyScopeParameter(Callback) { 254 | import std.algorithm.searching : any; 255 | import std.traits : ParameterStorageClass, ParameterStorageClassTuple; 256 | alias SC = ParameterStorageClassTuple!Callback; 257 | static if (SC.length == 0) enum hasAnyScopeParameter = false; 258 | else enum hasAnyScopeParameter = any!(c => c & ParameterStorageClass.scope_)([SC]); 259 | } 260 | 261 | static if (is(noreturn)) static if (is(ReturnType!(() => assert(0)) == noreturn)) // issue 299, requires newer host compilers 262 | version (unittest) { 263 | alias CB = noreturn delegate(int) @safe nothrow; 264 | alias wait = delegate noreturn(_) => assert(0); 265 | alias cancel = delegate noreturn(_, x) => assert(0); 266 | alias done = delegate noreturn(_) => assert(0); 267 | alias w = Waitable!(CB, wait, cancel, done); 268 | static assert (__traits(compiles, { asyncAwaitAny!(false, w); })); 269 | } 270 | -------------------------------------------------------------------------------- /source/vibe/internal/freelistref.d: -------------------------------------------------------------------------------- 1 | /** 2 | Utility functions for memory management 3 | 4 | Note that this module currently is a big sand box for testing allocation related stuff. 5 | Nothing here, including the interfaces, is final but rather a lot of experimentation. 6 | 7 | Copyright: © 2012-2013 Sönke Ludwig 8 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 9 | Authors: Sönke Ludwig 10 | */ 11 | module vibe.internal.freelistref; 12 | 13 | import vibe.container.internal.utilallocator; 14 | import vibe.internal.traits : synchronizedIsNothrow; 15 | 16 | import core.exception : OutOfMemoryError; 17 | import core.stdc.stdlib; 18 | import core.memory; 19 | import std.conv; 20 | import std.exception : assumeWontThrow; 21 | import std.traits; 22 | import std.algorithm; 23 | 24 | 25 | struct FreeListObjectAlloc(T, bool USE_GC = true, bool INIT = true, EXTRA = void) 26 | { 27 | enum ElemSize = AllocSize!T; 28 | enum ElemSlotSize = max(AllocSize!T + AllocSize!EXTRA, Slot.sizeof); 29 | 30 | static if( is(T == class) ){ 31 | alias TR = T; 32 | } else { 33 | alias TR = T*; 34 | } 35 | 36 | struct Slot { Slot* next; } 37 | 38 | private static Slot* s_firstFree; 39 | 40 | static TR alloc(ARGS...)(ARGS args) 41 | { 42 | void[] mem; 43 | if (s_firstFree !is null) { 44 | auto ret = s_firstFree; 45 | s_firstFree = s_firstFree.next; 46 | ret.next = null; 47 | mem = () @trusted { return (cast(void*)ret)[0 .. ElemSize]; } (); 48 | } else { 49 | //logInfo("alloc %s/%d", T.stringof, ElemSize); 50 | mem = Mallocator.instance.allocate(ElemSlotSize); 51 | static if(hasIndirections!T) () @trusted { GC.addRange(mem.ptr, ElemSlotSize, typeid(T)); } (); 52 | } 53 | 54 | // FIXME: this emplace has issues with qualified types, but Unqual!T may result in the wrong constructor getting called. 55 | static if (INIT) internalEmplace!(Unqual!T)(mem, args); 56 | 57 | return () @trusted { return cast(TR)mem.ptr; } (); 58 | } 59 | 60 | static void free(TR obj) 61 | { 62 | static if (INIT) { 63 | scope (failure) assert(0, "You shouldn't throw in destructors"); 64 | auto objc = obj; 65 | static if (is(TR == T*)) .destroy(*objc);//typeid(T).destroy(cast(void*)obj); 66 | else .destroy(objc); 67 | } 68 | 69 | auto sl = cast(Slot*)obj; 70 | sl.next = s_firstFree; 71 | s_firstFree = sl; 72 | //static if( hasIndirections!T ) GC.removeRange(cast(void*)obj); 73 | //Mallocator.instance.deallocate((cast(void*)obj)[0 .. ElemSlotSize]); 74 | } 75 | } 76 | 77 | @safe unittest { 78 | struct S {} 79 | FreeListObjectAlloc!S.alloc(); 80 | } 81 | 82 | 83 | template AllocSize(T) 84 | { 85 | static if (is(T == class)) { 86 | // workaround for a strange bug where AllocSize!SSLStream == 0: TODO: dustmite! 87 | enum dummy = T.stringof ~ __traits(classInstanceSize, T).stringof; 88 | enum AllocSize = __traits(classInstanceSize, T); 89 | } else { 90 | enum AllocSize = T.sizeof; 91 | } 92 | } 93 | 94 | struct FreeListRef(T, bool INIT = true) 95 | { 96 | alias ObjAlloc = FreeListObjectAlloc!(T, true, INIT, int); 97 | enum ElemSize = AllocSize!T; 98 | 99 | static if( is(T == class) ){ 100 | alias TR = T; 101 | } else { 102 | alias TR = T*; 103 | } 104 | 105 | private TR m_object; 106 | private size_t m_magic = 0x1EE75817; // workaround for compiler bug 107 | 108 | static FreeListRef opCall(ARGS...)(ARGS args) 109 | { 110 | //logInfo("refalloc %s/%d", T.stringof, ElemSize); 111 | FreeListRef ret; 112 | ret.m_object = ObjAlloc.alloc(args); 113 | ret.refCount = 1; 114 | return ret; 115 | } 116 | 117 | ~this() 118 | { 119 | //if( m_object ) logInfo("~this!%s(): %d", T.stringof, this.refCount); 120 | //if( m_object ) logInfo("ref %s destructor %d", T.stringof, refCount); 121 | //else logInfo("ref %s destructor %d", T.stringof, 0); 122 | clear(); 123 | m_magic = 0; 124 | m_object = null; 125 | } 126 | 127 | this(this) 128 | { 129 | checkInvariants(); 130 | if( m_object ){ 131 | //if( m_object ) logInfo("this!%s(this): %d", T.stringof, this.refCount); 132 | this.refCount++; 133 | } 134 | } 135 | 136 | bool opCast(T)() const if (is(T == bool)) { return m_object !is null; } 137 | 138 | void opAssign(FreeListRef other) 139 | { 140 | clear(); 141 | m_object = other.m_object; 142 | if( m_object ){ 143 | //logInfo("opAssign!%s(): %d", T.stringof, this.refCount); 144 | refCount++; 145 | } 146 | } 147 | 148 | void clear() 149 | { 150 | checkInvariants(); 151 | if (m_object) { 152 | if (--this.refCount == 0) 153 | () @trusted { ObjAlloc.free(m_object); } (); 154 | } 155 | 156 | m_object = null; 157 | m_magic = 0x1EE75817; 158 | } 159 | 160 | static if (is(T == class)) { 161 | @property inout(T) get() inout @safe nothrow { return m_object; } 162 | } else { 163 | @property ref inout(T) get() inout @safe nothrow { return *m_object; } 164 | } 165 | alias get this; 166 | 167 | private @property ref int refCount() 168 | const @trusted { 169 | auto ptr = cast(ubyte*)cast(void*)m_object; 170 | ptr += ElemSize; 171 | return *cast(int*)ptr; 172 | } 173 | 174 | private void checkInvariants() 175 | const { 176 | assert(m_magic == 0x1EE75817); 177 | assert(!m_object || refCount > 0); 178 | } 179 | } 180 | 181 | 182 | /// See issue #14194 183 | private T internalEmplace(T, Args...)(void[] chunk, auto ref Args args) 184 | if (is(T == class)) 185 | in { 186 | import std.string, std.format; 187 | assert(chunk.length >= T.sizeof, 188 | format("emplace: Chunk size too small: %s < %s size = %s", 189 | chunk.length, T.stringof, T.sizeof)); 190 | assert((cast(size_t) chunk.ptr) % T.alignof == 0, 191 | format("emplace: Misaligned memory block (0x%X): it must be %s-byte aligned for type %s", &chunk[0], T.alignof, T.stringof)); 192 | 193 | } do { 194 | enum classSize = __traits(classInstanceSize, T); 195 | auto result = () @trusted { return cast(T) chunk.ptr; } (); 196 | 197 | // Initialize the object in its pre-ctor state 198 | () @trusted { 199 | chunk[0 .. classSize] = typeid(T).initializer[]; // Avoid deprecation warning 200 | } (); 201 | 202 | // Call the ctor if any 203 | static if (is(typeof(result.__ctor(args)))) 204 | { 205 | // T defines a genuine constructor accepting args 206 | // Go the classic route: write .init first, then call ctor 207 | result.__ctor(args); 208 | } 209 | else 210 | { 211 | static assert(args.length == 0 && !is(typeof(&T.__ctor)), 212 | "Don't know how to initialize an object of type " 213 | ~ T.stringof ~ " with arguments " ~ Args.stringof); 214 | } 215 | return result; 216 | } 217 | 218 | /// Dittor 219 | private auto internalEmplace(T, Args...)(void[] chunk, auto ref Args args) nothrow 220 | if (!is(T == class)) 221 | in { 222 | import std.string, std.format; 223 | assert(chunk.length >= T.sizeof, 224 | format("emplace: Chunk size too small: %s < %s size = %s", 225 | chunk.length, T.stringof, T.sizeof).assumeWontThrow); 226 | assert((cast(size_t) chunk.ptr) % T.alignof == 0, 227 | format("emplace: Misaligned memory block (0x%X): it must be %s-byte aligned for type %s", &chunk[0], T.alignof, T.stringof).assumeWontThrow); 228 | 229 | } do { 230 | return emplace(() @trusted { return cast(T*)chunk.ptr; } (), args); 231 | } 232 | 233 | private void logDebug_(ARGS...)(string msg, ARGS args) {} 234 | -------------------------------------------------------------------------------- /source/vibe/internal/interfaceproxy.d: -------------------------------------------------------------------------------- 1 | module vibe.internal.interfaceproxy; 2 | 3 | import vibe.internal.traits; 4 | import vibe.internal.freelistref; 5 | import vibe.container.internal.utilallocator; 6 | import std.algorithm.mutation : move, swap; 7 | import std.meta : staticMap; 8 | import std.traits : BaseTypeTuple; 9 | 10 | 11 | O asInterface(I, O)(O obj) if (is(I == interface) && is(O : I)) { return obj; } 12 | InterfaceProxyClass!(I, O) asInterface(I, O)(O obj) if (is(I == interface) && !is(O : I)) { return new InterfaceProxyClass!(I, O)(obj); } 13 | 14 | InterfaceProxyClass!(I, O) asInterface(I, O, Allocator)(O obj, Allocator allocator) 15 | @trusted if (is(I == interface) && !is(O : I)) 16 | { 17 | alias R = InterfaceProxyClass!(I, O); 18 | return allocator.makeGCSafe!R(obj); 19 | } 20 | 21 | void freeInterface(I, O, Allocator)(InterfaceProxyClass!(I, O) inst, Allocator allocator) 22 | { 23 | allocator.disposeGCSafe(inst); 24 | } 25 | 26 | FreeListRef!(InterfaceProxyClass!(I, O)) asInterfaceFL(I, O)(O obj) { return FreeListRef!(InterfaceProxyClass!(I, O))(obj); } 27 | 28 | InterfaceProxy!I interfaceProxy(I, O)(O o) { return InterfaceProxy!I(o); } 29 | 30 | private final class InterfaceProxyClass(I, O) : I 31 | { 32 | import std.meta : AliasSeq; 33 | import std.traits : FunctionAttribute, MemberFunctionsTuple, ReturnType, ParameterTypeTuple, functionAttributes; 34 | 35 | private { 36 | O m_obj; 37 | } 38 | 39 | this(ref O obj) { swap(m_obj, obj); } 40 | 41 | mixin methodDefs!0; 42 | 43 | private mixin template methodDefs(size_t idx) { 44 | alias Members = AliasSeq!(__traits(allMembers, I)); 45 | static if (idx < Members.length) { 46 | mixin overloadDefs!(Members[idx]); 47 | mixin methodDefs!(idx+1); 48 | } 49 | } 50 | 51 | mixin template overloadDefs(string mem) { 52 | alias Overloads = MemberFunctionsTuple!(I, mem); 53 | 54 | private static string impl() 55 | { 56 | string ret; 57 | foreach (idx, F; Overloads) { 58 | alias R = ReturnType!F; 59 | enum attribs = functionAttributeString!F(false); 60 | static if (__traits(isVirtualMethod, F)) { 61 | static if (is(R == void)) 62 | ret ~= "override "~attribs~" void "~mem~"(ParameterTypeTuple!(Overloads["~idx.stringof~"]) params) { m_obj."~mem~"(params); }"; 63 | else 64 | ret ~= "override "~attribs~" ReturnType!(Overloads["~idx.stringof~"]) "~mem~"(ParameterTypeTuple!(Overloads["~idx.stringof~"]) params) { return m_obj."~mem~"(params); }"; 65 | } 66 | } 67 | return ret; 68 | } 69 | 70 | mixin(impl()); 71 | } 72 | } 73 | 74 | 75 | 76 | struct InterfaceProxy(I) if (is(I == interface)) { 77 | import std.meta : AliasSeq; 78 | import std.traits : FunctionAttribute, MemberFunctionsTuple, ReturnType, ParameterTypeTuple, functionAttributes; 79 | import vibe.internal.traits : checkInterfaceConformance; 80 | 81 | private { 82 | void*[8] m_value; 83 | enum maxSize = m_value.length * m_value[0].sizeof; 84 | Proxy m_intf; 85 | } 86 | 87 | this(IP : InterfaceProxy!J, J)(IP proxy) @safe 88 | { 89 | () @trusted { 90 | m_intf = proxy.m_intf; 91 | swap(proxy.m_value, m_value); 92 | proxy.m_intf = null; 93 | } (); 94 | } 95 | 96 | this(O)(O object) @trusted 97 | if (is(O == struct) || is(O == class) || is(O == interface)) 98 | { 99 | static assert(O.sizeof % m_value[0].sizeof == 0, "Sizeof object ("~O.stringof~") must be a multiple of a pointer size."); 100 | static assert(O.sizeof <= maxSize, "Object ("~O.stringof~") is too big to be stored in an InterfaceProxy."); 101 | import std.conv : emplace; 102 | 103 | static if (is(O == class) || is(O == interface)) { 104 | if (!object) return; 105 | } 106 | 107 | m_intf = ProxyImpl!O.get(); 108 | static if (is(O == struct)) 109 | emplace!O(m_value[0 .. O.sizeof/m_value[0].sizeof]); 110 | swap((cast(O[])m_value[0 .. O.sizeof/m_value[0].sizeof])[0], object); 111 | } 112 | 113 | this(typeof(null)) 114 | { 115 | } 116 | 117 | ~this() @safe scope 118 | { 119 | clear(); 120 | } 121 | 122 | this(this) @safe scope 123 | { 124 | if (m_intf) m_intf._postblit(m_value); 125 | } 126 | 127 | void clear() @safe nothrow scope 128 | { 129 | if (m_intf) { 130 | m_intf._destroy(m_value); 131 | m_intf = null; 132 | m_value[] = null; 133 | } 134 | } 135 | 136 | T extract(T)() 137 | @trusted nothrow { 138 | if (!m_intf || m_intf._typeInfo() !is typeid(T)) 139 | assert(false, "Extraction of wrong type from InterfaceProxy."); 140 | return (cast(T[])m_value[0 .. T.sizeof/m_value[0].sizeof])[0]; 141 | } 142 | 143 | void opAssign(IP : InterfaceProxy!J, J)(IP proxy) @safe 144 | { 145 | static assert(is(J : I), "Can only assign InterfaceProxy instances of derived interfaces."); 146 | 147 | clear(); 148 | if (proxy.m_intf) { 149 | m_intf = proxy.m_intf; 150 | m_value[] = proxy.m_value[]; 151 | proxy.m_intf = null; 152 | } 153 | } 154 | 155 | void opAssign(O)(O object) @trusted 156 | if (checkInterfaceConformance!(O, I) is null) 157 | { 158 | static assert(O.sizeof % m_value[0].sizeof == 0, "Sizeof object ("~O.stringof~") must be a multiple of a pointer size."); 159 | static assert(O.sizeof <= maxSize, "Object is too big to be stored in an InterfaceProxy."); 160 | import std.conv : emplace; 161 | clear(); 162 | m_intf = ProxyImpl!O.get(); 163 | static if (is(O == class)) 164 | (cast(O[])m_value[0 .. O.sizeof/m_value[0].sizeof])[0] = object; 165 | else emplace!O(m_value[0 .. O.sizeof/m_value[0].sizeof]); 166 | swap((cast(O[])m_value[0 .. O.sizeof/m_value[0].sizeof])[0], object); 167 | } 168 | 169 | bool opCast(T)() const @safe nothrow if (is(T == bool)) { return m_intf !is null; } 170 | 171 | mixin allMethods!0; 172 | 173 | private mixin template allMethods(size_t idx) { 174 | alias Members = AliasSeq!(__traits(allMembers, I)); 175 | static if (idx < Members.length) { 176 | static if (__traits(compiles, __traits(getMember, I, Members[idx]))) 177 | mixin overloadMethods!(Members[idx]); 178 | mixin allMethods!(idx+1); 179 | } 180 | } 181 | 182 | private mixin template overloadMethods(string member) { 183 | alias Overloads = AliasSeq!(__traits(getOverloads, I, member)); 184 | 185 | private static string impl() 186 | { 187 | string ret; 188 | foreach (idx, F; Overloads) { 189 | enum attribs = functionAttributeString!F(false); 190 | enum is_prop = functionAttributes!F & FunctionAttribute.property; 191 | ret ~= attribs~" ReturnType!(Overloads["~idx.stringof~"]) "~member~"("~parameterDecls!(F, idx)~")" 192 | ~ "{ assert(!!m_intf, \"Accessing null \"~I.stringof~\" interface proxy\");" 193 | ~ "return m_intf."~member~"(m_value, "~parameterNames!F~"); }"; 194 | } 195 | return ret; 196 | } 197 | 198 | mixin(impl()); 199 | } 200 | 201 | private interface Proxy : staticMap!(ProxyOf, BaseTypeTuple!I) { 202 | import std.meta : AliasSeq; 203 | import std.traits : FunctionAttribute, MemberFunctionsTuple, ReturnType, ParameterTypeTuple, functionAttributes; 204 | 205 | void _destroy(scope void[] stor) @safe nothrow scope; 206 | void _postblit(scope void[] stor) @safe nothrow scope; 207 | TypeInfo _typeInfo() @safe nothrow scope; 208 | 209 | mixin methodDecls!0; 210 | 211 | private mixin template methodDecls(size_t idx) { 212 | alias Members = AliasSeq!(__traits(derivedMembers, I)); 213 | static if (idx < Members.length) { 214 | static if (__traits(compiles, __traits(getMember, I, Members[idx]))) 215 | mixin overloadDecls!(Members[idx]); 216 | mixin methodDecls!(idx+1); 217 | } 218 | } 219 | 220 | private mixin template overloadDecls(string mem) { 221 | alias Overloads = AliasSeq!(__traits(getOverloads, I, mem)); 222 | 223 | private static string impl() 224 | { 225 | string ret; 226 | foreach (idx, F; Overloads) { 227 | enum attribs = functionAttributeString!F(false); 228 | enum vtype = functionAttributeThisType!F("void[]"); 229 | ret ~= "ReturnType!(Overloads["~idx.stringof~"]) "~mem~"(scope "~vtype~" obj, "~parameterDecls!(F, idx)~") "~attribs~";"; 230 | } 231 | return ret; 232 | } 233 | 234 | mixin(impl()); 235 | } 236 | } 237 | 238 | static final class ProxyImpl(O) : Proxy { 239 | static auto get() 240 | { 241 | static ProxyImpl impl; 242 | if (!impl) impl = new ProxyImpl; 243 | return impl; 244 | } 245 | 246 | override void _destroy(scope void[] stor) 247 | @trusted nothrow scope { 248 | static if (is(O == struct)) { 249 | try destroy(*_extract(stor)); 250 | catch (Exception e) assert(false, "Destructor has thrown: "~e.msg); 251 | } 252 | } 253 | 254 | override void _postblit(scope void[] stor) 255 | @trusted nothrow scope { 256 | static if (is(O == struct)) { 257 | try typeid(O).postblit(stor.ptr); 258 | catch (Exception e) assert(false, "Postblit contructor has thrown: "~e.msg); 259 | } 260 | } 261 | 262 | override TypeInfo _typeInfo() 263 | @safe nothrow scope { 264 | return typeid(O); 265 | } 266 | 267 | static inout(O)* _extract(return inout(void)[] stor) 268 | @trusted nothrow pure @nogc { 269 | if (stor.length < O.sizeof) assert(false); 270 | return cast(inout(O)*)stor.ptr; 271 | } 272 | 273 | mixin methodDefs!0; 274 | 275 | private mixin template methodDefs(size_t idx) { 276 | alias Members = AliasSeq!(__traits(allMembers, I)); 277 | static if (idx < Members.length) { 278 | static if (__traits(compiles, __traits(getMember, I, Members[idx]))) 279 | mixin overloadDefs!(Members[idx]); 280 | mixin methodDefs!(idx+1); 281 | } 282 | } 283 | 284 | private mixin template overloadDefs(string mem) { 285 | alias Overloads = AliasSeq!(__traits(getOverloads, I, mem)); 286 | 287 | private static string impl() 288 | { 289 | string ret; 290 | foreach (idx, F; Overloads) { 291 | alias R = ReturnType!F; 292 | alias P = ParameterTypeTuple!F; 293 | enum attribs = functionAttributeString!F(false); 294 | enum vtype = functionAttributeThisType!F("void[]"); 295 | 296 | static if (is(R == void)) 297 | ret ~= "override void "~mem~"(scope "~vtype~" obj, "~parameterDecls!(F, idx)~") "~attribs~" { _extract(obj)."~mem~"("~parameterNames!F~"); }"; 298 | else 299 | ret ~= "override ReturnType!(Overloads["~idx.stringof~"]) "~mem~"(scope "~vtype~" obj, "~parameterDecls!(F, idx)~") "~attribs~" { return _extract(obj)."~mem~"("~parameterNames!F~"); }"; 300 | } 301 | return ret; 302 | } 303 | 304 | mixin(impl()); 305 | } 306 | } 307 | } 308 | 309 | unittest { 310 | static interface I {} 311 | assert(!InterfaceProxy!I(null)); 312 | assert(!InterfaceProxy!I(cast(I)null)); 313 | } 314 | 315 | private string parameterDecls(alias F, size_t idx)() 316 | { 317 | import std.traits : ParameterTypeTuple, ParameterStorageClass, ParameterStorageClassTuple; 318 | 319 | string ret; 320 | alias PST = ParameterStorageClassTuple!F; 321 | foreach (i, PT; ParameterTypeTuple!F) { 322 | static if (i > 0) ret ~= ", "; 323 | static if (PST[i] & ParameterStorageClass.scope_) ret ~= "scope "; 324 | static if (PST[i] & ParameterStorageClass.out_) ret ~= "out "; 325 | static if (PST[i] & ParameterStorageClass.ref_) ret ~= "ref "; 326 | static if (PST[i] & ParameterStorageClass.lazy_) ret ~= "lazy "; 327 | ret ~= "ParameterTypeTuple!(Overloads["~idx.stringof~"])["~i.stringof~"] param_"~i.stringof; 328 | } 329 | return ret; 330 | } 331 | 332 | private string parameterNames(alias F)() 333 | { 334 | import std.traits : ParameterTypeTuple; 335 | 336 | string ret; 337 | foreach (i, PT; ParameterTypeTuple!F) { 338 | static if (i > 0) ret ~= ", "; 339 | ret ~= "param_"~i.stringof; 340 | } 341 | return ret; 342 | } 343 | 344 | private alias ProxyOf(I) = InterfaceProxy!I.Proxy; 345 | 346 | 347 | unittest { 348 | static interface I { 349 | @property int count() const; 350 | } 351 | 352 | static struct S { 353 | int* cnt; 354 | this(bool) { cnt = new int; *cnt = 1; } 355 | this(this) { if (cnt) (*cnt)++; } 356 | ~this() { if (cnt) (*cnt)--; } 357 | @property int count() const { return cnt ? *cnt : 0; } 358 | } 359 | 360 | auto s = S(true); 361 | assert(s.count == 1); 362 | 363 | auto t = interfaceProxy!I(s); 364 | assert(s.count == 2); 365 | 366 | t = interfaceProxy!I(s); 367 | assert(s.count == 2); 368 | 369 | t = s; 370 | assert(s.count == 2); 371 | 372 | s = S.init; 373 | assert(t.count == 1); 374 | 375 | s = t.extract!S; 376 | assert(s.count == 2); 377 | 378 | t = InterfaceProxy!I.init; 379 | assert(s.count == 1); 380 | 381 | t = s; 382 | assert(s.count == 2); 383 | 384 | s = S(true); 385 | assert(s.count == 1); 386 | assert(t.count == 1); 387 | 388 | { 389 | InterfaceProxy!I u; 390 | u = s; 391 | assert(u.count == 2); 392 | } 393 | assert(s.count == 1); 394 | } 395 | -------------------------------------------------------------------------------- /source/vibe/internal/list.d: -------------------------------------------------------------------------------- 1 | module vibe.internal.list; 2 | 3 | import core.atomic; 4 | 5 | struct CircularDList(T) 6 | { 7 | private { 8 | T m_pivot; 9 | } 10 | 11 | this(T pivot) 12 | { 13 | assert(pivot.prev is null && pivot.next is null); 14 | pivot.next = pivot.prev = pivot; 15 | m_pivot = pivot; 16 | assert(this.empty); 17 | } 18 | 19 | bool empty() const { return m_pivot.next is m_pivot; } 20 | 21 | 22 | @property T front() { return m_pivot.next is m_pivot ? null : m_pivot.next; } 23 | @property T back() { return m_pivot.prev is m_pivot ? null : m_pivot.prev; } 24 | 25 | void remove(T elem) 26 | { 27 | assert(elem !is m_pivot); 28 | elem.prev.next = elem.next; 29 | elem.next.prev = elem.prev; 30 | elem.prev = elem.next = null; 31 | } 32 | 33 | void insertBefore(T elem, T pivot) 34 | { 35 | assert(elem.prev is null && elem.next is null); 36 | elem.prev = pivot.prev; 37 | elem.next = pivot; 38 | pivot.prev.next = elem; 39 | pivot.prev = elem; 40 | } 41 | 42 | void insertAfter(T elem, T pivot) 43 | { 44 | assert(elem.prev is null && elem.next is null); 45 | elem.prev = pivot; 46 | elem.next = pivot.next; 47 | pivot.next.prev = elem; 48 | pivot.next = elem; 49 | } 50 | 51 | void insertFront(T elem) { insertAfter(elem, m_pivot); } 52 | 53 | void insertBack(T elem) { insertBefore(elem, m_pivot); } 54 | 55 | // NOTE: allowed to remove the current element 56 | int opApply(int delegate(T) @safe nothrow del) 57 | @safe nothrow { 58 | T prev = m_pivot; 59 | debug size_t counter = 0; 60 | while (prev.next !is m_pivot) { 61 | auto el = prev.next; 62 | if (auto ret = del(el)) 63 | return ret; 64 | if (prev.next is el) 65 | prev = prev.next; 66 | debug assert (++counter < 1_000_000, "Cycle in list?"); 67 | } 68 | return 0; 69 | } 70 | } 71 | 72 | unittest { 73 | static final class C { 74 | C prev, next; 75 | int i; 76 | this(int i) { this.i = i; } 77 | } 78 | 79 | alias L = CircularDList!C; 80 | auto l = L(new C(0)); 81 | assert(l.empty); 82 | 83 | auto c = new C(1); 84 | l.insertBack(c); 85 | assert(!l.empty); 86 | assert(l.front is c); 87 | assert(l.back is c); 88 | foreach (c; l) assert(c.i == 1); 89 | 90 | auto c2 = new C(2); 91 | l.insertFront(c2); 92 | assert(!l.empty); 93 | assert(l.front is c2); 94 | assert(l.back is c); 95 | foreach (c; l) assert(c.i == 1 || c.i == 2); 96 | 97 | l.remove(c); 98 | assert(!l.empty); 99 | assert(l.front is c2); 100 | assert(l.back is c2); 101 | foreach (c; l) assert(c.i == 2); 102 | 103 | l.remove(c2); 104 | assert(l.empty); 105 | } 106 | 107 | 108 | struct StackSList(T) 109 | { 110 | @safe nothrow: 111 | 112 | import core.atomic : cas; 113 | 114 | private T m_first; 115 | 116 | @property T first() { return m_first; } 117 | @property T front() { return m_first; } 118 | 119 | bool empty() const { return m_first is null; } 120 | 121 | void add(T elem) 122 | { 123 | debug iterate((el) { assert(el !is elem, "Double-insertion of list element."); return true; }); 124 | elem.next = m_first; 125 | m_first = elem; 126 | } 127 | 128 | bool remove(T elem) 129 | { 130 | debug uint counter = 0; 131 | T w = m_first, wp; 132 | while (w !is elem) { 133 | if (!w) return false; 134 | wp = w; 135 | w = w.next; 136 | debug assert(++counter < 1_000_000, "Cycle in linked list?"); 137 | } 138 | if (wp) wp.next = w.next; 139 | else m_first = w.next; 140 | return true; 141 | } 142 | 143 | void filter(scope bool delegate(T el) @safe nothrow del) 144 | { 145 | debug uint counter = 0; 146 | T w = m_first, pw; 147 | while (w !is null) { 148 | auto wnext = w.next; 149 | if (!del(w)) { 150 | if (pw) pw.next = wnext; 151 | else m_first = wnext; 152 | } else pw = w; 153 | w = wnext; 154 | debug assert(++counter < 1_000_000, "Cycle in linked list?"); 155 | } 156 | } 157 | 158 | void iterate(scope bool delegate(T el) @safe nothrow del) 159 | { 160 | debug uint counter = 0; 161 | T w = m_first; 162 | while (w !is null) { 163 | auto wnext = w.next; 164 | if (!del(w)) break; 165 | w = wnext; 166 | debug assert(++counter < 1_000_000, "Cycle in linked list?"); 167 | } 168 | } 169 | } 170 | 171 | unittest { 172 | static final class C { 173 | C next; 174 | int i; 175 | this(int i) { this.i = i; } 176 | } 177 | 178 | alias L = StackSList!C; 179 | L l; 180 | assert(l.empty); 181 | 182 | auto c = new C(1); 183 | l.add(c); 184 | assert(!l.empty); 185 | assert(l.front is c); 186 | l.iterate((el) { assert(el.i == 1); return true; }); 187 | 188 | auto c2 = new C(2); 189 | l.add(c2); 190 | assert(!l.empty); 191 | assert(l.front is c2); 192 | l.iterate((el) { assert(el.i == 1 || el.i == 2); return true; }); 193 | 194 | l.remove(c); 195 | assert(!l.empty); 196 | assert(l.front is c2); 197 | l.iterate((el) { assert(el.i == 2); return true; }); 198 | 199 | l.filter((el) => el.i == 0); 200 | assert(l.empty); 201 | } 202 | -------------------------------------------------------------------------------- /source/vibe/internal/string.d: -------------------------------------------------------------------------------- 1 | /** 2 | Utility functions for string processing 3 | 4 | Copyright: © 2012-2014 Sönke Ludwig 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 | module vibe.internal.string; 9 | 10 | public import std.string; 11 | 12 | import vibe.internal.array; 13 | import vibe.container.internal.utilallocator; 14 | 15 | import std.algorithm; 16 | import std.array; 17 | import std.ascii; 18 | import std.format; 19 | import std.uni; 20 | import std.utf; 21 | import core.exception; 22 | 23 | 24 | /** 25 | Takes a string with possibly invalid UTF8 sequences and outputs a valid UTF8 string as near to 26 | the original as possible. 27 | */ 28 | string sanitizeUTF8(immutable(ubyte)[] str) 29 | @safe pure nothrow { 30 | import std.encoding : sanitize; 31 | auto ustr = cast(immutable(char)[])str; 32 | return () @trusted { return sanitize(ustr); } (); 33 | } 34 | /// ditto 35 | string sanitizeUTF8(in ubyte[] str) 36 | @trusted pure nothrow { 37 | import std.encoding : sanitize; 38 | auto ustr = cast(immutable(char)[])str; 39 | auto ret = sanitize(ustr); 40 | if (ret.ptr is ustr.ptr) return ustr.idup; 41 | else return ret; 42 | } 43 | 44 | /** 45 | Strips the byte order mark of an UTF8 encoded string. 46 | This is useful when the string is coming from a file. 47 | */ 48 | string stripUTF8Bom(string str) 49 | @safe pure nothrow { 50 | if (str.length >= 3 && str[0 .. 3] == [0xEF, 0xBB, 0xBF]) 51 | return str[3 ..$]; 52 | return str; 53 | } 54 | 55 | 56 | /** 57 | Checks if all characters in 'str' are contained in 'chars'. 58 | */ 59 | bool allOf(string str, string chars) 60 | @safe pure { 61 | foreach (dchar ch; str) 62 | if (!chars.canFind(ch)) 63 | return false; 64 | return true; 65 | } 66 | 67 | ptrdiff_t indexOfCT(Char)(in Char[] s, dchar c, CaseSensitive cs = CaseSensitive.yes) 68 | @safe pure { 69 | if (__ctfe) { 70 | if (cs == CaseSensitive.yes) { 71 | foreach (i, dchar ch; s) 72 | if (ch == c) 73 | return i; 74 | } else { 75 | c = std.uni.toLower(c); 76 | foreach (i, dchar ch; s) 77 | if (std.uni.toLower(ch) == c) 78 | return i; 79 | } 80 | return -1; 81 | } else return std.string.indexOf(s, c, cs); 82 | } 83 | ptrdiff_t indexOfCT(Char)(in Char[] s, in Char[] needle) 84 | { 85 | if (__ctfe) { 86 | if (s.length < needle.length) return -1; 87 | foreach (i; 0 .. s.length - needle.length) 88 | if (s[i .. i+needle.length] == needle) 89 | return i; 90 | return -1; 91 | } else return std.string.indexOf(s, needle); 92 | } 93 | 94 | /** 95 | Checks if any character in 'str' is contained in 'chars'. 96 | */ 97 | bool anyOf(string str, string chars) 98 | @safe pure { 99 | foreach (ch; str) 100 | if (chars.canFind(ch)) 101 | return true; 102 | return false; 103 | } 104 | 105 | 106 | /// ASCII whitespace trimming (space and tab) 107 | string stripLeftA(string s) 108 | @safe pure nothrow { 109 | while (s.length > 0 && (s[0] == ' ' || s[0] == '\t')) 110 | s = s[1 .. $]; 111 | return s; 112 | } 113 | 114 | /// ASCII whitespace trimming (space and tab) 115 | string stripRightA(string s) 116 | @safe pure nothrow { 117 | while (s.length > 0 && (s[$-1] == ' ' || s[$-1] == '\t')) 118 | s = s[0 .. $-1]; 119 | return s; 120 | } 121 | 122 | /// ASCII whitespace trimming (space and tab) 123 | string stripA(string s) 124 | @safe pure nothrow { 125 | return stripLeftA(stripRightA(s)); 126 | } 127 | 128 | /// Finds the first occurence of any of the characters in `chars` 129 | ptrdiff_t indexOfAny(string str, string chars) 130 | @safe pure { 131 | foreach (i, char ch; str) 132 | if (chars.canFind(ch)) 133 | return i; 134 | return -1; 135 | } 136 | alias countUntilAny = indexOfAny; 137 | 138 | /** 139 | Finds the closing bracket (works with any of '[', '$(LPAREN)', '<', '{'). 140 | 141 | Params: 142 | str = input string 143 | nested = whether to skip nested brackets 144 | Returns: 145 | The index of the closing bracket or -1 for unbalanced strings 146 | and strings that don't start with a bracket. 147 | */ 148 | ptrdiff_t matchBracket(string str, bool nested = true) 149 | @safe pure nothrow { 150 | if (str.length < 2) return -1; 151 | 152 | char open = str[0], close = void; 153 | switch (str[0]) { 154 | case '[': close = ']'; break; 155 | case '(': close = ')'; break; 156 | case '<': close = '>'; break; 157 | case '{': close = '}'; break; 158 | default: return -1; 159 | } 160 | 161 | size_t level = 1; 162 | foreach (i, char c; str[1 .. $]) { 163 | if (nested && c == open) ++level; 164 | else if (c == close) --level; 165 | if (level == 0) return i + 1; 166 | } 167 | return -1; 168 | } 169 | 170 | @safe unittest 171 | { 172 | static struct Test { string str; ptrdiff_t res; } 173 | enum tests = [ 174 | Test("[foo]", 4), Test("", 4), Test("{baz}", 4), 175 | Test("[", -1), Test("[foo", -1), Test("ab[f]", -1), 176 | Test("[foo[bar]]", 9), Test("[foo{bar]]", 8), 177 | ]; 178 | foreach (test; tests) 179 | assert(matchBracket(test.str) == test.res); 180 | assert(matchBracket("[foo[bar]]", false) == 8); 181 | static assert(matchBracket("[foo]") == 4); 182 | } 183 | 184 | /// Same as std.string.format, just using an allocator. 185 | string formatAlloc(Allocator, ARGS...)(Allocator alloc, string fmt, ARGS args) 186 | { 187 | auto app = AllocAppender!string(alloc); 188 | formattedWrite(() @trusted { return &app; } (), fmt, args); 189 | return () @trusted { return app.data; } (); 190 | } 191 | 192 | /// Special version of icmp() with optimization for ASCII characters 193 | int icmp2(string a, string b) 194 | @safe pure { 195 | size_t i = 0, j = 0; 196 | 197 | // fast skip equal prefix 198 | size_t min_len = min(a.length, b.length); 199 | while( i < min_len && a[i] == b[i] ) i++; 200 | if( i > 0 && (a[i-1] & 0x80) ) i--; // don't stop half-way in a UTF-8 sequence 201 | j = i; 202 | 203 | // compare the differing character and the rest of the string 204 | while(i < a.length && j < b.length){ 205 | uint ac = cast(uint)a[i]; 206 | uint bc = cast(uint)b[j]; 207 | if( !((ac | bc) & 0x80) ){ 208 | i++; 209 | j++; 210 | if( ac >= 'A' && ac <= 'Z' ) ac += 'a' - 'A'; 211 | if( bc >= 'A' && bc <= 'Z' ) bc += 'a' - 'A'; 212 | if( ac < bc ) return -1; 213 | else if( ac > bc ) return 1; 214 | } else { 215 | dchar acp = decode(a, i); 216 | dchar bcp = decode(b, j); 217 | if( acp != bcp ){ 218 | acp = std.uni.toLower(acp); 219 | bcp = std.uni.toLower(bcp); 220 | if( acp < bcp ) return -1; 221 | else if( acp > bcp ) return 1; 222 | } 223 | } 224 | } 225 | 226 | if( i < a.length ) return 1; 227 | else if( j < b.length ) return -1; 228 | 229 | assert(i == a.length || j == b.length, "Strings equal but we didn't fully compare them!?"); 230 | return 0; 231 | } 232 | -------------------------------------------------------------------------------- /source/vibe/internal/traits.d: -------------------------------------------------------------------------------- 1 | /** 2 | Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos, 3 | some are dirty hacks that work only for vibe.d 4 | 5 | Copyright: © 2012 Sönke Ludwig 6 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 7 | Authors: Sönke Ludwig, Михаил Страшун 8 | */ 9 | 10 | module vibe.internal.traits; 11 | 12 | import vibe.internal.typetuple; 13 | 14 | 15 | /** 16 | Checks if given type is a getter function type 17 | 18 | Returns: `true` if argument is a getter 19 | */ 20 | template isPropertyGetter(T...) 21 | if (T.length == 1) 22 | { 23 | import std.traits : functionAttributes, FunctionAttribute, ReturnType, 24 | isSomeFunction; 25 | static if (isSomeFunction!(T[0])) { 26 | enum isPropertyGetter = 27 | (functionAttributes!(T[0]) & FunctionAttribute.property) != 0 28 | && !is(ReturnType!T == void); 29 | } 30 | else 31 | enum isPropertyGetter = false; 32 | } 33 | 34 | /// 35 | unittest 36 | { 37 | interface Test 38 | { 39 | @property int getter(); 40 | @property void setter(int); 41 | int simple(); 42 | } 43 | 44 | static assert(isPropertyGetter!(typeof(&Test.getter))); 45 | static assert(!isPropertyGetter!(typeof(&Test.setter))); 46 | static assert(!isPropertyGetter!(typeof(&Test.simple))); 47 | static assert(!isPropertyGetter!int); 48 | } 49 | 50 | /** 51 | Checks if given type is a setter function type 52 | 53 | Returns: `true` if argument is a setter 54 | */ 55 | template isPropertySetter(T...) 56 | if (T.length == 1) 57 | { 58 | import std.traits : functionAttributes, FunctionAttribute, ReturnType, 59 | isSomeFunction; 60 | 61 | static if (isSomeFunction!(T[0])) { 62 | enum isPropertySetter = 63 | (functionAttributes!(T) & FunctionAttribute.property) != 0 64 | && is(ReturnType!(T[0]) == void); 65 | } 66 | else 67 | enum isPropertySetter = false; 68 | } 69 | 70 | /// 71 | unittest 72 | { 73 | interface Test 74 | { 75 | @property int getter(); 76 | @property void setter(int); 77 | int simple(); 78 | } 79 | 80 | static assert(isPropertySetter!(typeof(&Test.setter))); 81 | static assert(!isPropertySetter!(typeof(&Test.getter))); 82 | static assert(!isPropertySetter!(typeof(&Test.simple))); 83 | static assert(!isPropertySetter!int); 84 | } 85 | 86 | /** 87 | Deduces single base interface for a type. Multiple interfaces 88 | will result in compile-time error. 89 | 90 | Params: 91 | T = interface or class type 92 | 93 | Returns: 94 | T if it is an interface. If T is a class, interface it implements. 95 | */ 96 | template baseInterface(T) 97 | if (is(T == interface) || is(T == class)) 98 | { 99 | import std.traits : InterfacesTuple; 100 | 101 | static if (is(T == interface)) { 102 | alias baseInterface = T; 103 | } 104 | else 105 | { 106 | alias Ifaces = InterfacesTuple!T; 107 | static assert ( 108 | Ifaces.length == 1, 109 | "Type must be either provided as an interface or implement only one interface" 110 | ); 111 | alias baseInterface = Ifaces[0]; 112 | } 113 | } 114 | 115 | /// 116 | unittest 117 | { 118 | interface I1 { } 119 | class A : I1 { } 120 | interface I2 { } 121 | class B : I1, I2 { } 122 | 123 | static assert (is(baseInterface!I1 == I1)); 124 | static assert (is(baseInterface!A == I1)); 125 | static assert (!is(typeof(baseInterface!B))); 126 | } 127 | 128 | 129 | /** 130 | Determins if a member is a public, non-static data field. 131 | */ 132 | template isRWPlainField(T, string M) 133 | { 134 | static if (!isRWField!(T, M)) enum isRWPlainField = false; 135 | else { 136 | //pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof); 137 | enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M))); 138 | } 139 | } 140 | 141 | /** 142 | Determines if a member is a public, non-static, de-facto data field. 143 | 144 | In addition to plain data fields, R/W properties are also accepted. 145 | */ 146 | template isRWField(T, string M) 147 | { 148 | import std.traits; 149 | import std.typetuple; 150 | 151 | static void testAssign()() { 152 | T t = void; 153 | __traits(getMember, t, M) = __traits(getMember, t, M); 154 | } 155 | 156 | // reject type aliases 157 | static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false; 158 | // reject non-public members 159 | else static if (!isPublicMember!(T, M)) enum isRWField = false; 160 | // reject static members 161 | else static if (!isNonStaticMember!(T, M)) enum isRWField = false; 162 | // reject non-typed members 163 | else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false; 164 | // reject void typed members (includes templates) 165 | else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false; 166 | // reject non-assignable members 167 | else static if (!__traits(compiles, testAssign!()())) enum isRWField = false; 168 | else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) { 169 | // If M is a function, reject if not @property or returns by ref 170 | private enum FA = functionAttributes!(__traits(getMember, T, M)); 171 | enum isRWField = (FA & FunctionAttribute.property) != 0; 172 | } else { 173 | enum isRWField = true; 174 | } 175 | } 176 | 177 | unittest { 178 | import std.algorithm; 179 | 180 | struct S { 181 | alias a = int; // alias 182 | int i; // plain RW field 183 | enum j = 42; // manifest constant 184 | static int k = 42; // static field 185 | private int privateJ; // private RW field 186 | 187 | this(Args...)(Args args) {} 188 | 189 | // read-write property (OK) 190 | @property int p1() { return privateJ; } 191 | @property void p1(int j) { privateJ = j; } 192 | // read-only property (NO) 193 | @property int p2() { return privateJ; } 194 | // write-only property (NO) 195 | @property void p3(int value) { privateJ = value; } 196 | // ref returning property (OK) 197 | @property ref int p4() return { return i; } 198 | // parameter-less template property (OK) 199 | @property ref int p5()() { return i; } 200 | // not treated as a property by DMD, so not a field 201 | @property int p6()() { return privateJ; } 202 | @property void p6(int j)() { privateJ = j; } 203 | 204 | static @property int p7() { return k; } 205 | static @property void p7(int value) { k = value; } 206 | 207 | ref int f1() return { return i; } // ref returning function (no field) 208 | 209 | int f2(Args...)(Args args) { return i; } 210 | 211 | ref int f3(Args...)(Args args) { return i; } 212 | 213 | void someMethod() {} 214 | 215 | ref int someTempl()() { return i; } 216 | } 217 | 218 | enum plainFields = ["i"]; 219 | enum fields = ["i", "p1", "p4", "p5"]; 220 | 221 | foreach (mem; __traits(allMembers, S)) { 222 | static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field."); 223 | else static assert(!fields.canFind(mem), mem~" not detected as field."); 224 | 225 | static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field."); 226 | else static assert(!plainFields.canFind(mem), mem~" not detected as plain field."); 227 | } 228 | } 229 | 230 | package T Tgen(T)(){ return T.init; } 231 | 232 | 233 | /** 234 | Tests if the protection of a member is public. 235 | */ 236 | template isPublicMember(T, string M) 237 | { 238 | import std.algorithm, std.typetuple : TypeTuple; 239 | 240 | static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false; 241 | else { 242 | alias MEM = TypeTuple!(__traits(getMember, T, M)); 243 | enum isPublicMember = __traits(getProtection, MEM).among("public", "export"); 244 | } 245 | } 246 | 247 | unittest { 248 | class C { 249 | int a; 250 | export int b; 251 | protected int c; 252 | private int d; 253 | package int e; 254 | void f() {} 255 | static void g() {} 256 | private void h() {} 257 | private static void i() {} 258 | } 259 | 260 | static assert (isPublicMember!(C, "a")); 261 | static assert (isPublicMember!(C, "b")); 262 | static assert (!isPublicMember!(C, "c")); 263 | static assert (!isPublicMember!(C, "d")); 264 | static assert (!isPublicMember!(C, "e")); 265 | static assert (isPublicMember!(C, "f")); 266 | static assert (isPublicMember!(C, "g")); 267 | static assert (!isPublicMember!(C, "h")); 268 | static assert (!isPublicMember!(C, "i")); 269 | 270 | struct S { 271 | int a; 272 | export int b; 273 | private int d; 274 | package int e; 275 | } 276 | static assert (isPublicMember!(S, "a")); 277 | static assert (isPublicMember!(S, "b")); 278 | static assert (!isPublicMember!(S, "d")); 279 | static assert (!isPublicMember!(S, "e")); 280 | 281 | S s; 282 | s.a = 21; 283 | assert(s.a == 21); 284 | } 285 | 286 | /** 287 | Tests if a member requires $(D this) to be used. 288 | */ 289 | template isNonStaticMember(T, string M) 290 | { 291 | import std.typetuple; 292 | import std.traits; 293 | 294 | alias MF = TypeTuple!(__traits(getMember, T, M)); 295 | static if (M.length == 0) { 296 | enum isNonStaticMember = false; 297 | } else static if (anySatisfy!(isSomeFunction, MF)) { 298 | enum isNonStaticMember = !__traits(isStaticFunction, MF); 299 | } else { 300 | enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }()); 301 | } 302 | } 303 | 304 | unittest { // normal fields 305 | struct S { 306 | int a; 307 | static int b; 308 | enum c = 42; 309 | void f(); 310 | static void g(); 311 | ref int h() return { return a; } 312 | static ref int i() { return b; } 313 | } 314 | static assert(isNonStaticMember!(S, "a")); 315 | static assert(!isNonStaticMember!(S, "b")); 316 | static assert(!isNonStaticMember!(S, "c")); 317 | static assert(isNonStaticMember!(S, "f")); 318 | static assert(!isNonStaticMember!(S, "g")); 319 | static assert(isNonStaticMember!(S, "h")); 320 | static assert(!isNonStaticMember!(S, "i")); 321 | } 322 | 323 | unittest { // tuple fields 324 | struct S(T...) { 325 | T a; 326 | static T b; 327 | } 328 | 329 | alias T = S!(int, float); 330 | auto p = T.b; 331 | static assert(isNonStaticMember!(T, "a")); 332 | static assert(!isNonStaticMember!(T, "b")); 333 | 334 | alias U = S!(); 335 | static assert(!isNonStaticMember!(U, "a")); 336 | static assert(!isNonStaticMember!(U, "b")); 337 | } 338 | 339 | 340 | /** 341 | Tests if a Group of types is implicitly convertible to a Group of target types. 342 | */ 343 | bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)() 344 | if (isGroup!TYPES && isGroup!TARGET_TYPES) 345 | { 346 | static assert(TYPES.expand.length == TARGET_TYPES.expand.length, 347 | "Argument count does not match."); 348 | foreach (i, V; TYPES.expand) 349 | if (!is(V : TARGET_TYPES.expand[i])) 350 | return false; 351 | return true; 352 | } 353 | 354 | /// Test if the type $(D DG) is a correct delegate for an opApply where the 355 | /// key/index is of type $(D TKEY) and the value of type $(D TVALUE). 356 | template isOpApplyDg(DG, TKEY, TVALUE) { 357 | import std.traits; 358 | static if (is(DG == delegate) && is(ReturnType!DG : int)) { 359 | private alias PTT = ParameterTypeTuple!(DG); 360 | private alias PSCT = ParameterStorageClassTuple!(DG); 361 | private alias STC = ParameterStorageClass; 362 | // Just a value 363 | static if (PTT.length == 1) { 364 | enum isOpApplyDg = (is(PTT[0] == TVALUE)); 365 | } else static if (PTT.length == 2) { 366 | enum isOpApplyDg = (is(PTT[0] == TKEY)) 367 | && (is(PTT[1] == TVALUE)); 368 | } else 369 | enum isOpApplyDg = false; 370 | } else { 371 | enum isOpApplyDg = false; 372 | } 373 | } 374 | 375 | unittest { 376 | static assert(isOpApplyDg!(int delegate(int, string), int, string)); 377 | static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string)); 378 | static assert(isOpApplyDg!(int delegate(int, ref string), int, string)); 379 | static assert(isOpApplyDg!(int delegate(ref int, string), int, string)); 380 | } 381 | 382 | // Synchronized statements are logically nothrow but dmd still marks them as throwing. 383 | // DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704 384 | import core.sync.mutex : Mutex; 385 | enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} }); 386 | 387 | 388 | /// Mixin template that checks a particular aggregate type for conformance with a specific interface. 389 | template validateInterfaceConformance(T, I) 390 | { 391 | import vibe.internal.traits : checkInterfaceConformance; 392 | static assert(checkInterfaceConformance!(T, I) is null, checkInterfaceConformance!(T, I)); 393 | } 394 | 395 | /** Checks an aggregate type for conformance with a specific interface. 396 | 397 | The value of this template is either `null`, or an error message indicating the first method 398 | of the interface that is not properly implemented by `T`. 399 | */ 400 | template checkInterfaceConformance(T, I) { 401 | import std.meta : AliasSeq; 402 | import std.traits : FunctionAttribute, FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple, ReturnType, functionAttributes, fullyQualifiedName; 403 | 404 | alias Members = AliasSeq!(__traits(allMembers, I)); 405 | 406 | template checkMemberConformance(string mem) { 407 | alias Overloads = AliasSeq!(__traits(getOverloads, I, mem)); 408 | template impl(size_t i) { 409 | static if (i < Overloads.length) { 410 | alias F = Overloads[i]; 411 | alias FT = FunctionTypeOf!F; 412 | alias PT = ParameterTypeTuple!F; 413 | alias RT = ReturnType!F; 414 | enum attribs = functionAttributeString!F(true); 415 | static if (functionAttributes!F & FunctionAttribute.property) { 416 | static if (PT.length > 0) { 417 | static if (!is(typeof(mixin("function RT (ref T t)"~attribs~"{ return t."~mem~" = PT.init; }")))) 418 | enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property setter `" ~ 419 | mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 420 | else enum string impl = impl!(i+1); 421 | } else { 422 | static if (!is(typeof(mixin("function RT(ref T t)"~attribs~"{ return t."~mem~"; }")))) 423 | enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property getter `" ~ 424 | mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 425 | else enum string impl = impl!(i+1); 426 | } 427 | } else { 428 | static if (is(RT == void)) { 429 | static if (!is(typeof(mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }")))) { 430 | static if (mem == "write" && PT.length == 2) { 431 | auto f = mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }"); 432 | } 433 | enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~ 434 | mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 435 | } 436 | else enum string impl = impl!(i+1); 437 | } else { 438 | static if (!is(typeof(mixin("function RT(ref T t, ref PT p)"~attribs~"{ return t."~mem~"(p); }")))) 439 | enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~ 440 | mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 441 | else enum string impl = impl!(i+1); 442 | } 443 | } 444 | } else enum string impl = null; 445 | } 446 | alias checkMemberConformance = impl!0; 447 | } 448 | 449 | template impl(size_t i) { 450 | static if (i < Members.length) { 451 | static if (__traits(compiles, __traits(getMember, I, Members[i]))) 452 | enum mc = checkMemberConformance!(Members[i]); 453 | else enum mc = null; 454 | static if (mc is null) enum impl = impl!(i+1); 455 | else enum impl = mc; 456 | } else enum string impl = null; 457 | } 458 | 459 | static if (is(T : I)) 460 | enum checkInterfaceConformance = null; 461 | else static if (is(T == struct) || is(T == class) || is(T == interface)) 462 | enum checkInterfaceConformance = impl!0; 463 | else 464 | enum checkInterfaceConformance = "Aggregate type expected, not " ~ T.stringof; 465 | } 466 | 467 | unittest { 468 | interface InputStream { 469 | @safe: 470 | @property bool empty() nothrow; 471 | void read(ubyte[] dst); 472 | } 473 | 474 | interface OutputStream { 475 | @safe: 476 | void write(scope const(ubyte)[] bytes); 477 | void flush(); 478 | void finalize(); 479 | void write(InputStream stream, ulong nbytes = 0); 480 | } 481 | 482 | static class OSClass : OutputStream { 483 | override void write(scope const(ubyte)[] bytes) {} 484 | override void flush() {} 485 | override void finalize() {} 486 | override void write(InputStream stream, ulong nbytes) {} 487 | } 488 | 489 | mixin validateInterfaceConformance!(OSClass, OutputStream); 490 | 491 | static struct OSStruct { 492 | @safe: 493 | void write(scope const(ubyte)[] bytes) {} 494 | void flush() {} 495 | void finalize() {} 496 | void write(IS)(IS stream, ulong nbytes) {} 497 | } 498 | 499 | mixin validateInterfaceConformance!(OSStruct, OutputStream); 500 | 501 | static struct NonOSStruct { 502 | @safe: 503 | void write(scope const(ubyte)[] bytes) {} 504 | void flush(bool) {} 505 | void finalize() {} 506 | void write(InputStream stream, ulong nbytes) {} 507 | } 508 | 509 | string removeUnittestLineNumbers(string s) { 510 | import std.string; 511 | 512 | string ret; 513 | size_t start; 514 | while (true) 515 | { 516 | size_t next = s.indexOf("__unittest_L", start); 517 | if (next == -1) 518 | break; 519 | size_t dot = s.indexOf('.', next); 520 | if (dot == -1) 521 | dot = s.length; 522 | else 523 | ret ~= s[start .. next + "__unittest".length]; 524 | start = dot; 525 | } 526 | return ret ~ s[start .. $]; 527 | } 528 | 529 | static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct, OutputStream)) == 530 | "`vibe.internal.traits.__unittest.NonOSStruct` does not implement method `flush` of type `@safe void()` from `vibe.internal.traits.__unittest.OutputStream`"); 531 | 532 | static struct NonOSStruct2 { 533 | void write(scope const(ubyte)[] bytes) {} // not @safe 534 | void flush(bool) {} 535 | void finalize() {} 536 | void write(InputStream stream, ulong nbytes) {} 537 | } 538 | 539 | // `in` used to show up as `const` / `const scope`. 540 | // With dlang/dmd#11474 it shows up as `in`. 541 | // Remove when support for v2.093.0 is dropped 542 | static if (removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) != 543 | "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(scope const(ubyte)[] bytes)` from `vibe.internal.traits.__unittest.OutputStream`") 544 | { 545 | // Fallback to pre-2.092+ 546 | static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) == 547 | "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(const(ubyte[]) bytes)` from `vibe.internal.traits.__unittest.OutputStream`"); 548 | } 549 | } 550 | 551 | string functionAttributeString(alias F)(bool restrictions_only) 552 | { 553 | import std.traits : FunctionAttribute, functionAttributes; 554 | 555 | auto attribs = functionAttributes!F; 556 | string ret; 557 | with (FunctionAttribute) { 558 | if (attribs & nogc) ret ~= " @nogc"; 559 | if (attribs & nothrow_) ret ~= " nothrow"; 560 | if (attribs & pure_) ret ~= " pure"; 561 | if (attribs & safe) ret ~= " @safe"; 562 | if (!restrictions_only) { 563 | if (attribs & property) ret ~= " @property"; 564 | if (attribs & ref_) ret ~= " ref"; 565 | if (attribs & shared_) ret ~= " shared"; 566 | if (attribs & const_) ret ~= " const"; 567 | } 568 | } 569 | return ret; 570 | } 571 | 572 | string functionAttributeThisType(alias F)(string tname) 573 | { 574 | import std.traits : FunctionAttribute, functionAttributes; 575 | 576 | auto attribs = functionAttributes!F; 577 | string ret = tname; 578 | with (FunctionAttribute) { 579 | if (attribs & shared_) ret = "shared("~ret~")"; 580 | if (attribs & const_) ret = "const("~ret~")"; 581 | } 582 | return ret; 583 | } 584 | -------------------------------------------------------------------------------- /source/vibe/internal/typetuple.d: -------------------------------------------------------------------------------- 1 | /** 2 | Additions to std.typetuple pending for inclusion into Phobos. 3 | 4 | Copyright: © 2013 Sönke Ludwig 5 | License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 | Authors: Михаил Страшун 7 | */ 8 | 9 | module vibe.internal.typetuple; 10 | 11 | import std.typetuple; 12 | import std.traits; 13 | 14 | /** 15 | TypeTuple which does not auto-expand. 16 | 17 | Useful when you need 18 | to multiple several type tuples as different template argument 19 | list parameters, without merging those. 20 | */ 21 | template Group(T...) 22 | { 23 | alias expand = T; 24 | } 25 | 26 | /// 27 | unittest 28 | { 29 | alias group = Group!(int, double, string); 30 | static assert (!is(typeof(group.length))); 31 | static assert (group.expand.length == 3); 32 | static assert (is(group.expand[1] == double)); 33 | } 34 | 35 | /** 36 | */ 37 | template isGroup(T...) 38 | { 39 | static if (T.length != 1) enum isGroup = false; 40 | else enum isGroup = 41 | !is(T[0]) && is(typeof(T[0]) == void) // does not evaluate to something 42 | && is(typeof(T[0].expand.length) : size_t) // expands to something with length 43 | && !is(typeof(&(T[0].expand))); // expands to not addressable 44 | } 45 | 46 | version (unittest) // NOTE: GDC complains about template definitions in unittest blocks 47 | { 48 | alias group = Group!(int, double, string); 49 | alias group2 = Group!(); 50 | 51 | template Fake(T...) 52 | { 53 | int[] expand; 54 | } 55 | alias fake = Fake!(int, double, string); 56 | 57 | alias fake2 = TypeTuple!(int, double, string); 58 | 59 | static assert (isGroup!group); 60 | static assert (isGroup!group2); 61 | static assert (!isGroup!fake); 62 | static assert (!isGroup!fake2); 63 | } 64 | 65 | /* Copied from Phobos as it is private there. 66 | */ 67 | private template isSame(ab...) 68 | if (ab.length == 2) 69 | { 70 | static if (is(ab[0]) && is(ab[1])) 71 | { 72 | enum isSame = is(ab[0] == ab[1]); 73 | } 74 | else static if (!is(ab[0]) && 75 | !is(ab[1]) && 76 | is(typeof(ab[0] == ab[1]) == bool) && 77 | (ab[0] == ab[1])) 78 | { 79 | static if (!__traits(compiles, &ab[0]) || 80 | !__traits(compiles, &ab[1])) 81 | enum isSame = (ab[0] == ab[1]); 82 | else 83 | enum isSame = __traits(isSame, ab[0], ab[1]); 84 | } 85 | else 86 | { 87 | enum isSame = __traits(isSame, ab[0], ab[1]); 88 | } 89 | } 90 | 91 | /** 92 | Compares two groups for element identity 93 | 94 | Params: 95 | Group1, Group2 = any instances of `Group` 96 | 97 | Returns: 98 | `true` if each element of Group1 is identical to 99 | the one of Group2 at the same index 100 | */ 101 | template Compare(alias Group1, alias Group2) 102 | if (isGroup!Group1 && isGroup!Group2) 103 | { 104 | private template implementation(size_t index) 105 | { 106 | static if (Group1.expand.length != Group2.expand.length) enum implementation = false; 107 | else static if (index >= Group1.expand.length) enum implementation = true; 108 | else static if (!isSame!(Group1.expand[index], Group2.expand[index])) enum implementation = false; 109 | else enum implementation = implementation!(index+1); 110 | } 111 | 112 | enum Compare = implementation!0; 113 | } 114 | 115 | /// 116 | unittest 117 | { 118 | alias one = Group!(int, double); 119 | alias two = Group!(int, double); 120 | alias three = Group!(double, int); 121 | static assert (Compare!(one, two)); 122 | static assert (!Compare!(one, three)); 123 | } 124 | -------------------------------------------------------------------------------- /tests/0-tcp.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "TCP semantics tests" 4 | copyright "Copyright © 2015-2020, Sönke Ludwig" 5 | dependency "vibe-core" path=".." 6 | +/ 7 | module tests; 8 | 9 | import eventcore.core; 10 | import vibe.core.core; 11 | import vibe.core.log; 12 | import vibe.core.net; 13 | import core.time; 14 | import std.datetime.stopwatch : StopWatch; 15 | 16 | enum Test { 17 | receive, 18 | receiveExisting, 19 | timeout, 20 | noTimeout, 21 | close 22 | } 23 | 24 | void test1() 25 | { 26 | Test test; 27 | Task lt; 28 | 29 | auto l = listenTCP(0, (conn) @safe nothrow { 30 | lt = Task.getThis(); 31 | try { 32 | while (!conn.empty) { 33 | assert(conn.fd() != StreamSocketFD.invalid); 34 | assert(conn.readLine() == "next"); 35 | auto curtest = test; 36 | conn.write("continue\n"); 37 | logInfo("Perform test %s", curtest); 38 | StopWatch sw; 39 | sw.start(); 40 | final switch (curtest) { 41 | case Test.receive: 42 | assert(conn.waitForData(2.seconds) == true); 43 | assert(cast(Duration)sw.peek < 2.seconds); // should receive something instantly 44 | assert(conn.readLine() == "receive"); 45 | break; 46 | case Test.receiveExisting: 47 | assert(conn.waitForData(2.seconds) == true); 48 | // TODO: validate that waitForData didn't yield! 49 | assert(cast(Duration)sw.peek < 2.seconds); // should receive something instantly 50 | assert(conn.readLine() == "receiveExisting"); 51 | break; 52 | case Test.timeout: 53 | assert(conn.waitForData(2.seconds) == false); 54 | assert(cast(Duration)sw.peek > 1900.msecs); // should wait for at least 2 seconds 55 | assert(conn.connected); 56 | break; 57 | case Test.noTimeout: 58 | assert(conn.waitForData(Duration.max) == true); 59 | assert(cast(Duration)sw.peek > 2.seconds); // data only sent after 3 seconds 60 | assert(conn.readLine() == "noTimeout"); 61 | break; 62 | case Test.close: 63 | assert(conn.waitForData(2.seconds) == false); 64 | assert(cast(Duration)sw.peek < 2.seconds); // connection should be closed instantly 65 | assert(conn.empty); 66 | conn.close(); 67 | assert(!conn.connected); 68 | return; 69 | } 70 | conn.write("ok\n"); 71 | } 72 | } catch (Exception e) { 73 | assert(false, e.msg); 74 | } 75 | }, "127.0.0.1"); 76 | scope (exit) l.stopListening; 77 | 78 | auto conn = connectTCP(l.bindAddress); 79 | 80 | test = Test.receive; 81 | conn.write("next\n"); 82 | assert(conn.readLine() == "continue"); 83 | conn.write("receive\n"); 84 | assert(conn.readLine() == "ok"); 85 | 86 | test = Test.receiveExisting; 87 | conn.write("next\nreceiveExisting\n"); 88 | assert(conn.readLine() == "continue"); 89 | assert(conn.readLine() == "ok"); 90 | 91 | test = Test.timeout; 92 | conn.write("next\n"); 93 | assert(conn.readLine() == "continue"); 94 | sleep(3.seconds); 95 | assert(conn.readLine() == "ok"); 96 | 97 | test = Test.noTimeout; 98 | conn.write("next\n"); 99 | assert(conn.readLine() == "continue"); 100 | sleep(3.seconds); 101 | conn.write("noTimeout\n"); 102 | assert(conn.readLine() == "ok"); 103 | 104 | test = Test.close; 105 | conn.write("next\n"); 106 | assert(conn.readLine() == "continue"); 107 | conn.close(); 108 | 109 | lt.join(); 110 | } 111 | 112 | void test2() 113 | { 114 | Task lt; 115 | logInfo("Perform test \"disconnect with pending write data\""); 116 | auto l = listenTCP(0, (conn) @safe nothrow { 117 | try { 118 | lt = Task.getThis(); 119 | sleep(1.seconds); 120 | StopWatch sw; 121 | sw.start(); 122 | try { 123 | assert(conn.waitForData() == true); 124 | assert(cast(Duration)sw.peek < 500.msecs); // waitForData should return immediately 125 | assert(conn.dataAvailableForRead); 126 | assert(conn.readAll() == "test"); 127 | conn.close(); 128 | } catch (Exception e) { 129 | assert(false, "Failed to read pending data: " ~ e.msg); 130 | } 131 | } catch (Exception e) { 132 | assert(false, e.msg); 133 | } 134 | }, "127.0.0.1"); 135 | scope (exit) l.stopListening; 136 | 137 | auto conn = connectTCP(l.bindAddress); 138 | conn.write("test"); 139 | conn.close(); 140 | 141 | sleep(100.msecs); 142 | 143 | assert(lt != Task.init); 144 | lt.join(); 145 | } 146 | 147 | void test3() 148 | { 149 | Task lt; 150 | logInfo("Perform test \"disconnect with pending read data\""); 151 | auto l = listenTCP(0, (conn) @safe nothrow { 152 | try { 153 | lt = Task.getThis(); 154 | sleep(1.seconds); 155 | conn.close(); 156 | conn = TCPConnection.init; 157 | } catch (Exception e) { 158 | assert(false, e.msg); 159 | } 160 | }, "127.0.0.1"); 161 | scope (exit) l.stopListening; 162 | 163 | ubyte[256] buf; 164 | auto conn = connectTCP(l.bindAddress); 165 | conn.readTimeout = 10.msecs; 166 | try { 167 | conn.read(buf); 168 | assert(false); 169 | } catch (Exception e) {} 170 | conn.close(); 171 | conn = TCPConnection.init; 172 | 173 | sleep(100.msecs); 174 | 175 | assert(lt != Task.init); 176 | lt.join(); 177 | } 178 | 179 | void main() 180 | { 181 | test1(); 182 | test2(); 183 | test3(); 184 | } 185 | 186 | string readLine(TCPConnection c) @safe 187 | { 188 | import std.string : indexOf; 189 | 190 | string ret; 191 | while (!c.empty) { 192 | auto buf = () @trusted { return cast(char[])c.peek(); }(); 193 | auto idx = buf.indexOf('\n'); 194 | if (idx < 0) { 195 | ret ~= buf; 196 | c.skip(buf.length); 197 | } else { 198 | ret ~= buf[0 .. idx]; 199 | c.skip(idx+1); 200 | break; 201 | } 202 | } 203 | return ret; 204 | } 205 | 206 | string readAll(TCPConnection c) @safe 207 | { 208 | import std.algorithm.comparison : min; 209 | 210 | ubyte[] ret; 211 | while (!c.empty) { 212 | auto len = min(c.leastSize, size_t.max); 213 | ret.length += len; 214 | c.read(ret[$-len .. $]); 215 | } 216 | return () @trusted { return cast(string) ret; }(); 217 | } 218 | -------------------------------------------------------------------------------- /tests/0-tcpproxy.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "TCP proxy test" 4 | copyright "Copyright © 2015-2020, Sönke Ludwig" 5 | dependency "vibe-core" path=".." 6 | +/ 7 | module tests; 8 | 9 | import vibe.core.core; 10 | import vibe.core.log; 11 | import vibe.core.net; 12 | import vibe.core.stream; 13 | import std.exception; 14 | import std.string; 15 | import std.range.primitives : front; 16 | import core.time; 17 | 18 | 19 | void testProtocol(TCPConnection server, bool terminate) 20 | { 21 | foreach (i; 0 .. 1) { 22 | foreach (j; 0 .. 1) { 23 | auto str = format("Hello, World #%s", i*100+j); 24 | server.write(str); 25 | server.write("\n"); 26 | auto reply = server.readLine(); 27 | assert(reply == format("Hash: %08X", typeid(string).getHash(&str)), "Unexpected reply"); 28 | } 29 | sleep(10.msecs); 30 | } 31 | 32 | assert(!server.dataAvailableForRead, "Still data available."); 33 | 34 | if (terminate) { 35 | // forcefully close connection 36 | server.close(); 37 | } else { 38 | server.write("quit\n"); 39 | enforce(server.readLine() == "Bye bye!"); 40 | // should have closed within 500 ms 41 | enforce(!server.waitForData(500.msecs)); 42 | assert(server.empty, "Server still connected."); 43 | } 44 | } 45 | 46 | void runTest() 47 | { 48 | import std.algorithm : find; 49 | import std.socket : AddressFamily; 50 | 51 | // server for a simple line based protocol 52 | auto l1 = listenTCP(0, (client) @safe nothrow { 53 | try 54 | { 55 | while (!client.empty) { 56 | auto ln = client.readLine(); 57 | if (ln == "quit") { 58 | client.write("Bye bye!\n"); 59 | client.close(); 60 | break; 61 | } 62 | 63 | client.write(format("Hash: %08X\n", 64 | () @trusted { return typeid(string).getHash(&ln); }())); 65 | } 66 | } 67 | catch (Exception e) 68 | assert(0, e.msg); 69 | }, "127.0.0.1"); 70 | scope (exit) l1.stopListening; 71 | 72 | // proxy server 73 | auto l2 = listenTCP(0, (client) @safe nothrow { 74 | try { 75 | auto server = connectTCP(l1.bindAddress); 76 | 77 | // pipe server to client as long as the server connection is alive 78 | auto t = runTask!(TCPConnection, TCPConnection)((client, server) nothrow { 79 | scope (failure) assert(false); 80 | scope (exit) client.close(); 81 | pipe(server, client); 82 | logInfo("Proxy 2 out"); 83 | }, client, server); 84 | 85 | // pipe client to server as long as the client connection is alive 86 | scope (exit) { 87 | server.close(); 88 | t.join(); 89 | } 90 | pipe(client, server); 91 | logInfo("Proxy out"); 92 | } catch (Exception e) 93 | assert(0, e.msg); 94 | }, "127.0.0.1"); 95 | scope (exit) l2.stopListening; 96 | 97 | // test server 98 | logInfo("Test protocol implementation on server"); 99 | testProtocol(connectTCP(l1.bindAddress), false); 100 | logInfo("Test protocol implementation on server with forced disconnect"); 101 | testProtocol(connectTCP(l1.bindAddress), true); 102 | 103 | // test proxy 104 | logInfo("Test protocol implementation on proxy"); 105 | testProtocol(connectTCP(l2.bindAddress), false); 106 | logInfo("Test protocol implementation on proxy with forced disconnect"); 107 | testProtocol(connectTCP(l2.bindAddress), true); 108 | } 109 | 110 | void main() 111 | { 112 | runTest(); 113 | } 114 | 115 | string readLine(TCPConnection c) @safe 116 | { 117 | import std.string : indexOf; 118 | 119 | string ret; 120 | while (!c.empty) { 121 | auto buf = () @trusted { return cast(char[])c.peek(); }(); 122 | auto idx = buf.indexOf('\n'); 123 | if (idx < 0) { 124 | ret ~= buf; 125 | c.skip(buf.length); 126 | } else { 127 | ret ~= buf[0 .. idx]; 128 | c.skip(idx+1); 129 | break; 130 | } 131 | } 132 | return ret; 133 | } 134 | -------------------------------------------------------------------------------- /tests/args.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "name": "tests", 4 | "description": "Command-line argument test", 5 | "dependencies": { 6 | "vibe-core": {"path": "../"} 7 | } 8 | } 9 | +/ 10 | module test; 11 | 12 | import vibe.core.args; 13 | import vibe.core.log; 14 | 15 | import std.stdio; 16 | 17 | shared static this() 18 | { 19 | string argtest; 20 | readOption("argtest", &argtest, "Test argument"); 21 | writeln("argtest=", argtest); 22 | } 23 | 24 | void main() 25 | { 26 | finalizeCommandLineOptions(); 27 | } 28 | -------------------------------------------------------------------------------- /tests/args.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | die() { echo "$@" 1>&2 ; exit 1; } 5 | 6 | ( dub --single args.d | sed 's/\x0D$//' | grep -q '^argtest=$' ) || die "Fail (no argument): '`dub --single args.d`'" 7 | ( dub --single args.d -- --argtest=aoeu | sed 's/\x0D$//' | grep -q '^argtest=aoeu$' ) || die "Fail (with argument): '`dub --single args.d -- --argtest=aoeu`'" 8 | ( ( ! dub --single args.d -- --inexisting 2>&1 ) | sed 's/\x0D$//' | grep -qF 'Unrecognized command line option' ) || die "Fail (unknown argument): '`dub --single args.d -- --inexisting 2>&1`'" 9 | 10 | echo 'OK' 11 | -------------------------------------------------------------------------------- /tests/dirwatcher.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "name": "tests", 4 | "description": "Test for vibe.d's DirectoryWatcher.", 5 | "dependencies": { 6 | "vibe-core": {"path": "../"} 7 | } 8 | } 9 | +/ 10 | import vibe.core.core; 11 | import vibe.core.file; 12 | import vibe.core.log; 13 | import vibe.core.path; 14 | import std.algorithm : canFind, all; 15 | import std.file, std.process; 16 | import std.format : format; 17 | import std.conv : to; 18 | import std.path : buildPath; 19 | import std.typecons : No, Yes; 20 | import core.exception : AssertError; 21 | import core.time : msecs, seconds; 22 | 23 | 24 | version (OSX) enum sleepTime = 3.seconds; 25 | else enum sleepTime = 500.msecs; 26 | 27 | 28 | void runTest() 29 | { 30 | auto dir = buildPath(tempDir, format("dirwatcher_test_%d", thisProcessID())); 31 | mkdir(dir); 32 | scope(exit) rmdirRecurse(dir); 33 | 34 | DirectoryWatcher watcher; 35 | try watcher = NativePath(dir).watchDirectory(No.recursive); 36 | catch (AssertError e) { 37 | logInfo("DirectoryWatcher not yet implemented. Skipping test."); 38 | return; 39 | } 40 | DirectoryChange[] changes; 41 | assert(!watcher.readChanges(changes, 1500.msecs)); 42 | 43 | auto foo = dir.buildPath("foo"); 44 | 45 | alias Type = DirectoryChangeType; 46 | static DirectoryChange dc(Type t, string p) { return DirectoryChange(t, NativePath(p)); } 47 | void check(DirectoryChange[] expected) 48 | { 49 | sleep(sleepTime); 50 | assert(watcher.readChanges(changes, 100.msecs), "Could not read changes for " ~ expected.to!string); 51 | assert(expected.all!((a)=> changes.canFind(a))(), "Change is not what was expected, got: " ~ changes.to!string ~ " but expected: " ~ expected.to!string); 52 | assert(!watcher.readChanges(changes, 0.msecs), "Changes were returned when they shouldn't have, for " ~ expected.to!string); 53 | } 54 | 55 | write(foo, null); 56 | check([dc(Type.added, foo)]); 57 | sleep(sleepTime); 58 | write(foo, [0, 1]); 59 | check([dc(Type.modified, foo)]); 60 | remove(foo); 61 | check([dc(Type.removed, foo)]); 62 | write(foo, null); 63 | sleep(sleepTime); 64 | write(foo, [0, 1]); 65 | sleep(sleepTime); 66 | remove(foo); 67 | check([dc(Type.added, foo), dc(Type.modified, foo), dc(Type.removed, foo)]); 68 | 69 | auto subdir = dir.buildPath("subdir"); 70 | mkdir(subdir); 71 | check([dc(Type.added, subdir)]); 72 | auto bar = subdir.buildPath("bar"); 73 | write(bar, null); 74 | assert(!watcher.readChanges(changes, 100.msecs)); 75 | remove(bar); 76 | assert(!watcher.readChanges(changes, 1500.msecs)); 77 | 78 | watcher = NativePath(dir).watchDirectory(Yes.recursive); 79 | assert(!watcher.readChanges(changes, 1500.msecs)); 80 | write(foo, null); 81 | sleep(sleepTime); 82 | write(foo, [0, 1]); 83 | sleep(sleepTime); 84 | remove(foo); 85 | 86 | write(bar, null); 87 | sleep(sleepTime); 88 | write(bar, [0, 1]); 89 | sleep(sleepTime); 90 | remove(bar); 91 | check([dc(Type.added, foo), dc(Type.modified, foo), dc(Type.removed, foo), 92 | dc(Type.added, bar), dc(Type.modified, bar), dc(Type.removed, bar)]); 93 | 94 | write(foo, null); 95 | sleep(sleepTime); 96 | rename(foo, bar); 97 | sleep(sleepTime); 98 | remove(bar); 99 | check([dc(Type.added, foo), dc(Type.removed, foo), dc(Type.added, bar), dc(Type.removed, bar)]); 100 | 101 | } 102 | 103 | int main() 104 | { 105 | int ret = 0; 106 | runTask({ 107 | try runTest(); 108 | catch (Throwable th) { 109 | logError("Test failed: %s", th.toString()); 110 | ret = 1; 111 | } finally exitEventLoop(true); 112 | }); 113 | runEventLoop(); 114 | return ret; 115 | } 116 | -------------------------------------------------------------------------------- /tests/issue-104-unreferenced-periodic-timer.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import vibe.core.core; 8 | 9 | import core.memory; 10 | import core.time; 11 | 12 | 13 | int main() 14 | { 15 | setTimer(10.seconds, { assert(false, "Event loop didn't exit in time."); }); 16 | 17 | // make sure that periodic timers for which no explicit reference is stored 18 | // are still getting invoked periodically 19 | size_t i = 0; 20 | setTimer(50.msecs, { if (i++ == 3) exitEventLoop(); }, true); 21 | 22 | return runEventLoop(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/issue-110-close-while-waitForData.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import core.time; 8 | import std.algorithm.searching : countUntil; 9 | import vibe.core.core; 10 | import vibe.core.net; 11 | 12 | ushort port; 13 | void main() 14 | { 15 | TCPListener listener; 16 | try { 17 | listener = listenTCP(0, (conn) @safe nothrow { 18 | sleepUninterruptible(200.msecs); 19 | }, "127.0.0.1"); 20 | port = listener.bindAddress.port; 21 | } catch (Exception e) assert(false, e.msg); 22 | 23 | TCPConnection tcp; 24 | try tcp = connectTCP("127.0.0.1", port); 25 | catch (Exception e) assert(false, e.msg); 26 | runTask({ 27 | sleepUninterruptible(10.msecs); 28 | tcp.close(); 29 | }); 30 | assert(!tcp.waitForData()); 31 | assert(!tcp.connected); 32 | listener.stopListening(); 33 | } 34 | -------------------------------------------------------------------------------- /tests/issue-115-connect-fail-leak.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import vibe.core.core; 8 | import vibe.core.log; 9 | import vibe.core.net; 10 | 11 | import core.time; 12 | 13 | 14 | void main() 15 | { 16 | // make sure that the internal driver is initialized, so that 17 | // base resources are all allocated 18 | sleep(1.msecs); 19 | 20 | auto initial = determineSocketCount(); 21 | 22 | foreach (i; 0 .. 3) 23 | test(); 24 | 25 | assert(determineSocketCount() <= initial + 1, "Sockets leaked!"); 26 | } 27 | 28 | void test() 29 | { 30 | TCPConnection conn; 31 | try { 32 | conn = connectTCP("127.0.0.1", 16565); 33 | assert(false, "Didn't expect TCP connection on port 16565 to succeed"); 34 | } catch (Exception) { } 35 | } 36 | 37 | size_t determineSocketCount() 38 | { 39 | import std.algorithm.searching : count; 40 | import std.exception : enforce; 41 | import std.range : iota; 42 | 43 | version (Posix) { 44 | import core.sys.posix.sys.resource : getrlimit, rlimit, RLIMIT_NOFILE; 45 | import core.sys.posix.fcntl : fcntl, F_GETFD; 46 | 47 | rlimit rl; 48 | enforce(getrlimit(RLIMIT_NOFILE, &rl) == 0); 49 | return iota(rl.rlim_cur).count!((fd) => fcntl(cast(int)fd, F_GETFD) != -1); 50 | } else { 51 | import core.sys.windows.winsock2 : getsockopt, SOL_SOCKET, SO_TYPE; 52 | 53 | int st; 54 | int stlen = st.sizeof; 55 | return iota(65536).count!(s => getsockopt(s, SOL_SOCKET, SO_TYPE, cast(void*)&st, &stlen) == 0); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/issue-157-consume-closed-channel.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module tests; 6 | 7 | import vibe.core.channel; 8 | import vibe.core.core; 9 | import core.time; 10 | 11 | void main() 12 | { 13 | auto tm = setTimer(10.seconds, { assert(false, "Test timeout."); }); 14 | scope (exit) tm.stop(); 15 | 16 | auto ch = createChannel!int(); 17 | 18 | auto p = runTask(() nothrow { 19 | sleepUninterruptible(1.seconds); 20 | ch.close(); 21 | }); 22 | 23 | auto c = runTask(() nothrow { 24 | while (!ch.empty) { 25 | try ch.consumeOne(); 26 | catch (Exception e) assert(false, e.msg); 27 | } 28 | }); 29 | 30 | p.join(); 31 | c.join(); 32 | } 33 | -------------------------------------------------------------------------------- /tests/issue-161-multiple-joiners.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | dependency "vibe-core" path=".." 4 | debugVersions "VibeTaskLog" "VibeAsyncLog" 5 | +/ 6 | module tests; 7 | 8 | import vibe.core.core; 9 | import vibe.core.log; 10 | import vibe.core.sync; 11 | import core.time; 12 | import core.stdc.stdlib : exit; 13 | 14 | 15 | void main() 16 | { 17 | setTimer(5.seconds, { logError("Test has hung."); exit(1); }); 18 | 19 | Task t; 20 | 21 | runTask({ 22 | t = runTask({ try sleep(100.msecs); catch (Exception e) assert(false, e.msg); }); 23 | try t.join(); 24 | catch (Exception e) assert(false, e.msg); 25 | }); 26 | 27 | // let the outer task run and start the inner task 28 | yield(); 29 | // let the outer task get another execution slice to write to t 30 | yield(); 31 | 32 | assert(t && t.running); 33 | 34 | t.join(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/issue-267-pipe.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module tests; 6 | import core.time, vibe.core.core, vibe.core.process; 7 | 8 | void main() 9 | { 10 | version (Windows) { 11 | import vibe.core.log : logInfo; 12 | logInfo("Skipping pipe test on Windows"); 13 | } else { 14 | auto p = pipe(); 15 | runTask(() 16 | { 17 | sleepUninterruptible(10.msecs); 18 | exitEventLoop(); 19 | }); 20 | runEventLoop(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/issue-331-tcp-connect-timeout.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import core.time; 8 | import vibe.core.core; 9 | import vibe.core.log; 10 | import vibe.core.net; 11 | import std.stdio; 12 | 13 | void main() 14 | { 15 | // warm up event loop to ensure any lazily created file descriptors are 16 | // there 17 | sleep(10.msecs); 18 | 19 | auto initial_count = determineSocketCount(); 20 | 21 | TCPConnection conn; 22 | try { 23 | conn = connectTCP("192.168.0.152", 1234, null, 0, 1.seconds); // some ip:port that would cause connection timeout 24 | assert(false); 25 | } catch (Exception e) { 26 | logInfo("Connection failed as expected..."); 27 | } 28 | 29 | // give the driver some time to actually cancel, if needed 30 | sleep(100.msecs); 31 | 32 | assert(determineSocketCount() == initial_count, "Socket leaked during connect timeout"); 33 | } 34 | 35 | size_t determineSocketCount() 36 | { 37 | import std.algorithm.searching : count; 38 | import std.exception : enforce; 39 | import std.range : iota; 40 | 41 | version (Posix) { 42 | import core.sys.posix.sys.resource : getrlimit, rlimit, RLIMIT_NOFILE; 43 | import core.sys.posix.fcntl : fcntl, F_GETFD; 44 | 45 | rlimit rl; 46 | enforce(getrlimit(RLIMIT_NOFILE, &rl) == 0); 47 | return iota(rl.rlim_cur).count!((fd) => fcntl(cast(int)fd, F_GETFD) != -1); 48 | } else { 49 | import core.sys.windows.winsock2 : getsockopt, SOL_SOCKET, SO_TYPE; 50 | 51 | int st; 52 | int stlen = st.sizeof; 53 | return iota(65536).count!(s => getsockopt(s, SOL_SOCKET, SO_TYPE, cast(void*)&st, &stlen) == 0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/issue-58-task-already-scheduled.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module tests; 6 | 7 | import vibe.core.sync; 8 | import vibe.core.core; 9 | import std.datetime; 10 | import core.atomic; 11 | 12 | shared ManualEvent ev; 13 | shared size_t counter; 14 | 15 | enum ntasks = 500; 16 | 17 | shared static this() 18 | { 19 | ev = createSharedManualEvent(); 20 | } 21 | 22 | void main() 23 | { 24 | setTaskStackSize(64*1024); 25 | 26 | runTask({ 27 | foreach (x; 0 .. ntasks) 28 | runWorkerTask(&worker); 29 | }); 30 | 31 | setTimer(dur!"msecs"(10), { ev.emit(); }); 32 | setTimer(dur!"seconds"(60), { assert(false, "Timers didn't fire within the time limit"); }); 33 | 34 | runApplication(); 35 | 36 | assert(atomicLoad(counter) == ntasks, "Event loop exited prematurely."); 37 | } 38 | 39 | void worker() nothrow 40 | { 41 | ev.wait(0); 42 | ev.emit(); 43 | setTimer(dur!"seconds"(1), { 44 | auto c = atomicOp!"+="(counter, 1); 45 | if (c == ntasks) exitEventLoop(true); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /tests/issue-66-yield-eventloop-exit.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module tests; 6 | 7 | import vibe.core.core; 8 | 9 | void main() 10 | { 11 | bool visited = false; 12 | runTask({ 13 | try yield(); 14 | catch (Exception e) assert(false, e.msg); 15 | visited = true; 16 | exitEventLoop(); 17 | }); 18 | runApplication(); 19 | assert(visited); 20 | } 21 | -------------------------------------------------------------------------------- /tests/pull-218-resolvehost-dns-address-family.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import std.socket: AddressFamily; 8 | 9 | import vibe.core.core; 10 | import vibe.core.net; 11 | 12 | void main() 13 | { 14 | runTask({ 15 | scope(exit) exitEventLoop(); 16 | 17 | auto addr = resolveHost("ip6.me", AddressFamily.INET); 18 | assert(addr.family == AddressFamily.INET); 19 | 20 | addr = resolveHost("ip6.me", AddressFamily.INET6); 21 | assert(addr.family == AddressFamily.INET6); 22 | 23 | try 24 | { 25 | resolveHost("ip4only.me", AddressFamily.INET6); 26 | assert(false); 27 | } 28 | catch(Exception) {} 29 | 30 | try 31 | { 32 | resolveHost("ip6only.me", AddressFamily.INET); 33 | assert(false); 34 | } 35 | catch(Exception) {} 36 | }); 37 | 38 | runEventLoop(); 39 | } 40 | -------------------------------------------------------------------------------- /tests/std.concurrency.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | description "Tests vibe.d's std.concurrency integration" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | 8 | import vibe.core.core; 9 | import vibe.core.log; 10 | import std.algorithm; 11 | import std.concurrency; 12 | import core.atomic; 13 | import core.time; 14 | import core.stdc.stdlib : exit; 15 | 16 | __gshared Tid t1, t2; 17 | shared watchdog_count = 0; 18 | 19 | void main() 20 | { 21 | t1 = spawn({ 22 | // ensure that asynchronous operations can run in parallel to receive() 23 | int wc = 0; 24 | MonoTime stime = MonoTime.currTime; 25 | runTask({ 26 | while (true) { 27 | sleepUntil((wc + 1) * 250.msecs, stime, 200.msecs); 28 | wc++; 29 | logInfo("Watchdog receiver %s", wc); 30 | } 31 | }); 32 | 33 | bool finished = false; 34 | try while (!finished) { 35 | logDebug("receive1"); 36 | receive( 37 | (string msg) { 38 | logInfo("Received string message: %s", msg); 39 | }, 40 | (int msg) { 41 | logInfo("Received int message: %s", msg); 42 | }); 43 | logDebug("receive2"); 44 | receive( 45 | (double msg) { 46 | logInfo("Received double: %s", msg); 47 | }, 48 | (int a, int b, int c) { 49 | logInfo("Received iii: %s %s %s", a, b, c); 50 | 51 | if (a == 1 && b == 2 && c == 3) 52 | finished = true; 53 | }); 54 | } 55 | catch (Exception e) assert(false, "Receiver thread failed: "~e.msg); 56 | 57 | logInfo("Receive loop finished."); 58 | version (OSX) enum tolerance = 4; // macOS CI VMs have particularly bad timing behavior 59 | else enum tolerance = 1; 60 | if (wc < 6 * 4 - tolerance) { 61 | logError("Receiver watchdog failure."); 62 | exit(1); 63 | } 64 | logInfo("Exiting normally"); 65 | }); 66 | 67 | t2 = spawn({ 68 | MonoTime stime = MonoTime.currTime; 69 | 70 | scope (failure) assert(false); 71 | sleepUntil(1.seconds, stime, 900.msecs); 72 | logInfo("send Hello World"); 73 | t1.send("Hello, World!"); 74 | 75 | sleepUntil(2.seconds, stime, 900.msecs); 76 | logInfo("send int 1"); 77 | t1.send(1); 78 | 79 | sleepUntil(3.seconds, stime, 900.msecs); 80 | logInfo("send double 1.2"); 81 | t1.send(1.2); 82 | 83 | sleepUntil(4.seconds, stime, 900.msecs); 84 | logInfo("send int 2"); 85 | t1.send(2); 86 | 87 | sleepUntil(5.seconds, stime, 900.msecs); 88 | logInfo("send 3xint 1 2 3"); 89 | t1.send(1, 2, 3); 90 | 91 | sleepUntil(6.seconds, stime, 900.msecs); 92 | logInfo("send string Bye bye"); 93 | t1.send("Bye bye"); 94 | 95 | sleep(100.msecs); 96 | logInfo("Exiting."); 97 | exitEventLoop(true); 98 | }); 99 | 100 | runApplication(); 101 | } 102 | 103 | // corrects for small timing inaccuracies to avoid the counter 104 | // getting systematically out of sync when sleep timing is inaccurate 105 | void sleepUntil(Duration until, MonoTime start_time, Duration min_sleep) 106 | nothrow { 107 | auto tm = MonoTime.currTime; 108 | auto timeout = max(start_time - tm + until, min_sleep); 109 | try sleep(timeout); 110 | catch (Exception e) assert(false, e.msg); 111 | } 112 | -------------------------------------------------------------------------------- /tests/tcp-read-timeout.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | dependency "vibe-core" path=".." 3 | +/ 4 | module tests; 5 | 6 | import vibe.core.core; 7 | import vibe.core.log; 8 | import vibe.core.net; 9 | 10 | import eventcore.driver : IOMode; 11 | import std.algorithm.comparison; 12 | import std.exception; 13 | import std.datetime.stopwatch; 14 | import std.stdio; 15 | 16 | void handleTcp(ref TCPConnection stream) 17 | @trusted nothrow { 18 | assumeWontThrow(stream.readTimeout = dur!"msecs"(500)); 19 | 20 | ubyte[] buffer = new ubyte[2048]; 21 | bool got_timeout = false; 22 | 23 | while (true) { 24 | ubyte[] tmp; 25 | auto sw = StopWatch(AutoStart.yes); 26 | try { 27 | tmp = buffer[0 .. stream.read(buffer, IOMode.once)]; 28 | } catch (ReadTimeoutException e) { 29 | assert (sw.peek >= 500.msecs, "Timeout occurred too early"); 30 | got_timeout = true; 31 | } catch (Exception e) { 32 | assert(false, "Unexpected exception on server side: " ~ e.msg); 33 | } 34 | 35 | if (tmp == "end") break; 36 | else stream.write(tmp).assumeWontThrow; 37 | } 38 | 39 | assert(got_timeout, "Expected timeout did not occur"); 40 | stream.close(); 41 | } 42 | 43 | void main() 44 | { 45 | version (Windows) logWarn("SKIPPING TEST DUE TO EVENTCORE BUG #225"); 46 | else { 47 | auto tm = setTimer(10.seconds, { assert(false, "Test timed out"); }); 48 | 49 | auto listeners = listenTCP(17000, (conn) @safe nothrow {handleTcp(conn);}); 50 | 51 | // closes the listening sockets 52 | scope (exit) 53 | foreach (l; listeners) 54 | l.stopListening(); 55 | 56 | // sleep one seconds let server start 57 | sleep(dur!"seconds"(1)); 58 | ubyte[] buffer = new ubyte[512]; 59 | auto client = connectTCP("127.0.0.1", 17000); 60 | client.readTimeout = 500.msecs; 61 | assert(client.connected, "connect server error!"); 62 | auto send = "hello word"; 63 | client.write(send); 64 | auto readed = client.read(buffer,IOMode.once); 65 | auto tmp = buffer[0 .. readed]; 66 | assert(tmp == send, "Client received unexpected echo data"); 67 | sleep(700.msecs); 68 | client.write("end"); 69 | assert(client.empty); 70 | client.close(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/vibe.core.concurrency.1408.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "std.concurrency integration issue" 4 | dependency "vibe-core" path="../" 5 | versions "VibeDefaultMain" 6 | +/ 7 | module test; 8 | 9 | import vibe.core.concurrency; 10 | import vibe.core.core; 11 | import vibe.core.log; 12 | import core.time : msecs; 13 | import std.functional : toDelegate; 14 | 15 | void test() 16 | nothrow { 17 | scope (failure) assert(false); 18 | 19 | auto t = runTask({ 20 | scope (failure) assert(false); 21 | bool gotit; 22 | receive((int i) { assert(i == 10); gotit = true; }); 23 | assert(gotit); 24 | sleep(10.msecs); 25 | }); 26 | 27 | t.tid.send(10); 28 | t.tid.send(11); // never received 29 | t.join(); 30 | 31 | // ensure that recycled fibers will get a clean message queue 32 | auto t2 = runTask({ 33 | scope (failure) assert(false); 34 | bool gotit; 35 | receive((int i) { assert(i == 12); gotit = true; }); 36 | assert(gotit); 37 | }); 38 | t2.tid.send(12); 39 | t2.join(); 40 | 41 | // test worker tasks 42 | auto t3 = runWorkerTaskH({ 43 | scope (failure) assert(false); 44 | bool gotit; 45 | receive((int i) { assert(i == 13); gotit = true; }); 46 | assert(gotit); 47 | }); 48 | 49 | t3.tid.send(13); 50 | sleep(10.msecs); 51 | 52 | logInfo("Success."); 53 | 54 | exitEventLoop(true); 55 | } 56 | 57 | shared static this() 58 | { 59 | setLogFormat(FileLogger.Format.threadTime, FileLogger.Format.threadTime); 60 | runTask(toDelegate(&test)); 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /tests/vibe.core.core.1590.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "Semaphore hang" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | import std.stdio; 8 | import std.socket; 9 | import std.datetime; 10 | import std.functional; 11 | import core.time; 12 | import vibe.core.core; 13 | import vibe.core.log; 14 | import vibe.core.concurrency; 15 | import vibe.core.connectionpool; 16 | 17 | class Conn {} 18 | 19 | void main() 20 | { 21 | runTask({ 22 | scope (failure) assert(false); 23 | 24 | // create pool with 2 max connections 25 | bool[int] results; 26 | auto pool = new ConnectionPool!Conn({ return new Conn; }, 2); 27 | auto task = Task.getThis(); // main task 28 | void worker(int id) nothrow { 29 | scope (failure) assert(false); 30 | { 31 | auto conn = pool.lockConnection(); // <-- worker(4) hangs here 32 | sleep(1.msecs); // <-- important, without sleep everything works fine 33 | } 34 | task.send(id); // send signal to the main task 35 | } 36 | // run 4 tasks (2 * pool max connections) 37 | runTask(&worker, 1); 38 | runTask(&worker, 2); 39 | runTask(&worker, 3); 40 | runTask(&worker, 4); 41 | 42 | // wait for first signal and run one more task 43 | results[receiveOnly!int] = true; 44 | runTask(&worker, 5); 45 | 46 | // wait for other signals 47 | results[receiveOnly!int] = true; 48 | results[receiveOnly!int] = true; 49 | results[receiveOnly!int] = true; 50 | results[receiveOnly!int] = true; 51 | 52 | foreach (r; results.byKey) 53 | assert(r >= 1 && r <= 5); 54 | 55 | exitEventLoop(); 56 | }); 57 | 58 | setTimer(1.seconds, { assert(false, "Test has hung."); }); 59 | 60 | runEventLoop(); 61 | } 62 | 63 | -------------------------------------------------------------------------------- /tests/vibe.core.core.1742.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "Semaphore hang" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | import std.stdio; 8 | import std.socket; 9 | import std.datetime; 10 | import std.functional; 11 | import core.time; 12 | import vibe.core.core; 13 | import vibe.core.log; 14 | import vibe.core.concurrency; 15 | import vibe.core.connectionpool; 16 | 17 | class Conn {} 18 | 19 | void main() 20 | { 21 | auto g = new Generator!int({ 22 | auto t = runTask({}); 23 | t.join(); 24 | yield(2); 25 | }); 26 | assert(!g.empty); 27 | assert(g.front == 2); 28 | g.popFront(); 29 | assert(g.empty); 30 | 31 | runTask({ 32 | scope (failure) assert(false); 33 | 34 | auto g2 = new Generator!int({ 35 | auto t = runTask({}); 36 | t.join(); 37 | yield(1); 38 | }); 39 | assert(!g2.empty); 40 | assert(g2.front == 1); 41 | g2.popFront(); 42 | assert(g2.empty); 43 | exitEventLoop(); 44 | }); 45 | 46 | setTimer(5.seconds, { 47 | assert(false, "Test has hung."); 48 | }); 49 | 50 | runApplication(); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tests/vibe.core.core.refcount.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "Invalid ref count after runTask" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | import vibe.core.core; 8 | import std.stdio; 9 | 10 | struct RC { 11 | int* rc; 12 | this(int* rc) nothrow { this.rc = rc; } 13 | this(this) nothrow { 14 | if (rc) { 15 | (*rc)++; 16 | try writefln("addref %s", *rc); 17 | catch (Exception e) assert(false, e.msg); 18 | } 19 | } 20 | ~this() nothrow { 21 | if (rc) { 22 | (*rc)--; 23 | try writefln("release %s", *rc); 24 | catch (Exception e) assert(false, e.msg); 25 | } 26 | } 27 | } 28 | 29 | void main() 30 | { 31 | int rc = 1; 32 | bool done = false; 33 | 34 | { 35 | auto s = RC(&rc); 36 | assert(rc == 1); 37 | runTask((RC st) nothrow { 38 | assert(rc == 2); 39 | st = RC.init; 40 | assert(rc == 1); 41 | exitEventLoop(); 42 | done = true; 43 | }, s); 44 | assert(rc == 2); 45 | } 46 | 47 | assert(rc == 1); 48 | 49 | runEventLoop(); 50 | 51 | assert(rc == 0); 52 | assert(done); 53 | } 54 | -------------------------------------------------------------------------------- /tests/vibe.core.file.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import vibe.core.file; 8 | import std.exception; 9 | import std.file : rmdirRecurse; 10 | import std.typecons : Yes; 11 | 12 | enum ubyte[] bytes(BYTES...) = [BYTES]; 13 | 14 | void main() 15 | { 16 | auto f = openFile("têst.dat", FileMode.createTrunc); 17 | assert(f.size == 0); 18 | assert(f.tell == 0); 19 | f.write(bytes!(1, 2, 3, 4, 5)); 20 | assert(f.size == 5); 21 | assert(f.tell == 5); 22 | f.seek(0); 23 | assert(f.tell == 0); 24 | f.write(bytes!(1, 2, 3, 4, 5)); 25 | assert(f.size == 5); 26 | assert(f.tell == 5); 27 | f.write(bytes!(6, 7, 8, 9, 10)); 28 | assert(f.size == 10); 29 | assert(f.tell == 10); 30 | 31 | auto fcopy = f; 32 | ubyte[5] dst; 33 | fcopy.seek(2); 34 | assert(f.tell == 2); 35 | fcopy = FileStream.init; 36 | 37 | f.read(dst); 38 | assert(f.tell == 7); 39 | assert(dst[] == bytes!(3, 4, 5, 6, 7)); 40 | f.close(); 41 | 42 | auto fi = getFileInfo("têst.dat"); 43 | assert(fi.name == "têst.dat"); 44 | assert(fi.isFile); 45 | assert(!fi.isDirectory); 46 | assert(!fi.isSymlink); 47 | assert(!fi.hidden); 48 | assert(fi.size == 10); 49 | 50 | assertThrown(getFileInfo("*impossible:file?")); 51 | 52 | bool found = false; 53 | listDirectory(".", (fi) { 54 | if (fi.name != "têst.dat") return true; 55 | assert(fi.isFile); 56 | assert(!fi.isDirectory); 57 | assert(!fi.isSymlink); 58 | assert(!fi.hidden); 59 | assert(fi.size == 10); 60 | found = true; 61 | return true; 62 | }); 63 | assert(found, "listDirectory did not find test file."); 64 | 65 | removeFile("têst.dat"); 66 | 67 | 68 | createDirectory("testdir/dir", Yes.recursive); 69 | listDirectory("testdir", (fi) { 70 | assert(fi.name == "dir"); 71 | assert(fi.isDirectory); 72 | assert(!fi.isSymlink); 73 | assert(!fi.hidden); 74 | return true; 75 | }); 76 | rmdirRecurse("testdir"); 77 | } 78 | -------------------------------------------------------------------------------- /tests/vibe.core.file.gcleak.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | dependency "vibe-core" path=".." 3 | +/ 4 | module test; 5 | 6 | import vibe.core.file; 7 | 8 | 9 | // this test ensures that leaking an open FileStream will not crash the 10 | // application 11 | void main() 12 | { 13 | auto fil = new FileStream; 14 | *fil = openFile("test.tmp", FileMode.createTrunc); 15 | fil = null; 16 | 17 | ubyte[] arr; 18 | foreach (i; 0 .. 1000) 19 | arr ~= "1234567890"; 20 | } 21 | -------------------------------------------------------------------------------- /tests/vibe.core.net.1376.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "tests" 3 | description "TCP disconnect task issue" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | 8 | import vibe.core.core; 9 | import vibe.core.net; 10 | import core.time : msecs; 11 | 12 | void main() 13 | { 14 | auto l = listenTCP(0, (conn) @safe nothrow { 15 | try { 16 | auto td = runTask!TCPConnection((conn) { 17 | ubyte [3] buf; 18 | try { 19 | conn.read(buf); 20 | assert(false, "Expected read() to throw an exception."); 21 | } catch (Exception) {} // expected 22 | }, conn); 23 | sleep(10.msecs); 24 | conn.close(); 25 | } 26 | catch (Exception e) 27 | assert(0, e.msg); 28 | }, "127.0.0.1"); 29 | 30 | runTask({ 31 | try { 32 | auto conn = connectTCP("127.0.0.1", l.bindAddress.port); 33 | conn.write("a"); 34 | conn.close(); 35 | } catch (Exception e) assert(false, e.msg); 36 | 37 | try { 38 | auto conn = connectTCP("127.0.0.1", l.bindAddress.port); 39 | conn.close(); 40 | } catch (Exception e) assert(false, e.msg); 41 | 42 | try sleep(50.msecs); 43 | catch (Exception e) assert(false, e.msg); 44 | 45 | exitEventLoop(); 46 | }); 47 | 48 | runApplication(); 49 | 50 | l.stopListening(); 51 | } 52 | -------------------------------------------------------------------------------- /tests/vibe.core.net.1429.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | description "TCP disconnect task issue" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | 8 | import vibe.core.core; 9 | import vibe.core.log : logInfo; 10 | import vibe.core.net; 11 | import core.time : MonoTime, msecs; 12 | 13 | void main() 14 | { 15 | auto udp = listenUDP(11429, "127.0.0.1"); 16 | 17 | runTask({ 18 | sleepUninterruptible(500.msecs); 19 | assert(false, "Receive call did not return in a timely manner. Killing process."); 20 | }); 21 | 22 | runTask({ 23 | auto start = MonoTime.currTime(); 24 | try { 25 | udp.recv(100.msecs); 26 | assert(false, "Timeout did not occur."); 27 | } catch (Exception e) { 28 | auto duration = MonoTime.currTime() - start; 29 | version (OSX) enum maxtolerance = 150.msecs; 30 | else enum maxtolerance = 50.msecs; 31 | assert(duration >= 99.msecs, "Timeout occurred too early"); 32 | assert(duration >= 99.msecs && duration < 100.msecs + maxtolerance, 33 | "Timeout occurred too late."); 34 | logInfo("UDP receive timeout test was successful."); 35 | exitEventLoop(); 36 | } 37 | }); 38 | 39 | runEventLoop(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/vibe.core.net.1441.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | description "TCP disconnect task issue" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | 8 | import vibe.core.core; 9 | import vibe.core.net; 10 | import core.time : msecs; 11 | import std.string : representation; 12 | 13 | void main() 14 | { 15 | import vibe.core.log; 16 | bool done = false; 17 | listenTCP(11375, (conn) { 18 | try { 19 | conn.write("foo".representation); 20 | conn.close(); 21 | } catch (Exception e) { 22 | assert(false, e.msg); 23 | } 24 | done = true; 25 | }); 26 | 27 | runTask({ 28 | scope (failure) assert(false); 29 | 30 | auto conn = connectTCP("127.0.0.1", 11375); 31 | conn.close(); 32 | 33 | sleep(50.msecs); 34 | assert(done); 35 | 36 | exitEventLoop(); 37 | }); 38 | 39 | runEventLoop(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/vibe.core.net.1452.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | description "Invalid memory operation on TCP connection leakage at shutdown" 4 | dependency "vibe-core" path="../" 5 | debugVersions "VibeAsyncLog" 6 | +/ 7 | module test; 8 | 9 | import vibe.core.core; 10 | import vibe.core.net; 11 | import core.time : msecs; 12 | 13 | class C { 14 | TCPConnection m_conn; 15 | 16 | this() 17 | { 18 | m_conn = connectTCP("example.com", 443); 19 | } 20 | 21 | ~this() 22 | { 23 | m_conn.close(); 24 | } 25 | } 26 | 27 | void main() 28 | { 29 | auto c = new C; 30 | // let druntime collect c during shutdown 31 | } 32 | -------------------------------------------------------------------------------- /tests/vibe.core.net.1726.d: -------------------------------------------------------------------------------- 1 | /++ dub.sdl: 2 | name "tests" 3 | description "TCP disconnect task issue" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module tests; 7 | 8 | import vibe.core.core; 9 | import vibe.core.net; 10 | import core.time : msecs; 11 | import vibe.core.log; 12 | 13 | ubyte[] buf; 14 | 15 | void performTest(bool reverse) 16 | { 17 | auto l = listenTCP(11375, (conn) @safe nothrow { 18 | bool read_ex = false; 19 | bool write_ex = false; 20 | auto rt = runTask!TCPConnection((conn) { 21 | try { 22 | conn.read(buf); 23 | assert(false, "Expected read() to throw an exception."); 24 | } catch (Exception) { 25 | read_ex = true; 26 | conn.close(); 27 | logInfo("read out"); 28 | } // expected 29 | }, conn); 30 | auto wt = runTask!TCPConnection((conn) { 31 | try sleep(reverse ? 100.msecs : 20.msecs); // give the connection time to establish 32 | catch (Exception e) assert(false, e.msg); 33 | try { 34 | // write enough to let the connection block long enough to let 35 | // the remote end close the connection 36 | // NOTE: on Windows, the first write() can actually complete 37 | // immediately, but the second one blocks 38 | foreach (i; 0 .. 2) conn.write(buf); 39 | assert(false, "Expected write() to throw an exception."); 40 | } catch (Exception) { 41 | write_ex = true; 42 | conn.close(); 43 | logInfo("write out"); 44 | } // expected 45 | }, conn); 46 | 47 | try { 48 | rt.join(); 49 | wt.join(); 50 | } catch (Exception e) 51 | assert(0, e.msg); 52 | assert(read_ex, "No read exception thrown"); 53 | assert(write_ex, "No write exception thrown"); 54 | logInfo("Test has finished successfully."); 55 | exitEventLoop(); 56 | }, "127.0.0.1"); 57 | 58 | runTask({ 59 | try { 60 | auto conn = connectTCP("127.0.0.1", 11375); 61 | sleep(reverse ? 20.msecs : 100.msecs); 62 | conn.close(); 63 | } catch (Exception e) assert(false, e.msg); 64 | }); 65 | 66 | runEventLoop(); 67 | 68 | l.stopListening(); 69 | } 70 | 71 | void main() 72 | { 73 | setTimer(10000.msecs, { assert(false, "Test has hung."); }); 74 | buf = new ubyte[512*1024*1024]; 75 | 76 | performTest(false); 77 | performTest(true); 78 | } 79 | -------------------------------------------------------------------------------- /tests/vibe.core.process.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | description "Subprocesses" 4 | dependency "vibe-core" path="../" 5 | +/ 6 | module test; 7 | 8 | import core.thread; 9 | import vibe.core.log; 10 | import vibe.core.core; 11 | import vibe.core.process; 12 | import std.algorithm; 13 | import std.array; 14 | import std.range; 15 | 16 | void testEcho() 17 | { 18 | foreach (i; 0..100) { 19 | auto procPipes = pipeProcess(["echo", "foo bar"], Redirect.stdout); 20 | 21 | auto output = procPipes.stdout.collectOutput(); 22 | 23 | assert(procPipes.process.wait() == 0); 24 | assert(procPipes.process.exited); 25 | 26 | assert(output == "foo bar\n"); 27 | } 28 | } 29 | 30 | void testCat() 31 | { 32 | auto procPipes = pipeProcess(["cat"]); 33 | 34 | string output; 35 | auto outputTask = runTask({ 36 | try output = procPipes.stdout.collectOutput(); 37 | catch (Exception e) assert(false, e.msg); 38 | }); 39 | 40 | auto inputs = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] 41 | .map!(s => s ~ "\n") 42 | .repeat(4000).join.array; 43 | foreach (input; inputs) { 44 | procPipes.stdin.write(input); 45 | } 46 | 47 | procPipes.stdin.close(); 48 | assert(procPipes.process.wait() == 0); 49 | 50 | outputTask.join(); 51 | 52 | assert(output == inputs.join()); 53 | } 54 | 55 | void testStderr() 56 | { 57 | auto program = q{ 58 | foreach (line; stdin.byLine()) 59 | stderr.writeln(line); 60 | }; 61 | auto procPipes = pipeProcess(["rdmd", "--eval", program], Redirect.stdin | Redirect.stderr); 62 | 63 | // Wait for rdmd to compile 64 | sleep(3.seconds); 65 | 66 | string output; 67 | auto outputTask = runTask({ 68 | try output = procPipes.stderr.collectOutput(); 69 | catch (Exception e) assert(false, e.msg); 70 | }); 71 | 72 | auto inputs = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] 73 | .map!(s => s ~ "\n") 74 | .repeat(4000).join.array; 75 | foreach (input; inputs) { 76 | procPipes.stdin.write(input); 77 | } 78 | 79 | procPipes.stdin.close(); 80 | assert(procPipes.process.wait() == 0); 81 | 82 | outputTask.join(); 83 | 84 | assert(output == inputs.join); 85 | } 86 | 87 | void testRandomDeath() 88 | { 89 | auto program = q{ 90 | import core.thread; 91 | import std.random; 92 | Thread.sleep(dur!"msecs"(uniform(0, 1000))); 93 | }; 94 | // Prime rdmd 95 | execute(["rdmd", "--eval", program]); 96 | 97 | foreach (i; 0..20) { 98 | auto process = spawnProcess(["rdmd", "--eval", program]); 99 | 100 | assert(!process.exited); 101 | 102 | sleep(800.msecs); 103 | try { 104 | process.kill(); 105 | } catch (Exception e) { 106 | } 107 | process.wait(); 108 | 109 | assert(process.exited); 110 | } 111 | } 112 | 113 | void testIgnoreSigterm() 114 | { 115 | auto program = q{ 116 | import core.thread; 117 | import core.sys.posix.signal; 118 | 119 | signal(SIGINT, SIG_IGN); 120 | signal(SIGTERM, SIG_IGN); 121 | 122 | foreach (line; stdin.byLine()) { 123 | writeln(line); 124 | stdout.flush(); 125 | } 126 | 127 | // Zombie 128 | while (true) Thread.sleep(100.dur!"msecs"); 129 | }; 130 | auto procPipes = pipeProcess( 131 | ["rdmd", "--eval", program], 132 | Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout); 133 | 134 | string output; 135 | auto outputTask = runTask({ 136 | try output = procPipes.stdout.collectOutput(); 137 | catch (Exception e) assert(false, e.msg); 138 | }); 139 | 140 | assert(!procPipes.process.exited); 141 | 142 | // Give the program some time to compile and install the signal handler 143 | sleep(4.seconds); 144 | 145 | procPipes.process.kill(); 146 | procPipes.stdin.write("foo\n"); 147 | 148 | assert(!procPipes.process.exited); 149 | 150 | assert(procPipes.process.waitOrForceKill(2.seconds) == -9); 151 | 152 | assert(procPipes.process.exited); 153 | 154 | outputTask.join(); 155 | 156 | assert(output == "foo\n"); 157 | } 158 | 159 | void testSimpleShell() 160 | { 161 | auto res = executeShell("echo foo"); 162 | 163 | assert(res.status == 0); 164 | assert(res.output == "foo\n"); 165 | } 166 | 167 | void testLineEndings() 168 | { 169 | auto program = q{ 170 | write("linux\n"); 171 | write("os9\r"); 172 | write("win\r\n"); 173 | }; 174 | auto res = execute(["rdmd", "--eval", program]); 175 | 176 | assert(res.status == 0); 177 | assert(res.output == "linux\nos9\rwin\r\n"); 178 | } 179 | 180 | void main() 181 | { 182 | import core.stdc.stdlib : abort; 183 | import core.time; 184 | import std.meta : AliasSeq; 185 | 186 | 187 | version (Windows) { 188 | import vibe.core.log : logInfo; 189 | logInfo("Skipping process test on Windows"); 190 | } else { 191 | runTask({ 192 | alias Tasks = AliasSeq!( 193 | testEcho, 194 | testCat, 195 | testStderr, 196 | testRandomDeath, 197 | testIgnoreSigterm, 198 | testSimpleShell, 199 | testLineEndings 200 | ); 201 | 202 | static foreach (alias task; Tasks) {{ 203 | auto t = runTask({ 204 | logInfo("Running test %s...", __traits(identifier, task)); 205 | auto tm = setTimer(60.seconds, { 206 | logError("Test %s timed out!", __traits(identifier, task)); 207 | abort(); 208 | }); 209 | try { 210 | task(); 211 | } catch (Exception e) { 212 | logError("Test %s failed: %s", __traits(identifier, task), e); 213 | abort(); 214 | } 215 | tm.stop(); 216 | }); 217 | t.join(); 218 | }} 219 | 220 | exitEventLoop(); 221 | }); 222 | 223 | runEventLoop(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /tests/vibe.core.stream.pipe.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "test" 3 | dependency "vibe-core" path=".." 4 | +/ 5 | module test; 6 | 7 | import vibe.core.core; 8 | import vibe.core.stream; 9 | import std.algorithm : min; 10 | import std.array : Appender, appender; 11 | import std.exception; 12 | import std.random; 13 | import core.time : Duration, msecs; 14 | 15 | void main() 16 | { 17 | auto datau = new uint[](2 * 1024 * 1024); 18 | foreach (ref u; datau) 19 | u = uniform!uint(); 20 | auto data = cast(ubyte[])datau; 21 | 22 | test(data, 0, 0.msecs, 0, 0.msecs); 23 | test(data, 32768, 1.msecs, 32768, 1.msecs); 24 | 25 | test(data, 32768, 0.msecs, 0, 0.msecs); 26 | test(data, 0, 0.msecs, 32768, 0.msecs); 27 | test(data, 32768, 0.msecs, 32768, 0.msecs); 28 | 29 | test(data, 1023*967, 10.msecs, 0, 0.msecs); 30 | test(data, 0, 0.msecs, 1023*967, 20.msecs); 31 | test(data, 1023*967, 10.msecs, 1023*967, 10.msecs); 32 | 33 | test(data, 1023*967, 10.msecs, 32768, 0.msecs); 34 | test(data, 32768, 0.msecs, 1023*967, 10.msecs); 35 | 36 | test(data, 1023*967, 10.msecs, 65535, 0.msecs); 37 | test(data, 65535, 0.msecs, 1023*967, 10.msecs); 38 | } 39 | 40 | void test(ubyte[] data, ulong read_sleep_freq, Duration read_sleep, 41 | ulong write_sleep_freq, Duration write_sleep) 42 | { 43 | test(data, ulong.max, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 44 | test(data, data.length * 2, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 45 | test(data, data.length / 2, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 46 | test(data, 64 * 1024, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 47 | test(data, 64 * 1024 - 57, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 48 | test(data, 557, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 49 | } 50 | 51 | void test(ubyte[] data, ulong chunk_limit, ulong read_sleep_freq, 52 | Duration read_sleep, ulong write_sleep_freq, Duration write_sleep) 53 | { 54 | test(data, ulong.max, chunk_limit, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 55 | test(data, 8 * 1024 * 1024, chunk_limit, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 56 | test(data, 8 * 1024 * 1024 - 37, chunk_limit, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 57 | test(data, 37, chunk_limit, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 58 | } 59 | 60 | void test(ubyte[] data, ulong data_limit, ulong chunk_limit, ulong read_sleep_freq, 61 | Duration read_sleep, ulong write_sleep_freq, Duration write_sleep) 62 | { 63 | import std.traits : EnumMembers; 64 | foreach (m; EnumMembers!PipeMode) 65 | test(data, m, data_limit, chunk_limit, read_sleep_freq, read_sleep, write_sleep_freq, write_sleep); 66 | } 67 | 68 | void test(ubyte[] data, PipeMode mode, ulong data_limit, ulong chunk_limit, 69 | ulong read_sleep_freq, Duration read_sleep, ulong write_sleep_freq, 70 | Duration write_sleep) 71 | { 72 | import vibe.core.log; 73 | logInfo("test RF=%s RS=%sms WF=%s WS=%sms CL=%s DL=%s M=%s", read_sleep_freq, read_sleep.total!"msecs", write_sleep_freq, write_sleep.total!"msecs", chunk_limit, data_limit, mode); 74 | 75 | auto input = TestInputStream(data, chunk_limit, read_sleep_freq, read_sleep); 76 | auto output = TestOutputStream(write_sleep_freq, write_sleep); 77 | auto datacmp = data[0 .. min(data.length, data_limit)]; 78 | 79 | input.pipe(output, data_limit, mode); 80 | if (output.m_data.data != datacmp) { 81 | logError("MISMATCH: %s b vs. %s b ([%(%s, %) ... %(%s, %)] vs. [%(%s, %) ... %(%s, %)])", 82 | output.m_data.data.length, datacmp.length, 83 | output.m_data.data[0 .. 6], output.m_data.data[$-6 .. $], 84 | datacmp[0 .. 6], datacmp[$-6 .. $]); 85 | assert(false); 86 | } 87 | 88 | // avoid leaking memory due to false pointers 89 | output.freeData(); 90 | } 91 | 92 | 93 | struct TestInputStream { 94 | private { 95 | const(ubyte)[] m_data; 96 | ulong m_chunkLimit = size_t.max; 97 | ulong m_sleepFrequency = 0; 98 | Duration m_sleepAmount; 99 | } 100 | 101 | this(const(ubyte)[] data, ulong chunk_limit, ulong sleep_frequency, Duration sleep_amount) 102 | { 103 | m_data = data; 104 | m_chunkLimit = chunk_limit; 105 | m_sleepFrequency = sleep_frequency; 106 | m_sleepAmount = sleep_amount; 107 | } 108 | 109 | @safe: 110 | 111 | @property bool empty() @blocking { return m_data.length == 0; } 112 | 113 | @property ulong leastSize() @blocking { return min(m_data.length, m_chunkLimit); } 114 | 115 | @property bool dataAvailableForRead() { assert(false); } 116 | 117 | const(ubyte)[] peek() { assert(false); } // currently not used by pipe() 118 | 119 | size_t read(scope ubyte[] dst, IOMode mode) 120 | @blocking { 121 | assert(mode == IOMode.all || mode == IOMode.once); 122 | if (mode == IOMode.once) 123 | dst = dst[0 .. min($, m_chunkLimit)]; 124 | auto oldsleeps = m_sleepFrequency ? m_data.length / m_sleepFrequency : 0; 125 | dst[] = m_data[0 .. dst.length]; 126 | m_data = m_data[dst.length .. $]; 127 | auto newsleeps = m_sleepFrequency ? m_data.length / m_sleepFrequency : 0; 128 | if (oldsleeps != newsleeps) { 129 | if (m_sleepAmount > 0.msecs) 130 | sleep(m_sleepAmount * (oldsleeps - newsleeps)); 131 | else yield(); 132 | } 133 | return dst.length; 134 | } 135 | void read(scope ubyte[] dst) @blocking { auto n = read(dst, IOMode.all); assert(n == dst.length); } 136 | } 137 | 138 | mixin validateInputStream!TestInputStream; 139 | 140 | struct TestOutputStream { 141 | private { 142 | Appender!(ubyte[]) m_data; 143 | ulong m_sleepFrequency = 0; 144 | Duration m_sleepAmount; 145 | } 146 | 147 | this(ulong sleep_frequency, Duration sleep_amount) 148 | { 149 | m_data = appender!(ubyte[]); 150 | m_data.reserve(2*1024*1024); 151 | m_sleepFrequency = sleep_frequency; 152 | m_sleepAmount = sleep_amount; 153 | } 154 | 155 | void freeData() 156 | { 157 | import core.memory : GC; 158 | auto d = m_data.data; 159 | m_data.clear(); 160 | GC.free(d.ptr); 161 | } 162 | 163 | @safe: 164 | 165 | void finalize() @safe @blocking {} 166 | void flush() @safe @blocking {} 167 | 168 | size_t write(in ubyte[] bytes, IOMode mode) 169 | @safe @blocking { 170 | assert(mode == IOMode.all); 171 | 172 | auto oldsleeps = m_sleepFrequency ? m_data.data.length / m_sleepFrequency : 0; 173 | m_data.put(bytes); 174 | auto newsleeps = m_sleepFrequency ? m_data.data.length / m_sleepFrequency : 0; 175 | if (oldsleeps != newsleeps) { 176 | if (m_sleepAmount > 0.msecs) 177 | sleep(m_sleepAmount * (newsleeps - oldsleeps)); 178 | else yield(); 179 | } 180 | return bytes.length; 181 | } 182 | void write(scope const(ubyte)[] bytes) @blocking { auto n = write(bytes, IOMode.all); assert(n == bytes.length); } 183 | void write(scope const(char)[] bytes) @blocking { write(cast(const(ubyte)[])bytes); } 184 | } 185 | 186 | mixin validateOutputStream!TestOutputStream; 187 | --------------------------------------------------------------------------------