├── .github └── workflows │ ├── build.yml │ └── examples.yml ├── .gitignore ├── LICENCE ├── README.md ├── bench ├── harness │ ├── bench-local.sh │ ├── bench-remote.sh │ ├── http_load.d │ ├── plot.py │ └── rusage.d ├── read-file.d ├── static_http │ ├── hello.d │ ├── hello.js │ ├── hello_go │ │ ├── go.mod │ │ ├── hello.go │ │ └── hello_go │ ├── hello_go_fast │ │ ├── go.mod │ │ ├── go.sum │ │ ├── hello_fast │ │ └── hello_fast.go │ ├── hello_threaded.d │ ├── hello_undertow │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── me │ │ │ └── olshansky │ │ │ └── HelloUndertow.java │ └── hello_vibed.d └── write-file.d ├── docs └── index.html ├── dub.json ├── examples ├── channels.d ├── event.d ├── evently.d ├── io.d ├── join.d ├── offloaded.d ├── pool.d ├── run-all.sh ├── select.d ├── semaphore.d ├── sleepy.d └── task_local.d ├── img ├── DApp.png └── DApp.svg ├── src ├── mecca │ ├── containers │ │ └── lists.d │ ├── division.d │ └── time_queue.d └── photon │ ├── core.d │ ├── ds │ ├── common.d │ ├── intrusive_queue.d │ └── ring_queue.d │ ├── exceptions.d │ ├── freebsd │ ├── core.d │ └── support.d │ ├── linux │ ├── core.d │ ├── support.d │ └── syscalls.d │ ├── macos │ ├── core.d │ └── support.d │ ├── package.d │ ├── reactor.d │ ├── support.d │ ├── task.d │ ├── threadpool.d │ └── windows │ ├── core.d │ └── support.d └── tests ├── await.d ├── curl_download.d ├── echo_client.d ├── echo_server.d ├── go_same_thread.d ├── issue28.d ├── ping_pong_fiber_half_duplex.d ├── ping_pong_full_duplex.d ├── ping_pong_full_duplex_n.d ├── ping_pong_thread_half_duplex.d ├── poll_timer.d ├── sleep.d └── zmq.d /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: Build on supported platforms 6 | 7 | on: 8 | push: 9 | branches: [ "master" ] 10 | pull_request: 11 | branches: [ "master" ] 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [windows-latest, ubuntu-latest, macos-latest] 21 | dc: [ldc-latest] 22 | 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Install D compiler 28 | uses: dlang-community/setup-dlang@v1 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | 32 | - name: Run tests 33 | run: dub -q test 34 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: Run all examples 6 | 7 | on: 8 | push: 9 | branches: [ "master" ] 10 | pull_request: 11 | branches: [ "master" ] 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [windows-latest, ubuntu-latest, macos-latest] 21 | dc: [ldc-latest] 22 | 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Install D compiler 28 | uses: dlang-community/setup-dlang@v1 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | 32 | - name: Run tests 33 | run: cd examples && ./run-all.sh 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Other stuff 40 | ############# 41 | *.gdb_history 42 | 43 | # Datafiles from benchmarks 44 | *.csv 45 | 46 | .dub -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Photon - a transparent lightweight fiber scheduler 2 | 3 | ![Build](https://github.com/DmitryOlshansky/photon/actions/workflows/build.yml/badge.svg) 4 | ![Examples](https://github.com/DmitryOlshansky/photon/actions/workflows/examples.yml/badge.svg) 5 | 6 | ## Intro 7 | 8 | Photon is a minimalistic multi-threaded fiber scheduler and event loop that works transparently with traditional blocking I/O C/C++/D/Rust libraries w/o degrading performance. For example one can run multituide of downloads with `std.net.curl` with fibers, no blocking - it is as fast as threads but using less resources. Think of it as Golang style concurrency that is brought to D transparently. 9 | 10 | Just like its particle cousing, Photon’s nature is dual, seeking to unify 2 different concepts (such as async and blocking I/O) in many ways: 11 | Fibers and Threads can be mixed and matched in coherent way. 12 | LibC syscall wrapper is overriden to be aware of D fiber scheduling and transparently uses the same eventloop if called on fiber and is passed through otherwise. 13 | 14 | Explicit async model with tasks/futures could be integrated with pseudoblocking fiber scheduling, 15 | D Ranges with asynchronous event streams are a natural extension looking forward. 16 | 17 | ## Building 18 | 19 | Photon switched to using Dub for D targets. The following will build release version of the library: 20 | ``` 21 | dub build -b release 22 | 23 | ``` 24 | See more about [DUB](https://dub.pm/getting-started/first-steps/#building-a-third-party-project). 25 | 26 | ## Usage 27 | 28 | See [DDoc documentation](https://dmitryolshansky.github.io/photon/) and [the examples folder](https://github.com/DmitryOlshansky/photon/tree/master/examples). 29 | Typically the photon-powered program looks like this: 30 | 31 | ```d 32 | #!/usr/bin/env dub 33 | /+ dub.json: 34 | { 35 | "name" : "photon-example", 36 | "dependencies": { 37 | "photon": "0.18.6" 38 | } 39 | } 40 | +/ 41 | import photon; 42 | 43 | void main() { 44 | initPhoton(); // initializes Photon's data structures 45 | go({ 46 | // the root fiber task goes here, typically this is accepting or connecting sockets, 47 | // spawning new fibers each to handle new connections. 48 | }); 49 | runScheduler(); // run all fibers on the scheduler, until all are completed 50 | } 51 | ``` 52 | 53 | ## Blocking, async and pseudo-blocking 54 | 55 | In short, there are roughly 3 ways (glossing over OS specific abilities) to tackle I/O. 56 | 57 | 1. Synchronous (blocking) I/O, where a kernel will block a thread and wake it up once the resource is available. This has the advantage of simple programming model at the expense of hogging a precious OS resource - threads, in addition to an expensive round-trip through the OS kernel to perform a context switch. 58 | 59 | 2. Explicit asynchronous (async) I/O. Typically involves passing a callback that is triggered on completion. More convenient model builds an algebra of Promise/Future objects on top of callbacks, such objects are then manipulated in a functional way. Lastly async/await extensions to some languages rewrite away the error-prone callback code via code transformation. Scalability comes from the fact that the thread continues on with its work after registering callback, so a single thread may process a multitude of sockets at the same time. 60 | 61 | 3. Pseudosynchronous (pseudoblocking) I/O, this is built on the green thread (Fiber in D) concept. First it makes fibers cheap, typically by allocating a modest stack size and/or growing it on demand. Secondly the runtime of the language (or the library) takes the burden of the context switch thus making it cheap to go from one green thread to the other. The moment a fiber wants to do a synchronous I/O the runtime will instead do async I/O and transparently switch context to another fiber. 62 | 63 | There are oversimplifications in the above introductions. In particular on Linux AIO (Asynchronous I/O) is a separate thing from non-blocking I/O and typically event-loops that implement schemes 2 & 3 noted above would use non-blocking I/O + kernel event system. More on that below. 64 | 65 | Go language would be a remarkably popular example of 3rd option - Goroutines are green threads that get scheduled (mapped) to a handful of OS threads in the language runtime. 66 | 67 | C# and Dart would be an examples of 2nd option - language extension to tackle explicit asynchronous I/O (and not only I/O). Many other languages follow suit. 68 | 69 | Current situation with I/O in DLang looks roughly like the following diagram. Due to D's ability to call into arbitrary C libraries we have the full Zoo of options without any of the benefits. To be specific we have normal synchronous I/O in std library (std.socket etc.), fiber-based I/O scheduling as an opt-in library, sometimes explicit async I/O of kind in 3rd party C libraries and synchronous I/O in the general mass of C libraries. 70 | 71 | The landscape is naturally a wild west, see below 72 | 73 | ![D Application I/O Zoo diagram](img/DApp.png) 74 | 75 | Specifically note the points of interaction 1-4: 76 | 1. Fiber-aware D library talks to vibe.d via special wrappers of most socket operations, this is pseudoblocking model. Note that a library has to depend on vibe.d to benefit + user has to run vibe.d scheduler for it to work. 77 | 2. Some 3-rd party library (typically C) provides callback-driven asynchronous API and talks to its own choice of event library/direct syscalls. This still allows a fiber to wait on it without blocking the thread but requires a bit of "interconnect" code. 78 | 3. D library may call some libc primitive that does synchronous I/O call, notably hostname resolution in libc is like that. It breaks fiber-aware pseudoblocking model and there is no static checking against it. 79 | 4. Finally most 3rd party C client libraries do synchronous I/O calls with the same caveats. 80 | 81 | What's important to understand is that all of the above can be mixed and matched (and in fact inevitably will, if we are not careful). To underline the problem: 82 | - explicit async mixes well with explicit async of the same flavor, badly with any other model. But at least it's an explicit problem that can be solved on per use case basis. 83 | - pseudoblocking only works if everything is following pseudoblocking model, also explicit async can integrate passably well, any single blocking primitive rapidly degrades pseudoblocking model 84 | - finally blocking I/O is bane of all, it trivially spills through into any other model code with disastrous consequences 85 | 86 | The end result is a brittle ecosystem where even if you have a 3rd party "driver" for your database it is likely not compatible with your I/O scheme, and the most widely compatible scheme (blocking I/O) is not scalable. Some libraries have 2 versions - one on top of vibe.d sockets and the other on top of std.socket. 87 | 88 | ## Solution 89 | 90 | This project is going for a bold approach to solve this problem once and for all: 91 | 1. Replacing the libc syscall wrapper so that any blocking call relying on it (which is next to all) is transparently rewired to go through pseudoblocking runtime. All 3rd party libraries do fiber-aware pseudoblocking I/O automatically. 92 | 2. The rest of the libraries that do explicit async I/O stay as their are, their syscall are passed through. In the future we will intercept them as well to re-route to our eventloop, basically emulating the likes of `select`, `poll` and `epoll` in user-space by reusing the same event cache. 93 | 3. Finally vibe.d may produce a thin-shelled version that forward all of calls to blocking I/O to reuse our scheduler. 94 | 95 | Note: the approach of overriding underlying libc facilities is not something new or uncalled for, e.g. jemalloc does it fitfully to replace default libc memory allocator. 96 | -------------------------------------------------------------------------------- /bench/harness/bench-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -lt 3 ] ; then 3 | echo "Usage: RANGE RUNS NAME " 4 | echo "Example: ./bench-local 10-1k:10 10x3 photon ./hello-photon" 5 | echo "Means in range of 10 - 1000 connections, step 10" 6 | echo "10 seconds per run, with summary of 3 runs" 7 | exit 1 8 | fi 9 | RANGE=$1 10 | RUNS=$2 11 | NAME=$3 12 | shift 3 13 | rusage 0.05:$NAME-res.csv "$@" & 14 | SERV=$! 15 | sleep 1 16 | http_load $RANGE $RUNS 127.0.0.1:8080 > $NAME-http.csv & 17 | HTTP=$! 18 | tail -F $NAME-http.csv & 19 | TAIL=$! 20 | wait $HTTP 21 | kill -INT $SERV 22 | kill -INT $TAIL 23 | -------------------------------------------------------------------------------- /bench/harness/bench-remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | if [ $# -lt 3 ] ; then 3 | echo "Usage: RANGE RUNS SERVER-HOST SERVER-HOST-LOCAL CLIENT-HOST NAME " 4 | echo "Example: ./bench-local 10-1k:10 10x3 localhost localhost localhost photon ./hello-photon" 5 | echo "Means in range of 10 - 1000 connections, step 10" 6 | echo "10 seconds per run, with summary of 3 runs" 7 | exit 1 8 | fi 9 | RANGE=$1 10 | RUNS=$2 11 | SERVNODE=$3 12 | SERVNODELOCAL=$4 13 | CLIENTNODE=$5 14 | NAME=$6 15 | shift 6 16 | 17 | ssh $SERVNODE ./unlimited.sh rusage 0.05:$NAME-res.csv "$@" & 18 | SERV=$! 19 | sleep 1 20 | ssh $CLIENTNODE ./unlimited.sh http_load $RANGE $RUNS http://$SERVNODELOCAL:8080 > $NAME-http.csv & 21 | HTTP=$! 22 | wait $HTTP 23 | kill -INT $SERV 24 | rsync $SERVNODE:$NAME-res.csv 25 | wait -------------------------------------------------------------------------------- /bench/harness/http_load.d: -------------------------------------------------------------------------------- 1 | // Performs multiple runs of weighttp tool 2 | // varying the concurency range in steps 3 | // Inspired by GWAN benchmark tool (ab.c) 4 | 5 | module http_load; 6 | 7 | import core.cpuid, core.thread; 8 | import std.getopt, std.array, std.algorithm, std.numeric, 9 | std.conv, std.datetime, std.exception, 10 | std.stdio, std.process, std.regex; 11 | 12 | void usage() { 13 | stderr.writeln("Usage: http_load START-STOP:STEP TIMExRUNS http://server[:port]/path"); 14 | } 15 | 16 | long fromMetric(string num) { 17 | assert(num.length > 0); 18 | long mult; 19 | switch(num[$-1]){ 20 | case 'm': 21 | mult = 1_000_000; 22 | num = num[0..$-1]; 23 | break; 24 | case 'k': 25 | mult = 1_000; 26 | num = num[0..$-1]; 27 | break; 28 | default: 29 | mult = 1; 30 | } 31 | return mult * num.to!long; 32 | } 33 | 34 | int main(string[] argv) { 35 | bool trace = false; 36 | getopt(argv, 37 | "v", &trace 38 | ); 39 | if (argv.length < 4) { 40 | usage(); 41 | return 1; 42 | } 43 | auto m = matchFirst(argv[1], `^(\d+(?:[km]?))-(\d+(?:[km]?)):(\d+(?:[km]?))$`); 44 | if (!m) { 45 | stderr.writeln("Can't parse 'range' argument:", argv[1]); 46 | usage(); 47 | return 1; 48 | } 49 | string url = argv[3]; 50 | long start = fromMetric(m[1]); 51 | long stop = fromMetric(m[2]); 52 | long step = fromMetric(m[3]); 53 | auto m2 = matchFirst(argv[2], `^(\d+)x(\d+)$`); 54 | if (!m2) { 55 | stderr.writeln("Can't parse 'runs' argument:", argv[2]); 56 | usage(); 57 | return 1; 58 | } 59 | int numThreads = threadsPerCPU; 60 | int time = m2[1].to!int; 61 | int runs = m2[2].to!int; 62 | writefln("time,concurrency,RPS(min),RPS(avg),RPS(max),errors(max),lat(75%%),lat(99%%)"); 63 | for(long c = start; c <= stop; c += step) { 64 | c = c / step * step; // truncate to step 65 | if (c < numThreads) c = numThreads; 66 | auto dt = Clock.currTime(); 67 | double[] rps = new double[runs]; 68 | double[] perc75 = new double[runs]; 69 | double[] perc99 = new double[runs]; 70 | long[] errors = new long[runs]; 71 | foreach(r; 0..runs) { 72 | double multiplier(const(char)[] s){ 73 | if (s == "") return 1; 74 | else if(s == "u") return 1e-6; 75 | else if(s == "m") return 1e-3; 76 | else throw new Exception("Unknown multiplier " ~ s.to!string); 77 | } 78 | auto cmd = ["wrk", "--latency", "-c", c.to!string, "-t", numThreads.to!string, "-d", time.to!string, url]; 79 | if(trace) stderr.writeln(cmd.join(" ")); 80 | auto pipes = pipeProcess(cmd); 81 | foreach (line; pipes.stdout.byLine) { 82 | auto result = matchFirst(line, `Socket errors: connect (\d+), read (\d+), write (\d+), timeout (\d+)`); 83 | if(result) { 84 | errors[r] = result[1].to!long + result[2].to!long + result[3].to!long + result[4].to!long; 85 | } 86 | result = matchFirst(line, `Requests/sec:\s*([\d.]+)`); 87 | if (result) { 88 | rps[r] = result[1].to!double; 89 | } 90 | result = matchFirst(line, `75%\s*([\d.]+)([mu])?s`); 91 | if (result) { 92 | perc75[r] = result[1].to!double * multiplier(result[2]); 93 | } 94 | result = matchFirst(line, `99%\s*([\d.]+)([mu])?s`); 95 | if (result) { 96 | perc99[r] = result[1].to!double * multiplier(result[2]); 97 | } 98 | } 99 | if (wait(pipes.pid)) { 100 | stderr.writeln("wrk failed, stopping benchmark"); 101 | return 1; 102 | } 103 | } 104 | writefln("%s,%d,%f,%f,%f,%d,%f,%f", 105 | dt.toISOExtString, c, reduce!(min)(rps), mean(rps), reduce!max(rps), 106 | reduce!max(errors), mean(perc75), mean(perc99)); 107 | stdout.flush(); 108 | Thread.sleep(100.msecs); 109 | } 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /bench/harness/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import matplotlib.pyplot as plt 3 | import pandas as pd 4 | import numpy as np 5 | from datetime import datetime 6 | import sys 7 | from tqdm import tqdm 8 | 9 | RSS = 'RSS(MB)' 10 | READ = 'read(MB)' 11 | WRITE = 'written(MB)' 12 | USER = 'user cpu time(sec)' 13 | KERNEL = 'kernel cpu time(sec)' 14 | NAMES = [RSS, READ, WRITE, USER, KERNEL] 15 | 16 | httpArg = sys.argv[1] 17 | resArg = sys.argv[2] 18 | destArg = sys.argv[3] 19 | 20 | http = pd.read_csv(httpArg) 21 | res = pd.read_csv(resArg) 22 | 23 | http['time'] = pd.to_datetime(http['time']) 24 | res['time'] = pd.to_datetime(res['time']) 25 | 26 | results = http.copy() 27 | for n in NAMES: 28 | results[n] = pd.Series(0,index=np.arange(len(http)), dtype=np.float64) 29 | 30 | for i in tqdm(range(len(http))): 31 | start = http['time'][i] 32 | if i != len(http) - 1: 33 | end = http['time'][i+1] 34 | slice = res[(res['time'] > start) & (res['time'] < end)] 35 | else: 36 | slice = res[(res['time'] > start)] 37 | results[RSS][i] = slice[RSS].agg('max') 38 | results[USER][i] = slice[USER].agg('sum') 39 | results[KERNEL][i] = slice[KERNEL].agg('sum') 40 | results[READ][i] = slice[READ].agg('sum') 41 | results[WRITE][i] = slice[WRITE].agg('sum') 42 | 43 | results.to_csv(destArg) 44 | -------------------------------------------------------------------------------- /bench/harness/rusage.d: -------------------------------------------------------------------------------- 1 | // Runs a process 2 | // dump resource usage of a process 3 | // each ~100ms 4 | // to a rusage.log 5 | 6 | import std.exception, std.stdio, std.format, std.string, std.conv, std.datetime, std.process; 7 | import core.thread; 8 | import core.sys.posix.unistd; 9 | import core.sys.posix.signal; 10 | 11 | struct Stats { 12 | string name; 13 | long rbytes, wbytes; // read/write(-ish) syscalls 14 | long stime, utime; // CPU time in clock ticks 15 | long rss; // resident memory pages 16 | 17 | auto delta(Stats rhs) { 18 | return Stats( 19 | name, 20 | rbytes - rhs.rbytes, 21 | wbytes - rhs.wbytes, 22 | stime - rhs.stime, 23 | utime - rhs.utime, 24 | rss 25 | ); 26 | } 27 | } 28 | 29 | shared int targetPid; 30 | immutable double MB = 1024.0 * 1024.0; 31 | 32 | extern(C) void signal_handler(int sig, siginfo_t*, void*) 33 | { 34 | kill(targetPid, sig); 35 | _exit(9); 36 | } 37 | 38 | Stats statsOf(int pid) { 39 | string ioPath = format("/proc/%d/io", pid); 40 | string statPath = format("/proc/%d/stat", pid); 41 | long rd; 42 | long wr; 43 | File stat = File(statPath, "r"); 44 | File io = File(ioPath, "r"); 45 | io.readf("rchar: %d\nwchar: %d\n", &rd, &wr); 46 | int _; 47 | string name, rest; 48 | stat.readf("%d (%s) %s", _, name, rest); 49 | string[] fields = rest.split(" "); 50 | long stime = fields[11].to!long; 51 | long utime = fields[12].to!long; 52 | long rss = fields[21].to!long; 53 | return Stats(name, rd, wr, stime, utime, rss); 54 | } 55 | 56 | int main(string[] argv) { 57 | if (argv.length < 3) { 58 | stderr.writeln("Usage: rusage period:log-file [args*]\n\n" ~ 59 | "Example: rusage 0.1:usage.csv find /\nTo sample each 0.1 second and store to usage.csv"); 60 | return 1; 61 | } 62 | string params = argv[1]; 63 | double period; 64 | string logFile; 65 | params.formattedRead("%f:%s", period, logFile); 66 | File log = File(logFile, "w"); 67 | Duration sampling = (period*1000).to!int.msecs; 68 | Pid p = spawnProcess(argv[2..$]); 69 | double pageSize = 1.0*sysconf(_SC_PAGE_SIZE); 70 | double tickSize = 1.0*sysconf(_SC_CLK_TCK); 71 | targetPid = p.processID; 72 | sigaction_t action; 73 | action.sa_sigaction = &signal_handler; 74 | enforce(sigaction(SIGTERM, &action, null) >= 0); 75 | enforce(sigaction(SIGINT, &action, null) >= 0); 76 | log.writeln("time,name,read(MB),written(MB),kernel cpu time(sec),user cpu time(sec),RSS(MB)"); 77 | Stats prev; 78 | for(;;) { 79 | auto status = tryWait(p); 80 | if (status.terminated) break; 81 | try { 82 | auto stats = statsOf(targetPid); 83 | auto delta = stats.delta(prev); 84 | prev = stats; 85 | SysTime dt = Clock.currTime; 86 | log.writefln("%s,%s,%f,%f,%f,%f,%f", 87 | dt.toISOExtString, stats.name, 88 | delta.rbytes / MB, 89 | delta.wbytes / MB, 90 | delta.stime / tickSize, 91 | delta.utime / tickSize, 92 | delta.rss * pageSize / MB 93 | ); 94 | } 95 | catch (Exception e){ 96 | stderr.writeln("Error: ", e); 97 | } 98 | log.flush(); 99 | Thread.sleep(sampling); 100 | } 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /bench/read-file.d: -------------------------------------------------------------------------------- 1 | module read_file; 2 | import std.stdio; 3 | import std.file; 4 | import std.utf : byChar; 5 | import std.string; 6 | import core.sys.posix.fcntl; 7 | import core.sys.posix.unistd; 8 | import photon; 9 | 10 | void main(){ 11 | initPhoton(); 12 | std.file.write("file.txt", "Read Test"); 13 | go({ 14 | 15 | int fd = open("file.txt", O_RDONLY); 16 | char[20] buf; 17 | long r = core.sys.posix.unistd.read(fd, buf.ptr, buf.length); 18 | writef("return r = %d\n", r); 19 | if (r >= 0) 20 | writef("return = %s\n", buf[0..r]); 21 | 22 | }); 23 | runScheduler(); 24 | } 25 | -------------------------------------------------------------------------------- /bench/static_http/hello.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "name" : "hello", 5 | "dependencies": { 6 | "photon": { "path" : "../.." }, 7 | "photon-http": "~>0.6.3" 8 | } 9 | } 10 | +/ 11 | import std.stdio; 12 | import std.socket; 13 | 14 | import photon, photon.http; 15 | 16 | class HelloWorldProcessor : HttpProcessor { 17 | HttpHeader[] headers = [HttpHeader("Content-Type", "text/plain; charset=utf-8")]; 18 | 19 | this(Socket sock){ super(sock); } 20 | 21 | override void handle(HttpRequest req) { 22 | respondWith("Hello, world!", 200, headers); 23 | } 24 | } 25 | 26 | void server_worker(Socket client) { 27 | scope processor = new HelloWorldProcessor(client); 28 | try { 29 | processor.run(); 30 | } 31 | catch(Exception e) { 32 | stderr.writeln(e); 33 | } 34 | } 35 | 36 | void server() { 37 | try { 38 | Socket server = new TcpSocket(); 39 | server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 40 | server.bind(new InternetAddress("0.0.0.0", 8080)); 41 | server.listen(1000); 42 | 43 | debug writeln("Started server"); 44 | 45 | void processClient(Socket client) { 46 | go(() => server_worker(client)); 47 | } 48 | 49 | while(true) { 50 | try { 51 | debug writeln("Waiting for server.accept()"); 52 | Socket client = server.accept(); 53 | debug writeln("New client accepted"); 54 | processClient(client); 55 | } 56 | catch(Exception e) { 57 | writefln("Failure to accept %s", e); 58 | } 59 | } 60 | } catch (Exception e) { 61 | stderr.writefln("Got exception while setting up the server: %s", e); 62 | } 63 | } 64 | 65 | void main() { 66 | initPhoton(); 67 | go(() => server()); 68 | runScheduler(); 69 | } 70 | -------------------------------------------------------------------------------- /bench/static_http/hello.js: -------------------------------------------------------------------------------- 1 | // Running: 2 | // node hello.js 3 | 4 | var http = require('http'); 5 | 6 | http.createServer(function (req, res) { 7 | res.writeHead(200, {'Content-Type': 'text/plain'}); 8 | res.end('Hello, World!'); 9 | }).listen(8080); 10 | -------------------------------------------------------------------------------- /bench/static_http/hello_go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DmitryOlshansky/hello_go 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /bench/static_http/hello_go/hello.go: -------------------------------------------------------------------------------- 1 | // Building: 2 | // go build -o hello-golang hello.go 3 | // Running with 1 core: 4 | // GOMAXPROCS=1 ./hello-golang 5 | // Running with all cores 6 | // ./hello-golang 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "net/http" 12 | ) 13 | 14 | func handler(w http.ResponseWriter, r *http.Request) { 15 | fmt.Fprintf(w, "Hello, world!") 16 | } 17 | 18 | func main() { 19 | http.HandleFunc("/", handler) 20 | http.ListenAndServe(":8080", nil) 21 | } 22 | -------------------------------------------------------------------------------- /bench/static_http/hello_go/hello_go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryOlshansky/photon/d03ad582d03083dbaf4a53eb0ad08a18206df517/bench/static_http/hello_go/hello_go -------------------------------------------------------------------------------- /bench/static_http/hello_go_fast/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DmitryOlshansky/hello_fast 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.1.0 // indirect 7 | github.com/klauspost/compress v1.17.6 // indirect 8 | github.com/valyala/bytebufferpool v1.0.0 // indirect 9 | github.com/valyala/fasthttp v1.52.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /bench/static_http/hello_go_fast/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 2 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 3 | github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= 4 | github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 5 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 6 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 7 | github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= 8 | github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= 9 | -------------------------------------------------------------------------------- /bench/static_http/hello_go_fast/hello_fast: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryOlshansky/photon/d03ad582d03083dbaf4a53eb0ad08a18206df517/bench/static_http/hello_go_fast/hello_fast -------------------------------------------------------------------------------- /bench/static_http/hello_go_fast/hello_fast.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "github.com/valyala/fasthttp" 7 | ) 8 | 9 | func main() { 10 | h := requestHandler 11 | if err := fasthttp.ListenAndServe("0.0.0.0:8080", h); err != nil { 12 | log.Fatalf("Error in ListenAndServe: %s", err) 13 | } 14 | } 15 | 16 | func requestHandler(ctx *fasthttp.RequestCtx) { 17 | fmt.Fprintf(ctx, "Hello, world!") 18 | } -------------------------------------------------------------------------------- /bench/static_http/hello_threaded.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "name" : "hello-threaded", 5 | "dependencies": { 6 | "photon-http": "~>0.4.5" 7 | } 8 | } 9 | +/ 10 | import std.stdio; 11 | import std.socket; 12 | import core.thread; 13 | 14 | import photon.http; 15 | 16 | class HelloWorldProcessor : HttpProcessor { 17 | HttpHeader[] headers = [HttpHeader("Content-Type", "text/plain; charset=utf-8")]; 18 | 19 | this(Socket sock){ super(sock); } 20 | 21 | override void handle(HttpRequest req) { 22 | respondWith("Hello, world!", 200, headers); 23 | } 24 | } 25 | 26 | void server_worker(Socket client) { 27 | scope processor = new HelloWorldProcessor(client); 28 | processor.run(); 29 | } 30 | 31 | void server() { 32 | Socket server = new TcpSocket(); 33 | server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 34 | server.bind(new InternetAddress("0.0.0.0", 8080)); 35 | server.listen(1000); 36 | 37 | debug writeln("Started server"); 38 | 39 | void processClient(Socket client) { 40 | new Thread(() => server_worker(client)).start(); 41 | } 42 | 43 | while(true) { 44 | try { 45 | debug writeln("Waiting for server.accept()"); 46 | Socket client = server.accept(); 47 | debug writeln("New client accepted"); 48 | processClient(client); 49 | } 50 | catch(Exception e) { 51 | writefln("Failure to accept %s", e); 52 | } 53 | } 54 | } 55 | 56 | void main() { 57 | new Thread(() => server()).start(); 58 | } 59 | -------------------------------------------------------------------------------- /bench/static_http/hello_undertow/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.olshansky 8 | hello_undertow 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | io.undertow 14 | undertow-core 15 | 2.1.0.Final 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-compiler-plugin 23 | 3.11.0 24 | 25 | 21 26 | 21 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-shade-plugin 32 | 3.1.0 33 | 34 | 35 | 37 | 38 | me.olshansky.HelloUndertow 39 | 1.0 40 | 41 | 42 | 43 | 44 | 45 | 46 | package 47 | 48 | shade 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /bench/static_http/hello_undertow/src/main/java/me/olshansky/HelloUndertow.java: -------------------------------------------------------------------------------- 1 | package me.olshansky; 2 | 3 | import java.net.Inet4Address; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | 7 | import org.xnio.BufferAllocator; 8 | import org.xnio.ByteBufferSlicePool; 9 | import org.xnio.ChannelListener; 10 | import org.xnio.ChannelListeners; 11 | import org.xnio.OptionMap; 12 | import org.xnio.Options; 13 | import org.xnio.Pool; 14 | import org.xnio.StreamConnection; 15 | import org.xnio.Xnio; 16 | import org.xnio.XnioWorker; 17 | import org.xnio.channels.AcceptingChannel; 18 | import org.xnio.ssl.JsseXnioSsl; 19 | import org.xnio.ssl.SslConnection; 20 | import org.xnio.ssl.XnioSsl; 21 | 22 | import io.undertow.Undertow; 23 | import io.undertow.Undertow.ListenerType; 24 | import io.undertow.UndertowOptions; 25 | import io.undertow.server.HttpHandler; 26 | import io.undertow.server.HttpServerExchange; 27 | import io.undertow.server.protocol.http.HttpOpenListener; 28 | import io.undertow.util.Headers; 29 | import org.xnio.Xnio; 30 | import org.xnio.XnioWorker; 31 | import org.xnio.OptionMap; 32 | 33 | public class HelloUndertow { 34 | public static void main(final String[] args) throws Exception { 35 | final int bufferSize = 256; 36 | final int buffersPerRegion = 100; 37 | int ioThreads = Runtime.getRuntime().availableProcessors(); 38 | int workers = Runtime.getRuntime().availableProcessors(); 39 | if (args.length > 0) { 40 | ioThreads = Integer.parseInt(args[0]); 41 | workers = Integer.parseInt(args[1]); 42 | } 43 | HttpHandler rootHandler = new HttpHandler() { 44 | @Override 45 | public void handleRequest(final HttpServerExchange exchange) throws Exception { 46 | exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); 47 | exchange.getResponseSender().send("Hello World"); 48 | } 49 | }; 50 | Xnio xnio = Xnio.getInstance(); 51 | 52 | XnioWorker worker = xnio.createWorker(OptionMap.builder() 53 | .set(Options.WORKER_IO_THREADS, workers) 54 | .set(Options.WORKER_TASK_CORE_THREADS, workers) 55 | .set(Options.WORKER_TASK_MAX_THREADS, workers) 56 | .set(Options.TCP_NODELAY, true) 57 | .getMap()); 58 | 59 | OptionMap serverOptions = OptionMap.builder().getMap(); 60 | 61 | OptionMap socketOptions = OptionMap.builder() 62 | .set(Options.WORKER_IO_THREADS, workers) 63 | .set(Options.TCP_NODELAY, true) 64 | .set(Options.REUSE_ADDRESSES, true) 65 | .getMap(); 66 | 67 | Pool buffers = new ByteBufferSlicePool(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, bufferSize, bufferSize * buffersPerRegion); 68 | 69 | HttpOpenListener openListener = new HttpOpenListener(buffers, OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).addAll(serverOptions).getMap()); 70 | openListener.setRootHandler(rootHandler); 71 | ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(openListener); 72 | AcceptingChannel server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName("127.0.0.1"), 8080), acceptListener, socketOptions); 73 | server.resumeAccepts(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /bench/static_http/hello_vibed.d: -------------------------------------------------------------------------------- 1 | /+ dub.sdl: 2 | name "hello-vibed" 3 | dependency "vibe-d" version="~>0.10.0" 4 | versions "VibeDefaultMain" 5 | +/ 6 | import vibe.d; 7 | 8 | void serve(HTTPServerRequest req, HTTPServerResponse res) 9 | { 10 | res.writeBody("Hello, world!"); 11 | } 12 | 13 | shared static this() 14 | { 15 | auto router = new URLRouter; 16 | router.get("/", &serve); 17 | 18 | auto settings = new HTTPServerSettings; 19 | settings.port = 8080; 20 | 21 | listenHTTP(settings, router); 22 | } 23 | -------------------------------------------------------------------------------- /bench/write-file.d: -------------------------------------------------------------------------------- 1 | module read_file; 2 | import std.stdio; 3 | import std.file; 4 | import std.utf : byChar; 5 | import std.string; 6 | import core.sys.posix.fcntl; 7 | import core.sys.posix.unistd; 8 | static import std.conv; 9 | import photon; 10 | 11 | void main(){ 12 | initPhoton(); 13 | go({ 14 | int fd = open("write.txt", O_RDWR | O_CREAT | O_TRUNC, std.conv.octal!644); 15 | if (fd < 0) { 16 | stderr.writefln("Error opening fd = %d", fd); 17 | assert(0); 18 | } 19 | char[] buf = "Write Test".dup; 20 | long r = core.sys.posix.unistd.write(fd, buf.ptr, buf.length); 21 | writef("return r = %d\n", r); 22 | if (r >= 0) 23 | writef("return = %s\n", buf[0..r]); 24 | }); 25 | runScheduler(); 26 | } 27 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Dmitry Olshansky" 4 | ], 5 | "copyright": "Copyright © 2023, Dmitry Olshansky", 6 | "description": "Photon - a transparent fiber scheduler", 7 | "dependencies": { 8 | "sharded-map" : "2.7.0" 9 | }, 10 | "dflags": ["-oq"], 11 | "license": "BOOST", 12 | "name": "photon", 13 | "targetType": "library" 14 | } -------------------------------------------------------------------------------- /examples/channels.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for channels API", 11 | "license": "BOOST", 12 | "name": "channels" 13 | } 14 | +/ 15 | module examples.channels; 16 | 17 | import std.algorithm, std.datetime, std.range, std.stdio; 18 | import photon; 19 | 20 | void first(shared Channel!string work, shared Channel!int completion) { 21 | delay(2.msecs); 22 | work.put("first #1"); 23 | delay(2.msecs); 24 | work.put("first #2"); 25 | delay(2.msecs); 26 | work.put("first #3"); 27 | completion.put(1); 28 | } 29 | 30 | void second(shared Channel!string work, shared Channel!int completion) { 31 | delay(3.msecs); 32 | work.put("second #1"); 33 | delay(3.msecs); 34 | work.put("second #2"); 35 | completion.put(2); 36 | } 37 | 38 | void main() { 39 | initPhoton(); 40 | auto jobQueue = channel!string(2); 41 | auto finishQueue = channel!int(1); 42 | go({ 43 | first(jobQueue, finishQueue); 44 | }); 45 | go({ // producer # 2 46 | second(jobQueue, finishQueue); 47 | }); 48 | go({ // consumer 49 | foreach (item; jobQueue) { 50 | delay(1.seconds); 51 | writeln(item); 52 | } 53 | }); 54 | go({ // closer 55 | auto completions = finishQueue.take(2).array; 56 | assert(completions.length == 2); 57 | jobQueue.close(); // all producers are done 58 | }); 59 | runScheduler(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /examples/event.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "name" : "event", 5 | "dependencies": { 6 | "photon": { "path" : ".." } 7 | } 8 | } 9 | +/ 10 | module examples.event; 11 | 12 | import core.thread; 13 | import std.stdio; 14 | 15 | import photon; 16 | 17 | shared Event ev1, ev2; 18 | enum iterations = 2; 19 | 20 | void firstJob() { 21 | writeln("Wait for the second to trigger the event"); 22 | ev1.waitAndReset(); 23 | writeln("First triggers event for the second"); 24 | ev2.trigger(); 25 | writeln("First is done"); 26 | } 27 | 28 | void secondJob() { 29 | writeln("Second triggers event for the first"); 30 | ev1.trigger(); 31 | writeln("Triggered event for the first, now waiting for trigger back"); 32 | ev2.waitAndReset(); 33 | writeln("Second is done"); 34 | } 35 | 36 | void main() { 37 | initPhoton(); 38 | ev1 = event(false); 39 | ev2 = event(false); 40 | go(&firstJob); 41 | go(&secondJob); 42 | runScheduler(); 43 | } -------------------------------------------------------------------------------- /examples/evently.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2024, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." } 10 | }, 11 | "description": "A test for sleep function interception", 12 | "license": "BOOST", 13 | "name": "evently" 14 | } 15 | +/ 16 | module examples.evently; 17 | 18 | import std.stdio; 19 | import core.time; 20 | import core.thread; 21 | import photon; 22 | 23 | enum EVENTS = 1000; 24 | 25 | shared Event[EVENTS] events; 26 | 27 | void task(int n) { 28 | go({ 29 | events[n].waitAndReset(); 30 | writeln("Wait is over"); 31 | }); 32 | } 33 | 34 | void main() { 35 | initPhoton(); 36 | writeln("Starting a bunch of fibers each waiting on a distinct event"); 37 | foreach (i; 0..EVENTS) { 38 | events[i] = event(false); 39 | } 40 | foreach (i; 0..EVENTS) { 41 | task(i); 42 | } 43 | go({ 44 | delay(1.seconds); 45 | foreach_reverse (i; 0..EVENTS) { 46 | events[i].trigger(); 47 | } 48 | }); 49 | runScheduler(); 50 | } 51 | -------------------------------------------------------------------------------- /examples/io.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2024, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." } 10 | }, 11 | "description": "A test for file read/write thread offload", 12 | "license": "BOOST", 13 | "name": "io" 14 | } 15 | +/ 16 | version(Windows) { 17 | 18 | void main() { 19 | 20 | } 21 | 22 | } else: 23 | import std.stdio; 24 | import core.sys.posix.unistd; 25 | import photon; 26 | 27 | void read_a_lot() { 28 | ubyte[] buf = new ubyte[2^^20]; 29 | int file = open("/dev/zero", O_RDONLY); 30 | scope(exit) close(file); 31 | foreach (_; 0..16_000) { 32 | read(file, buf.ptr, buf.length); 33 | } 34 | } 35 | 36 | void main() { 37 | initPhoton(); 38 | go({ 39 | goOnSameThread({ 40 | writeln("Starting to read a lot"); 41 | read_a_lot(); 42 | writeln("Done reading a lot"); 43 | }); 44 | goOnSameThread({ 45 | writeln("Starting to read a lot #2"); 46 | read_a_lot(); 47 | writeln("Done reading a lot #2"); 48 | }); 49 | goOnSameThread({ 50 | writeln("Starting to read a lot #3"); 51 | read_a_lot(); 52 | writeln("Done reading a lot #3"); 53 | }); 54 | }); 55 | runScheduler(); 56 | } -------------------------------------------------------------------------------- /examples/join.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for task join API", 11 | "license": "BOOST", 12 | "name": "join" 13 | } 14 | +/ 15 | module examples.join; 16 | 17 | import photon; 18 | 19 | void main() { 20 | initPhoton(); 21 | shared int flag = 0; 22 | auto task1 = go({ 23 | flag = 1; 24 | }); 25 | auto failedTask = go({ 26 | throw new Exception("Boom!"); 27 | }); 28 | auto task2 = go({ 29 | task1.join(); 30 | assert(flag == 1); 31 | try { 32 | failedTask.join(); 33 | assert(false); 34 | } catch(Exception e) { 35 | assert(e.msg == "Boom!"); 36 | } 37 | }); 38 | runScheduler(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/offloaded.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2025, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." } 10 | }, 11 | "description": "A test for offload primitive", 12 | "license": "BOOST", 13 | "name": "offloaded" 14 | } 15 | +/ 16 | module offloaded; 17 | import photon; 18 | 19 | double gauss(double a, double b, double function(double) f, double step) { 20 | double sum = 0.0; 21 | for (double x = a; x < b; x += step) { 22 | sum += (f(x+step) + f(x))/2 * step; 23 | } 24 | return sum; 25 | } 26 | 27 | void boom() { 28 | throw new Exception("Boom!"); 29 | } 30 | 31 | long fib(long n) { 32 | if (n <= 2) return 1; 33 | else { 34 | return offload(() => fib(n-1)) + offload(() => fib(n-2)); 35 | } 36 | } 37 | 38 | void main() { 39 | initPhoton(); 40 | go({ 41 | goOnSameThread({ 42 | writeln("Blocking computation"); 43 | writeln("Integral:", gauss(0.0, 10.0, x => x*x, 1e-7)); 44 | }); 45 | goOnSameThread({ 46 | writeln("Blocking computation"); 47 | writeln("Integral:", gauss(0.0, 10.0, x => x*x, 1e-7)); 48 | }); 49 | goOnSameThread({ 50 | writeln("Nonblocking computation"); 51 | writeln("Integral: ", offload(() => gauss(0.0, 10.0, x => x*x, 1e-7))); 52 | }); 53 | goOnSameThread({ 54 | writeln("Nonblocking computation"); 55 | writeln("Integral: ", offload(() => gauss(0.0, 10.0, x => x*x, 1e-7))); 56 | }); 57 | goOnSameThread({ 58 | writeln("Catching exception from offloaded computation"); 59 | try { 60 | offload(&boom); 61 | assert(0); 62 | } catch(Exception e) { 63 | assert(e.msg == "Boom!"); 64 | } 65 | }); 66 | goOnSameThread({ 67 | writeln("Recursive offload"); 68 | writeln("Fib(15):", offload(() => fib(15))); 69 | }); 70 | }); 71 | runScheduler(); 72 | } -------------------------------------------------------------------------------- /examples/pool.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for pool API", 11 | "license": "BOOST", 12 | "name": "pool" 13 | } 14 | +/ 15 | module examples.pool; 16 | 17 | import core.atomic, core.time; 18 | import std.stdio; 19 | import photon; 20 | 21 | 22 | void main() { 23 | initPhoton(); 24 | shared int active; 25 | shared int closed; 26 | auto counts = pool(2, 1.seconds, () => atomicFetchAdd(active, 1), (ref int i) { atomicFetchAdd(closed, 1); }); 27 | go({ 28 | auto first = counts.acquire(); 29 | writeln("acquire: ", first.get); 30 | auto second = counts.acquire(); 31 | writeln("acquire: ", second.get); 32 | go({ 33 | delay(1.seconds); 34 | counts.release(first); 35 | writeln("closed:", closed); 36 | }); 37 | auto third = counts.acquire(); 38 | writeln("acquire:", third.get); 39 | counts.dispose(third); // now we have allocated == 1 and empty free list 40 | writeln("closed: ", closed); 41 | auto fourth = counts.acquire(); // so this should allocate new connection 42 | writeln("acquire: ", fourth.get); 43 | counts.release(fourth); 44 | counts.release(second); 45 | delay(2.seconds); // give time for the cleaner to cleanup stale connections 46 | writeln("closed: ", 3); 47 | auto fifth = counts.acquire(); 48 | writeln("acquire: ", fifth.get); 49 | counts.shutdown(); 50 | writeln("after shutdown"); 51 | }); 52 | runScheduler(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | for name in *.d ; do 3 | dub $name || exit 1 4 | done 5 | 6 | -------------------------------------------------------------------------------- /examples/select.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for select over channels API", 11 | "license": "BOOST", 12 | "name": "select" 13 | } 14 | +/ 15 | module examples.select; 16 | 17 | import std.range, std.datetime, std.stdio; 18 | 19 | import photon; 20 | 21 | void main() { 22 | initPhoton(); 23 | auto first = channel!(int)(2); 24 | auto second = channel!(string)(1); 25 | go({ 26 | delay(500.msecs); 27 | first.put(0); 28 | first.put(1); 29 | delay(500.msecs); 30 | second.put("ping"); 31 | }); 32 | go({ 33 | foreach ( _; 0..3) { 34 | select( 35 | first, { 36 | writefln("Got first %s", first.take(1)); 37 | }, 38 | second, { 39 | writefln("Got second %s", second.take(1)); 40 | } 41 | ); 42 | } 43 | }); 44 | runScheduler(); 45 | } 46 | -------------------------------------------------------------------------------- /examples/semaphore.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "name" : "semaphore", 5 | "dependencies": { 6 | "photon": { "path" : ".." } 7 | } 8 | } 9 | +/ 10 | module examples.semaphore; 11 | 12 | import std.stdio; 13 | import photon; 14 | import core.thread; 15 | import core.time; 16 | 17 | void main() { 18 | initPhoton(); 19 | shared Semaphore sem = semaphore(0); 20 | void waitingTask(int n) { 21 | go({ 22 | writefln("Fiber #%d started!", n); 23 | sem.wait(); 24 | writefln("Fiber #%d exited!", n); 25 | }); 26 | } 27 | foreach (i; 0..3) { 28 | waitingTask(i); 29 | } 30 | go({ 31 | auto tm = timer(); 32 | writeln("Main fiber started!"); 33 | tm.wait(1000.msecs); 34 | writeln("Releasing two fibers!"); 35 | sem.trigger(2); 36 | tm.wait(1000.msecs); 37 | writeln("Releasing one fiber!"); 38 | sem.trigger(1); 39 | writeln("Main fiber exited!"); 40 | }); 41 | runScheduler(); 42 | sem.dispose(); 43 | } -------------------------------------------------------------------------------- /examples/sleepy.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2024, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." } 10 | }, 11 | "description": "A test for sleep function interception", 12 | "license": "BOOST", 13 | "name": "sleepy" 14 | } 15 | +/ 16 | module examples.sleepy; 17 | 18 | import std.stdio; 19 | import core.time; 20 | import core.thread; 21 | import photon; 22 | 23 | 24 | void task(string msg, Duration duration) { 25 | Thread.sleep(duration / 2); 26 | delay(duration / 2); 27 | writeln(msg); 28 | } 29 | 30 | void main() { 31 | initPhoton(); 32 | writeln("Starting a bunch of fibers and threads, each waiting 1 second"); 33 | foreach (_; 0..1000) { 34 | go({ 35 | task("fiber sleep is over", 1.seconds); 36 | }); 37 | } 38 | foreach (_; 0..100) { 39 | new Thread({ 40 | task("thread sleep is over", 1.seconds); 41 | }).start(); 42 | } 43 | runScheduler(); 44 | } 45 | -------------------------------------------------------------------------------- /examples/task_local.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2024, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." } 10 | }, 11 | "description": "A test for task local storage", 12 | "license": "BOOST", 13 | "name": "task_local" 14 | } 15 | +/ 16 | import core.atomic; 17 | import photon; 18 | 19 | shared int counter = 2; 20 | 21 | struct Destructible { 22 | char field; 23 | this(char ch) { field = ch; } 24 | ~this() { 25 | if (!__ctfe) { 26 | atomicOp!"-="(counter, 1); 27 | } 28 | } 29 | } 30 | 31 | TaskLocal!string message; 32 | 33 | 34 | void main() { 35 | static TaskLocal!Destructible destructible = Destructible('A'); 36 | initPhoton(); 37 | goOnSameThread({ 38 | assert(message == null); 39 | message = "Hello, world"; 40 | assert(message == "Hello, world"); 41 | assert(destructible.field == 'A'); 42 | destructible.field = 'B'; 43 | assert(destructible.field == 'B'); 44 | }); 45 | goOnSameThread({ 46 | assert(message == null); 47 | message = "Huh?"; 48 | assert(message == "Huh?"); 49 | assert(destructible.field == 'A'); 50 | destructible.field = 'C'; 51 | assert(destructible.field == 'C'); 52 | }); 53 | runScheduler(); 54 | assert(counter == 0); 55 | } -------------------------------------------------------------------------------- /img/DApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryOlshansky/photon/d03ad582d03083dbaf4a53eb0ad08a18206df517/img/DApp.png -------------------------------------------------------------------------------- /img/DApp.svg: -------------------------------------------------------------------------------- 1 | 2 |
D Application
D Application
D Fiber-Aware
library
D Fiber-Aware<div>library</div>
vibe.d
vibe.d
Linux Kernel
Linux Kernel
Event library
(e.g. libevent)
[Not supported by viewer]
LibC syscall-wrapper
LibC syscall-wrapper
D sync library
(e.g. std.socket)
[Not supported by viewer]
LibC
LibC
3rd party
sync C library
[Not supported by viewer]
3rd party
async D or C library
3rd party<div>async D or C library</div>
1
1
2
2
3
3
4
4
-------------------------------------------------------------------------------- /src/mecca/division.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Library for efficient integer division by a single divider 3 | * 4 | * When repeatedly dividing (with integer arithmetics) by the same divider, there are certain tricks that allow 5 | * for quicker operation that the CPU's divide command. This is payed for by higher computation work during the setup stage. 6 | * 7 | * For compile time known values, the compiler already performs this trick. This module is meant for run-time known values 8 | * that are used repeatedly. 9 | * 10 | * This code is a D adaptation of libdivide ($(LINK http://libdivide.com)). 11 | */ 12 | 13 | /* 14 | Copyright (C) 2010 ridiculous_fish libdivide@ridiculousfish.com 15 | Copyright (C) 2017 Weka.IO 16 | 17 | Notice that though the original libdivide is available under either the zlib license or Boost, the D adaptation here 18 | is only available under the Boost license. 19 | 20 | Please see the AUTHORS file for full copyright and license information. 21 | */ 22 | module mecca.division; 23 | 24 | import std.stdint; 25 | 26 | /** 27 | * Signed 32 bit divisor 28 | * 29 | * Simply use on right side of division operation 30 | */ 31 | struct S32Divisor { 32 | /// 33 | unittest { 34 | assert (1000 / S32Divisor(50) == 20); 35 | // Can be used with CTFE 36 | static assert (1000 / S32Divisor(50) == 20); 37 | } 38 | 39 | alias Type = typeof(magic); 40 | int32_t magic; 41 | uint8_t more; 42 | 43 | this(int32_t d) { 44 | assert (d > 0, "d<=0"); 45 | 46 | // If d is a power of 2, or negative a power of 2, we have to use a shift. This is especially 47 | // important because the magic algorithm fails for -1. To check if d is a power of 2 or its inverse, 48 | // it suffices to check whether its absolute value has exactly one bit set. This works even for INT_MIN, 49 | // because abs(INT_MIN) == INT_MIN, and INT_MIN has one bit set and is a power of 2. 50 | uint32_t absD = cast(uint32_t)(d < 0 ? -d : d); //gcc optimizes this to the fast abs trick 51 | if ((absD & (absD - 1)) == 0) { //check if exactly one bit is set, don't care if absD is 0 since that's divide by zero 52 | this.magic = 0; 53 | this.more = cast(uint8_t)(libdivide__count_trailing_zeros32(absD) | (d < 0 ? LIBDIVIDE_NEGATIVE_DIVISOR : 0) | LIBDIVIDE_S32_SHIFT_PATH); 54 | } 55 | else { 56 | const uint32_t floor_log_2_d = cast(uint8_t)(31 - libdivide__count_leading_zeros32(absD)); 57 | assert(floor_log_2_d >= 1); 58 | 59 | uint8_t more; 60 | //the dividend here is 2**(floor_log_2_d + 31), so the low 32 bit word is 0 and the high word is floor_log_2_d - 1 61 | uint32_t rem, proposed_m; 62 | proposed_m = libdivide_64_div_32_to_32(1U << (floor_log_2_d - 1), 0, absD, &rem); 63 | const uint32_t e = absD - rem; 64 | 65 | /* We are going to start with a power of floor_log_2_d - 1. This works if works if e < 2**floor_log_2_d. */ 66 | if (e < (1U << floor_log_2_d)) { 67 | /* This power works */ 68 | more = cast(uint8_t)(floor_log_2_d - 1); 69 | } 70 | else { 71 | // We need to go one higher. This should not make proposed_m overflow, but it will make it negative 72 | // when interpreted as an int32_t. 73 | proposed_m += proposed_m; 74 | const uint32_t twice_rem = rem + rem; 75 | if (twice_rem >= absD || twice_rem < rem) proposed_m += 1; 76 | more = cast(uint8_t)(floor_log_2_d | LIBDIVIDE_ADD_MARKER | (d < 0 ? LIBDIVIDE_NEGATIVE_DIVISOR : 0)); //use the general algorithm 77 | } 78 | proposed_m += 1; 79 | this.magic = (d < 0 ? -cast(int32_t)proposed_m : cast(int32_t)proposed_m); 80 | this.more = more; 81 | } 82 | } 83 | 84 | ref auto opAssign(int32_t d) { 85 | this.__ctor(d); 86 | return this; 87 | } 88 | 89 | int32_t opBinaryRight(string op: "/")(int32_t dividend) const pure nothrow @safe @nogc { 90 | if (more & LIBDIVIDE_S32_SHIFT_PATH) { 91 | uint8_t shifter = more & LIBDIVIDE_32_SHIFT_MASK; 92 | int32_t q = dividend + ((dividend >> 31) & ((1 << shifter) - 1)); 93 | q = q >> shifter; 94 | int32_t shiftMask = cast(int8_t)(more >> 7); //must be arithmetic shift and then sign-extend 95 | q = (q ^ shiftMask) - shiftMask; 96 | return q; 97 | } 98 | else { 99 | int32_t q = libdivide__mullhi_s32(magic, dividend); 100 | if (more & LIBDIVIDE_ADD_MARKER) { 101 | int32_t sign = cast(int8_t)(more >> 7); //must be arithmetic shift and then sign extend 102 | q += ((dividend ^ sign) - sign); 103 | } 104 | q >>= more & LIBDIVIDE_32_SHIFT_MASK; 105 | q += (q < 0); 106 | return q; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Unsigned 32 bit divisor 113 | * 114 | * Simply use on right side of division operation 115 | */ 116 | struct U32Divisor { 117 | /// 118 | unittest { 119 | assert (1000 / U32Divisor(31) == 32); 120 | // Can be used with CTFE 121 | static assert (1000 / U32Divisor(31) == 32); 122 | } 123 | 124 | alias Type = typeof(magic); 125 | uint32_t magic; 126 | uint8_t more; 127 | 128 | this(uint32_t d) { 129 | assert (d > 0, "d==0"); 130 | if ((d & (d - 1)) == 0) { 131 | this.magic = 0; 132 | this.more = cast(uint8_t)(libdivide__count_trailing_zeros32(d) | LIBDIVIDE_U32_SHIFT_PATH); 133 | } 134 | else { 135 | const uint32_t floor_log_2_d = 31 - libdivide__count_leading_zeros32(d); 136 | 137 | uint8_t more; 138 | uint32_t rem, proposed_m; 139 | proposed_m = libdivide_64_div_32_to_32(1U << floor_log_2_d, 0, d, &rem); 140 | 141 | assert(rem > 0 && rem < d); 142 | const uint32_t e = d - rem; 143 | 144 | /* This power works if e < 2**floor_log_2_d. */ 145 | if (e < (1U << floor_log_2_d)) { 146 | /* This power works */ 147 | more = cast(uint8_t)floor_log_2_d; 148 | } 149 | else { 150 | // We have to use the general 33-bit algorithm. We need to compute (2**power) / d. 151 | // However, we already have (2**(power-1))/d and its remainder. By doubling both, and then 152 | // correcting the remainder, we can compute the larger division. */ 153 | proposed_m += proposed_m; //don't care about overflow here - in fact, we expect it 154 | const uint32_t twice_rem = rem + rem; 155 | if (twice_rem >= d || twice_rem < rem) proposed_m += 1; 156 | more = cast(uint8_t)(floor_log_2_d | LIBDIVIDE_ADD_MARKER); 157 | } 158 | this.magic = 1 + proposed_m; 159 | this.more = more; 160 | //result.more's shift should in general be ceil_log_2_d. But if we used the smaller power, we 161 | //subtract one from the shift because we're using the smaller power. If we're using the larger power, 162 | //we subtract one from the shift because it's taken care of by the add indicator. 163 | //So floor_log_2_d happens to be correct in both cases. 164 | } 165 | } 166 | 167 | ref auto opAssign(uint32_t d) { 168 | this.__ctor(d); 169 | return this; 170 | } 171 | 172 | uint32_t opBinaryRight(string op: "/")(uint32_t dividend) const pure nothrow @safe @nogc { 173 | if (more & LIBDIVIDE_U32_SHIFT_PATH) { 174 | return dividend >> (more & LIBDIVIDE_32_SHIFT_MASK); 175 | } 176 | else { 177 | uint32_t q = libdivide__mullhi_u32(magic, dividend); 178 | if (more & LIBDIVIDE_ADD_MARKER) { 179 | uint32_t t = ((dividend - q) >> 1) + q; 180 | return t >> (more & LIBDIVIDE_32_SHIFT_MASK); 181 | } 182 | else { 183 | return q >> more; //all upper bits are 0 - don't need to mask them off 184 | } 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Signed 64 bit divisor 191 | * 192 | * Simply use on right side of division operation 193 | */ 194 | struct S64Divisor { 195 | /// 196 | unittest { 197 | assert (1000 / S64Divisor(81) == 12); 198 | // Can be used with CTFE 199 | static assert (1000 / S64Divisor(81) == 12); 200 | } 201 | 202 | alias Type = typeof(magic); 203 | int64_t magic; 204 | uint8_t more; 205 | 206 | this(int64_t d) nothrow @trusted @nogc { 207 | assert (d > 0, "d<=0"); 208 | // If d is a power of 2, or negative a power of 2, we have to use a shift. This is especially important 209 | // because the magic algorithm fails for -1. To check if d is a power of 2 or its inverse, it suffices 210 | // to check whether its absolute value has exactly one bit set. This works even for INT_MIN, 211 | // because abs(INT_MIN) == INT_MIN, and INT_MIN has one bit set and is a power of 2. 212 | const uint64_t absD = cast(uint64_t)(d < 0 ? -d : d); //gcc optimizes this to the fast abs trick 213 | if ((absD & (absD - 1)) == 0) { //check if exactly one bit is set, don't care if absD is 0 since that's divide by zero 214 | this.more = cast(ubyte)(libdivide__count_trailing_zeros64(absD) | (d < 0 ? LIBDIVIDE_NEGATIVE_DIVISOR : 0)); 215 | this.magic = 0; 216 | } 217 | else { 218 | const uint32_t floor_log_2_d = cast(uint32_t)(63 - libdivide__count_leading_zeros64(absD)); 219 | 220 | //the dividend here is 2**(floor_log_2_d + 63), so the low 64 bit word is 0 and the high word is floor_log_2_d - 1 221 | uint8_t more; 222 | uint64_t rem, proposed_m; 223 | proposed_m = libdivide_128_div_64_to_64(1UL << (floor_log_2_d - 1), 0, absD, &rem); // XXX This line is not @safe 224 | const uint64_t e = absD - rem; 225 | 226 | /* We are going to start with a power of floor_log_2_d - 1. This works if works if e < 2**floor_log_2_d. */ 227 | if (e < (1UL << floor_log_2_d)) { 228 | /* This power works */ 229 | more = cast(ubyte)(floor_log_2_d - 1); 230 | } 231 | else { 232 | // We need to go one higher. This should not make proposed_m overflow, but it will make it 233 | // negative when interpreted as an int32_t. 234 | proposed_m += proposed_m; 235 | const uint64_t twice_rem = rem + rem; 236 | if (twice_rem >= absD || twice_rem < rem) proposed_m += 1; 237 | more = cast(ubyte)(floor_log_2_d | LIBDIVIDE_ADD_MARKER | (d < 0 ? LIBDIVIDE_NEGATIVE_DIVISOR : 0)); 238 | } 239 | proposed_m += 1; 240 | this.more = more; 241 | this.magic = (d < 0 ? -cast(int64_t)proposed_m : cast(int64_t)proposed_m); 242 | } 243 | } 244 | 245 | ref auto opAssign(int64_t d) { 246 | this.__ctor(d); 247 | return this; 248 | } 249 | 250 | int64_t opBinaryRight(string op: "/")(int64_t dividend) const pure nothrow @safe @nogc { 251 | if (magic == 0) { //shift path 252 | uint32_t shifter = more & LIBDIVIDE_64_SHIFT_MASK; 253 | int64_t q = dividend + ((dividend >> 63) & ((1L << shifter) - 1)); 254 | q = q >> shifter; 255 | int64_t shiftMask = cast(int8_t)(more >> 7); //must be arithmetic shift and then sign-extend 256 | q = (q ^ shiftMask) - shiftMask; 257 | return q; 258 | } 259 | else { 260 | int64_t q = libdivide__mullhi_s64(magic, dividend); 261 | if (more & LIBDIVIDE_ADD_MARKER) { 262 | int64_t sign = cast(int8_t)(more >> 7); //must be arithmetic shift and then sign extend 263 | q += ((dividend ^ sign) - sign); 264 | } 265 | q >>= more & LIBDIVIDE_64_SHIFT_MASK; 266 | q += (q < 0); 267 | return q; 268 | } 269 | } 270 | } 271 | 272 | /** 273 | * Unsigned 64 bit divisor 274 | * 275 | * Simply use on right side of division operation 276 | */ 277 | struct U64Divisor { 278 | /// 279 | unittest { 280 | assert (1_000_000_000_000 / S64Divisor(1783) == 560_852_495); 281 | // Can be used with CTFE 282 | static assert (1_000_000_000_000 / S64Divisor(1783) == 560_852_495); 283 | } 284 | 285 | alias Type = typeof(magic); 286 | uint64_t magic; 287 | uint8_t more; 288 | 289 | this(uint64_t d) { 290 | assert (d > 0, "d==0"); 291 | if ((d & (d - 1)) == 0) { 292 | this.more = cast(uint8_t)(libdivide__count_trailing_zeros64(d) | LIBDIVIDE_U64_SHIFT_PATH); 293 | this.magic = 0; 294 | } 295 | else { 296 | const uint32_t floor_log_2_d = 63 - libdivide__count_leading_zeros64(d); 297 | 298 | uint64_t proposed_m, rem; 299 | uint8_t more; 300 | proposed_m = libdivide_128_div_64_to_64(1UL << floor_log_2_d, 0, d, &rem); //== (1 << (64 + floor_log_2_d)) / d 301 | 302 | assert(rem > 0 && rem < d); 303 | const uint64_t e = d - rem; 304 | 305 | /* This power works if e < 2**floor_log_2_d. */ 306 | if (e < (1UL << floor_log_2_d)) { 307 | /* This power works */ 308 | more = cast(uint8_t)floor_log_2_d; 309 | } 310 | else { 311 | // We have to use the general 65-bit algorithm. We need to compute (2**power) / d. However, 312 | // we already have (2**(power-1))/d and its remainder. By doubling both, and then correcting 313 | // the remainder, we can compute the larger division. 314 | proposed_m += proposed_m; //don't care about overflow here - in fact, we expect it 315 | const uint64_t twice_rem = rem + rem; 316 | if (twice_rem >= d || twice_rem < rem) proposed_m += 1; 317 | more = cast(uint8_t)(floor_log_2_d | LIBDIVIDE_ADD_MARKER); 318 | } 319 | this.magic = 1 + proposed_m; 320 | this.more = more; 321 | //result.more's shift should in general be ceil_log_2_d. But if we used the smaller power, we subtract 322 | //one from the shift because we're using the smaller power. If we're using the larger power, we subtract 323 | //one from the shift because it's taken care of by the add indicator. So floor_log_2_d happens to be 324 | //correct in both cases, which is why we do it outside of the if statement. 325 | } 326 | } 327 | 328 | ref auto opAssign(uint64_t d) { 329 | this.__ctor(d); 330 | return this; 331 | } 332 | 333 | uint64_t opBinaryRight(string op: "/")(uint64_t dividend) const pure nothrow @safe @nogc { 334 | if (more & LIBDIVIDE_U64_SHIFT_PATH) { 335 | return dividend >> (more & LIBDIVIDE_64_SHIFT_MASK); 336 | } 337 | else { 338 | uint64_t q = libdivide__mullhi_u64(magic, dividend); 339 | if (more & LIBDIVIDE_ADD_MARKER) { 340 | uint64_t t = ((dividend - q) >> 1) + q; 341 | return t >> (more & LIBDIVIDE_64_SHIFT_MASK); 342 | } 343 | else { 344 | return q >> more; //all upper bits are 0 - don't need to mask them off 345 | } 346 | } 347 | } 348 | } 349 | 350 | /// Automatically selects the correct divisor based on type 351 | auto divisor(T)(T value) { 352 | static if (is(T == uint32_t)) { 353 | return U32Divisor(value); 354 | } 355 | else static if (is(T == int32_t)) { 356 | return S32Divisor(value); 357 | } 358 | else static if (is(T == uint64_t)) { 359 | return U64Divisor(value); 360 | } 361 | else static if (is(T == int64_t)) { 362 | return S64Divisor(value); 363 | } 364 | else { 365 | static assert (false, "T must be an int, uint, long, ulong, not " ~ T.stringof); 366 | } 367 | } 368 | 369 | private: 370 | enum { 371 | LIBDIVIDE_32_SHIFT_MASK = 0x1F, 372 | LIBDIVIDE_64_SHIFT_MASK = 0x3F, 373 | LIBDIVIDE_ADD_MARKER = 0x40, 374 | LIBDIVIDE_U32_SHIFT_PATH = 0x80, 375 | LIBDIVIDE_U64_SHIFT_PATH = 0x80, 376 | LIBDIVIDE_S32_SHIFT_PATH = 0x20, 377 | LIBDIVIDE_NEGATIVE_DIVISOR = 0x80, 378 | } 379 | 380 | static int32_t libdivide__count_trailing_zeros32(uint32_t val) pure nothrow @safe @nogc { 381 | /* Fast way to count trailing zeros */ 382 | //return __builtin_ctz(val); 383 | /* Dorky way to count trailing zeros. Note that this hangs for val = 0! */ 384 | int32_t result = 0; 385 | val = (val ^ (val - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest 386 | while (val) { 387 | val >>= 1; 388 | result++; 389 | } 390 | return result; 391 | } 392 | 393 | static int32_t libdivide__count_leading_zeros32(uint32_t val) pure nothrow @safe @nogc { 394 | /* Fast way to count leading zeros */ 395 | //return __builtin_clz(val); 396 | /* Dorky way to count leading zeros. Note that this hangs for val = 0! */ 397 | int32_t result = 0; 398 | while (! (val & (1U << 31))) { 399 | val <<= 1; 400 | result++; 401 | } 402 | return result; 403 | } 404 | 405 | static uint32_t libdivide_64_div_32_to_32(uint32_t u1, uint32_t u0, uint32_t v, uint32_t *r) pure nothrow @safe @nogc { 406 | //libdivide_64_div_32_to_32: divides a 64 bit uint {u1, u0} by a 32 bit uint {v}. The result must fit in 32 bits. 407 | //Returns the quotient directly and the remainder in *r 408 | //#if (LIBDIVIDE_IS_X86_64) 409 | // uint32_t result; 410 | // __asm__("divl %[v]" 411 | // : "=a"(result), "=d"(*r) 412 | // : [v] "r"(v), "a"(u0), "d"(u1) 413 | // ); 414 | // return result; 415 | //} 416 | //#else 417 | uint64_t n = ((cast(uint64_t)u1) << 32) | u0; 418 | uint32_t result = cast(uint32_t)(n / v); 419 | *r = cast(uint32_t)(n - result * cast(uint64_t)v); 420 | return result; 421 | //#endif 422 | } 423 | 424 | static uint32_t libdivide__mullhi_u32(uint32_t x, uint32_t y) pure nothrow @safe @nogc { 425 | uint64_t xl = x, yl = y; 426 | uint64_t rl = xl * yl; 427 | return cast(uint32_t)(rl >> 32); 428 | } 429 | 430 | static int32_t libdivide__count_trailing_zeros64(uint64_t val) pure nothrow @safe @nogc { 431 | // Fast way to count trailing zeros. Note that we disable this in 32 bit because gcc does something horrible - 432 | // it calls through to a dynamically bound function. 433 | //return __builtin_ctzll(val); 434 | // Pretty good way to count trailing zeros. Note that this hangs for val = 0 435 | assert (val != 0); 436 | uint32_t lo = val & 0xFFFFFFFF; 437 | if (lo != 0) return libdivide__count_trailing_zeros32(lo); 438 | return 32 + libdivide__count_trailing_zeros32(cast(uint32_t) (val >> 32)); 439 | } 440 | 441 | static int64_t libdivide__mullhi_s64(int64_t x, int64_t y) pure nothrow @safe @nogc { 442 | static if (is(cent)) { 443 | cent xl = x, yl = y; 444 | cent rl = xl * yl; 445 | return cast(cent)(rl >> 64); 446 | } 447 | else { 448 | //full 128 bits are x0 * y0 + (x0 * y1 << 32) + (x1 * y0 << 32) + (x1 * y1 << 64) 449 | const uint32_t mask = 0xFFFFFFFF; 450 | const uint32_t x0 = cast(uint32_t)(x & mask), y0 = cast(uint32_t)(y & mask); 451 | const int32_t x1 = cast(int32_t)(x >> 32), y1 = cast(int32_t)(y >> 32); 452 | const uint32_t x0y0_hi = libdivide__mullhi_u32(x0, y0); 453 | const int64_t t = x1*cast(int64_t)y0 + x0y0_hi; 454 | const int64_t w1 = x0*cast(int64_t)y1 + (t & mask); 455 | return x1*cast(int64_t)y1 + (t >> 32) + (w1 >> 32); 456 | } 457 | } 458 | 459 | static int32_t libdivide__mullhi_s32(int32_t x, int32_t y) pure nothrow @safe @nogc { 460 | int64_t xl = x, yl = y; 461 | int64_t rl = xl * yl; 462 | return cast(int32_t)(rl >> 32); //needs to be arithmetic shift 463 | } 464 | 465 | static int32_t libdivide__count_leading_zeros64(uint64_t val) pure nothrow @safe @nogc { 466 | /* Fast way to count leading zeros */ 467 | //return __builtin_clzll(val); 468 | /* Dorky way to count leading zeros. Note that this hangs for val = 0! */ 469 | int32_t result = 0; 470 | while (! (val & (1UL << 63))) { 471 | val <<= 1; 472 | result++; 473 | } 474 | return result; 475 | } 476 | 477 | static uint64_t libdivide_128_div_64_to_64(uint64_t u1, uint64_t u0, uint64_t v, uint64_t *r) pure nothrow @safe @nogc { 478 | const uint64_t b = (1UL << 32); // Number base (16 bits). 479 | uint64_t un1, un0, // Norm. dividend LSD's. 480 | vn1, vn0, // Norm. divisor digits. 481 | q1, q0, // Quotient digits. 482 | un64, un21, un10, // Dividend digit pairs. 483 | rhat; // A remainder. 484 | int s; // Shift amount for norm. 485 | 486 | if (u1 >= v) { // If overflow, set rem. 487 | if (r !is null) { // to an impossible value, 488 | *r = cast(uint64_t)(-1); // and return the largest 489 | } 490 | else { 491 | return cast(uint64_t)(-1); // possible quotient. 492 | } 493 | } 494 | 495 | /* count leading zeros */ 496 | s = libdivide__count_leading_zeros64(v); // 0 <= s <= 63. 497 | if (s > 0) { 498 | v = v << s; // Normalize divisor. 499 | un64 = (u1 << s) | ((u0 >> (64 - s)) & (-s >> 31)); 500 | un10 = u0 << s; // Shift dividend left. 501 | } 502 | else { 503 | // Avoid undefined behavior. 504 | un64 = u1 | u0; 505 | un10 = u0; 506 | } 507 | 508 | vn1 = v >> 32; // Break divisor up into 509 | vn0 = v & 0xFFFFFFFF; // two 32-bit digits. 510 | 511 | un1 = un10 >> 32; // Break right half of 512 | un0 = un10 & 0xFFFFFFFF; // dividend into two digits. 513 | 514 | q1 = un64/vn1; // Compute the first 515 | rhat = un64 - q1*vn1; // quotient digit, q1. 516 | again1: 517 | if (q1 >= b || q1*vn0 > b*rhat + un1) { 518 | q1 = q1 - 1; 519 | rhat = rhat + vn1; 520 | if (rhat < b) goto again1; 521 | } 522 | 523 | un21 = un64*b + un1 - q1*v; // Multiply and subtract. 524 | 525 | q0 = un21/vn1; // Compute the second 526 | rhat = un21 - q0*vn1; // quotient digit, q0. 527 | again2: 528 | if (q0 >= b || q0*vn0 > b*rhat + un0) { 529 | q0 = q0 - 1; 530 | rhat = rhat + vn1; 531 | if (rhat < b) goto again2; 532 | } 533 | 534 | if (r !is null) { // If remainder is wanted, 535 | *r = (un21*b + un0 - q0*v) >> s; // return it. 536 | } 537 | return q1*b + q0; 538 | } 539 | 540 | static uint64_t libdivide__mullhi_u64(uint64_t x, uint64_t y) pure nothrow @safe @nogc { 541 | static if (is(ucent)) { 542 | ucent xl = x, yl = y; 543 | ucent rl = xl * yl; 544 | return cast(ucent)(rl >> 64); 545 | } 546 | else { 547 | //full 128 bits are x0 * y0 + (x0 * y1 << 32) + (x1 * y0 << 32) + (x1 * y1 << 64) 548 | const uint32_t mask = 0xFFFFFFFF; 549 | const uint32_t x0 = cast(uint32_t)(x & mask), x1 = cast(uint32_t)(x >> 32); 550 | const uint32_t y0 = cast(uint32_t)(y & mask), y1 = cast(uint32_t)(y >> 32); 551 | const uint32_t x0y0_hi = libdivide__mullhi_u32(x0, y0); 552 | const uint64_t x0y1 = x0 * cast(uint64_t)y1; 553 | const uint64_t x1y0 = x1 * cast(uint64_t)y0; 554 | const uint64_t x1y1 = x1 * cast(uint64_t)y1; 555 | 556 | uint64_t temp = x1y0 + x0y0_hi; 557 | uint64_t temp_lo = temp & mask, temp_hi = temp >> 32; 558 | return x1y1 + temp_hi + ((temp_lo + x0y1) >> 32); 559 | } 560 | } 561 | 562 | unittest { 563 | import std.random; 564 | import std.string; 565 | import std.typetuple; 566 | 567 | int counter; 568 | alias divisors = TypeTuple!(S32Divisor, U32Divisor, S64Divisor, U64Divisor); 569 | enum tests = 5000; 570 | 571 | foreach(D; divisors) { 572 | foreach(j; 0 .. tests) { 573 | D.Type d = uniform(1, D.Type.max); 574 | D.Type n = uniform(D.Type.min, D.Type.max); 575 | D.Type q = n / d; 576 | 577 | D d2 = D(d); 578 | D.Type q2 = n / d2; 579 | assert (q == q2, "%s/%s = %s, not %s".format(n, d, q, q2)); 580 | counter++; 581 | } 582 | } 583 | assert(counter == divisors.length * tests, "counter=%s".format(counter)); 584 | } 585 | -------------------------------------------------------------------------------- /src/photon/core.d: -------------------------------------------------------------------------------- 1 | module photon.core; 2 | 3 | version(OSX) version = Darwin; 4 | else version(iOS) version = Darwin; 5 | else version(TVOS) version = Darwin; 6 | else version(WatchOS) version = Darwin; 7 | else version(VisionOS) version = Darwin; 8 | 9 | // TODO: time to factor out common parts of schedulers? 10 | version(linux) public import photon.reactor; 11 | else version(FreeBSD) public import photon.freebsd.core; 12 | else version(Darwin) public import photon.reactor; 13 | else version(Windows) public import photon.windows.core; 14 | -------------------------------------------------------------------------------- /src/photon/ds/common.d: -------------------------------------------------------------------------------- 1 | module photon.ds.common; 2 | 3 | import core.atomic; 4 | 5 | 6 | // T becomes thread-local b/c it's stolen from shared resource 7 | auto steal(T)(ref shared T arg) 8 | { 9 | for (;;) { 10 | auto v = atomicLoad(arg); 11 | if(cas(&arg, v, cast(shared(T))null)) return v; 12 | } 13 | } 14 | 15 | ref T unshared(T)(ref shared T value) 16 | if (!is(T : U*, U)) { 17 | return *cast(T*)&value; 18 | } 19 | 20 | T unshared(T)(shared T value) 21 | if (is(T == class)){ 22 | return *cast(T*)&value; 23 | } 24 | 25 | ref T* unshared(T)(ref shared(T)* value) { 26 | return *cast(T**)&value; 27 | } 28 | 29 | 30 | // intrusive list helper 31 | T removeFromList(T)(T head, T item) { 32 | if (head == item) return head.next; 33 | else { 34 | auto p = head; 35 | while(p.next) { 36 | if (p.next == item) 37 | p.next = item.next; 38 | else 39 | p = p.next; 40 | } 41 | return head; 42 | } 43 | } 44 | 45 | struct FreeList(E) { 46 | static if(is(E == class)) { 47 | alias T = E; 48 | } else { 49 | alias T = E*; 50 | } 51 | shared static T head; 52 | 53 | static T alloc() { 54 | shared T h; 55 | for (;;) { 56 | h = atomicLoad(head); 57 | if (h is null) break; 58 | if (cas(&head, h, h.next)) { 59 | break; 60 | } 61 | } 62 | if (h) return cast(T)h; 63 | else return new E; 64 | } 65 | 66 | static void dispose(T item) { 67 | for (;;) { 68 | auto h = atomicLoad(head); 69 | item.next = h; 70 | if (cas(&head, h, cast(shared)item)) { 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | 77 | alias Seq(T...) = T; 78 | 79 | unittest { 80 | static class Entry { 81 | size_t value; 82 | shared Entry next; 83 | } 84 | static struct SEntry { 85 | size_t value; 86 | shared SEntry* next; 87 | } 88 | foreach (E; Seq!(Entry, SEntry)) { 89 | static if(is(E == class)) { 90 | alias T = E; 91 | } else { 92 | alias T = E*; 93 | } 94 | T[] entries = new T[10]; 95 | foreach (i, ref e; entries) { 96 | e = FreeList!E.alloc(); 97 | e.value = i; 98 | } 99 | FreeList!E.dispose(entries[9]); 100 | FreeList!E.dispose(entries[8]); 101 | auto e = FreeList!E.alloc(); 102 | assert(e.value == 8); 103 | e = FreeList!E.alloc(); 104 | assert(e.value == 9); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/photon/ds/intrusive_queue.d: -------------------------------------------------------------------------------- 1 | module photon.ds.intrusive_queue; 2 | 3 | import photon.ds.common; 4 | import core.internal.spinlock; 5 | 6 | shared struct IntrusiveQueue(T, Event) 7 | if (is(T : Object)) { 8 | private: 9 | SpinLock lock = SpinLock(SpinLock.Contention.brief); 10 | T head; 11 | T tail; 12 | bool exhausted = true; 13 | public: 14 | Event event; 15 | 16 | this(Event ev) { 17 | event = ev; 18 | } 19 | 20 | bool push(T item) { 21 | item.next = null; 22 | lock.lock(); 23 | bool wasEmpty = tail is null; 24 | if (wasEmpty) { 25 | head = tail = cast(shared)item; 26 | } 27 | else { 28 | tail.next = cast(shared)item; 29 | tail = cast(shared)item; 30 | } 31 | lock.unlock(); 32 | return wasEmpty; 33 | } 34 | 35 | bool tryPop(ref T item) nothrow { 36 | lock.lock(); 37 | if (!head) { 38 | lock.unlock(); 39 | return false; 40 | } 41 | else { 42 | item = head.unshared; 43 | head = head.next; 44 | if (head is null) tail = null; 45 | lock.unlock(); 46 | return true; 47 | } 48 | } 49 | 50 | // drain the whole queue in one go 51 | T drain() nothrow { 52 | lock.lock(); 53 | if (head is null) { 54 | lock.unlock(); 55 | return null; 56 | } 57 | else { 58 | auto r = head.unshared; 59 | head = tail = null; 60 | lock.unlock(); 61 | return r; 62 | } 63 | } 64 | } 65 | 66 | unittest { 67 | static class Box(T) { 68 | Box next; 69 | T item; 70 | this(T k) { 71 | item = k; 72 | } 73 | } 74 | 75 | static struct EmptyEvent { 76 | shared nothrow void trigger(){} 77 | } 78 | shared q = IntrusiveQueue!(Box!int, EmptyEvent)(); 79 | q.push(new Box!int(1)); 80 | q.push(new Box!int(2)); 81 | q.push(new Box!int(3)); 82 | Box!int ret; 83 | q.tryPop(ret); 84 | assert(ret.item == 1); 85 | q.tryPop(ret); 86 | assert(ret.item == 2); 87 | 88 | q.push(new Box!int(4)); 89 | q.tryPop(ret); 90 | assert(ret.item == 3); 91 | q.tryPop(ret); 92 | assert(ret.item == 4); 93 | q.push(new Box!int(5)); 94 | 95 | q.tryPop(ret); 96 | assert(ret.item == 5); 97 | assert(q.tryPop(ret) == false); 98 | } 99 | -------------------------------------------------------------------------------- /src/photon/ds/ring_queue.d: -------------------------------------------------------------------------------- 1 | module photon.ds.ring_queue; 2 | 3 | import core.atomic; 4 | import core.internal.spinlock; 5 | import core.stdc.stdlib; 6 | import core.lifetime; 7 | import core.memory; 8 | import std.traits; 9 | 10 | import photon.exceptions; 11 | 12 | 13 | struct RingQueue(T, Event) 14 | { 15 | T* store; 16 | size_t length; 17 | size_t fetch, insert, size; 18 | Event cts, rtr; // clear to send, ready to recieve 19 | bool closed; 20 | shared size_t refCount; 21 | AlignedSpinLock lock; 22 | 23 | this(size_t capacity, Event cts, Event rtr) 24 | { 25 | store = cast(T*)malloc(T.sizeof * capacity); 26 | static if (hasIndirections!T) { 27 | GC.addRange(store, T.sizeof * capacity); 28 | } 29 | length = capacity; 30 | size = 0; 31 | fetch = insert = 0; 32 | this.cts = move(cts); 33 | this.rtr = move(rtr); 34 | closed = false; 35 | refCount = 1; 36 | lock = AlignedSpinLock(SpinLock.Contention.brief); 37 | } 38 | 39 | void push(T ctx) 40 | { 41 | lock.lock(); 42 | while (size == length) { 43 | lock.unlock(); 44 | cts.waitAndReset(); 45 | lock.lock(); 46 | } 47 | if (closed) { 48 | lock.unlock(); 49 | throw new ChannelClosed(); 50 | } 51 | bool notify = false; 52 | move(ctx, store[insert++]); 53 | if (insert == length) insert = 0; 54 | if (size == 0) notify = true; 55 | size += 1; 56 | lock.unlock(); 57 | if (notify) rtr.trigger(); 58 | } 59 | 60 | bool tryPop(out T output) 61 | { 62 | lock.lock(); 63 | while (size == 0 && !closed) { 64 | lock.unlock(); 65 | rtr.waitAndReset(); 66 | lock.lock(); 67 | } 68 | if (size == 0 && closed) { 69 | lock.unlock(); 70 | return false; 71 | } 72 | move(store[fetch++], output); 73 | if (fetch == length) fetch = 0; 74 | size -= 1; 75 | lock.unlock(); 76 | cts.trigger(); 77 | return true; 78 | } 79 | 80 | bool readyToRead() { 81 | lock.lock(); 82 | scope(exit) lock.unlock(); 83 | return size > 0; 84 | } 85 | 86 | bool empty() { 87 | lock.lock(); 88 | scope(exit) lock.unlock(); 89 | if (closed && size == 0) return true; 90 | return false; 91 | } 92 | 93 | void close() { 94 | lock.lock(); 95 | closed = true; 96 | lock.unlock(); 97 | cts.trigger(); 98 | rtr.trigger(); 99 | } 100 | 101 | void retain() { 102 | auto cnt = atomicFetchAdd(refCount, 1); 103 | assert(cnt != 0); 104 | } 105 | 106 | // true if time to release 107 | bool release() { 108 | auto cnt = atomicFetchSub(refCount, 1); 109 | if (cnt == 1) return true; 110 | return false; 111 | } 112 | } 113 | 114 | auto allocRingQueue(T, Event)(size_t capacity, Event cts, Event rtr){ 115 | alias Q = RingQueue!(T,Event); 116 | auto ptr = cast(Q*)malloc(Q.sizeof); 117 | emplace(ptr, capacity, move(cts), move(rtr)); 118 | return ptr; 119 | } 120 | 121 | void disposeRingQueue(T, Event)(RingQueue!(T, Event)* q) { 122 | static if (hasIndirections!T) { 123 | GC.removeRange(q.store); 124 | } 125 | free(q.store); 126 | free(q); 127 | } 128 | 129 | unittest { 130 | import photon; 131 | import std.exception; 132 | auto cts = event(false); 133 | auto rtr = event(false); 134 | auto q = allocRingQueue!(int, Event)(2, move(cast()cts), move(cast()rtr)); 135 | q.push(1); 136 | q.push(2); 137 | int result; 138 | assert(q.tryPop(result) && result == 1); 139 | assert(q.tryPop(result) && result == 2); 140 | q.retain(); 141 | assert(!q.release()); 142 | assert(q.release()); 143 | q.close(); 144 | assert(!q.tryPop(result)); 145 | assertThrown!ChannelClosed(q.push(3)); 146 | disposeRingQueue(q); 147 | } 148 | 149 | unittest { 150 | import photon; 151 | auto cts = Event(false); 152 | auto rtr = Event(false); 153 | auto q = allocRingQueue!(string, Event)(1, move(cts), move(rtr)); 154 | assert(!q.empty); 155 | q.push("hello"); 156 | string result; 157 | assert(q.tryPop(result) && result == "hello"); 158 | assert(!q.empty); 159 | q.push("world"); 160 | q.close(); 161 | assert(q.tryPop(result) && result == "world"); 162 | assert(!q.tryPop(result)); 163 | assert(q.empty); 164 | } -------------------------------------------------------------------------------- /src/photon/exceptions.d: -------------------------------------------------------------------------------- 1 | module photon.exceptions; 2 | 3 | class ChannelClosed : Exception 4 | { 5 | this(string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @nogc @safe 6 | { 7 | super("Put on the closed channel", file, line, nextInChain); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/photon/freebsd/support.d: -------------------------------------------------------------------------------- 1 | module photon.freebsd.support; 2 | version(FreeBSD): 3 | import core.sys.posix.unistd; 4 | import core.stdc.errno; 5 | import core.stdc.stdlib; 6 | import core.thread; 7 | import core.stdc.config; 8 | import core.sys.posix.pthread; 9 | 10 | enum int MSG_DONTWAIT = 0x80; 11 | enum int SOCK_NONBLOCK = 0x800; 12 | 13 | extern(C) int eventfd(uint initial, int flags) nothrow; 14 | extern(C) void perror(const(char) *s) nothrow; 15 | 16 | T checked(T: ssize_t)(T value, const char* msg="unknown place") nothrow { 17 | if (value < 0) { 18 | perror(msg); 19 | _exit(cast(int)-value); 20 | } 21 | return value; 22 | } 23 | 24 | ssize_t withErrorno(ssize_t resp) nothrow { 25 | if(resp < 0) { 26 | //logf("Syscall ret %d", resp); 27 | errno = cast(int)-resp; 28 | return -1; 29 | } 30 | else { 31 | return resp; 32 | } 33 | } 34 | 35 | void logf(string file = __FILE__, int line = __LINE__, T...)(string msg, T args) 36 | { 37 | debug(photon) { 38 | try { 39 | import std.stdio; 40 | stderr.writefln(msg, args); 41 | stderr.writefln("\tat %s:%s:[LWP:%s]", file, line, pthread_self()); 42 | } 43 | catch(Throwable t) { 44 | abort(); 45 | } 46 | } 47 | } 48 | 49 | 50 | @nogc: 51 | nothrow: 52 | 53 | 54 | private // helpers 55 | { 56 | 57 | /* Size definition for CPU sets. */ 58 | enum 59 | { 60 | __CPU_SETSIZE = 1024, 61 | __NCPUBITS = 8 * cpu_mask.sizeof, 62 | } 63 | 64 | /* Macros */ 65 | 66 | /* Basic access functions. */ 67 | size_t __CPUELT()(size_t cpu) pure 68 | { 69 | return cpu / __NCPUBITS; 70 | } 71 | cpu_mask __CPUMASK()(size_t cpu) pure 72 | { 73 | return 1UL << (cpu % __NCPUBITS); 74 | } 75 | 76 | cpu_mask __CPU_SET_S()(size_t cpu, size_t setsize, cpu_set_t* cpusetp) pure 77 | { 78 | if (cpu < 8 * setsize) 79 | { 80 | cpusetp.__bits[__CPUELT(cpu)] |= __CPUMASK(cpu); 81 | return __CPUMASK(cpu); 82 | } 83 | 84 | return 0; 85 | } 86 | } 87 | 88 | /// Type for array elements in 'cpu_set_t'. 89 | alias c_ulong cpu_mask; 90 | 91 | /// Data structure to describe CPU mask. 92 | struct cpu_set_t 93 | { 94 | cpu_mask[__CPU_SETSIZE / __NCPUBITS] __bits; 95 | } 96 | 97 | /// Access macros for 'cpu_set' (missing a lot of them) 98 | 99 | cpu_mask CPU_SET()(size_t cpu, cpu_set_t* cpusetp) pure 100 | { 101 | return __CPU_SET_S(cpu, cpu_set_t.sizeof, cpusetp); 102 | } 103 | 104 | /* Functions */ 105 | extern(C): 106 | int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 107 | int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 108 | 109 | -------------------------------------------------------------------------------- /src/photon/linux/core.d: -------------------------------------------------------------------------------- 1 | module photon.linux.core; 2 | version(linux): 3 | package(photon): 4 | 5 | import core.internal.spinlock; 6 | import core.sys.posix.sys.types; 7 | import core.sys.posix.sys.socket; 8 | import core.sys.posix.poll; 9 | import core.sys.posix.netinet.in_; 10 | import core.sys.posix.unistd; 11 | import core.sys.linux.epoll; 12 | import core.sys.linux.timerfd; 13 | import core.sys.linux.sys.eventfd; 14 | import core.sync.mutex; 15 | import core.stdc.errno; 16 | import core.atomic; 17 | import core.sys.posix.stdlib: abort; 18 | import core.sys.posix.fcntl; 19 | import core.memory; 20 | import core.sys.posix.sys.mman; 21 | import core.sys.posix.pthread; 22 | import core.stdc.stdlib; 23 | import core.sys.linux.sys.signalfd; 24 | 25 | import photon.linux.support; 26 | import photon.linux.syscalls; 27 | import photon.reactor; 28 | import photon.ds.common; 29 | import photon.ds.intrusive_queue; 30 | import photon.threadpool; 31 | import photon.task; 32 | 33 | immutable size_t pageSize; 34 | 35 | shared static this() { 36 | pageSize = sysconf(_SC_PAGESIZE); 37 | } 38 | 39 | shared struct RawEvent { 40 | nothrow: 41 | this(int init) { 42 | fd = eventfd(init, 0).checked("raw event"); 43 | } 44 | 45 | void waitAndReset() { 46 | union U { 47 | ulong cnt; 48 | ubyte[8] bytes; 49 | } 50 | U value; 51 | ssize_t r; 52 | do { 53 | r = raw_read(fd, value.bytes.ptr, 8); 54 | } while(r < 0 && errno == EINTR); 55 | r.checked("event reset"); 56 | } 57 | 58 | void trigger() { 59 | union U { 60 | ulong cnt; 61 | ubyte[8] bytes; 62 | } 63 | U value; 64 | value.cnt = 1; 65 | ssize_t r; 66 | do { 67 | r = raw_write(fd, value.bytes.ptr, 8); 68 | } while(r < 0 && errno == EINTR); 69 | r.checked("event trigger"); 70 | } 71 | 72 | void close() { 73 | .close(fd); 74 | } 75 | 76 | int fd; 77 | } 78 | 79 | /// 80 | public struct Event { 81 | nothrow @trusted: 82 | private int evfd; 83 | 84 | this(bool signaled) { 85 | evfd = eventfd(signaled ? 1 : 0, EFD_NONBLOCK).checked("Event constructor"); 86 | interceptFd!(Fcntl.noop)(evfd); 87 | } 88 | 89 | package(photon) int fd() { return evfd; } 90 | 91 | package(photon) int fd() shared { return evfd; } 92 | 93 | @disable this(this); 94 | 95 | /// Wait for the event to be triggered, then reset and return atomically 96 | void waitAndReset() { 97 | byte[8] bytes = void; 98 | ssize_t r; 99 | do { 100 | r = read(evfd, bytes.ptr, bytes.sizeof); 101 | } while (r < 0 && errno == EINTR); 102 | } 103 | 104 | void waitAndReset() shared { 105 | this.unshared.waitAndReset(); 106 | } 107 | 108 | void reset() { 109 | waitAndReset(); 110 | } 111 | 112 | void reset() shared { 113 | waitAndReset(); 114 | } 115 | 116 | /// Trigger the event. 117 | void trigger() { 118 | union U { 119 | ulong cnt; 120 | ubyte[8] bytes; 121 | } 122 | U value; 123 | value.cnt = 1; 124 | ssize_t r; 125 | do { 126 | r = write(evfd, value.bytes.ptr, value.sizeof); 127 | } while(r < 0 && errno == EINTR); 128 | } 129 | 130 | void trigger() shared { 131 | this.unshared.trigger(); 132 | } 133 | 134 | /// Free this event 135 | void dispose() { 136 | close(evfd); 137 | } 138 | 139 | void dispose() shared { 140 | this.unshared.dispose(); 141 | } 142 | } 143 | 144 | /// 145 | public auto event(bool triggered) { 146 | return Event(triggered); 147 | } 148 | 149 | /// 150 | public struct Semaphore { 151 | nothrow @trusted: 152 | private int evfd; 153 | /// 154 | this(int count) { 155 | evfd = eventfd(count, EFD_NONBLOCK | EFD_SEMAPHORE).checked("Semaphore constructor"); 156 | interceptFd!(Fcntl.noop)(evfd); 157 | } 158 | 159 | int fd() { return evfd; } 160 | 161 | int fd() shared { return evfd; } 162 | 163 | @disable this(this); 164 | 165 | /// 166 | void wait() { 167 | ubyte[8] bytes = void; 168 | ssize_t r; 169 | do { 170 | // go through event loop 171 | r = read(evfd, bytes.ptr, bytes.sizeof); 172 | logf("READ SEM %d", r); 173 | } while (r < 0 && errno == EINTR); 174 | } 175 | 176 | void wait() shared { 177 | this.unshared.wait(); 178 | } 179 | 180 | void reset() { 181 | wait(); 182 | } 183 | 184 | void reset() shared { 185 | wait(); 186 | } 187 | 188 | /// 189 | void trigger(int count) { 190 | union U { 191 | ulong cnt; 192 | ubyte[8] bytes; 193 | } 194 | U value; 195 | value.cnt = count; 196 | ssize_t r; 197 | do { 198 | r = write(evfd, value.bytes.ptr, value.sizeof); 199 | logf("WRITE SEM %d", r); 200 | } while(r < 0 && errno == EINTR); 201 | } 202 | 203 | void trigger(int count) shared { 204 | this.unshared.trigger(count); 205 | } 206 | 207 | /// Free this semaphore 208 | void dispose() { 209 | close(evfd).checked("dispose semaphore"); 210 | } 211 | 212 | void dispose() shared { 213 | this.unshared.dispose(); 214 | } 215 | } 216 | 217 | /// 218 | public auto nothrow semaphore(int initialCount) { 219 | return Semaphore(initialCount); 220 | } 221 | 222 | public struct Timer { 223 | @trusted: 224 | alias Callback = void delegate() @safe nothrow; 225 | void wait(Duration d) { 226 | delay(d); 227 | } 228 | void stop() nothrow {} 229 | bool pending() nothrow { return false; } 230 | void rearm(Duration dur) nothrow {} 231 | } 232 | 233 | /// 234 | public auto timer() { 235 | return Timer(); 236 | } 237 | 238 | enum int MAX_EVENTS = 500; 239 | shared long inited; 240 | 241 | public void initPhoton() nothrow @trusted 242 | { 243 | auto s = atomicFetchAdd(inited, 1); 244 | if (s != 0) return; 245 | cpu_set_t cpus; 246 | size_t threads = 0; 247 | if (sched_getaffinity(gettid(), cpus.sizeof, &cpus) < 0) { 248 | perror("sched_getaffinity"); 249 | } 250 | for (size_t i = 0; i < cpus.sizeof*8; i++) { 251 | if (CPU_GET(i, &cpus)) 252 | threads += 1; 253 | } 254 | debug(photon_single) { 255 | threads = 1; 256 | } 257 | 258 | ssize_t fdMax = sysconf(_SC_OPEN_MAX).checked; 259 | fdMax = fdMax > 2^^24 ? 2^^24 : fdMax; 260 | ssize_t size = ((fdMax * Descriptor.sizeof) + pageSize-1) & ~(pageSize-1); 261 | descriptors = (cast(shared(Descriptor*)) mmap(null, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0))[0..fdMax]; 262 | checked(cast(ssize_t)descriptors.ptr, "mmap failed"); 263 | size = ((fdMax * DescriptorState.sizeof) + pageSize-1) & ~(pageSize-1); 264 | descriptorStates = (cast(shared(DescriptorState*)) mmap(null, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0))[0..fdMax]; 265 | checked(cast(ssize_t)descriptorStates.ptr, "mmap failed"); 266 | scheds = new SchedulerBlock[threads]; 267 | foreach(ref sched; scheds) { 268 | sched.queue = IntrusiveQueue!(FiberExt, RawEvent)(RawEvent(0)); 269 | sched.event_loop = cast(int)epoll_create1(0).checked("ERROR: Failed to create event-loop!"); 270 | epoll_event event; 271 | event.events = EPOLLIN; 272 | event.data.fd = sched.queue.event.fd; 273 | epoll_ctl(sched.event_loop, EPOLL_CTL_ADD, sched.queue.event.fd, &event).checked("registering event"); 274 | } 275 | 276 | initWorkQueues(threads); 277 | } 278 | 279 | void processEventsEntry(size_t numSched, Duration timeout) 280 | { 281 | //for (;;) { 282 | epoll_event[MAX_EVENTS] events = void; 283 | auto sched = &scheds[numSched]; 284 | int r; 285 | do { 286 | r = epoll_wait(sched.event_loop, events.ptr, MAX_EVENTS, timeout > dur!"hours"(1000) ? -1 : cast(int)timeout.total!"msecs"); 287 | } while (r < 0 && errno == EINTR); 288 | for (int n = 0; n < r; n++) { 289 | int fd = events[n].data.fd; 290 | if (fd == sched.queue.event.fd) { 291 | sched.queue.event.waitAndReset(); 292 | } 293 | else { 294 | auto descriptor = descriptors.ptr + fd; 295 | if (descriptorStates[fd] == DescriptorState.NONBLOCKING) { 296 | if (events[n].events & EPOLLIN) { 297 | auto readers = &descriptor.readers; 298 | readers.lock(); 299 | logf("Read event for fd=%d", fd); 300 | auto state = readers.state; 301 | logf("read state = %d", state); 302 | final switch(state) with(ReaderState) { 303 | case EMPTY: 304 | case UNCERTAIN: 305 | case READY: 306 | readers.state = READY; 307 | readers.signal(numSched, fd); 308 | break; 309 | case READING: 310 | case READING_SIGNALED: 311 | readers.state = READING_SIGNALED; 312 | readers.unlock(); 313 | break; 314 | } 315 | } 316 | if (events[n].events & EPOLLOUT) { 317 | logf("Write event for fd=%d", fd); 318 | auto writers = &descriptor.writers; 319 | writers.lock(); 320 | auto state = writers.state; 321 | logf("write state = %d", state); 322 | final switch(state) with(WriterState) { 323 | case FULL: 324 | case READY: 325 | case UNCERTAIN: 326 | writers.state = READY; 327 | writers.signal(numSched, fd); 328 | break; 329 | case WRITING: 330 | case WRITING_SIGNALED: 331 | writers.state = WRITING_SIGNALED; 332 | writers.unlock(); 333 | break; 334 | } 335 | } 336 | } 337 | } 338 | } 339 | } 340 | 341 | void notifyEventloop(size_t n) nothrow { 342 | scheds[n].queue.event.trigger(); 343 | } 344 | 345 | auto __syscall(T...)(T args) { 346 | pragma(inline, true); 347 | return syscall(args); 348 | } 349 | 350 | enum Fcntl: int { explicit = 0, msg = MSG_DONTWAIT, sock = SOCK_NONBLOCK, noop = 0xFFFFF } 351 | enum SyscallKind { accept, read, write, connect } 352 | 353 | // intercept - a filter for file descriptor, changes flags and register on first use 354 | void interceptFd(Fcntl needsFcntl)(int fd) nothrow { 355 | logf("Hit interceptFD"); 356 | if (fd < 0 || fd >= descriptors.length) return; 357 | if (cas(&descriptorStates[fd], DescriptorState.NOT_INITED, DescriptorState.INITIALIZING)) { 358 | logf("First use, registering fd = %s", fd); 359 | epoll_event event; 360 | event.events = EPOLLIN | EPOLLOUT | EPOLLET; 361 | event.data.fd = fd; 362 | size_t n = currentFiber !is null ? currentFiber.numScheduler : 0; 363 | if (epoll_ctl(scheds[n].event_loop, EPOLL_CTL_ADD, fd, &event) < 0 && errno == EPERM) { 364 | logf("isSocket = false FD = %s", fd); 365 | descriptorStates[fd] = DescriptorState.THREADPOOL; 366 | } 367 | else { 368 | logf("isSocket = true FD = %s", fd); 369 | static if(needsFcntl == Fcntl.explicit) { 370 | int flags = fcntl(fd, F_GETFL, 0); 371 | fcntl(fd, F_SETFL, flags | O_NONBLOCK).checked("setting non-blocking I/O"); 372 | logf("Setting FCNTL. %x", cast(void*)currentFiber); 373 | } 374 | descriptorStates[fd] = DescriptorState.NONBLOCKING; 375 | } 376 | } 377 | while(descriptorStates[fd] == DescriptorState.INITIALIZING) {} 378 | } 379 | 380 | // ====================================================================================== 381 | // SYSCALL warappers intercepts 382 | // ====================================================================================== 383 | nothrow: 384 | 385 | extern(C) private int nanosleep(const timespec* req, timespec* rem) { 386 | if (currentFiber !is null) { 387 | delay(req); 388 | return 0; 389 | } else { 390 | __syscall(SYS_NANOSLEEP, req, rem); 391 | return 0; 392 | } 393 | } 394 | 395 | extern(C) ssize_t accept4(int sockfd, sockaddr *addr, socklen_t *addrlen, int flags) 396 | { 397 | return universalSyscall!(SYS_ACCEPT4, "accept4", SyscallKind.accept, Fcntl.sock, EWOULDBLOCK) 398 | (sockfd, cast(size_t) addr, cast(size_t) addrlen, flags); 399 | } 400 | -------------------------------------------------------------------------------- /src/photon/linux/support.d: -------------------------------------------------------------------------------- 1 | module photon.linux.support; 2 | version(linux): 3 | 4 | import core.sys.posix.unistd; 5 | import core.sys.linux.timerfd; 6 | import core.stdc.errno; 7 | import core.stdc.stdlib; 8 | import std.stdio : perror; 9 | import core.thread; 10 | import core.stdc.config; 11 | import core.sys.posix.pthread; 12 | import photon.linux.syscalls; 13 | 14 | enum int MSG_DONTWAIT = 0x40; 15 | enum int SOCK_NONBLOCK = 0x800; 16 | 17 | T checked(T: ssize_t)(T value, const char* msg="unknown place") nothrow { 18 | if (value < 0) { 19 | perror(msg); 20 | _exit(cast(int)-value); 21 | } 22 | return value; 23 | } 24 | 25 | void logf(string file = __FILE__, int line = __LINE__, T...)(string msg, T args) 26 | { 27 | debug(photon) { 28 | try { 29 | import std.stdio; 30 | stderr.writefln(msg, args); 31 | stderr.writefln("\tat %s:%s:[LWP:%s]", file, line, pthread_self()); 32 | } 33 | catch(Throwable t) { 34 | abort(); 35 | } 36 | } 37 | } 38 | 39 | 40 | @nogc: 41 | nothrow: 42 | 43 | 44 | private // helpers 45 | { 46 | 47 | /* Size definition for CPU sets. */ 48 | enum 49 | { 50 | __CPU_SETSIZE = 1024, 51 | __NCPUBITS = 8 * cpu_mask.sizeof, 52 | } 53 | 54 | /* Macros */ 55 | 56 | /* Basic access functions. */ 57 | size_t __CPUELT()(size_t cpu) pure 58 | { 59 | return cpu / __NCPUBITS; 60 | } 61 | cpu_mask __CPUMASK()(size_t cpu) pure 62 | { 63 | return 1UL << (cpu % __NCPUBITS); 64 | } 65 | 66 | cpu_mask __CPU_SET_S()(size_t cpu, size_t setsize, cpu_set_t* cpusetp) pure 67 | { 68 | if (cpu < 8 * setsize) 69 | { 70 | cpusetp.__bits[__CPUELT(cpu)] |= __CPUMASK(cpu); 71 | return __CPUMASK(cpu); 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | cpu_mask __CPU_GET_S()(size_t cpu, size_t setsize, cpu_set_t* cpusetp) pure 78 | { 79 | if (cpu < 8 * setsize) 80 | { 81 | if (cpusetp.__bits[__CPUELT(cpu)] & __CPUMASK(cpu)) 82 | return __CPUMASK(cpu); 83 | } 84 | 85 | return 0; 86 | } 87 | } 88 | 89 | /// Type for array elements in 'cpu_set_t'. 90 | alias c_ulong cpu_mask; 91 | 92 | /// Data structure to describe CPU mask. 93 | struct cpu_set_t 94 | { 95 | cpu_mask[__CPU_SETSIZE / __NCPUBITS] __bits; 96 | } 97 | 98 | /// Access macros for 'cpu_set' (missing a lot of them) 99 | 100 | cpu_mask CPU_SET()(size_t cpu, cpu_set_t* cpusetp) pure 101 | { 102 | return __CPU_SET_S(cpu, cpu_set_t.sizeof, cpusetp); 103 | } 104 | 105 | cpu_mask CPU_GET()(size_t cpu, cpu_set_t* cpusetp) pure 106 | { 107 | return __CPU_GET_S(cpu, cpu_set_t.sizeof, cpusetp); 108 | } 109 | 110 | /* Functions */ 111 | extern(C): 112 | int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 113 | int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 114 | 115 | -------------------------------------------------------------------------------- /src/photon/linux/syscalls.d: -------------------------------------------------------------------------------- 1 | ///Syscall definitions and direct calls that bypass our libc intercepts 2 | module photon.linux.syscalls; 3 | version(linux): 4 | import core.sys.posix.sys.types; 5 | import core.sys.posix.netinet.in_; 6 | import core.sys.posix.poll; 7 | import core.sys.linux.epoll; 8 | import core.sys.linux.timerfd; 9 | 10 | import photon.linux.support; 11 | 12 | nothrow: 13 | 14 | version (X86) { 15 | enum int SYS_READ = 0x3, SYS_SOCKETPAIR = 0x168; //TODO test on x86 16 | int syscall(int ident, int n, int arg1, int arg2) 17 | { 18 | int ret; 19 | 20 | asm nothrow 21 | { 22 | mov EAX, ident; 23 | mov EBX, n[EBP]; 24 | mov ECX, arg1[EBP]; 25 | mov EDX, arg2[EBP]; 26 | int 0x80; 27 | mov ret, EAX; 28 | } 29 | return ret; 30 | } 31 | 32 | int syscall(int ident, int n, int arg1, int arg2, int arg3) 33 | { 34 | int ret; 35 | 36 | asm nothrow 37 | { 38 | mov EAX, ident; 39 | mov EBX, n[EBP]; 40 | mov ECX, arg1[EBP]; 41 | mov EDX, arg2[EBP]; 42 | mov ESI, arg3[EBP]; 43 | int 0x80; 44 | mov ret, EAX; 45 | } 46 | return ret; 47 | } 48 | 49 | int syscall(int ident, int n, int arg1, int arg2, int arg3, int arg4) 50 | { 51 | int ret; 52 | 53 | asm nothrow 54 | { 55 | mov EAX, ident; 56 | mov EBX, n[EBP]; 57 | mov ECX, arg1[EBP]; 58 | mov EDX, arg2[EBP]; 59 | mov ESI, arg3[EBP]; 60 | mov EDI, arg4[EBP]; 61 | int 0x80; 62 | mov ret, EAX; 63 | } 64 | return ret; 65 | } 66 | } else version (X86_64) { 67 | enum int 68 | SYS_READ = 0x0, 69 | SYS_WRITE = 0x1, 70 | SYS_CLOSE = 3, 71 | SYS_PPOLL = 271, 72 | SYS_GETTID = 186, 73 | SYS_SOCKETPAIR = 0x35, 74 | SYS_ACCEPT = 0x2b, 75 | SYS_ACCEPT4 = 0x120, 76 | SYS_CONNECT = 0x2a, 77 | SYS_SENDTO = 0x2c, 78 | SYS_RECVFROM = 45, 79 | SYS_NANOSLEEP = 35; 80 | 81 | extern(C) ssize_t syscall(size_t number, ...); 82 | } else version(AArch64){ 83 | enum int 84 | SYS_READ = 0x3f, 85 | SYS_WRITE = 0x40, 86 | SYS_CLOSE = 0x39, 87 | SYS_PPOLL = 0x49, 88 | SYS_GETTID = 0xb0, 89 | SYS_SOCKETPAIR = 0xc7, 90 | SYS_ACCEPT = 0xca, 91 | SYS_ACCEPT4 = 0xf2, 92 | SYS_CONNECT = 0xcb, 93 | SYS_SENDTO = 0xce, 94 | SYS_RECVFROM = 0xcf, 95 | SYS_NANOSLEEP = 0x65; 96 | 97 | extern(C) ssize_t syscall(size_t number, ...); 98 | } 99 | 100 | extern(C) int gettid(); 101 | 102 | ssize_t raw_read(int fd, void *buf, size_t count) nothrow { 103 | logf("Raw read on FD=%d", fd); 104 | return syscall(SYS_READ, fd, cast(ssize_t) buf, cast(ssize_t) count); 105 | } 106 | 107 | ssize_t raw_write(int fd, const void *buf, size_t count) nothrow 108 | { 109 | logf("Raw write on FD=%d", fd); 110 | return syscall(SYS_WRITE, fd, cast(size_t) buf, count); 111 | } 112 | 113 | int raw_poll(pollfd *fds, nfds_t nfds, int timeout) 114 | { 115 | logf("Raw poll"); 116 | timespec ts; 117 | ts.tv_sec = timeout/1000; 118 | ts.tv_nsec = (timeout % 1000) * 1000000; 119 | return cast(int)syscall(SYS_PPOLL, cast(size_t)fds, cast(size_t) nfds, timeout < 0 ? null : &ts, null); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/photon/macos/core.d: -------------------------------------------------------------------------------- 1 | module photon.macos.core; 2 | version(OSX) version = Darwin; 3 | else version(iOS) version = Darwin; 4 | else version(TVOS) version = Darwin; 5 | else version(WatchOS) version = Darwin; 6 | else version(VisionOS) version = Darwin; 7 | version(Darwin): 8 | package(photon): 9 | 10 | import std.string; 11 | import std.format; 12 | import std.exception; 13 | import std.conv; 14 | import std.array; 15 | import std.meta; 16 | import std.random; 17 | import std.concurrency; 18 | 19 | import core.thread; 20 | import core.internal.spinlock; 21 | import core.sys.posix.sys.types; 22 | import core.sys.posix.sys.socket; 23 | import core.sys.posix.poll; 24 | import core.sys.posix.netinet.in_; 25 | import core.sys.posix.unistd; 26 | import core.sync.mutex; 27 | import core.stdc.errno; 28 | import core.stdc.signal; 29 | import core.stdc.time; 30 | import core.stdc.stdlib; 31 | import core.atomic; 32 | import core.sys.posix.stdlib: abort; 33 | import core.sys.posix.fcntl; 34 | import core.memory; 35 | import core.sys.posix.sys.mman; 36 | import core.sys.posix.pthread; 37 | import core.sys.darwin.sys.event; 38 | import core.sys.darwin.dlfcn; 39 | import core.sys.darwin.mach.thread_act; 40 | 41 | import photon.macos.support; 42 | import photon.reactor; 43 | 44 | alias KEvent = kevent_t; 45 | 46 | enum SYS_READ = 3; 47 | enum SYS_WRITE = 4; 48 | enum SYS_ACCEPT = 30; 49 | enum SYS_CONNECT = 98; 50 | enum SYS_SENDTO = 133; 51 | enum SYS_RECVFROM = 29; 52 | enum SYS_CLOSE = 6; 53 | enum SYS_GETTID = 286; 54 | enum SYS_POLL = 230; 55 | 56 | immutable size_t pageSize; 57 | 58 | shared static this() { 59 | pageSize = sysconf(_SC_PAGESIZE); 60 | } 61 | 62 | shared struct RawEvent { 63 | nothrow: 64 | this(int dummy) { 65 | int[2] fds; 66 | pipe(fds).checked("event creation"); 67 | this.fds = fds; 68 | } 69 | 70 | void waitAndReset() { 71 | byte[1] bytes = void; 72 | ssize_t r; 73 | do { 74 | r = raw_read(fds[0], bytes.ptr, 1); 75 | } while(r < 0 && errno == EINTR); 76 | r.checked("event reset"); 77 | } 78 | 79 | void trigger() { 80 | ubyte[1] bytes; 81 | ssize_t r; 82 | do { 83 | r = raw_write(fds[1], bytes.ptr, 1); 84 | } while(r < 0 && errno == EINTR); 85 | r.checked("event trigger"); 86 | } 87 | 88 | void close() { 89 | .close(fds[0]); 90 | .close(fds[1]); 91 | } 92 | 93 | private int[2] fds; 94 | } 95 | 96 | public struct Timer { 97 | @trusted: 98 | alias Callback = void delegate() @safe nothrow; 99 | void wait(Duration d) { 100 | delay(d); 101 | } 102 | void stop() nothrow {} 103 | bool pending() nothrow { return false; } 104 | void rearm(Duration dur) nothrow {} 105 | } 106 | 107 | /// 108 | public auto timer() { 109 | return Timer(); 110 | } 111 | 112 | public struct Event { 113 | nothrow: 114 | @disable this(this); 115 | 116 | this(bool signaled) { 117 | int[2] fds; 118 | pipe(fds).checked("pipe creation for event"); 119 | if (signaled) trigger(); 120 | this.fds = fds; 121 | } 122 | 123 | package(photon) int fd() shared { return fds[0]; } 124 | 125 | package(photon) int fd() { return fds[0]; } 126 | 127 | /// Wait for the event to be triggered, then reset and return atomically 128 | void waitAndReset() { 129 | byte[4096] bytes = void; 130 | ssize_t r; 131 | do { 132 | r = read(fds[0], bytes.ptr, bytes.sizeof); 133 | } while(r < 0 && errno == EINTR); 134 | } 135 | 136 | void waitAndReset() shared { 137 | this.unshared.waitAndReset(); 138 | } 139 | 140 | package(photon) void reset() { 141 | waitAndReset(); 142 | } 143 | 144 | package(photon) void reset() shared { 145 | waitAndReset(); 146 | } 147 | 148 | /// Trigger the event. 149 | void trigger() { 150 | ubyte[1] bytes = void; 151 | ssize_t r; 152 | do { 153 | r = write(fds[1], bytes.ptr, 1); 154 | } while(r < 0 && errno == EINTR); 155 | } 156 | 157 | void trigger() shared { 158 | this.unshared.trigger(); 159 | } 160 | 161 | /// 162 | void dispose() { 163 | close(fds[0]); 164 | close(fds[1]); 165 | } 166 | 167 | void dispose() shared { 168 | this.unshared.dispose(); 169 | } 170 | 171 | private int[2] fds; 172 | } 173 | 174 | /// 175 | public nothrow auto event(bool signaled) { 176 | return cast(shared)Event(signaled); 177 | } 178 | 179 | 180 | public struct Semaphore { 181 | nothrow: 182 | @disable this(this); 183 | 184 | this(int initial) { 185 | int[2] fds; 186 | pipe(fds).checked("pipe initilization for semaphore"); 187 | if (initial > 0) { 188 | trigger(initial); 189 | } 190 | this.fds = fds; 191 | } 192 | 193 | package(photon) int fd() { return fds[0]; } 194 | 195 | package(photon) int fd() shared { return fds[0]; } 196 | /// 197 | void wait() { 198 | byte[1] bytes = void; 199 | ssize_t r; 200 | do { 201 | r = read(fds[0], bytes.ptr, bytes.sizeof); 202 | } while(r < 0 && errno == EINTR); 203 | } 204 | 205 | /// 206 | void wait() shared { 207 | this.unshared.wait(); 208 | } 209 | 210 | package(photon) void reset() { 211 | wait(); 212 | } 213 | 214 | package(photon) void reset() shared { 215 | wait(); 216 | } 217 | 218 | /// 219 | void trigger(int count) { 220 | ubyte[4096] bytes = void; 221 | ssize_t size = count > 4096 ? 4096 : count; 222 | ssize_t r; 223 | do { 224 | r = write(fds[1], bytes.ptr, size); 225 | } while(r < 0 && errno == EINTR); 226 | } 227 | 228 | /// 229 | void trigger(int count) shared { 230 | this.unshared.trigger(count); 231 | } 232 | 233 | /// 234 | void dispose() { 235 | close(fds[0]); 236 | close(fds[1]); 237 | } 238 | 239 | /// 240 | void dispose() shared { 241 | this.unshared.dispose(); 242 | } 243 | 244 | private int[2] fds; 245 | } 246 | 247 | /// 248 | public nothrow auto semaphore(int initial) { 249 | return cast(shared)Semaphore(initial); 250 | } 251 | 252 | enum int MAX_EVENTS = 500; 253 | shared long inited; 254 | 255 | void notifyEventloop(size_t n) nothrow { 256 | KEvent event; 257 | event.ident = n; 258 | event.filter = EVFILT_USER; 259 | event.flags = EV_ADD | EV_ENABLE | EV_ONESHOT; 260 | event.fflags = NOTE_TRIGGER; 261 | logf("Notifying event loop %d", n); 262 | kevent(scheds[n].event_loop, &event, 1, null, 0, null).checked("notifying event loop"); 263 | } 264 | 265 | int getCurrentKqueue() nothrow { 266 | return currentFiber !is null ? scheds[currentFiber.numScheduler].event_loop : scheds[0].event_loop; 267 | } 268 | 269 | 270 | public void initPhoton() nothrow @trusted 271 | { 272 | auto s = atomicFetchAdd(inited, 1); 273 | if (s != 0) return; 274 | int threads = cast(int)sysconf(_SC_NPROCESSORS_ONLN).checked; 275 | debug(photon_single) { 276 | threads = 1; 277 | } 278 | ssize_t fdMax = sysconf(_SC_OPEN_MAX).checked; 279 | fdMax = fdMax > 2^^24 ? 2^^24 : fdMax; 280 | ssize_t size = ((fdMax * Descriptor.sizeof) + pageSize-1) & ~(pageSize-1); 281 | descriptors = (cast(shared(Descriptor*)) mmap(null, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0))[0..fdMax]; 282 | checked(cast(ssize_t)descriptors.ptr, "mmap failed"); 283 | size = ((fdMax * DescriptorState.sizeof) + pageSize-1) & ~(pageSize-1); 284 | descriptorStates = (cast(shared(DescriptorState*)) mmap(null, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0))[0..fdMax]; 285 | checked(cast(ssize_t)descriptorStates.ptr, "mmap failed"); 286 | scheds = new SchedulerBlock[threads]; 287 | foreach(ref sched; scheds) { 288 | sched.queue = IntrusiveQueue!(FiberExt, RawEvent)(RawEvent(0)); 289 | sched.event_loop = kqueue(); 290 | checked(sched.event_loop, "kqueue init failed"); 291 | } 292 | initWorkQueues(threads); 293 | } 294 | 295 | void processEventsEntry(size_t n, Duration timeout) 296 | { 297 | KEvent[MAX_EVENTS] ke; 298 | timespec ts; 299 | ts.tv_sec = timeout.total!"seconds"(); 300 | ts.tv_nsec = (timeout - ts.tv_sec.seconds).total!"nsecs"; 301 | int cnt = kevent(scheds[n].event_loop, null, 0, ke.ptr, MAX_EVENTS, timeout == Duration.max ? null : &ts); 302 | if (cnt < 0) return; 303 | for (int i = 0; i < cnt; i++) { 304 | auto fd = cast(int)ke[i].ident; 305 | auto filter = ke[i].filter; 306 | auto descriptor = descriptors.ptr + fd; 307 | if (filter == EVFILT_READ) { 308 | auto readers = &descriptor.readers; 309 | readers.lock(); 310 | logf("Read event for fd=%d", fd); 311 | auto state = readers.state; 312 | logf("read state = %d", state); 313 | final switch(state) with(ReaderState) { 314 | case EMPTY: 315 | case UNCERTAIN: 316 | case READY: 317 | readers.state = READY; 318 | readers.signal(n, fd); 319 | break; 320 | case READING: 321 | case READING_SIGNALED: 322 | readers.state = READING_SIGNALED; 323 | readers.unlock(); 324 | break; 325 | } 326 | } 327 | if (filter == EVFILT_WRITE) { 328 | logf("Write event for fd=%d", fd); 329 | auto writers = &descriptor.writers; 330 | writers.lock(); 331 | auto state = writers.state; 332 | logf("write state = %d", state); 333 | final switch(state) with(WriterState) { 334 | case FULL: 335 | case READY: 336 | case UNCERTAIN: 337 | writers.state = READY; 338 | writers.signal(n, fd); 339 | break; 340 | case WRITING: 341 | case WRITING_SIGNALED: 342 | writers.state = WRITING_SIGNALED; 343 | writers.unlock(); 344 | break; 345 | } 346 | } 347 | if (filter == EVFILT_USER) { 348 | logf("USER event %s", ke[i].ident); 349 | } 350 | } 351 | } 352 | 353 | enum Fcntl: int { explicit = 0, msg = MSG_DONTWAIT, noop = 0xFFFFF } 354 | enum SyscallKind { accept, read, write, connect } 355 | 356 | // intercept - a filter for file descriptor, changes flags and register on first use 357 | void interceptFd(Fcntl needsFcntl)(int fd) nothrow { 358 | logf("Hit interceptFD"); 359 | if (fd < 0 || fd >= descriptors.length) return; 360 | if (cas(&descriptorStates[fd], DescriptorState.NOT_INITED, DescriptorState.INITIALIZING)) { 361 | logf("First use, registering fd = %s", fd); 362 | KEvent[2] ke; 363 | ke[0].ident = fd; 364 | ke[1].ident = fd; 365 | ke[0].filter = EVFILT_READ; 366 | ke[1].filter = EVFILT_WRITE; 367 | ke[1].flags = ke[0].flags = EV_ADD | EV_ENABLE | EV_CLEAR; 368 | int ret = kevent(getCurrentKqueue, ke.ptr, 2, null, 0, null); 369 | if (ret < 0) { 370 | descriptorStates[fd] = DescriptorState.THREADPOOL; 371 | } else { 372 | static if(needsFcntl == Fcntl.explicit) { 373 | int flags = fcntl(fd, F_GETFL, 0); 374 | fcntl(fd, F_SETFL, flags | O_NONBLOCK).checked("registering for non-blocking I/O"); 375 | logf("Setting FCNTL. %x", cast(void*)currentFiber); 376 | } 377 | descriptorStates[fd] = DescriptorState.NONBLOCKING; 378 | } 379 | } 380 | } 381 | 382 | int gettid() 383 | { 384 | return cast(int)__syscall(SYS_GETTID); 385 | } 386 | 387 | alias Nanosleep = int function(const timespec* req, timespec* rem); 388 | Nanosleep nanosleepPtr; 389 | 390 | extern(C) private int nanosleep(const timespec* req, timespec* rem) { 391 | if (currentFiber !is null) { 392 | delay(req); 393 | return 0; 394 | } else { 395 | if (nanosleepPtr == null) { 396 | nanosleepPtr = cast(Nanosleep)dlsym(RTLD_NEXT, "nanosleep"); 397 | } 398 | return nanosleepPtr(req, rem); 399 | } 400 | } 401 | 402 | ssize_t raw_read(int fd, void *buf, size_t count) nothrow { 403 | logf("Raw read on FD=%d", fd); 404 | return __syscall(SYS_READ, fd, cast(ssize_t) buf, cast(ssize_t) count); 405 | } 406 | 407 | ssize_t raw_write(int fd, const void *buf, size_t count) nothrow 408 | { 409 | logf("Raw write on FD=%d", fd); 410 | return __syscall(SYS_WRITE, fd, cast(size_t) buf, count); 411 | } 412 | 413 | int raw_poll(pollfd *fds, nfds_t nfds, int timeout) nothrow 414 | { 415 | logf("Raw poll"); 416 | return cast(int)__syscall(SYS_POLL, cast(size_t)fds, cast(size_t) nfds, timeout); 417 | } 418 | -------------------------------------------------------------------------------- /src/photon/macos/support.d: -------------------------------------------------------------------------------- 1 | module photon.macos.support; 2 | version(OSX) version = Darwin; 3 | else version(iOS) version = Darwin; 4 | else version(TVOS) version = Darwin; 5 | else version(WatchOS) version = Darwin; 6 | else version(VisionOS) version = Darwin; 7 | version(Darwin): 8 | import core.sys.posix.unistd; 9 | import core.stdc.errno; 10 | import core.stdc.stdlib; 11 | import std.stdio : perror; 12 | import core.thread; 13 | import core.stdc.config; 14 | import core.sys.posix.pthread; 15 | import core.sys.darwin.sys.event; 16 | 17 | enum int MSG_DONTWAIT = 0x80; 18 | alias off_t = long; 19 | alias quad_t = ulong; 20 | extern(C) nothrow off_t __syscall(quad_t number, ...); 21 | 22 | T checked(T: ssize_t)(T value, const char* msg="unknown place") nothrow { 23 | if (value < 0) { 24 | perror(msg); 25 | _exit(cast(int)-value); 26 | } 27 | return value; 28 | } 29 | 30 | void logf(string file = __FILE__, int line = __LINE__, T...)(string msg, T args) 31 | { 32 | debug(photon) { 33 | try { 34 | import std.stdio; 35 | stderr.writefln(msg, args); 36 | stderr.writefln("\tat %s:%s:[LWP:%s]", file, line, pthread_self()); 37 | } 38 | catch(Throwable t) { 39 | abort(); 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/photon/package.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Photon is a lightweight transparent fiber scheduler. It's inspired by Golang's green thread model and 3 | the spawn function is called `go` doing the same job that Golang's keyword does. 4 | The framework API surface is kept to a minimum, many programs can be written using only 5 | three primitives: `initPhoton` to initialize Photon, `runScheduler` to start fiber scheduler and 6 | `go` to create tasks, including the initial tasks. 7 | 8 | Example, showcasing channels and std.range interop: 9 | ---- 10 | module examples.channels; 11 | 12 | import std.algorithm, std.datetime, std.range, std.stdio; 13 | import photon; 14 | 15 | void first(shared Channel!string work, shared Channel!int completion) { 16 | delay(2.msecs); 17 | work.put("first #1"); 18 | delay(2.msecs); 19 | work.put("first #2"); 20 | delay(2.msecs); 21 | work.put("first #3"); 22 | completion.put(1); 23 | } 24 | 25 | void second(shared Channel!string work, shared Channel!int completion) { 26 | delay(3.msecs); 27 | work.put("second #1"); 28 | delay(3.msecs); 29 | work.put("second #2"); 30 | completion.put(2); 31 | } 32 | 33 | void main() { 34 | initPhoton(); 35 | auto jobQueue = channel!string(2); 36 | auto finishQueue = channel!int(1); 37 | go({ 38 | first(jobQueue, finishQueue); 39 | }); 40 | go({ // producer # 2 41 | second(jobQueue, finishQueue); 42 | }); 43 | go({ // consumer 44 | foreach (item; jobQueue) { 45 | delay(1.seconds); 46 | writeln(item); 47 | } 48 | }); 49 | go({ // closer 50 | auto completions = finishQueue.take(2).array; 51 | assert(completions.length == 2); 52 | jobQueue.close(); // all producers are done 53 | }); 54 | runScheduler(); 55 | } 56 | ---- 57 | +/ 58 | module photon; 59 | 60 | import core.thread; 61 | import core.atomic; 62 | import core.internal.spinlock; 63 | import core.lifetime; 64 | import std.meta; 65 | 66 | import photon.ds.common; 67 | import photon.ds.ring_queue; 68 | import mecca.containers.lists; 69 | 70 | 71 | public import photon.core; 72 | public import photon.threadpool; 73 | public import photon.task; 74 | 75 | version(PhotonDocs) { 76 | 77 | /// Task result allows one fiber to wait on the other by joining the execution. 78 | public struct Task { 79 | void join(); 80 | } 81 | 82 | /// Initialize event loop and internal data structures for Photon scheduler. 83 | public Task initPhoton() nothrow @trusted; 84 | 85 | /// Setup a fiber task to run on the Photon scheduler. 86 | public Task go(void delegate() func) @trusted; 87 | 88 | /// ditto 89 | public Task go(void function() func) @safe; 90 | 91 | /// Same as go but make sure the fiber is scheduled on the same thread of the threadpool. 92 | /// Could be useful if there is a need to propagate TLS variable. 93 | public Task goOnSameThread(void delegate() func) @trusted; 94 | 95 | /// ditto 96 | public Task goOnSameThread(void function() func) @safe; 97 | 98 | /** 99 | Run work on a dedicated thread pool and pass the result back to the calling fiber or thread. 100 | This avoids blocking event loop on computationally intensive tasks. 101 | */ 102 | T offload(T)(T delegate() work) @trusted; 103 | 104 | /** 105 | Suspend the current fiber or thread for req amount of time. 106 | Note the resolution of wait is in milliseconds, a delay of zero will still yield the execution. 107 | */ 108 | public void delay(Duration req); 109 | 110 | /// Yields the execution of current fiber or thread 111 | public void yield(); 112 | 113 | } 114 | 115 | /// Number of threads running the scheduler loop 116 | size_t schedulerThreads() @safe nothrow { return scheds.length; } 117 | 118 | /// Start sheduler and run fibers until all are terminated. 119 | void runScheduler() @trusted 120 | { 121 | assert(scheds.length > 0, "Need to initialize with initPhoton"); 122 | startWorkQueue(scheds.length); 123 | Thread runThread(size_t n){ // damned D lexical capture "semantics" 124 | auto t = new Thread(() => schedulerEntry(n)); 125 | t.start(); 126 | return t; 127 | } 128 | Thread[] threads = new Thread[scheds.length-1]; 129 | foreach (i; 0..threads.length){ 130 | threads[i] = runThread(i+1); 131 | } 132 | schedulerEntry(0); 133 | foreach (t; threads) 134 | t.join(); 135 | terminateWorkQueues(); 136 | } 137 | 138 | ///Initialize and run fibers with the given main 139 | void runPhoton(void delegate() main) @trusted { 140 | initPhoton(); 141 | go(main); 142 | runScheduler(); 143 | } 144 | 145 | shared struct Mutex { 146 | @trusted: 147 | nothrow: 148 | private: 149 | shared Semaphore sem; 150 | long counter; 151 | 152 | this(long cnt) { 153 | sem = semaphore(0); 154 | counter = cnt; 155 | } 156 | 157 | @disable this(this); 158 | public: 159 | /// 160 | void lock() { 161 | auto v = atomicFetchSub(counter, 1); 162 | if (v <= 0) { 163 | sem.wait(); 164 | } 165 | } 166 | 167 | /// 168 | bool tryLock() { 169 | return cas(&counter, 1L, 0L); 170 | } 171 | 172 | /// 173 | bool locked() { 174 | return counter != 1; 175 | } 176 | 177 | /// 178 | void unlock() { 179 | auto v = atomicFetchAdd(counter, 1); 180 | if (v < 0) { 181 | sem.trigger(1); 182 | } 183 | } 184 | /// 185 | void dispose() { 186 | sem.dispose(); 187 | } 188 | } 189 | 190 | /// Create non-recursive mutex 191 | auto mutex() @trusted nothrow { 192 | return cast(shared)Mutex(1); 193 | } 194 | 195 | /// 196 | unittest { 197 | initPhoton(); 198 | auto mtx = mutex(); 199 | int counter = 0; 200 | go({ 201 | foreach (_; 0..100) { 202 | mtx.lock(); 203 | int c = counter; 204 | delay(1.msecs); 205 | counter = c + 1; 206 | mtx.unlock(); 207 | } 208 | }); 209 | go({ 210 | foreach (_; 0..100) { 211 | mtx.lock(); 212 | int c = counter; 213 | delay(1.msecs); 214 | counter = c + 1; 215 | mtx.unlock(); 216 | } 217 | }); 218 | runScheduler(); 219 | mtx.dispose(); 220 | assert(counter == 200); 221 | } 222 | 223 | /// 224 | struct RecursiveMutex { 225 | nothrow: 226 | @trusted: 227 | private: 228 | shared Semaphore sem; 229 | long counter; 230 | FiberExt owner; 231 | long recCount; 232 | SpinLock splk; 233 | 234 | this(long cnt) { 235 | sem = semaphore(0); 236 | counter = cnt; 237 | recCount = 0; 238 | splk = SpinLock(SpinLock.Contention.brief); 239 | } 240 | 241 | @disable this(this); 242 | public: 243 | /// 244 | void lock() shared { 245 | assert(currentFiber); 246 | splk.lock(); 247 | if (this.unshared.owner is currentFiber) { 248 | this.unshared.recCount++; 249 | splk.unlock(); 250 | return; 251 | } 252 | auto v = this.unshared.counter--; 253 | bool suspend; 254 | if (v <= 0) { 255 | suspend = true; 256 | } else { 257 | this.unshared.owner = currentFiber; 258 | assert(this.recCount == 0); 259 | } 260 | splk.unlock(); 261 | if (suspend) { 262 | sem.wait(); 263 | splk.lock(); 264 | this.unshared.owner = currentFiber; 265 | assert(this.recCount == 0); 266 | splk.unlock(); 267 | } 268 | } 269 | 270 | /// 271 | bool tryLock() shared { 272 | assert(currentFiber); 273 | splk.lock(); 274 | scope(exit) splk.unlock(); 275 | if (this.unshared.owner is currentFiber) { 276 | this.unshared.recCount++; 277 | return true; 278 | } 279 | else if(this.unshared.owner is null) { 280 | assert(this.unshared.counter == 1); 281 | this.unshared.owner = currentFiber; 282 | this.unshared.counter = 0; 283 | return true; 284 | } else { 285 | return false; 286 | } 287 | } 288 | 289 | /// 290 | bool locked() shared { 291 | assert(currentFiber); 292 | splk.lock(); 293 | scope(exit) splk.unlock(); 294 | return this.unshared.owner !is null; 295 | } 296 | 297 | /// 298 | void unlock() shared { 299 | assert(currentFiber); 300 | splk.lock(); 301 | assert(owner is cast(shared)currentFiber); 302 | if (this.unshared.recCount != 0) { 303 | this.unshared.recCount--; 304 | splk.unlock(); 305 | return; 306 | } 307 | this.unshared.owner = null; 308 | auto v = this.unshared.counter++; 309 | bool notify; 310 | if (v < 0) { 311 | notify = true; 312 | } 313 | splk.unlock(); 314 | if (notify) { 315 | sem.trigger(1); 316 | } 317 | } 318 | /// 319 | void dispose() shared { 320 | sem.dispose(); 321 | } 322 | } 323 | 324 | /// Create recursive mutex 325 | auto recursiveMutex() @trusted nothrow { 326 | return cast(shared)RecursiveMutex(1); 327 | } 328 | 329 | 330 | unittest { 331 | static void testTryLock(alias createM)(int lockTimes) { 332 | initPhoton(); 333 | auto m = createM(); 334 | auto ev = event(0); 335 | shared bool unlocked = false; 336 | go({ 337 | m.lock(); 338 | ev.trigger(); 339 | go({ 340 | ev.waitAndReset(); 341 | assert(m.locked()); 342 | assert(m.tryLock() == false); 343 | ev.waitAndReset(); 344 | assert(!m.locked()); 345 | foreach (_; 0..lockTimes) { 346 | bool locked = m.tryLock(); 347 | assert(locked); 348 | } 349 | assert(m.locked()); 350 | foreach (_; 0..lockTimes) { 351 | m.unlock(); 352 | } 353 | assert(!m.locked()); 354 | }); 355 | delay(100.msecs); 356 | m.unlock(); 357 | ev.trigger(); 358 | }); 359 | runScheduler(); 360 | ev.dispose(); 361 | m.dispose(); 362 | } 363 | testTryLock!(mutex)(1); 364 | testTryLock!(recursiveMutex)(3); 365 | } 366 | 367 | unittest { 368 | enum ITERS = 1000; 369 | enum COUNT = 10; 370 | enum LOCK_CNT = 3; 371 | enum JOBS = 20; 372 | static void testMutex(M, alias createM)(int iters, int count, int lockingTimes, int jobs) { 373 | initPhoton(); 374 | auto mtxs = new shared(M)[count]; 375 | int[] counters = new int[count]; 376 | foreach (i; 0..count) { 377 | mtxs[i] = createM(); 378 | } 379 | shared size_t cnt = 0; 380 | void task() { 381 | foreach(_; 0..iters){ 382 | foreach (i; 0..count) { 383 | foreach (__; 0..lockingTimes) 384 | mtxs[i].lock(); 385 | counters[i]++; 386 | foreach(__;0..lockingTimes) 387 | mtxs[i].unlock(); 388 | } 389 | } 390 | } 391 | foreach(_; 0..jobs) { 392 | go(&task); 393 | } 394 | runScheduler(); 395 | foreach (i; 0..COUNT) { 396 | assert(counters[i] == ITERS * JOBS); 397 | } 398 | } 399 | testMutex!(Mutex, mutex)(ITERS, COUNT, 1, JOBS); 400 | testMutex!(RecursiveMutex, recursiveMutex)(ITERS, COUNT, LOCK_CNT, JOBS); 401 | } 402 | 403 | public struct Condition { 404 | nothrow: 405 | @trusted: 406 | private: 407 | alias Waiters = LinkedList!(AwaitingFiber*, "next", "prev"); 408 | Waiters waiters; 409 | SpinLock splk; 410 | public: 411 | /// 412 | void wait(M)(ref M mutex) shared { 413 | try { 414 | assert(currentFiber !is null); 415 | splk.lock(); 416 | mutex.unlock(); 417 | auto f = currentFiber; 418 | auto await = AwaitingFiber(cast(shared)&f); 419 | this.unshared.waiters.append(&await); 420 | splk.unlock(); 421 | FiberExt.yield(); 422 | mutex.lock(); 423 | } catch (Throwable t) { assert(false, t.toString()); } 424 | } 425 | 426 | /// 427 | bool wait(M)(ref M mutex, Duration d) shared { 428 | try { 429 | assert(currentFiber !is null); 430 | splk.lock(); 431 | mutex.unlock(); 432 | auto f = currentFiber; 433 | AwaitingFiber await = AwaitingFiber(cast(shared)&f); 434 | this.unshared.waiters.append(&await); 435 | TimedFiber tm = timerEntry(&f, d); 436 | timeQueue.insert(&tm); 437 | splk.unlock(); 438 | FiberExt.yield(); 439 | bool success = false; 440 | if (currentFiber.wakeFd == WAKE_TIMER) { 441 | splk.lock(); 442 | if (await.next != null) 443 | this.unshared.waiters.remove(&await); 444 | splk.unlock(); 445 | } else { 446 | timeQueue.cancel(&tm); 447 | success = true; 448 | } 449 | mutex.lock(); 450 | return success; 451 | } catch(Throwable t) { assert(false, t.toString()); } 452 | } 453 | 454 | /// 455 | void signal() shared { 456 | assert(currentFiber !is null); 457 | splk.lock(); 458 | AwaitingFiber* waiter; 459 | if (!this.unshared.waiters.empty) { 460 | waiter = this.unshared.waiters.popHead(); 461 | } 462 | splk.unlock(); 463 | if (waiter) { 464 | waiter.schedule(currentFiber.numScheduler, WAKE_TRIGGER); 465 | } 466 | } 467 | 468 | /// 469 | void broadcast() shared { 470 | assert(currentFiber !is null); 471 | splk.lock(); 472 | Waiters list = this.unshared.waiters; 473 | this.unshared.waiters = Waiters.init; 474 | splk.unlock(); 475 | while (!list.empty) { 476 | AwaitingFiber* waiter = list.popHead(); 477 | waiter.schedule(currentFiber.numScheduler, WAKE_TRIGGER); 478 | } 479 | } 480 | } 481 | 482 | /// Create a conditional variable 483 | auto condition() @trusted nothrow { 484 | return cast(shared)Condition.init; 485 | } 486 | 487 | unittest { 488 | void simpleCondTest(alias signal, alias wait)() { 489 | initPhoton(); 490 | auto cond = condition(); 491 | auto mtx = mutex(); 492 | int counter = 0; 493 | enum MAX = 10000; 494 | go({ 495 | for (;;) { 496 | mtx.lock(); 497 | while (counter % 2 != 0) { 498 | wait(cond, mtx); 499 | } 500 | counter++; 501 | if (counter >= MAX) { 502 | mtx.unlock(); 503 | signal(cond); 504 | break; 505 | } 506 | mtx.unlock(); 507 | signal(cond); 508 | } 509 | }); 510 | go({ 511 | for (;;) { 512 | mtx.lock(); 513 | while (counter % 2 != 1) { 514 | wait(cond, mtx); 515 | } 516 | counter++; 517 | if (counter >= MAX) { 518 | mtx.unlock(); 519 | signal(cond); 520 | break; 521 | } 522 | mtx.unlock(); 523 | signal(cond); 524 | } 525 | }); 526 | runScheduler(); 527 | mtx.dispose(); 528 | assert(counter == MAX + 1); 529 | } 530 | simpleCondTest!((ref cnd) => cnd.signal(), (ref cnd, ref mtx) => cnd.wait(mtx)); 531 | simpleCondTest!((ref cnd) => cnd.signal(), (ref cnd, ref mtx) => cnd.wait(mtx, 100.msecs)); 532 | simpleCondTest!((ref cnd) => cnd.broadcast(), (ref cnd, ref mtx) => cnd.wait(mtx)); 533 | simpleCondTest!((ref cnd) => cnd.broadcast(), (ref cnd, ref mtx) => cnd.wait(mtx, 100.msecs)); 534 | } 535 | 536 | unittest { 537 | initPhoton(); 538 | auto cond = condition(); 539 | auto mtx = mutex(); 540 | go({ 541 | mtx.lock(); 542 | auto s = MonoTime.currTime; 543 | cond.wait(mtx, 10.msecs); 544 | auto s2 = MonoTime.currTime; 545 | assert((s2 - s).total!"msecs" >= 10); 546 | mtx.unlock(); 547 | }); 548 | runScheduler(); 549 | } 550 | 551 | /++ 552 | A ref-counted channel that is safe to share between multiple fibers. 553 | In essence it's a multiple producer single consumer queue, that implements 554 | `OutputRange` and `InputRange` concepts. 555 | +/ 556 | struct Channel(T) { 557 | @trusted: 558 | private: 559 | shared RingQueue!(T, Event)* buf_; 560 | shared T item_; 561 | bool loaded; 562 | 563 | ref T item() shared { 564 | return *cast(T*)&item_; 565 | } 566 | 567 | ref buf() shared { 568 | return *cast(RingQueue!(T, Event)**)&buf_; 569 | } 570 | 571 | ref T item() { 572 | return *cast(T*)&item_; 573 | } 574 | 575 | ref buf() { 576 | return *cast(RingQueue!(T, Event)**)&buf_; 577 | } 578 | public: 579 | this(size_t capacity) { 580 | buf_ = cast(shared)allocRingQueue!T(capacity, Event(false), Event(false)); 581 | } 582 | 583 | this(this) { 584 | buf.retain(); 585 | } 586 | 587 | /// OutputRange contract - puts a new item into the channel. 588 | void put(T value) { 589 | buf.push(move(value)); 590 | } 591 | 592 | void put(T value) shared { 593 | buf.push(move(value)); 594 | } 595 | 596 | void close() shared { 597 | buf.close(); 598 | } 599 | 600 | /++ 601 | Part of InputRange contract - checks if there is an item in the queue. 602 | Returns `true` if channel is closed and its buffer is exhausted. 603 | +/ 604 | bool empty() { 605 | if (loaded) return false; 606 | loaded = buf.tryPop(item); 607 | return !loaded; 608 | } 609 | 610 | bool empty() shared { 611 | if (loaded) return false; 612 | loaded = buf.tryPop(item); 613 | return !loaded; 614 | } 615 | 616 | /++ 617 | Part of InputRange contract - returns an item available in the channel. 618 | +/ 619 | ref T front() { 620 | return cast()item; 621 | } 622 | 623 | ref T front() shared { 624 | return item; 625 | } 626 | 627 | /++ 628 | Part of InputRange contract - advances range forward. 629 | +/ 630 | void popFront() { 631 | loaded = false; 632 | } 633 | 634 | void popFront() shared { 635 | loaded = false; 636 | } 637 | 638 | ~this() { 639 | if (buf) { 640 | if (buf.release) { 641 | disposeRingQueue(buf); 642 | buf_ = null; 643 | } 644 | } 645 | } 646 | } 647 | 648 | /++ 649 | Create a new shared `Channel` with given capacity. 650 | +/ 651 | auto channel(T)(size_t capacity = 1) @safe { 652 | return cast(shared)Channel!T(capacity); 653 | } 654 | 655 | /// 656 | unittest { 657 | runPhoton({ 658 | import std.range.primitives, std.traits; 659 | import std.algorithm; 660 | static assert(isInputRange!(Channel!int)); 661 | static assert(isInputRange!(Unqual!(shared Channel!int))); 662 | static assert(isOutputRange!(shared Channel!int, int)); 663 | // 664 | auto ch = channel!int(10); 665 | foreach (i; 0..10){ 666 | ch.put(i); 667 | } 668 | ch.close(); 669 | auto sum = ch.sum; 670 | assert(sum == 45); 671 | }); 672 | } 673 | 674 | 675 | /++ 676 | Multiplex between multiple channels, executes a lambda attached to the first 677 | channel that becomes ready to read. 678 | +/ 679 | void select(Args...)(auto ref Args args) @trusted 680 | if (allSatisfy!(isChannel, Even!Args) && allSatisfy!(isHandler, Odd!Args)) { 681 | void delegate()[args.length/2] handlers = void; 682 | Event*[args.length/2] events = void; 683 | static foreach (i, v; args) { 684 | static if(i % 2 == 0) { 685 | events[i/2] = &v.buf.rtr; 686 | } 687 | else { 688 | handlers[i/2] = v; 689 | } 690 | } 691 | foreach (i, channel; Even!(args)) { 692 | if (channel.buf.readyToRead()) 693 | return handlers[i](); 694 | } 695 | for (;;) { 696 | auto n = awaitAny(events[]); 697 | L_dispatch: 698 | switch(n) { 699 | static foreach (i, channel; Even!(args)) { 700 | case i: 701 | if (channel.buf.readyToRead()) 702 | return handlers[n](); 703 | break L_dispatch; 704 | } 705 | default: 706 | assert(0); 707 | } 708 | } 709 | } 710 | 711 | /// Trait for testing if a type is Channel 712 | enum isChannel(T) = is(T == Channel!(V), V); 713 | 714 | enum isHandler(T) = is(T : void delegate()); 715 | 716 | private template Even(T...) { 717 | static assert(T.length % 2 == 0); 718 | static if (T.length > 0) { 719 | alias Even = AliasSeq!(T[0], Even!(T[2..$])); 720 | } 721 | else { 722 | alias Even = AliasSeq!(); 723 | } 724 | } 725 | 726 | private template Odd(T...) { 727 | static assert(T.length % 2 == 0); 728 | static if (T.length > 0) { 729 | alias Odd = AliasSeq!(T[1], Odd!(T[2..$])); 730 | } 731 | else { 732 | alias Odd = AliasSeq!(); 733 | } 734 | } 735 | 736 | unittest { 737 | static assert(Even!(1, 2, 3, 4) == AliasSeq!(1, 3)); 738 | static assert(Odd!(1, 2, 3, 4) == AliasSeq!(2, 4)); 739 | static assert(isChannel!(Channel!int)); 740 | static assert(isChannel!(shared Channel!int)); 741 | } 742 | 743 | struct PooledEntry(T) { 744 | import std.datetime; 745 | private: 746 | PooledEntry* next; 747 | SysTime lastUsed; 748 | T item; 749 | } 750 | 751 | struct Pooled(T) { 752 | alias get this; 753 | @property ref get() { return pointer.item; } 754 | private PooledEntry!T* pointer; 755 | } 756 | 757 | /// Generic pool 758 | class Pool(T) { 759 | import std.datetime, photon.ds.common; 760 | private this(size_t size, Duration maxIdle, T delegate() open, void delegate(ref T) close) { 761 | this.size = size; 762 | this.maxIdle = maxIdle; 763 | this.open = open; 764 | this.close = close; 765 | this.allocated = 0; 766 | this.ready = event(0); 767 | this.working = true; 768 | this.lock = SpinLock(SpinLock.Contention.brief); 769 | go({ 770 | while(this.working) { 771 | delay(1.seconds); 772 | auto time = Clock.currTime(); 773 | lock.lock(); 774 | PooledEntry!T* stale; 775 | PooledEntry!T* fresh; 776 | PooledEntry!T* current = pool; 777 | while (current != null) { 778 | if (current.lastUsed + maxIdle < time) { 779 | auto next = current.next; 780 | current.next = stale; 781 | stale = current; 782 | current = next; 783 | } 784 | else { 785 | auto next = current.next; 786 | current.next = fresh; 787 | fresh = current; 788 | current = next; 789 | } 790 | } 791 | pool = fresh; 792 | lock.unlock(); 793 | current = stale; 794 | size_t count = 0; 795 | while (current != null) { 796 | close(current.item); 797 | current = current.next; 798 | count++; 799 | } 800 | atomicFetchSub(allocated, count); 801 | if (count > 0) ready.trigger(); 802 | } 803 | }); 804 | } 805 | 806 | // Acquire resource from the pool 807 | Pooled!T acquire() { 808 | for (;;) { 809 | lock.lock(); 810 | if (pool != null) { 811 | auto next = pool.next; 812 | auto ret = pool; 813 | ret.next = null; 814 | pool = next; 815 | lock.unlock(); 816 | return Pooled!T(ret); 817 | } 818 | lock.unlock(); 819 | if (allocated < size) { 820 | size_t current = allocated; 821 | size_t next = current + 1; 822 | if (cas(&allocated, current, next)) { 823 | // since we are not in the pool yet, lastUsed is ignored 824 | auto item = new PooledEntry!T(null, SysTime.init, open()); 825 | return Pooled!T(item); 826 | } 827 | } 828 | ready.waitAndReset(); 829 | } 830 | } 831 | 832 | Pooled!T acquire() shared { 833 | return this.unshared.acquire(); 834 | } 835 | 836 | /// Put pooled item to reuse 837 | void release(Pooled!T item) { 838 | item.pointer.lastUsed = Clock.currTime(); 839 | lock.lock(); 840 | item.pointer.next = pool; 841 | pool = item.pointer; 842 | lock.unlock(); 843 | ready.trigger(); 844 | } 845 | 846 | void release(Pooled!T item) shared { 847 | this.unshared.release(item); 848 | } 849 | 850 | /// call on items that errored or cannot be reused for some reason 851 | void dispose(Pooled!T item) { 852 | atomicFetchSub(allocated, 1); 853 | close(item.pointer.item); 854 | ready.trigger(); 855 | } 856 | 857 | void dispose(Pooled!T item) shared { 858 | return this.unshared.dispose(item); 859 | } 860 | 861 | void shutdown() { 862 | working = false; 863 | auto current = pool; 864 | while (current != null) { 865 | close(current.item); 866 | current = current.next; 867 | } 868 | } 869 | 870 | void shutdown() shared { 871 | return this.unshared.shutdown(); 872 | } 873 | private: 874 | SpinLock lock; 875 | shared Event ready; 876 | PooledEntry!T* pool; 877 | shared size_t allocated; 878 | size_t size; 879 | Duration maxIdle; 880 | T delegate() open; 881 | void delegate(ref T) close; 882 | shared bool working; 883 | } 884 | 885 | 886 | /// Create generic pool for resources, open creates new resource, close releases the resource. 887 | auto pool(T)(size_t size, Duration maxIdle, T delegate() open, void delegate(ref T) close) @trusted { 888 | return cast(shared) new Pool!T(size, maxIdle, open, close); 889 | } -------------------------------------------------------------------------------- /src/photon/support.d: -------------------------------------------------------------------------------- 1 | module photon.support; 2 | 3 | version(OSX) version = Darwin; 4 | else version(iOS) version = Darwin; 5 | else version(TVOS) version = Darwin; 6 | else version(WatchOS) version = Darwin; 7 | else version(VisionOS) version = Darwin; 8 | 9 | version(linux) public import photon.linux.support; 10 | else version(FreeBSD) public import photon.freebsd.support; 11 | else version(Darwin) public import photon.macos.support; 12 | else version(Windows) public import photon.windows.support; 13 | -------------------------------------------------------------------------------- /src/photon/task.d: -------------------------------------------------------------------------------- 1 | module photon.task; 2 | 3 | import photon.core; 4 | import std.concurrency; 5 | 6 | /** Represents a single task started with photon.go 7 | 8 | Note that the Task type is considered weakly isolated and thus can be 9 | passed between threads using vibe.core.concurrency.send or by passing 10 | it as a parameter to vibe.core.core.runWorkerTask. 11 | */ 12 | struct Task { 13 | package: 14 | shared FiberExt _fiber; 15 | static ThreadInfo s_tidInfo; 16 | 17 | this(FiberExt fiber) 18 | @trusted nothrow { 19 | this._fiber = cast(shared)fiber; 20 | } 21 | 22 | public: 23 | enum basePriority = 0x00010000; 24 | // NOTE: this is a template function to avoid the compiler treating it as a 25 | // move constructor 26 | this()(Task other) 27 | @safe nothrow { 28 | _fiber = other._fiber; 29 | } 30 | 31 | /** Returns the Task instance belonging to the calling task. 32 | */ 33 | static Task getThis() @safe nothrow 34 | { 35 | return Task(currentFiber); 36 | } 37 | 38 | nothrow { 39 | package @property FiberExt taskFiber() @system { return cast()_fiber; } 40 | @property FiberExt fiber() @system { return cast()this._fiber; } 41 | 42 | /** Determines if the task is still running or scheduled to be run. 43 | */ 44 | @property bool running() 45 | const @trusted { 46 | return _fiber.unshared.state != Fiber.State.TERM; 47 | } 48 | 49 | @property ref ThreadInfo tidInfo() @system { return _fiber ? cast()_fiber.tidInfo : s_tidInfo; } // FIXME: this is not thread safe! 50 | @property ref const(ThreadInfo) tidInfo() const @system { return _fiber ? cast()_fiber.tidInfo : s_tidInfo; } // FIXME: this is not thread safe! 51 | 52 | /** Gets the `Tid` associated with this task for use with 53 | `std.concurrency`. 54 | */ 55 | @property Tid tid() @trusted { return tidInfo.ident; } 56 | /// ditto 57 | @property const(Tid) tid() const @trusted { return tidInfo.ident; } 58 | } 59 | 60 | T opCast(T)() const @safe nothrow if (is(T == bool)) { return _fiber !is null; } 61 | T opCast(T)() const shared @safe nothrow if (is(T == bool)) { return _fiber !is null; } 62 | 63 | void join() @trusted { if (_fiber) _fiber.join(); } 64 | void joinUninterruptible() @trusted nothrow { if (_fiber) _fiber.joinNothrow(); } 65 | void interrupt() @trusted nothrow { 66 | //noop 67 | } 68 | 69 | bool opEquals(scope ref const(Task) other) const @safe nothrow { 70 | return _fiber is other._fiber; 71 | } 72 | bool opEquals(scope const(Task) other) const @safe nothrow { 73 | return _fiber is other._fiber; 74 | } 75 | bool opEquals(scope shared(const(Task)) other) const shared @safe nothrow { 76 | return _fiber is other._fiber; 77 | } 78 | } 79 | 80 | /// Task local storage 81 | struct TaskLocal(T) { 82 | private: 83 | size_t offset = size_t.max; 84 | T initial; 85 | 86 | static void dtor(void* pointer) { 87 | destroy(*cast(T*)pointer); 88 | } 89 | public: 90 | this(T value) { 91 | initial = value; 92 | } 93 | 94 | @disable this(this); 95 | 96 | ref opAssign(T value) { 97 | storage = value; 98 | } 99 | 100 | ref storage() { 101 | size_t size = (T.sizeof + 0xf) & ~0xf; 102 | if (offset == size_t.max) { 103 | offset = FiberExt.flsAlloc(); 104 | } 105 | void* data = currentFiber.flsGet(offset, &initial, size, &dtor); 106 | return *cast(T*)data; 107 | } 108 | 109 | alias this = storage; 110 | } -------------------------------------------------------------------------------- /src/photon/threadpool.d: -------------------------------------------------------------------------------- 1 | module photon.threadpool; 2 | 3 | package(photon): 4 | 5 | import core.thread, core.atomic; 6 | import std.random; 7 | 8 | import photon.ds.intrusive_queue; 9 | import photon.core; 10 | 11 | 12 | alias Work = void delegate(); 13 | 14 | class WorkItem { 15 | this(FiberExt fiber, Work work) nothrow { 16 | this.fiber = fiber; 17 | this.work = work; 18 | } 19 | 20 | void done() { 21 | version(FreeBSD) 22 | fiber.schedule(); 23 | else 24 | fiber.schedule(size_t.max, WAKE_TRIGGER); // schedule from "remote scheduler" 25 | } 26 | 27 | FiberExt fiber; // if waking up fiber 28 | Work work; // universal work 29 | WorkItem next; // for intrusive queue 30 | } 31 | 32 | struct WorkQueue { 33 | IntrusiveQueue!(WorkItem, RawEvent) runq; 34 | shared int assigned; 35 | ubyte[64 - runq.sizeof - assigned.sizeof] padding; 36 | } 37 | 38 | static assert (WorkQueue.sizeof == 64); 39 | 40 | __gshared WorkQueue[] queues; 41 | __gshared Thread[] workThreads; 42 | shared bool workQueueTerminated = false; 43 | 44 | void initWorkQueues(size_t threads) nothrow { 45 | queues = new WorkQueue[threads]; 46 | workThreads = new Thread[threads]; 47 | foreach (ref q; queues) { 48 | q.runq = IntrusiveQueue!(WorkItem, RawEvent)(RawEvent(0)); 49 | } 50 | } 51 | 52 | void terminateWorkQueues() { 53 | workQueueTerminated = true; 54 | foreach (ref q; queues) { 55 | q.runq.event.trigger(); 56 | } 57 | foreach (ref t; workThreads) { 58 | t.join(); 59 | } 60 | } 61 | 62 | void startWorkQueue(size_t threads) { 63 | workQueueTerminated = false; 64 | void run(size_t n) nothrow { 65 | try { 66 | workThreads[n] = new Thread(() => processWorkQueue(n)); 67 | workThreads[n].start(); 68 | } catch(Exception) {} 69 | } 70 | foreach (i; 0..threads) { 71 | run(i); 72 | } 73 | } 74 | 75 | void processWorkQueue(size_t i) { 76 | while (!workQueueTerminated) { 77 | queues[i].runq.event.waitAndReset(); 78 | WorkItem w = queues[i].runq.drain(); 79 | while(w !is null) { 80 | auto next = w.next; 81 | w.work(); 82 | w.done(); 83 | atomicOp!"-="(queues[i].assigned, 1); 84 | w = next; 85 | } 86 | } 87 | } 88 | 89 | void pushWork(WorkItem item) nothrow { 90 | uint choice; 91 | try { 92 | if (queues.length == 1) choice = 0; 93 | else { 94 | uint a = uniform!"[)"(0, cast(uint)queues.length); 95 | uint b = uniform!"[)"(0, cast(uint)queues.length-1); 96 | if (a == b) b = cast(uint)queues.length-1; 97 | uint loadA = queues[a].assigned; 98 | uint loadB = queues[b].assigned; 99 | if (loadA < loadB) choice = a; 100 | else choice = b; 101 | } 102 | atomicOp!"+="(queues[choice].assigned, 1); 103 | bool wasEmpty = queues[choice].runq.push(item); 104 | if (wasEmpty) queues[choice].runq.event.trigger(); 105 | } catch(Throwable t) { 106 | } 107 | } 108 | 109 | public: 110 | 111 | T offload(T)(T function() work) { 112 | return offload(() => work()); 113 | } 114 | 115 | T offload(T)(T delegate() work) { 116 | if (currentFiber is null) return work(); 117 | static if (!is(T == void)) { 118 | T result; 119 | } 120 | Throwable thr; 121 | //TODO: pool items? 122 | auto item = new WorkItem(currentFiber, () { 123 | try { 124 | static if (is(T == void)) { 125 | work(); 126 | } else { 127 | result = work(); 128 | } 129 | } catch(Throwable t) { 130 | thr = t; 131 | } 132 | }); 133 | pushWork(item); 134 | FiberExt.yield(); 135 | if (thr) throw thr; 136 | static if (!is(T == void)) { 137 | return result; 138 | } 139 | } 140 | 141 | T offload(T)(T delegate() nothrow work) nothrow { 142 | if (currentFiber is null) return work(); 143 | static if (!is(T == void)) { 144 | T result; 145 | } 146 | //TODO: pool items? 147 | auto item = new WorkItem(currentFiber, () { 148 | static if (is(T == void)) { 149 | work(); 150 | } else { 151 | result = work(); 152 | } 153 | }); 154 | pushWork(item); 155 | FiberExt.yield(); 156 | static if (!is(T == void)) { 157 | return result; 158 | } 159 | } -------------------------------------------------------------------------------- /src/photon/windows/support.d: -------------------------------------------------------------------------------- 1 | module photon.windows.support; 2 | version(Windows): 3 | import core.sys.windows.core; 4 | import core.sys.windows.winsock2; 5 | import std.format; 6 | import core.stdc.stdlib; 7 | 8 | struct WSABUF 9 | { 10 | uint length; 11 | void* buf; 12 | } 13 | 14 | extern(Windows) SOCKET WSASocketW( 15 | int af, 16 | int type, 17 | int protocol, 18 | void* lpProtocolInfo, 19 | DWORD g, 20 | DWORD dwFlags 21 | ) nothrow; 22 | 23 | // hackish, we do not use LPCONDITIONPROC 24 | alias LPCONDITIONPROC = void*; 25 | alias LPWSABUF = WSABUF*; 26 | 27 | extern(Windows) SOCKET WSAAccept( 28 | SOCKET s, 29 | sockaddr *addr, 30 | LPINT addrlen, 31 | LPCONDITIONPROC lpfnCondition, 32 | DWORD_PTR dwCallbackData 33 | ) nothrow; 34 | 35 | extern(Windows) int WSARecv( 36 | SOCKET s, 37 | LPWSABUF lpBuffers, 38 | DWORD dwBufferCount, 39 | LPDWORD lpNumberOfBytesRecvd, 40 | LPDWORD lpFlags, 41 | LPWSAOVERLAPPED lpOverlapped, 42 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 43 | ) nothrow; 44 | 45 | extern(Windows) int WSASend( 46 | SOCKET s, 47 | LPWSABUF lpBuffers, 48 | DWORD dwBufferCount, 49 | LPDWORD lpNumberOfBytesSent, 50 | DWORD dwFlags, 51 | LPWSAOVERLAPPED lpOverlapped, 52 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 53 | ) nothrow; 54 | 55 | struct OVERLAPPED_ENTRY { 56 | ULONG_PTR lpCompletionKey; 57 | LPOVERLAPPED lpOverlapped; 58 | ULONG_PTR Internal; 59 | DWORD dwNumberOfBytesTransferred; 60 | } 61 | 62 | alias LPOVERLAPPED_ENTRY = OVERLAPPED_ENTRY*; 63 | 64 | extern(Windows) BOOL GetQueuedCompletionStatusEx( 65 | HANDLE CompletionPort, 66 | LPOVERLAPPED_ENTRY lpCompletionPortEntries, 67 | ULONG ulCount, 68 | PULONG ulNumEntriesRemoved, 69 | DWORD dwMilliseconds, 70 | BOOL fAlertable 71 | ) nothrow; 72 | 73 | enum WSA_FLAG_OVERLAPPED = 0x01; 74 | 75 | struct TP_POOL; 76 | 77 | alias PTP_POOL = TP_POOL*; 78 | 79 | extern(Windows) PTP_POOL CreateThreadpool( 80 | PVOID reserved 81 | ) nothrow; 82 | 83 | extern(Windows) void SetThreadpoolThreadMaximum( 84 | PTP_POOL ptpp, 85 | DWORD cthrdMost 86 | ) nothrow; 87 | 88 | extern(Windows) BOOL SetThreadpoolThreadMinimum( 89 | PTP_POOL ptpp, 90 | DWORD cthrdMic 91 | ) nothrow; 92 | 93 | alias TP_VERSION = DWORD; 94 | alias PTP_VERSION = TP_VERSION*; 95 | 96 | struct TP_CALLBACK_INSTANCE; 97 | alias PTP_CALLBACK_INSTANCE = TP_CALLBACK_INSTANCE*; 98 | 99 | alias PTP_SIMPLE_CALLBACK = extern(Windows) VOID function(PTP_CALLBACK_INSTANCE, PVOID); 100 | 101 | enum TP_CALLBACK_PRIORITY : int { 102 | TP_CALLBACK_PRIORITY_HIGH, 103 | TP_CALLBACK_PRIORITY_NORMAL, 104 | TP_CALLBACK_PRIORITY_LOW, 105 | TP_CALLBACK_PRIORITY_INVALID, 106 | TP_CALLBACK_PRIORITY_COUNT = TP_CALLBACK_PRIORITY_INVALID 107 | } 108 | 109 | struct TP_POOL_STACK_INFORMATION { 110 | SIZE_T StackReserve; 111 | SIZE_T StackCommit; 112 | } 113 | alias PTP_POOL_STACK_INFORMATION = TP_POOL_STACK_INFORMATION*; 114 | 115 | struct TP_CLEANUP_GROUP; 116 | alias PTP_CLEANUP_GROUP = TP_CLEANUP_GROUP*; 117 | 118 | alias PTP_CLEANUP_GROUP_CANCEL_CALLBACK = extern(Windows) VOID function(PVOID, PVOID); 119 | 120 | struct ACTIVATION_CONTEXT; 121 | 122 | struct TP_CALLBACK_ENVIRON_V3 { 123 | TP_VERSION Version; 124 | PTP_POOL Pool; 125 | PTP_CLEANUP_GROUP CleanupGroup; 126 | PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback; 127 | PVOID RaceDll; 128 | ACTIVATION_CONTEXT* ActivationContext; 129 | PTP_SIMPLE_CALLBACK FinalizationCallback; 130 | DWORD Flags; 131 | TP_CALLBACK_PRIORITY CallbackPriority; 132 | DWORD Size; 133 | } 134 | 135 | alias TP_CALLBACK_ENVIRON = TP_CALLBACK_ENVIRON_V3; 136 | alias PTP_CALLBACK_ENVIRON = TP_CALLBACK_ENVIRON*; 137 | 138 | VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON cbe) nothrow { 139 | cbe.Pool = NULL; 140 | cbe.CleanupGroup = NULL; 141 | cbe.CleanupGroupCancelCallback = NULL; 142 | cbe.RaceDll = NULL; 143 | cbe.ActivationContext = NULL; 144 | cbe.FinalizationCallback = NULL; 145 | cbe.Flags = 0; 146 | cbe.Version = 3; 147 | cbe.CallbackPriority = TP_CALLBACK_PRIORITY.TP_CALLBACK_PRIORITY_NORMAL; 148 | cbe.Size = TP_CALLBACK_ENVIRON.sizeof; 149 | } 150 | 151 | extern(Windows) void CloseThreadpool( 152 | PTP_POOL ptpp 153 | ) nothrow; 154 | 155 | // inline "function" 156 | VOID SetThreadpoolCallbackPool(PTP_CALLBACK_ENVIRON cbe, PTP_POOL pool) nothrow { cbe.Pool = pool; } 157 | 158 | struct TP_WORK; 159 | alias PTP_WORK = TP_WORK*; 160 | 161 | struct TP_WAIT; 162 | alias PTP_WAIT = TP_WAIT*; 163 | 164 | struct TP_TIMER; 165 | alias PTP_TIMER = TP_TIMER*; 166 | 167 | alias TP_WAIT_RESULT = DWORD; 168 | 169 | alias PTP_WORK_CALLBACK = extern(Windows) VOID function (PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work); 170 | alias PTP_WAIT_CALLBACK = extern(Windows) VOID function (PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult); 171 | alias PTP_TIMER_CALLBACK = extern(Windows) VOID function(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer); 172 | 173 | extern(Windows) PTP_WORK CreateThreadpoolWork( 174 | PTP_WORK_CALLBACK pfnwk, 175 | PVOID pv, 176 | PTP_CALLBACK_ENVIRON pcbe 177 | ) nothrow; 178 | 179 | extern(Windows) PTP_WAIT CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe) nothrow; 180 | 181 | extern(Windows) void SubmitThreadpoolWork( 182 | PTP_WORK pwk 183 | ) nothrow; 184 | 185 | extern(Windows) void CloseThreadpoolWork( 186 | PTP_WORK pwk 187 | ) nothrow; 188 | 189 | extern(Windows) void SetThreadpoolWait( 190 | PTP_WAIT pwa, 191 | HANDLE h, 192 | PFILETIME pftTimeout 193 | ) nothrow; 194 | 195 | extern(Windows) void CloseThreadpoolWait( 196 | PTP_WAIT pwa 197 | ) nothrow; 198 | 199 | extern(Windows) PTP_TIMER CreateThreadpoolTimer( 200 | PTP_TIMER_CALLBACK pfnti, 201 | PVOID pv, 202 | PTP_CALLBACK_ENVIRON pcbe 203 | ) nothrow; 204 | 205 | extern(Windows) void SetThreadpoolTimer( 206 | PTP_TIMER pti, 207 | PFILETIME pftDueTime, 208 | DWORD msPeriod, 209 | DWORD msWindowLength 210 | ) nothrow; 211 | 212 | extern(Windows) void CloseThreadpoolTimer( 213 | PTP_TIMER pti 214 | ) nothrow; 215 | 216 | 217 | void outputToConsole(const(wchar)[] msg) nothrow 218 | { 219 | HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE); 220 | uint size = cast(uint)msg.length; 221 | WriteConsole(output, msg.ptr, size, &size, null); 222 | } 223 | 224 | void logf(T...)(const(wchar)[] fmt, T args) 225 | { 226 | debug(photon) try { 227 | formattedWrite(&outputToConsole, fmt, args); 228 | formattedWrite(&outputToConsole, "\n"); 229 | } 230 | catch (Exception e) { 231 | outputToConsole("ARGH!"w); 232 | } 233 | } 234 | 235 | void checked(bool arg, string msg) nothrow { 236 | if (!arg) { 237 | try { 238 | formattedWrite(&outputToConsole, msg); 239 | formattedWrite(&outputToConsole, "\n"); 240 | } catch (Throwable t) {} 241 | abort(); 242 | } 243 | } 244 | 245 | -------------------------------------------------------------------------------- /tests/await.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for awaitAny API", 11 | "license": "BOOST", 12 | "name": "channels" 13 | } 14 | +/ 15 | module tests.await; 16 | 17 | import std.stdio, std.datetime; 18 | import photon; 19 | 20 | void main(){ 21 | initPhoton(); 22 | auto e = event(false); 23 | auto s = semaphore(0); 24 | go({ 25 | delay(1.seconds); 26 | e.trigger(); 27 | delay(1.seconds); 28 | s.trigger(1); 29 | }); 30 | go({ // awaiter 31 | auto n = awaitAny(e, s); 32 | assert(n == 0); 33 | writeln("Await ", n); 34 | delay(3.seconds); 35 | n = awaitAny(e, s); 36 | assert(n == 1); 37 | writeln(n); 38 | writeln("Await ", n); 39 | 40 | }); 41 | runScheduler(); 42 | } -------------------------------------------------------------------------------- /tests/curl_download.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "Curl download - a simple example of using std.net.curl with Photon", 11 | "license": "BOOST", 12 | "name": "curl_download" 13 | } 14 | +/ 15 | import std.algorithm, std.net.curl, std.string, std.datetime.stopwatch, std.range, std.stdio; 16 | import std.file : remove; 17 | import core.thread; 18 | import photon; 19 | 20 | // try your own urls 21 | immutable urls = [ 22 | "https://mirror.yandex.ru/debian/doc/FAQ/debian-faq.en.html.tar.gz", 23 | "https://mirror.yandex.ru/debian/doc/FAQ/debian-faq.en.pdf.gz", 24 | "https://mirror.yandex.ru/debian/doc/FAQ/debian-faq.en.ps.gz", 25 | "https://mirror.yandex.ru/debian/doc/FAQ/debian-faq.en.txt.gz", 26 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-monitor-1.8.1-9.fc28.noarch.rpm", 27 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-native-1.8.1-9.fc28.x86_64.rpm", 28 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-server-base-1.8.1-9.fc28.noarch.rpm", 29 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-shell-1.8.1-9.fc28.x86_64.rpm", 30 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-tracer-1.8.1-9.fc28.noarch.rpm", 31 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/accumulo-tserver-1.8.1-9.fc28.noarch.rpm", 32 | "http://www.v6.mirror.yandex.ru/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/a/acegisecurity-1.0.7-9.fc28.noarch.rpm" 33 | ]; 34 | 35 | void main(){ 36 | initPhoton(); 37 | void spawnDownload(string url, string file) { 38 | go(() => download(url, file)); 39 | } 40 | Thread threadDownload(string url, string file) { 41 | auto t = new Thread(() => download(url, file)); 42 | t.start(); 43 | return t; 44 | } 45 | StopWatch sw; 46 | sw.reset(); 47 | sw.start(); 48 | foreach(url; urls) { 49 | download(url, url.split('/').back); 50 | } 51 | sw.stop(); 52 | writefln("Sequentially: %s ms", sw.peek.total!"msecs"); 53 | 54 | foreach(url; urls) { 55 | remove(url.split('/').back); 56 | } 57 | 58 | sw.reset(); 59 | sw.start(); 60 | urls 61 | .map!(url => threadDownload(url, url.split('/').back)) 62 | .array 63 | .each!(t => t.join()); 64 | sw.stop(); 65 | writefln("Threads: %s ms", sw.peek.total!"msecs"); 66 | 67 | foreach(url; urls) { 68 | remove(url.split('/').back); 69 | } 70 | 71 | sw.reset(); 72 | sw.start(); 73 | foreach(url; urls) { 74 | spawnDownload(url, url.split('/').back); 75 | } 76 | runScheduler(); 77 | sw.stop(); 78 | writefln("Concurrently: %s ms", sw.peek.total!"msecs"); 79 | 80 | } -------------------------------------------------------------------------------- /tests/echo_client.d: -------------------------------------------------------------------------------- 1 | // Simple client provided to use with echo_server 2 | // each line typed is sent to the server, followed by receive to print the response 3 | module tests.echo_client; 4 | 5 | import std.conv; 6 | import std.socket; 7 | import std.stdio; 8 | 9 | int main(string[] argv) { 10 | if (argv.length != 3) { 11 | stderr.writeln("Usage: ./echo_client "); 12 | return 1; 13 | } 14 | auto host = argv[1]; 15 | auto port = to!ushort(argv[2]); 16 | auto target = new InternetAddress(host, port); 17 | Socket sock = new TcpSocket(); 18 | sock.connect(target); 19 | string line; 20 | char[256] buf=void; 21 | for (;;) { 22 | line = readln(); 23 | sock.send(line); 24 | auto res = sock.receive(buf[]); 25 | if (res == 0) return 0; 26 | else if(res < 0) { 27 | return 1; 28 | } 29 | else { 30 | writeln(buf[0..res]); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/echo_server.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "Echo server - a simple server that sends whatever it receives", 11 | "license": "BOOST", 12 | "name": "echo_server" 13 | } 14 | +/ 15 | module echo_server; 16 | 17 | import std.stdio; 18 | import std.socket; 19 | import std.conv; 20 | import std.string; 21 | import std.algorithm; 22 | import std.conv; 23 | import std.format; 24 | import std.range; 25 | 26 | import core.thread; 27 | 28 | import photon; 29 | 30 | // telnet localhost 1337 31 | void server_worker(Socket client) { 32 | ubyte[1024] buffer; 33 | scope(exit) { 34 | client.shutdown(SocketShutdown.BOTH); 35 | client.close(); 36 | } 37 | writefln("Started server_worker, client = %s", client); 38 | for(;;) { 39 | ptrdiff_t received = client.receive(buffer); 40 | if (received < 0) { 41 | perror("Error while reading from client"); 42 | return; 43 | } 44 | else if (received == 0) { //socket is closed (eof) 45 | return; 46 | } 47 | else { 48 | writefln("Server_worker received:\n<%s>", cast(char[])buffer[0.. received]); 49 | } 50 | ptrdiff_t sent; 51 | do { 52 | ptrdiff_t ret = client.send(buffer[sent .. received]); 53 | if (ret < 0) { 54 | perror("Error while writing to client"); 55 | return; 56 | } 57 | sent += ret; 58 | } while(sent < received); 59 | } 60 | } 61 | 62 | void server() { 63 | Socket server = new TcpSocket(); 64 | server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 65 | server.bind(new InternetAddress("127.0.0.1", 1337)); 66 | server.listen(1000); 67 | 68 | writefln("Started server"); 69 | void processClient(Socket client) { 70 | go(() => server_worker(client)); 71 | } 72 | while(true) { 73 | writefln("Waiting for server.accept()"); 74 | Socket client = server.accept(); 75 | writefln("New client accepted %s", client); 76 | processClient(client); 77 | } 78 | } 79 | 80 | void main() { 81 | initPhoton(); 82 | go(() => server()); 83 | runScheduler(); 84 | } 85 | -------------------------------------------------------------------------------- /tests/go_same_thread.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "Simple verification that goOnSameThread indeed puts fiber on the calling fiber's scheduler", 11 | "license": "BOOST", 12 | "name": "go_same_thread" 13 | } 14 | +/ 15 | import photon; 16 | import std.stdio; 17 | 18 | int placeholder; 19 | int placeholder2; 20 | 21 | void main(){ 22 | initPhoton(); 23 | placeholder = 4; 24 | goOnSameThread({ 25 | assert(placeholder == 4); 26 | writeln("Func version is done"); 27 | }); 28 | int k = 0; 29 | goOnSameThread({ 30 | assert(k == 1); 31 | writeln("Delegate version is done"); 32 | }); 33 | k = 1; 34 | go({ 35 | placeholder2 = 42; 36 | goOnSameThread({ 37 | assert(placeholder2 == 42); 38 | writeln("Spawning inside of go is done"); 39 | }); 40 | }); 41 | runScheduler(); 42 | } -------------------------------------------------------------------------------- /tests/issue28.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2025, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for sleeps on Windows", 11 | "license": "BOOST", 12 | "name": "issue28" 13 | } 14 | +/ 15 | import std.stdio; 16 | import std.range; 17 | import std.concurrency; 18 | import core.thread; 19 | 20 | import photon; 21 | 22 | void main() 23 | { 24 | initPhoton; 25 | 26 | go({ 27 | foreach (i; 1000.iota) 28 | { 29 | write("."); 30 | // photon.yield; // hang 31 | // Thread.sleep(1.nsecs); // broken concurrency 32 | delay(1.nsecs); // slow 33 | } 34 | }); 35 | 36 | foreach (i; 32.iota) 37 | { 38 | go({ write("#"); }); 39 | } 40 | 41 | runScheduler; 42 | } -------------------------------------------------------------------------------- /tests/ping_pong_fiber_half_duplex.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import core.sys.posix.unistd : write, _exit; 3 | import core.sys.posix.sys.types; 4 | import std.socket; 5 | import core.stdc.errno; 6 | import core.sys.posix.sys.socket; 7 | import core.sys.posix.fcntl; 8 | import core.thread; 9 | import core.sys.posix.stdlib: abort; 10 | static import core.sys.posix.unistd; 11 | import photon; 12 | import photon.support; 13 | 14 | void check(int code) { 15 | if(code < 0) 16 | abort(); 17 | } 18 | 19 | // if this writes say 100 bytes total 20 | void writer(int fd) { 21 | logf("", fd); 22 | auto s = "simple read write\n"; 23 | write(fd, s.ptr, s.length).checked; 24 | logf(""); 25 | } 26 | 27 | // it must read the exact same amount (in total) that would be 100 bytes 28 | void reader(int fd) { 29 | logf("", fd); 30 | char[100] buf; 31 | ssize_t total = 17; 32 | ssize_t bytes = 0; 33 | while(bytes < total) { 34 | ssize_t resp = core.sys.posix.unistd.read(fd, buf.ptr + bytes, total - bytes).checked; 35 | logf("read resp = %s", resp); 36 | bytes += resp; 37 | } 38 | logf(""); 39 | } 40 | 41 | void main() { 42 | int[2] socks; 43 | initPhoton(); 44 | check(socketpair(AF_UNIX, SOCK_STREAM, 0, socks)); 45 | logf("socks = %s", socks); 46 | // spawn a thread to run I/O loop 47 | // spawn thread to write stuff 48 | auto wr = new Thread(() => reader(socks[0])); 49 | wr.start(); 50 | 51 | // spawn fiber to read stuff 52 | go(() => writer(socks[1])); 53 | runScheduler(); 54 | // 55 | wr.join(); 56 | } 57 | -------------------------------------------------------------------------------- /tests/ping_pong_full_duplex.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import core.sys.posix.unistd : write, _exit; 3 | import core.sys.posix.sys.types; 4 | import std.socket; 5 | import core.stdc.errno; 6 | import core.sys.posix.sys.socket; 7 | import core.sys.posix.fcntl; 8 | import core.thread; 9 | import core.sys.posix.stdlib: abort; 10 | import photon; 11 | import photon.support; 12 | static import core.sys.posix.unistd; 13 | 14 | void check(int code) { 15 | if(code < 0) 16 | abort(); 17 | } 18 | 19 | // if this writes say 100 bytes total 20 | void writerReader(int fd1, int fd2) { 21 | logf("", fd1, fd2); 22 | auto s = "simple read write\n"; 23 | write(fd1, s.ptr, s.length).checked; 24 | 25 | logf("", fd1, fd2); 26 | 27 | char[100] buf2; 28 | ssize_t total = 17; 29 | ssize_t bytes = 0; 30 | while(bytes < total) { 31 | ssize_t resp = core.sys.posix.unistd.read(fd2, buf2.ptr + bytes, total - bytes).checked; 32 | logf("read1 resp = %s", resp); 33 | bytes += resp; 34 | } 35 | 36 | logf(""); 37 | } 38 | 39 | // it must read the exact same amount (in total) that would be 100 bytes 40 | void readerWriter(int fd1, int fd2) { 41 | logf("", fd1, fd2); 42 | char[100] buf; 43 | ssize_t total = 17; 44 | ssize_t bytes = 0; 45 | while(bytes < total) { 46 | ssize_t resp = core.sys.posix.unistd.read(fd1, buf.ptr + bytes, total - bytes).checked; 47 | logf("read2 resp = %s", resp); 48 | bytes += resp; 49 | } 50 | 51 | logf("", fd1, fd2); 52 | 53 | auto s = "simple read write\n"; 54 | char[] buf2 = s.dup; 55 | write(fd2, s.ptr, s.length).checked; 56 | logf(""); 57 | } 58 | 59 | void main() { 60 | int[2] socks1, socks2; 61 | initPhoton(); 62 | check(socketpair(AF_UNIX, SOCK_STREAM, 0, socks1)); 63 | check(socketpair(AF_UNIX, SOCK_STREAM, 0, socks2)); 64 | logf("socks1 = %s", socks1); 65 | logf("socks2 = %s", socks2); 66 | // spawn a thread to run I/O loop 67 | // spawn thread to write stuff 68 | auto wr = new Thread(() => writerReader(socks1[0], socks2[0])); 69 | wr.start(); 70 | 71 | // spawn fiber to read stuff 72 | go(() => readerWriter(socks1[1], socks2[1])); 73 | runScheduler(); 74 | // 75 | wr.join(); 76 | } 77 | -------------------------------------------------------------------------------- /tests/ping_pong_full_duplex_n.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import core.sys.posix.unistd : write, _exit; 3 | import core.sys.posix.sys.types; 4 | import core.memory; 5 | import std.socket; 6 | import core.stdc.errno; 7 | import core.stdc.string; 8 | import std.string; 9 | import std.getopt; 10 | import core.sys.posix.sys.socket; 11 | import core.sys.posix.fcntl; 12 | import core.thread; 13 | import core.time; 14 | import core.sys.posix.stdlib: abort; 15 | static import core.sys.posix.unistd; 16 | import std.conv : to; 17 | import photon; 18 | import photon.support; 19 | 20 | void check(int code) { 21 | if(code < 0) 22 | abort(); 23 | } 24 | 25 | // if this writes say 100 bytes total 26 | void writerReader(int fd, string toSend, string toRecv) { 27 | logf("", fd); 28 | auto s = "simple read write\n"; 29 | write(fd, toSend.ptr, toSend.length).checked; 30 | 31 | logf("", fd); 32 | 33 | char[100] buf2 = 0; 34 | ssize_t total = toRecv.length; 35 | ssize_t bytes = 0; 36 | while(bytes < total) { 37 | ssize_t resp = core.sys.posix.unistd.read(fd, buf2.ptr + bytes, total - bytes).checked; 38 | logf("read1 resp = %s", resp); 39 | bytes += resp; 40 | } 41 | 42 | assert(cmp(fromStringz(buf2.ptr), toRecv) == 0); 43 | 44 | logf("", fd); 45 | } 46 | 47 | // it must read the exact same amount (in total) that would be 100 bytes 48 | void readerWriter(int fd, string toSend, string toRecv) { 49 | logf("", fd); 50 | char[100] buf = 0; 51 | ssize_t total = toRecv.length; 52 | ssize_t bytes = 0; 53 | while(bytes < total) { 54 | ssize_t resp = core.sys.posix.unistd.read(fd, buf.ptr + bytes, total - bytes).checked; 55 | logf("read2 resp = %s", resp); 56 | bytes += resp; 57 | } 58 | 59 | assert(cmp(fromStringz(buf.ptr), toRecv) == 0); 60 | logf("", fd); 61 | 62 | auto s = "simple read write\n"; 63 | char[] buf2 = s.dup; 64 | write(fd, toSend.ptr, toSend.length).checked; 65 | logf("", fd); 66 | } 67 | 68 | Thread threadPingPong(int fd, string toSend, string toRecv) { 69 | return new Thread(() => writerReader(fd, toSend, toRecv)); 70 | } 71 | 72 | void fiberPongPing(int fd, string toSend, string toRecv) { 73 | go(() => readerWriter(fd, toSend, toRecv)); 74 | } 75 | 76 | void main(string[] args) { 77 | int NR; 78 | getopt(args, 79 | "count", &NR); 80 | int[][] socks = new int[][](NR, 2); 81 | string s1 = "first read write\n"; 82 | string s2 = "second read write\n"; 83 | initPhoton(); 84 | for(int i = 0; i < NR; i++) { 85 | check(socketpair(AF_UNIX, SOCK_STREAM, 0, socks[i].ptr[0..2])); 86 | //logf("socks[i] = %s", i, socks[i]); 87 | } 88 | 89 | logf("socks = %s", socks); 90 | // spawn a thread to run I/O loop 91 | // spawn thread to write stuff 92 | Thread[] wrs; 93 | for(int i = 0; i < NR; i ++) { 94 | auto a = socks[i][0]; 95 | auto wr = threadPingPong(a, s2, s1); 96 | logf("wr = %s", cast(void*)wr); 97 | wr.start(); 98 | wrs ~= wr; 99 | } 100 | 101 | // spawn fiber to read stuff 102 | for(int i = 0; i < NR; i++) { 103 | logf("socks[i][1] = %d", socks[i][1]); 104 | auto a = socks[i][1]; 105 | fiberPongPing(a, s1, s2); 106 | } 107 | runScheduler(); 108 | // 109 | foreach(w; wrs) { 110 | w.join(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/ping_pong_thread_half_duplex.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import core.sys.posix.unistd : write, _exit; 3 | import core.sys.posix.sys.types; 4 | import std.socket; 5 | import core.stdc.errno; 6 | import core.sys.posix.sys.socket; 7 | import core.sys.posix.fcntl; 8 | import core.thread; 9 | import core.sys.posix.stdlib: abort; 10 | import photon; 11 | 12 | void check(int code) { 13 | if(code < 0) 14 | abort(); 15 | } 16 | 17 | // if this writes say 100 bytes total 18 | void writer(int fd) { 19 | logf("", fd); 20 | auto s = "simple read write\n"; 21 | write(fd, s.ptr, s.length).checked; 22 | logf(""); 23 | } 24 | 25 | // it must read the exact same amount (in total) that would be 100 bytes 26 | void reader(int fd) { 27 | logf("", fd); 28 | char[100] buf; 29 | ssize_t total = 17; 30 | ssize_t bytes = 0; 31 | while(bytes < total) { 32 | ssize_t resp = core.sys.posix.unistd.read(fd, buf.ptr + bytes, total - bytes).checked; 33 | logf("read resp = %s", resp); 34 | bytes += resp; 35 | } 36 | logf(""); 37 | } 38 | 39 | void main() { 40 | int[2] socks; 41 | initPhoton(); 42 | check(socketpair(AF_UNIX, SOCK_STREAM, 0, socks)); 43 | logf("socks = %s", socks); 44 | // spawn a thread to run I/O loop 45 | // spawn thread to write stuff 46 | auto wr = new Thread(() => writer(socks[0])); 47 | wr.start(); 48 | 49 | // spawn fiber to read stuff 50 | go(() => reader(socks[1])); 51 | runScheduler(); 52 | // 53 | wr.join(); 54 | } 55 | -------------------------------------------------------------------------------- /tests/poll_timer.d: -------------------------------------------------------------------------------- 1 | module poll_test; 2 | import std.stdio; 3 | import core.sys.posix.sys.socket; 4 | import core.sys.posix.unistd : read, write, _exit; 5 | import core.sys.posix.sys.types; 6 | import core.stdc.errno; 7 | import core.sys.posix.sys.socket; 8 | import core.thread; 9 | import core.sys.posix.stdlib: abort; 10 | import core.sys.posix.poll; 11 | import core.sys.posix.fcntl; 12 | import photon; 13 | import photon.linux.support; 14 | import photon.linux.syscalls; 15 | 16 | shared int idx = 0; 17 | 18 | void check(int code) { 19 | if(code < 0) 20 | abort(); 21 | } 22 | 23 | void writer(int fd) { 24 | writefln("", fd); 25 | auto s = "wait and write\n"; 26 | for (int i = 0; i < 30; ++i) { 27 | logf("writer idx = %d", i); 28 | idx = i; 29 | ssize_t rc = write(fd, s.ptr, s.length).checked("write fail"); 30 | logf("write rc = %d", rc); 31 | Thread.sleep(1.seconds); 32 | } 33 | logf(""); 34 | } 35 | 36 | void reader(int fd) { 37 | logf("", fd); 38 | char[100] buf; 39 | ssize_t total = 15; 40 | int timeout = 2000; 41 | bool finished = false; 42 | pollfd fds; 43 | fds.fd = fd; 44 | fds.events = POLLIN; 45 | fds.revents = 0; 46 | do { 47 | int rc = poll(&fds, 1, timeout).checked("poll"); 48 | logf("rc = %d", rc); 49 | if (rc == 0) continue; 50 | if (idx == 29) finished = true; 51 | //logf("preparing to read"); 52 | ssize_t resp = read(fds.fd, buf.ptr, total).checked("read fail"); 53 | logf("read resp = %s", resp); 54 | } while(!finished); 55 | logf(" "); 56 | } 57 | 58 | void main(){ 59 | int[2] socks; 60 | 61 | initPhoton(); 62 | socketpair(AF_UNIX, SOCK_STREAM, 0, socks).checked("here"); 63 | logf("socks = %s", socks); 64 | auto wr = new Thread(() => writer(socks[0])); 65 | wr.start(); 66 | 67 | spawn(() => reader(socks[1])); 68 | runScheduler(); 69 | 70 | wr.join(); 71 | } 72 | -------------------------------------------------------------------------------- /tests/sleep.d: -------------------------------------------------------------------------------- 1 | /+ dub.json: 2 | { 3 | "authors": [ 4 | "Dmitry Olshansky" 5 | ], 6 | "copyright": "Copyright © 2024, Dmitry Olshansky", 7 | "dependencies": { 8 | "photon": { "path": ".." } 9 | }, 10 | "description": "A test for thread sleep and fiber sleep API", 11 | "license": "BOOST", 12 | "name": "sleep" 13 | } 14 | +/ 15 | module tests.await; 16 | 17 | import std.stdio, std.datetime; 18 | import photon; 19 | 20 | void main(){ 21 | initPhoton(); 22 | auto e = event(false); 23 | auto s = semaphore(0); 24 | delay(1.seconds); 25 | go({ 26 | delay(1.seconds); 27 | }); 28 | go({ 29 | delay(1.seconds); 30 | }); 31 | runScheduler(); 32 | } -------------------------------------------------------------------------------- /tests/zmq.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.json: 3 | { 4 | "authors": [ 5 | "Dmitry Olshansky" 6 | ], 7 | "copyright": "Copyright © 2024, Dmitry Olshansky", 8 | "dependencies": { 9 | "photon": { "path": ".." }, 10 | "zmqd": "1.3.0" 11 | }, 12 | "description": "A test for ZeroMQ interop", 13 | "license": "BOOST", 14 | "name": "zmq" 15 | } 16 | +/ 17 | import std.stdio; 18 | import zmqd; 19 | import photon; 20 | import core.thread; 21 | 22 | void main() 23 | { 24 | initPhoton(); 25 | shared bool terminated = false; 26 | go({ 27 | // Socket to talk to clients 28 | auto responder = Socket(SocketType.rep); 29 | writeln("Got socket"); 30 | responder.bind("tcp://*:5555"); 31 | writeln("Binded socket"); 32 | 33 | while (!terminated) { 34 | ubyte[10] buffer; 35 | responder.receive(buffer); 36 | writefln("Received: \"%s\"", cast(string)buffer); 37 | Thread.sleep(1.seconds); 38 | responder.send("World"); 39 | } 40 | }); 41 | go({ 42 | writeln ("Connecting to hello world server..."); 43 | auto requester = Socket(SocketType.req); 44 | requester.connect("tcp://localhost:5555"); 45 | 46 | foreach (int requestNbr; 0..10) 47 | { 48 | ubyte[10] buffer; 49 | writefln("Sending Hello #%s", requestNbr); 50 | if (requestNbr == 9) terminated = true; 51 | requester.send("Hello"); 52 | requester.receive(buffer); 53 | writefln("Received: %s #%s", cast(string)buffer, requestNbr); 54 | } 55 | }); 56 | runScheduler(); 57 | } --------------------------------------------------------------------------------