├── .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 ├── pool.d ├── run-all.sh ├── select.d ├── semaphore.d └── sleepy.d ├── img ├── DApp.png └── DApp.svg ├── src └── photon │ ├── 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 │ ├── support.d │ └── windows │ ├── core.d │ └── support.d └── tests ├── await.d ├── curl_download.d ├── echo_client.d ├── echo_server.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 /.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.9.0" 38 | } 39 | } 40 | +/ 41 | import photon; 42 | 43 | void main() { 44 | startloop(); // start the event loop thread and 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 | runFibers(); // 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 | startloop(); 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 | runFibers(); 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.4.5" 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 | Socket server = new TcpSocket(); 38 | server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 39 | server.bind(new InternetAddress("0.0.0.0", 8080)); 40 | server.listen(1000); 41 | 42 | debug writeln("Started server"); 43 | 44 | void processClient(Socket client) { 45 | go(() => server_worker(client)); 46 | } 47 | 48 | while(true) { 49 | try { 50 | debug writeln("Waiting for server.accept()"); 51 | Socket client = server.accept(); 52 | debug writeln("New client accepted"); 53 | processClient(client); 54 | } 55 | catch(Exception e) { 56 | writefln("Failure to accept %s", e); 57 | } 58 | } 59 | } 60 | 61 | void main() { 62 | startloop(); 63 | go(() => server()); 64 | runFibers(); 65 | } 66 | -------------------------------------------------------------------------------- /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/782a31c6649de007e07b99fec1f4c8fdd21935f2/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/782a31c6649de007e07b99fec1f4c8fdd21935f2/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 | 19 12 | 19 13 | 14 | 15 | 16 | 17 | io.undertow 18 | undertow-core 19 | 1.4.22.Final 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-shade-plugin 27 | 3.1.0 28 | 29 | 30 | 32 | 33 | me.olshansky.HelloUndertow 34 | 1.0 35 | 36 | 37 | 38 | 39 | 40 | 41 | package 42 | 43 | shade 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /bench/static_http/hello_undertow/src/main/java/me/olshansky/HelloUndertow.java: -------------------------------------------------------------------------------- 1 | package me.olshansky; 2 | 3 | import io.undertow.Undertow; 4 | import io.undertow.server.HttpHandler; 5 | import io.undertow.server.HttpServerExchange; 6 | import io.undertow.util.Headers; 7 | 8 | public class HelloUndertow { 9 | public static void main(final String[] args) { 10 | Undertow server = Undertow.builder() 11 | .addHttpListener(8080, "0.0.0.0") 12 | .setHandler(new HttpHandler() { 13 | 14 | public void handleRequest(final HttpServerExchange exchange) { 15 | exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); 16 | exchange.getResponseSender().send("Hello, world!"); 17 | } 18 | }).build(); 19 | server.start(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | startloop(); 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 | runFibers(); 26 | } 27 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | photon 7 | 507 | 508 | 509 |
510 |
511 |

photon

512 |
513 |
514 |

515 | Photon is a lightweight transparent fiber scheduler. It's inspired by Golang's green thread model and 516 | the spawn function is called go doing the same job that Golang's keyword does. 517 | The framework API surface is kept to a minimum, many programs can be written using only 518 | three primitives: startloop to initialize Photon, runFibers to start fiber scheduler and 519 | go to create tasks, including the initial tasks. 520 | 521 |

522 |
523 |
524 |

Discussion

525 |

526 | Example, showcasing channels and std.range interop: 527 | 528 |

529 |
530 |
531 |
    532 |
  1. /+ dub.json: 533 | { 534 | "authors": [ 535 | "Dmitry Olshansky" 536 | ], 537 | "copyright": "Copyright © 2024, Dmitry Olshansky", 538 | "dependencies": { 539 | "photon": { "path": ".." } 540 | }, 541 | "description": "A test for channels API", 542 | "license": "BOOST", 543 | "name": "channels" 544 | } 545 | / 546 | module examples.channels; 547 | 548 | import std.algorithm, std.datetime, std.range, std.stdio; 549 | import photon; 550 | 551 | void first(shared Channel!string work, shared Channel!int completion) { 552 | delay(2.msecs); 553 | work.put("first #1"); 554 | delay(2.msecs); 555 | work.put("first #2"); 556 | delay(2.msecs); 557 | work.put("first #3"); 558 | completion.put(1); 559 | } 560 | 561 | void second(shared Channel!string work, shared Channel!int completion) { 562 | delay(3.msecs); 563 | work.put("second #1"); 564 | delay(3.msecs); 565 | work.put("second #2"); 566 | completion.put(2); 567 | } 568 | 569 | void main() { 570 | startloop(); 571 | auto jobQueue = channel!string(2); 572 | auto finishQueue = channel!int(1); 573 | go({ 574 | first(jobQueue, finishQueue); 575 | }); 576 | go({ // producer # 2 577 | second(jobQueue, finishQueue); 578 | }); 579 | go({ // consumer 580 | foreach (item; jobQueue) { 581 | delay(1.seconds); 582 | writeln(item); 583 | } 584 | }); 585 | go({ // closer 586 | auto completions = finishQueue.take(2).array; 587 | assert(completions.length == 2); 588 | jobQueue.close(); // all producers are done 589 | }); 590 | runFibers(); 591 | } 592 |
  2. 593 |
594 |
595 |
596 |
597 | 598 |

599 |
600 | 601 |
602 |
603 |
604 |
    605 |
  • 606 |
    607 |
    608 | startloop 609 |
    610 |
    611 |
    612 |
    613 |

    Declaration

    614 |
    615 |

    616 | 617 | void startloop(); 618 | 619 | 620 |

    621 |
    622 |
    623 |
    624 |
    625 |
    626 |
    627 |
    628 |

    629 | Initialize event loop and internal data structures for Photon scheduler. 630 |

    631 |
    632 | 633 |
    634 | 635 |
    636 | 637 |
  • 638 |
    639 |
    640 | go 641 |
    642 |
    643 |
    644 |
    645 |

    Declaration

    646 |
    647 |

    648 | 649 | void go(void delegate() func); 650 |
    651 | void go(void function() func); 652 | 653 |
    654 |

    655 |
    656 |
    657 |
    658 |
    659 |
    660 |
    661 |
    662 |

    663 | Setup a fiber task to run on the Photon scheduler. 664 |

    665 |
    666 | 667 |
    668 | 669 |
    670 | 671 |
  • 672 |
    673 |
    674 | runFibers 675 |
    676 |
    677 |
    678 |
    679 |

    Declaration

    680 |
    681 |

    682 | 683 | void runFibers(); 684 | 685 | 686 |

    687 |
    688 |
    689 |
    690 |
    691 |
    692 |
    693 |
    694 |

    695 | Start sheduler and run fibers until all are terminated. 696 |

    697 |
    698 | 699 |
    700 | 701 |
    702 | 703 |
  • 704 |
    705 |
    706 | Channel 707 |
    708 |
    709 |
    710 |
    711 |

    Declaration

    712 |
    713 |

    714 | 715 | struct Channel(T); 716 | 717 | 718 |

    719 |
    720 |
    721 |
    722 |
    723 |
    724 |
    725 |
    726 |

    727 | A ref-counted channel that is safe to share between multiple fibers. 728 | In essence it's a multiple producer single consumer queue, that implements 729 | OutputRange and InputRange concepts. 730 |

    731 |
    732 | 733 |
    734 |
      735 |
    • 736 |
      737 |
      738 | put 739 |
      740 |
      741 |
      742 |
      743 |

      Declaration

      744 |
      745 |

      746 | 747 | void put(T value); 748 | 749 | 750 |

      751 |
      752 |
      753 |
      754 |
      755 |
      756 |
      757 |
      758 |

      759 | OutputRange contract - puts a new item into the channel. 760 |

      761 |
      762 | 763 |
      764 | 765 |
      766 | 767 |
    • 768 |
      769 |
      770 | empty 771 |
      772 |
      773 |
      774 |
      775 |

      Declaration

      776 |
      777 |

      778 | 779 | bool empty(); 780 | 781 | 782 |

      783 |
      784 |
      785 |
      786 |
      787 |
      788 |
      789 |
      790 |

      791 | Part of InputRange contract - checks if there is an item in the queue. 792 | Returns true if channel is closed and its buffer is exhausted. 793 |

      794 |
      795 | 796 |
      797 | 798 |
      799 | 800 |
    • 801 |
      802 |
      803 | front 804 |
      805 |
      806 |
      807 |
      808 |

      Declaration

      809 |
      810 |

      811 | 812 | ref T front(); 813 | 814 | 815 |

      816 |
      817 |
      818 |
      819 |
      820 |
      821 |
      822 |
      823 |

      824 | Part of InputRange contract - returns an item available in the channel. 825 |

      826 |
      827 | 828 |
      829 | 830 |
      831 | 832 |
    • 833 |
      834 |
      835 | popFront 836 |
      837 |
      838 |
      839 |
      840 |

      Declaration

      841 |
      842 |

      843 | 844 | void popFront(); 845 | 846 | 847 |

      848 |
      849 |
      850 |
      851 |
      852 |
      853 |
      854 |
      855 |

      856 | Part of InputRange contract - advances range forward. 857 |

      858 |
      859 | 860 |
      861 | 862 |
      863 | 864 |
    • 865 |
    866 | 867 |
    868 | 869 |
  • 870 |
    871 |
    872 | channel 873 |
    874 |
    875 |
    876 |
    877 |

    Declaration

    878 |
    879 |

    880 | 881 | auto channel(T)(size_t capacity = 1); 882 | 883 | 884 |

    885 |
    886 |
    887 |
    888 |
    889 |
    890 |
    891 |
    892 |

    893 | Create a new shared Channel with given capacity. 894 |

    895 |
    896 | 897 |
    898 | 899 |
    900 | 901 |
  • 902 |
    903 |
    904 | select 905 |
    906 |
    907 |
    908 |
    909 |

    Declaration

    910 |
    911 |

    912 | 913 | void select(Args...)(auto ref Args args) if (allSatisfy!(isChannel, Even!Args) && allSatisfy!(isHandler, Odd!Args)); 914 | 915 | 916 |

    917 |
    918 |
    919 |
    920 |
    921 |
    922 |
    923 |
    924 |

    925 | Multiplex between multiple channels, executes a lambda attached to the first 926 | channel that becomes ready to read. 927 |

    928 |
    929 | 930 |
    931 | 932 |
    933 | 934 |
  • 935 |
    936 |
    937 | isChannel 938 |
    939 |
    940 |
    941 |
    942 |

    Declaration

    943 |
    944 |

    945 | 946 | enum auto isChannel(T); 947 | 948 | 949 |

    950 |
    951 |
    952 |
    953 |
    954 |
    955 |
    956 |
    957 |

    958 | Trait for testing if a type is Channel 959 |

    960 |
    961 | 962 |
    963 | 964 |
    965 | 966 |
  • 967 |
968 |
969 |
970 |
971 |
972 |
973 | 974 | 975 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Dmitry Olshansky" 4 | ], 5 | "copyright": "Copyright © 2023, Dmitry Olshansky", 6 | "dependencies": { 7 | "sharded-map": "~>2.7.0" 8 | }, 9 | "description": "Photon - a transparent fiber scheduler", 10 | "license": "BOOST", 11 | "name": "photon", 12 | "targetType": "library" 13 | } -------------------------------------------------------------------------------- /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 | startloop(); 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 | runFibers(); 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 | ev1 = event(false); 38 | ev2 = event(false); 39 | startloop(); 40 | go(&firstJob); 41 | go(&secondJob); 42 | runFibers(); 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 | startloop(); 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 | runFibers(); 50 | } 51 | -------------------------------------------------------------------------------- /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 | startloop(); 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); 30 | auto second = counts.acquire(); 31 | writeln("acquire: ", second); 32 | go({ 33 | delay(1.seconds); 34 | counts.release(first); 35 | writeln("closed:", closed); 36 | }); 37 | auto third = counts.acquire(); 38 | writeln("acquire:", third); 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); 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); 49 | counts.shutdown(); 50 | writeln("after shutdown"); 51 | }); 52 | runFibers(); 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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 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); 26 | writeln(msg); 27 | } 28 | 29 | void main() { 30 | startloop(); 31 | writeln("Starting a bunch of fibers and threads, each waiting 1 second"); 32 | foreach (_; 0..1000) { 33 | go({ 34 | task("fiber sleep is over", 1.seconds); 35 | }); 36 | } 37 | foreach (_; 0..100) { 38 | new Thread({ 39 | task("thread sleep is over", 1.seconds); 40 | }).start(); 41 | } 42 | runFibers(); 43 | } 44 | -------------------------------------------------------------------------------- /img/DApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryOlshansky/photon/782a31c6649de007e07b99fec1f4c8fdd21935f2/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/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 | } -------------------------------------------------------------------------------- /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 | void push(T item) { 21 | item.next = null; 22 | lock.lock(); 23 | if (tail is null) { 24 | head = tail = cast(shared)item; 25 | bool shouldTrigger = exhausted; 26 | exhausted = false; 27 | lock.unlock(); 28 | if (shouldTrigger) event.trigger(); 29 | } 30 | else { 31 | tail.next = cast(shared)item; 32 | tail = cast(shared)item; 33 | lock.unlock(); 34 | } 35 | } 36 | 37 | bool tryPop(ref T item) nothrow { 38 | lock.lock(); 39 | if (!head) { 40 | exhausted = true; 41 | lock.unlock(); 42 | return false; 43 | } 44 | else { 45 | item = head.unshared; 46 | head = head.next; 47 | if (head is null) tail = null; 48 | lock.unlock(); 49 | return true; 50 | } 51 | } 52 | 53 | // drain the whole queue in one go 54 | T drain() nothrow { 55 | lock.lock(); 56 | if (head is null) { 57 | exhausted = true; 58 | lock.unlock(); 59 | return null; 60 | } 61 | else { 62 | auto r = head.unshared; 63 | head = tail = null; 64 | lock.unlock(); 65 | return r; 66 | } 67 | } 68 | } 69 | 70 | unittest { 71 | static class Box(T) { 72 | Box next; 73 | T item; 74 | this(T k) { 75 | item = k; 76 | } 77 | } 78 | 79 | static struct EmptyEvent { 80 | shared nothrow void trigger(){} 81 | } 82 | shared q = IntrusiveQueue!(Box!int, EmptyEvent)(); 83 | q.push(new Box!int(1)); 84 | q.push(new Box!int(2)); 85 | q.push(new Box!int(3)); 86 | Box!int ret; 87 | q.tryPop(ret); 88 | assert(ret.item == 1); 89 | q.tryPop(ret); 90 | assert(ret.item == 2); 91 | 92 | q.push(new Box!int(4)); 93 | q.tryPop(ret); 94 | assert(ret.item == 3); 95 | q.tryPop(ret); 96 | assert(ret.item == 4); 97 | q.push(new Box!int(5)); 98 | 99 | q.tryPop(ret); 100 | assert(ret.item == 5); 101 | assert(q.tryPop(ret) == false); 102 | } 103 | -------------------------------------------------------------------------------- /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 | 8 | import photon.exceptions; 9 | 10 | 11 | struct RingQueue(T, Event) 12 | { 13 | T* store; 14 | size_t length; 15 | size_t fetch, insert, size; 16 | Event cts, rtr; // clear to send, ready to recieve 17 | bool closed; 18 | shared size_t refCount; 19 | AlignedSpinLock lock; 20 | 21 | this(size_t capacity, Event cts, Event rtr) 22 | { 23 | store = cast(T*)malloc(T.sizeof * capacity); 24 | length = capacity; 25 | size = 0; 26 | fetch = insert = 0; 27 | this.cts = move(cts); 28 | this.rtr = move(rtr); 29 | closed = false; 30 | refCount = 1; 31 | lock = AlignedSpinLock(SpinLock.Contention.brief); 32 | } 33 | 34 | void push(T ctx) 35 | { 36 | lock.lock(); 37 | while (size == length) { 38 | lock.unlock(); 39 | cts.waitAndReset(); 40 | lock.lock(); 41 | } 42 | if (closed) { 43 | lock.unlock(); 44 | throw new ChannelClosed(); 45 | } 46 | bool notify = false; 47 | move(ctx, store[insert++]); 48 | if (insert == length) insert = 0; 49 | if (size == 0) notify = true; 50 | size += 1; 51 | lock.unlock(); 52 | if (notify) rtr.trigger(); 53 | } 54 | 55 | bool tryPop(out T output) 56 | { 57 | lock.lock(); 58 | while (size == 0 && !closed) { 59 | lock.unlock(); 60 | rtr.waitAndReset(); 61 | lock.lock(); 62 | } 63 | if (size == 0 && closed) { 64 | lock.unlock(); 65 | return false; 66 | } 67 | move(store[fetch++], output); 68 | if (fetch == length) fetch = 0; 69 | size -= 1; 70 | lock.unlock(); 71 | cts.trigger(); 72 | return true; 73 | } 74 | 75 | bool readyToRead() { 76 | lock.lock(); 77 | scope(exit) lock.unlock(); 78 | return size > 0; 79 | } 80 | 81 | bool empty() { 82 | lock.lock(); 83 | scope(exit) lock.unlock(); 84 | if (closed && size == 0) return true; 85 | return false; 86 | } 87 | 88 | void close() { 89 | lock.lock(); 90 | closed = true; 91 | lock.unlock(); 92 | cts.trigger(); 93 | rtr.trigger(); 94 | } 95 | 96 | void retain() { 97 | auto cnt = atomicFetchAdd(refCount, 1); 98 | assert(cnt != 0); 99 | } 100 | 101 | // true if time to release 102 | bool release() { 103 | auto cnt = atomicFetchSub(refCount, 1); 104 | if (cnt == 1) return true; 105 | return false; 106 | } 107 | } 108 | 109 | auto allocRingQueue(T, Event)(size_t capacity, Event cts, Event rtr){ 110 | alias Q = RingQueue!(T,Event); 111 | auto ptr = cast(Q*)malloc(Q.sizeof); 112 | emplace(ptr, capacity, move(cts), move(rtr)); 113 | return ptr; 114 | } 115 | 116 | void disposeRingQueue(T, Event)(RingQueue!(T, Event)* q) { 117 | free(q.store); 118 | free(q); 119 | } 120 | 121 | unittest { 122 | import photon; 123 | import std.exception; 124 | auto cts = Event(false); 125 | auto rtr = Event(false); 126 | auto q = allocRingQueue!(int, Event)(2, move(cts), move(rtr)); 127 | q.push(1); 128 | q.push(2); 129 | int result; 130 | assert(q.tryPop(result) && result == 1); 131 | assert(q.tryPop(result) && result == 2); 132 | q.retain(); 133 | assert(!q.release()); 134 | assert(q.release()); 135 | q.close(); 136 | assert(!q.tryPop(result)); 137 | assertThrown!ChannelClosed(q.push(3)); 138 | disposeRingQueue(q); 139 | } 140 | 141 | unittest { 142 | import photon; 143 | auto cts = Event(false); 144 | auto rtr = Event(false); 145 | auto q = allocRingQueue!(string, Event)(1, move(cts), move(rtr)); 146 | assert(!q.empty); 147 | q.push("hello"); 148 | string result; 149 | assert(q.tryPop(result) && result == "hello"); 150 | assert(!q.empty); 151 | q.push("world"); 152 | q.close(); 153 | assert(q.tryPop(result) && result == "world"); 154 | assert(!q.tryPop(result)); 155 | assert(q.empty); 156 | } -------------------------------------------------------------------------------- /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/core.d: -------------------------------------------------------------------------------- 1 | module photon.freebsd.core; 2 | version(FreeBSD): 3 | private: 4 | 5 | import std.stdio; 6 | import std.string; 7 | import std.format; 8 | import std.exception; 9 | import std.conv; 10 | import std.array; 11 | import core.thread; 12 | import core.internal.spinlock; 13 | import core.sys.posix.sys.types; 14 | import core.sys.posix.sys.socket; 15 | import core.sys.posix.poll; 16 | import core.sys.posix.netinet.in_; 17 | import core.sys.freebsd.unistd; 18 | import core.sync.mutex; 19 | import core.stdc.errno; 20 | import core.atomic; 21 | import core.sys.posix.stdlib: abort; 22 | import core.sys.posix.fcntl; 23 | import core.memory; 24 | import core.sys.posix.sys.mman; 25 | import core.sys.posix.pthread; 26 | import core.sys.linux.sys.signalfd; 27 | 28 | import photon.freebsd.support; 29 | import photon.freebsd.syscalls; 30 | import photon.ds.common; 31 | import photon.ds.intrusive_queue; 32 | 33 | // T becomes thread-local b/c it's stolen from shared resource 34 | auto steal(T)(ref shared T arg) 35 | { 36 | for (;;) { 37 | auto v = atomicLoad(arg); 38 | if(cas(&arg, v, cast(shared(T))null)) return v; 39 | } 40 | } 41 | 42 | 43 | shared struct RawEvent { 44 | nothrow: 45 | this(int init) { 46 | fd = eventfd(init, 0); 47 | } 48 | 49 | void waitAndReset() { 50 | byte[8] bytes = void; 51 | ssize_t r; 52 | do { 53 | r = raw_read(fd, 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 | int fd; 73 | } 74 | 75 | struct Timer { 76 | nothrow: 77 | private timer_t timer; 78 | 79 | static void ms2ts(timespec *ts, ulong ms) 80 | { 81 | ts.tv_sec = ms / 1000; 82 | ts.tv_nsec = (ms % 1000) * 1000000; 83 | } 84 | 85 | void arm(int timeout) { 86 | timespec ts_timeout; 87 | ms2ts(&ts_timeout, timeout); //convert miliseconds to timespec 88 | itimerspec its; 89 | its.it_value = ts_timeout; 90 | its.it_interval.tv_sec = 0; 91 | its.it_interval.tv_nsec = 0; 92 | timer_settime(timer, 0, &its, null); 93 | } 94 | 95 | void disarm() { 96 | itimerspec its; // zeros 97 | timer_settime(timer, 0, &its, null); 98 | } 99 | 100 | void dispose() { 101 | timer_delete(timer).checked; 102 | } 103 | } 104 | 105 | Timer timer() { 106 | int timerfd = timer_create(CLOCK_MONOTONIC, null, null).checked; 107 | interceptFd!(Fcntl.noop)(timerfd); 108 | return Timer(timerfd); 109 | } 110 | 111 | enum EventState { ready, fibers }; 112 | 113 | shared struct Event { 114 | SpinLock lock = SpinLock(SpinLock.Contention.brief); 115 | EventState state = EventState.ready; 116 | FiberExt awaiting; 117 | 118 | void reset() { 119 | lock.lock(); 120 | auto f = awaiting.unshared; 121 | awaiting = null; 122 | state = EventState.ready; 123 | lock.unlock(); 124 | while(f !is null) { 125 | auto next = f.next; 126 | f.schedule(); 127 | f = next; 128 | } 129 | } 130 | 131 | bool ready(){ 132 | lock.lock(); 133 | scope(exit) lock.unlock(); 134 | return state == EventState.ready; 135 | } 136 | 137 | void await(){ 138 | lock.lock(); 139 | scope(exit) lock.unlock(); 140 | if (state == EventState.ready) return; 141 | if (currentFiber !is null) { 142 | currentFiber.next = awaiting.unshared; 143 | awaiting = cast(shared)currentFiber; 144 | state = EventState.fibers; 145 | } 146 | else abort(); //TODO: threads 147 | } 148 | } 149 | 150 | struct AwaitingFiber { 151 | shared FiberExt fiber; 152 | AwaitingFiber* next; 153 | 154 | void scheduleAll(int wakeFd) nothrow 155 | { 156 | auto w = &this; 157 | FiberExt head; 158 | // first process all AwaitingFibers since they are on stack 159 | do { 160 | auto fiber = steal(w.fiber); 161 | if (fiber) { 162 | fiber.unshared.next = head; 163 | head = fiber.unshared; 164 | } 165 | w = w.next; 166 | } while(w); 167 | while(head) { 168 | logf("Waking with FD=%d", wakeFd); 169 | head.wakeFd = wakeFd; 170 | head.schedule(); 171 | head = head.next; 172 | } 173 | } 174 | } 175 | 176 | class FiberExt : Fiber { 177 | FiberExt next; 178 | uint numScheduler; 179 | int wakeFd; // recieves fd that woken us up 180 | 181 | enum PAGESIZE = 4096; 182 | 183 | this(void function() fn, uint numSched) nothrow { 184 | super(fn); 185 | numScheduler = numSched; 186 | } 187 | 188 | this(void delegate() dg, uint numSched) nothrow { 189 | super(dg); 190 | numScheduler = numSched; 191 | } 192 | 193 | void schedule() nothrow 194 | { 195 | scheds[numScheduler].queue.push(this); 196 | } 197 | } 198 | 199 | FiberExt currentFiber; 200 | shared RawEvent termination; // termination event, triggered once last fiber exits 201 | shared pthread_t eventLoop; // event loop, runs outside of D runtime 202 | shared int alive; // count of non-terminated Fibers scheduled 203 | 204 | struct SchedulerBlock { 205 | shared IntrusiveQueue!(FiberExt, RawEvent) queue; 206 | shared uint assigned; 207 | size_t[2] padding; 208 | } 209 | static assert(SchedulerBlock.sizeof == 64); 210 | 211 | package(photon) shared SchedulerBlock[] scheds; 212 | 213 | enum int MAX_EVENTS = 500; 214 | enum int SIGNAL = 42; 215 | 216 | struct kevent { 217 | size_t ident; /* identifier for this event */ 218 | short filter; /* filter for event */ 219 | ushort flags; /* action flags for kqueue */ 220 | uint fflags; /* filter flag value */ 221 | long data; /* filter data value */ 222 | void* udata; /* opaque user data identifier */ 223 | ulong[4] ext; /* extensions */ 224 | }; 225 | 226 | package(photon) void schedulerEntry(size_t n) 227 | { 228 | int tid = gettid(); 229 | cpu_set_t mask; 230 | CPU_SET(n, &mask); 231 | sched_setaffinity(tid, mask.sizeof, &mask).checked("sched_setaffinity"); 232 | shared SchedulerBlock* sched = scheds.ptr + n; 233 | while (alive > 0) { 234 | sched.queue.event.waitAndReset(); 235 | for(;;) { 236 | FiberExt f = sched.queue.drain(); 237 | if (f is null) break; // drained an empty queue, time to sleep 238 | do { 239 | auto next = f.next; //save next, it will be reused on scheduling 240 | currentFiber = f; 241 | logf("Fiber %x started", cast(void*)f); 242 | try { 243 | f.call(); 244 | } 245 | catch (Exception e) { 246 | stderr.writeln(e); 247 | atomicOp!"-="(alive, 1); 248 | } 249 | if (f.state == FiberExt.State.TERM) { 250 | logf("Fiber %s terminated", cast(void*)f); 251 | atomicOp!"-="(alive, 1); 252 | } 253 | f = next; 254 | } while(f !is null); 255 | } 256 | } 257 | termination.trigger(); 258 | } 259 | 260 | public void go(void delegate() func) { 261 | import std.random; 262 | uint choice; 263 | if (scheds.length == 1) choice = 0; 264 | else { 265 | uint a = uniform!"[)"(0, cast(uint)scheds.length); 266 | uint b = uniform!"[)"(0, cast(uint)scheds.length-1); 267 | if (a == b) b = cast(uint)scheds.length-1; 268 | uint loadA = scheds[a].assigned; 269 | uint loadB = scheds[b].assigned; 270 | if (loadA < loadB) choice = a; 271 | else choice = b; 272 | } 273 | atomicOp!"+="(scheds[choice].assigned, 1); 274 | atomicOp!"+="(alive, 1); 275 | auto f = new FiberExt(func, choice); 276 | logf("Assigned %x -> %d scheduler", cast(void*)f, choice); 277 | f.schedule(); 278 | } 279 | 280 | shared Descriptor[] descriptors; 281 | shared int event_loop_fd; 282 | shared int signal_loop_fd; 283 | 284 | enum ReaderState: uint { 285 | EMPTY = 0, 286 | UNCERTAIN = 1, 287 | READING = 2, 288 | READY = 3 289 | } 290 | 291 | enum WriterState: uint { 292 | READY = 0, 293 | UNCERTAIN = 1, 294 | WRITING = 2, 295 | FULL = 3 296 | } 297 | 298 | enum DescriptorState: uint { 299 | NOT_INITED, 300 | INITIALIZING, 301 | NONBLOCKING, 302 | THREADPOOL 303 | } 304 | 305 | // list of awaiting fibers 306 | shared struct Descriptor { 307 | ReaderState _readerState; 308 | AwaitingFiber* _readerWaits; 309 | WriterState _writerState; 310 | AwaitingFiber* _writerWaits; 311 | DescriptorState state; 312 | nothrow: 313 | ReaderState readerState()() { 314 | return atomicLoad(_readerState); 315 | } 316 | 317 | WriterState writerState()() { 318 | return atomicLoad(_writerState); 319 | } 320 | 321 | // try to change state & return whatever it happend to be in the end 322 | bool changeReader()(ReaderState from, ReaderState to) { 323 | return cas(&_readerState, from, to); 324 | } 325 | 326 | // ditto for writer 327 | bool changeWriter()(WriterState from, WriterState to) { 328 | return cas(&_writerState, from, to); 329 | } 330 | 331 | // 332 | shared(AwaitingFiber)* readWaiters()() { 333 | return atomicLoad(_readerWaits); 334 | } 335 | 336 | // 337 | shared(AwaitingFiber)* writeWaiters()(){ 338 | return atomicLoad(_writerWaits); 339 | } 340 | 341 | // try to enqueue reader fiber given old head 342 | bool enqueueReader()(shared(AwaitingFiber)* fiber) { 343 | auto head = readWaiters; 344 | if (head == fiber) { 345 | return true; // TODO: HACK 346 | } 347 | fiber.next = head; 348 | return cas(&_readerWaits, head, fiber); 349 | } 350 | 351 | void removeReader()(shared(AwaitingFiber)* fiber) { 352 | auto head = steal(_readerWaits); 353 | if (head is null || head.next is null) return; 354 | head = removeFromList(head.unshared, fiber); 355 | cas(&_readerWaits, head, cast(shared(AwaitingFiber*))null); 356 | } 357 | 358 | // try to enqueue writer fiber given old head 359 | bool enqueueWriter()(shared(AwaitingFiber)* fiber) { 360 | auto head = writeWaiters; 361 | if (head == fiber) { 362 | return true; // TODO: HACK 363 | } 364 | fiber.next = head; 365 | return cas(&_writerWaits, head, fiber); 366 | } 367 | 368 | void removeWriter()(shared(AwaitingFiber)* fiber) { 369 | auto head = steal(_writerWaits); 370 | if (head is null || head.next is null) return; 371 | head = removeFromList(head.unshared, fiber); 372 | cas(&_writerWaits, head, cast(shared(AwaitingFiber*))null); 373 | } 374 | 375 | // try to schedule readers - if fails - someone added a reader, it's now his job to check state 376 | void scheduleReaders()(int wakeFd) { 377 | auto w = steal(_readerWaits); 378 | if (w) w.unshared.scheduleAll(wakeFd); 379 | } 380 | 381 | // try to schedule writers, ditto 382 | void scheduleWriters()(int wakeFd) { 383 | auto w = steal(_writerWaits); 384 | if (w) w.unshared.scheduleAll(wakeFd); 385 | } 386 | } 387 | 388 | extern(C) void graceful_shutdown_on_signal(int, siginfo_t*, void*) 389 | { 390 | version(photon_tracing) printStats(); 391 | _exit(9); 392 | } 393 | 394 | version(photon_tracing) 395 | void printStats() 396 | { 397 | // TODO: report on various events in eventloop/scheduler 398 | string msg = "Tracing report:\n\n"; 399 | write(2, msg.ptr, msg.length); 400 | } 401 | 402 | shared int kq; 403 | 404 | public void startloop() 405 | { 406 | import core.cpuid; 407 | uint threads = threadsPerCPU; 408 | kq = kqueue(); 409 | enforce(kq != -1); 410 | 411 | eventLoop = pthread_create(cast(pthread_t*)&eventLoop, null, &processEventsEntry, null); 412 | } 413 | 414 | package(photon) void stoploop() 415 | { 416 | void* ret; 417 | pthread_join(eventLoop, &ret); 418 | } 419 | 420 | extern(C) void* processEventsEntry(void*) 421 | { 422 | kevent ke; 423 | for (;;) { 424 | if (kevent(kq, null, 0, &ke, 1, null) != -1) { 425 | logf("A write occured on the file"); 426 | } 427 | } 428 | } 429 | 430 | enum Fcntl: int { explicit = 0, msg = MSG_DONTWAIT, sock = SOCK_NONBLOCK, noop = 0xFFFFF } 431 | enum SyscallKind { accept, read, write, connect } 432 | 433 | // intercept - a filter for file descriptor, changes flags and register on first use 434 | void interceptFd(Fcntl needsFcntl)(int fd) nothrow { 435 | logf("Hit interceptFD"); 436 | if (fd < 0 || fd >= descriptors.length) return; 437 | if (cas(&descriptors[fd].state, DescriptorState.NOT_INITED, DescriptorState.INITIALIZING)) { 438 | logf("First use, registering fd = %s", fd); 439 | static if(needsFcntl == Fcntl.explicit) { 440 | int flags = fcntl(fd, F_GETFL, 0); 441 | fcntl(fd, F_SETFL, flags | O_NONBLOCK).checked; 442 | logf("Setting FCNTL. %x", cast(void*)currentFiber); 443 | } 444 | kevent ke; 445 | ke.ident = fd; 446 | ke.filter = EVFILT_READ | EVFILT_WRITE; 447 | ke.flags = EV_ADD | EV_ENABLE; 448 | timespec timeout; 449 | timeout.tv_nsec = 1000; 450 | enforce(kevent(kq, null, 0, &ke, 1, ×pec) >= 0); 451 | } 452 | } 453 | 454 | void deregisterFd(int fd) nothrow { 455 | if(fd >= 0 && fd < descriptors.length) { 456 | auto descriptor = descriptors.ptr + fd; 457 | atomicStore(descriptor._writerState, WriterState.READY); 458 | atomicStore(descriptor._readerState, ReaderState.EMPTY); 459 | descriptor.scheduleReaders(fd); 460 | descriptor.scheduleWriters(fd); 461 | atomicStore(descriptor.state, DescriptorState.NOT_INITED); 462 | } 463 | } 464 | 465 | ssize_t universalSyscall(size_t ident, string name, SyscallKind kind, Fcntl fcntlStyle, ssize_t ERR, T...) 466 | (int fd, T args) nothrow { 467 | if (currentFiber is null) { 468 | logf("%s PASSTHROUGH FD=%s", name, fd); 469 | return syscall(ident, fd, args).withErrorno; 470 | } 471 | else { 472 | logf("HOOKED %s FD=%d", name, fd); 473 | interceptFd!(fcntlStyle)(fd); 474 | shared(Descriptor)* descriptor = descriptors.ptr + fd; 475 | if (atomicLoad(descriptor.state) == DescriptorState.THREADPOOL) { 476 | logf("%s syscall THREADPOLL FD=%d", name, fd); 477 | //TODO: offload syscall to thread-pool 478 | return syscall(ident, fd, args).withErrorno; 479 | } 480 | L_start: 481 | shared AwaitingFiber await = AwaitingFiber(cast(shared)currentFiber, null); 482 | // set flags argument if able to avoid direct fcntl calls 483 | static if (fcntlStyle != Fcntl.explicit) 484 | { 485 | args[2] |= fcntlStyle; 486 | } 487 | //if (kind == SyscallKind.accept) 488 | logf("kind:s args:%s", kind, args); 489 | static if(kind == SyscallKind.accept || kind == SyscallKind.read) { 490 | auto state = descriptor.readerState; 491 | logf("%s syscall state is %d. Fiber %x", name, state, cast(void*)currentFiber); 492 | final switch (state) with (ReaderState) { 493 | case EMPTY: 494 | logf("EMPTY - enqueue reader"); 495 | if (!descriptor.enqueueReader(&await)) goto L_start; 496 | // changed state to e.g. READY or UNCERTAIN in meantime, may need to reschedule 497 | if (descriptor.readerState != EMPTY) descriptor.scheduleReaders(fd); 498 | FiberExt.yield(); 499 | goto L_start; 500 | case UNCERTAIN: 501 | descriptor.changeReader(UNCERTAIN, READING); // may became READY or READING 502 | goto case READING; 503 | case READY: 504 | descriptor.changeReader(READY, READING); // always succeeds if 1 fiber reads 505 | goto case READING; 506 | case READING: 507 | ssize_t resp = syscall(ident, fd, args); 508 | static if (kind == SyscallKind.accept) { 509 | if (resp >= 0) // for accept we never know if we emptied the queue 510 | descriptor.changeReader(READING, UNCERTAIN); 511 | else if (resp == -ERR || resp == -EAGAIN) { 512 | if (descriptor.changeReader(READING, EMPTY)) 513 | goto case EMPTY; 514 | goto L_start; // became UNCERTAIN or READY in meantime 515 | } 516 | } 517 | else static if (kind == SyscallKind.read) { 518 | if (resp == args[1]) // length is 2nd in (buf, length, ...) 519 | descriptor.changeReader(READING, UNCERTAIN); 520 | else if(resp >= 0) 521 | descriptor.changeReader(READING, EMPTY); 522 | else if (resp == -ERR || resp == -EAGAIN) { 523 | if (descriptor.changeReader(READING, EMPTY)) 524 | goto case EMPTY; 525 | goto L_start; // became UNCERTAIN or READY in meantime 526 | } 527 | } 528 | else 529 | static assert(0); 530 | return withErrorno(resp); 531 | } 532 | } 533 | else static if(kind == SyscallKind.write || kind == SyscallKind.connect) { 534 | //TODO: Handle short-write b/c of EWOULDBLOCK to apear as fully blocking? 535 | auto state = descriptor.writerState; 536 | logf("%s syscall state is %d. Fiber %x", name, state, cast(void*)currentFiber); 537 | final switch (state) with (WriterState) { 538 | case FULL: 539 | logf("FULL FD=%d Fiber %x", fd, cast(void*)currentFiber); 540 | if (!descriptor.enqueueWriter(&await)) goto L_start; 541 | // changed state to e.g. READY or UNCERTAIN in meantime, may need to reschedule 542 | if (descriptor.writerState != FULL) descriptor.scheduleWriters(fd); 543 | FiberExt.yield(); 544 | goto L_start; 545 | case UNCERTAIN: 546 | logf("UNCERTAIN on FD=%d Fiber %x", fd, cast(void*)currentFiber); 547 | descriptor.changeWriter(UNCERTAIN, WRITING); // may became READY or WRITING 548 | goto case WRITING; 549 | case READY: 550 | descriptor.changeWriter(READY, WRITING); // always succeeds if 1 fiber writes 551 | goto case WRITING; 552 | case WRITING: 553 | ssize_t resp = syscall(ident, fd, args); 554 | static if (kind == SyscallKind.connect) { 555 | if(resp >= 0) { 556 | descriptor.changeWriter(WRITING, READY); 557 | } 558 | else if (resp == -ERR || resp == -EALREADY) { 559 | if (descriptor.changeWriter(WRITING, FULL)) { 560 | goto case FULL; 561 | } 562 | goto L_start; // became UNCERTAIN or READY in meantime 563 | } 564 | return withErrorno(resp); 565 | } 566 | else { 567 | if (resp == args[1]) // (buf, len) args to syscall 568 | descriptor.changeWriter(WRITING, UNCERTAIN); 569 | else if(resp >= 0) { 570 | logf("Short-write on FD=%d, become FULL", fd); 571 | descriptor.changeWriter(WRITING, FULL); 572 | } 573 | else if (resp == -ERR || resp == -EAGAIN) { 574 | if (descriptor.changeWriter(WRITING, FULL)) { 575 | logf("Sudden block on FD=%d, become FULL", fd); 576 | goto case FULL; 577 | } 578 | goto L_start; // became UNCERTAIN or READY in meantime 579 | } 580 | return withErrorno(resp); 581 | } 582 | } 583 | } 584 | assert(0); 585 | } 586 | } 587 | 588 | // ====================================================================================== 589 | // SYSCALL warappers intercepts 590 | // ====================================================================================== 591 | 592 | extern(C) ssize_t read(int fd, void *buf, size_t count) nothrow 593 | { 594 | return universalSyscall!(SYS_READ, "READ", SyscallKind.read, Fcntl.explicit, EWOULDBLOCK) 595 | (fd, cast(size_t)buf, count); 596 | } 597 | 598 | extern(C) ssize_t write(int fd, const void *buf, size_t count) 599 | { 600 | return universalSyscall!(SYS_WRITE, "WRITE", SyscallKind.write, Fcntl.explicit, EWOULDBLOCK) 601 | (fd, cast(size_t)buf, count); 602 | } 603 | 604 | extern(C) ssize_t accept(int sockfd, sockaddr *addr, socklen_t *addrlen) 605 | { 606 | return universalSyscall!(SYS_ACCEPT, "accept", SyscallKind.accept, Fcntl.explicit, EWOULDBLOCK) 607 | (sockfd, cast(size_t) addr, cast(size_t) addrlen); 608 | } 609 | 610 | extern(C) ssize_t accept4(int sockfd, sockaddr *addr, socklen_t *addrlen, int flags) 611 | { 612 | return universalSyscall!(SYS_ACCEPT4, "accept4", SyscallKind.accept, Fcntl.sock, EWOULDBLOCK) 613 | (sockfd, cast(size_t) addr, cast(size_t) addrlen, flags); 614 | } 615 | 616 | extern(C) ssize_t connect(int sockfd, const sockaddr *addr, socklen_t *addrlen) 617 | { 618 | return universalSyscall!(SYS_CONNECT, "connect", SyscallKind.connect, Fcntl.explicit, EINPROGRESS) 619 | (sockfd, cast(size_t) addr, cast(size_t) addrlen); 620 | } 621 | 622 | extern(C) ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 623 | const sockaddr *dest_addr, socklen_t addrlen) 624 | { 625 | return universalSyscall!(SYS_SENDTO, "sendto", SyscallKind.write, Fcntl.explicit, EWOULDBLOCK) 626 | (sockfd, cast(size_t) buf, len, flags, cast(size_t) dest_addr, cast(size_t) addrlen); 627 | } 628 | 629 | extern(C) size_t recv(int sockfd, void *buf, size_t len, int flags) nothrow { 630 | sockaddr_in src_addr; 631 | src_addr.sin_family = AF_INET; 632 | src_addr.sin_port = 0; 633 | src_addr.sin_addr.s_addr = htonl(INADDR_ANY); 634 | ssize_t addrlen = sockaddr_in.sizeof; 635 | return recvfrom(sockfd, buf, len, flags, cast(sockaddr*)&src_addr, &addrlen); 636 | } 637 | 638 | extern(C) private ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, 639 | sockaddr *src_addr, ssize_t* addrlen) nothrow 640 | { 641 | return universalSyscall!(SYS_RECVFROM, "RECVFROM", SyscallKind.read, Fcntl.msg, EWOULDBLOCK) 642 | (sockfd, cast(size_t)buf, len, flags, cast(size_t)src_addr, cast(size_t)addrlen); 643 | } 644 | 645 | extern(C) private ssize_t poll(pollfd *fds, nfds_t nfds, int timeout) 646 | { 647 | bool nonBlockingCheck(ref ssize_t result) { 648 | bool uncertain; 649 | L_cacheloop: 650 | foreach (ref fd; fds[0..nfds]) { 651 | interceptFd!(Fcntl.explicit)(fd.fd); 652 | fd.revents = 0; 653 | auto descriptor = descriptors.ptr + fd.fd; 654 | if (fd.events & POLLIN) { 655 | auto state = descriptor.readerState; 656 | logf("Found event %d for reader in select", state); 657 | switch(state) with(ReaderState) { 658 | case READY: 659 | fd.revents |= POLLIN; 660 | break; 661 | case EMPTY: 662 | break; 663 | default: 664 | uncertain = true; 665 | break L_cacheloop; 666 | } 667 | } 668 | if (fd.events & POLLOUT) { 669 | auto state = descriptor.writerState; 670 | logf("Found event %d for writer in select", state); 671 | switch(state) with(WriterState) { 672 | case READY: 673 | fd.revents |= POLLOUT; 674 | break; 675 | case FULL: 676 | break; 677 | default: 678 | uncertain = true; 679 | break L_cacheloop; 680 | } 681 | } 682 | } 683 | // fallback to system poll call if descriptor state is uncertain 684 | if (uncertain) { 685 | logf("Fallback to system poll, descriptors have uncertain state"); 686 | ssize_t p = raw_poll(fds, nfds, 0); 687 | if (p != 0) { 688 | result = p; 689 | logf("Raw poll returns %d", result); 690 | return true; 691 | } 692 | } 693 | else { 694 | ssize_t j = 0; 695 | foreach (i; 0..nfds) { 696 | if (fds[i].revents) { 697 | fds[j++] = fds[i]; 698 | } 699 | } 700 | logf("Using our own event cache: %d events", j); 701 | if (j > 0) { 702 | result = cast(ssize_t)j; 703 | return true; 704 | } 705 | } 706 | return false; 707 | } 708 | if (currentFiber is null) { 709 | logf("POLL PASSTHROUGH!"); 710 | return raw_poll(fds, nfds, timeout); 711 | } 712 | else { 713 | logf("HOOKED POLL %d fds timeout %d", nfds, timeout); 714 | if (nfds < 0) return -EINVAL.withErrorno; 715 | if (nfds == 0) { 716 | if (timeout == 0) return 0; 717 | shared AwaitingFiber aw = shared(AwaitingFiber)(cast(shared)currentFiber); 718 | Timer tm = timer(); 719 | descriptors[tm.fd].enqueueReader(&aw); 720 | scope(exit) tm.dispose(); 721 | tm.arm(timeout); 722 | logf("Timer fd=%d", tm.fd); 723 | Fiber.yield(); 724 | logf("Woke up after select %x. WakeFd=%d", cast(void*)currentFiber, currentFiber.wakeFd); 725 | return 0; 726 | } 727 | foreach(ref fd; fds[0..nfds]) { 728 | if (fd.fd < 0 || fd.fd >= descriptors.length) return -EBADF.withErrorno; 729 | fd.revents = 0; 730 | } 731 | ssize_t result = 0; 732 | if (timeout <= 0) return raw_poll(fds, nfds, timeout); 733 | if (nonBlockingCheck(result)) return result; 734 | shared AwaitingFiber aw = shared(AwaitingFiber)(cast(shared)currentFiber); 735 | foreach (i; 0..nfds) { 736 | if (fds[i].events & POLLIN) 737 | descriptors[fds[i].fd].enqueueReader(&aw); 738 | else if(fds[i].events & POLLOUT) 739 | descriptors[fds[i].fd].enqueueWriter(&aw); 740 | } 741 | Timer tm = timer(); 742 | scope(exit) tm.dispose(); 743 | tm.arm(timeout); 744 | descriptors[tm.fd].enqueueReader(&aw); 745 | Fiber.yield(); 746 | tm.disarm(); 747 | atomicStore(descriptors[tm.fd]._readerWaits, cast(shared(AwaitingFiber)*)null); 748 | foreach (i; 0..nfds) { 749 | if (fds[i].events & POLLIN) 750 | descriptors[fds[i].fd].removeReader(&aw); 751 | else if(fds[i].events & POLLOUT) 752 | descriptors[fds[i].fd].removeWriter(&aw); 753 | } 754 | logf("Woke up after select %x. WakeFD=%d", cast(void*)currentFiber, currentFiber.wakeFd); 755 | if (currentFiber.wakeFd == tm.fd) return 0; 756 | else { 757 | nonBlockingCheck(result); 758 | return result; 759 | } 760 | } 761 | } 762 | 763 | extern(C) private ssize_t close(int fd) nothrow 764 | { 765 | logf("HOOKED CLOSE FD=%d", fd); 766 | deregisterFd(fd); 767 | return cast(int)withErrorno(syscall(SYS_CLOSE, fd)); 768 | } 769 | -------------------------------------------------------------------------------- /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/support.d: -------------------------------------------------------------------------------- 1 | module photon.linux.support; 2 | version(linux): 3 | import core.sys.posix.unistd; 4 | import core.sys.linux.timerfd; 5 | import core.stdc.errno; 6 | import core.stdc.stdlib; 7 | import core.thread; 8 | import core.stdc.config; 9 | import core.sys.posix.pthread; 10 | import photon.linux.syscalls; 11 | 12 | enum int MSG_DONTWAIT = 0x40; 13 | enum int SOCK_NONBLOCK = 0x800; 14 | 15 | extern(C) void perror(const(char) *s) nothrow; 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 | ssize_t withErrorno(ssize_t resp) nothrow { 26 | if(resp < 0) { 27 | //logf("Syscall ret %d", resp); 28 | errno = cast(int)-resp; 29 | return -1; 30 | } 31 | else { 32 | return resp; 33 | } 34 | } 35 | 36 | void logf(string file = __FILE__, int line = __LINE__, T...)(string msg, T args) 37 | { 38 | debug(photon) { 39 | try { 40 | import std.stdio; 41 | stderr.writefln(msg, args); 42 | stderr.writefln("\tat %s:%s:[LWP:%s]", file, line, pthread_self()); 43 | } 44 | catch(Throwable t) { 45 | abort(); 46 | } 47 | } 48 | } 49 | 50 | 51 | @nogc: 52 | nothrow: 53 | 54 | 55 | private // helpers 56 | { 57 | 58 | /* Size definition for CPU sets. */ 59 | enum 60 | { 61 | __CPU_SETSIZE = 1024, 62 | __NCPUBITS = 8 * cpu_mask.sizeof, 63 | } 64 | 65 | /* Macros */ 66 | 67 | /* Basic access functions. */ 68 | size_t __CPUELT()(size_t cpu) pure 69 | { 70 | return cpu / __NCPUBITS; 71 | } 72 | cpu_mask __CPUMASK()(size_t cpu) pure 73 | { 74 | return 1UL << (cpu % __NCPUBITS); 75 | } 76 | 77 | cpu_mask __CPU_SET_S()(size_t cpu, size_t setsize, cpu_set_t* cpusetp) pure 78 | { 79 | if (cpu < 8 * setsize) 80 | { 81 | 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 | /* Functions */ 106 | extern(C): 107 | int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 108 | int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); 109 | 110 | -------------------------------------------------------------------------------- /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_POLL = 7, 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 | size_t syscall(size_t ident) nothrow 82 | { 83 | size_t ret; 84 | 85 | asm nothrow 86 | { 87 | mov RAX, ident; 88 | syscall; 89 | mov ret, RAX; 90 | } 91 | return ret; 92 | } 93 | 94 | size_t syscall(size_t ident, size_t n) nothrow 95 | { 96 | size_t ret; 97 | 98 | asm nothrow 99 | { 100 | mov RAX, ident; 101 | mov RDI, n; 102 | syscall; 103 | mov ret, RAX; 104 | } 105 | return ret; 106 | } 107 | 108 | size_t syscall(size_t ident, size_t n, size_t arg1) nothrow 109 | { 110 | size_t ret; 111 | 112 | asm nothrow 113 | { 114 | mov RAX, ident; 115 | mov RDI, n; 116 | mov RSI, arg1; 117 | syscall; 118 | mov ret, RAX; 119 | } 120 | return ret; 121 | } 122 | 123 | size_t syscall(size_t ident, size_t n, size_t arg1, size_t arg2) nothrow 124 | { 125 | size_t ret; 126 | 127 | asm nothrow 128 | { 129 | mov RAX, ident; 130 | mov RDI, n; 131 | mov RSI, arg1; 132 | mov RDX, arg2; 133 | syscall; 134 | mov ret, RAX; 135 | } 136 | return ret; 137 | } 138 | 139 | size_t syscall(size_t ident, size_t n, size_t arg1, size_t arg2, size_t arg3) nothrow 140 | { 141 | size_t ret; 142 | 143 | asm nothrow 144 | { 145 | mov RAX, ident; 146 | mov RDI, n; 147 | mov RSI, arg1; 148 | mov RDX, arg2; 149 | mov R10, arg3; 150 | syscall; 151 | mov ret, RAX; 152 | } 153 | return ret; 154 | } 155 | 156 | size_t syscall(size_t ident, size_t n, size_t arg1, size_t arg2, size_t arg3, size_t arg4) nothrow 157 | { 158 | size_t ret; 159 | 160 | asm nothrow 161 | { 162 | mov RAX, ident; 163 | mov RDI, n; 164 | mov RSI, arg1; 165 | mov RDX, arg2; 166 | mov R10, arg3; 167 | mov R8, arg4; 168 | syscall; 169 | mov ret, RAX; 170 | } 171 | return ret; 172 | } 173 | 174 | size_t syscall(size_t ident, size_t n, size_t arg1, size_t arg2, size_t arg3, size_t arg4, size_t arg5) nothrow 175 | { 176 | size_t ret; 177 | 178 | asm nothrow 179 | { 180 | mov RAX, ident; 181 | mov RDI, n; 182 | mov RSI, arg1; 183 | mov RDX, arg2; 184 | mov R10, arg3; 185 | mov R8, arg4; 186 | mov R9, arg5; 187 | syscall; 188 | mov ret, RAX; 189 | } 190 | return ret; 191 | } 192 | } 193 | 194 | int gettid() 195 | { 196 | return cast(int)syscall(SYS_GETTID); 197 | } 198 | 199 | ssize_t raw_read(int fd, void *buf, size_t count) nothrow { 200 | logf("Raw read on FD=%d", fd); 201 | return syscall(SYS_READ, fd, cast(ssize_t) buf, cast(ssize_t) count).withErrorno; 202 | } 203 | 204 | ssize_t raw_write(int fd, const void *buf, size_t count) nothrow 205 | { 206 | logf("Raw write on FD=%d", fd); 207 | return syscall(SYS_WRITE, fd, cast(size_t) buf, count).withErrorno; 208 | } 209 | 210 | ssize_t raw_poll(pollfd *fds, nfds_t nfds, int timeout) 211 | { 212 | logf("Raw poll"); 213 | return syscall(SYS_POLL, cast(size_t)fds, cast(size_t) nfds, timeout).withErrorno; 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/photon/macos/support.d: -------------------------------------------------------------------------------- 1 | module photon.macos.support; 2 | version(OSX): 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 | import core.sys.darwin.sys.event; 10 | 11 | enum int MSG_DONTWAIT = 0x80; 12 | alias off_t = long; 13 | alias quad_t = ulong; 14 | extern(C) nothrow off_t __syscall(quad_t number, ...); 15 | extern(C) void perror(const(char) *s) nothrow; 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 | -------------------------------------------------------------------------------- /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: `startloop` to initialize Photon, `runFibers` 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 | startloop(); 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 | runFibers(); 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.ring_queue; 67 | 68 | version(Windows) public import photon.windows.core; 69 | else version(linux) public import photon.linux.core; 70 | else version(freeBSD) public import photon.freebsd.core; 71 | else version(OSX) public import photon.macos.core; 72 | else static assert(false, "Target OS not supported by Photon yet!"); 73 | 74 | version(PhotonDocs) { 75 | 76 | /// Initialize event loop and internal data structures for Photon scheduler. 77 | public void startloop(); 78 | 79 | /// Setup a fiber task to run on the Photon scheduler. 80 | public void go(void delegate() func); 81 | 82 | /// ditto 83 | public void go(void function() func); 84 | 85 | 86 | } 87 | 88 | /// Start sheduler and run fibers until all are terminated. 89 | void runFibers() 90 | { 91 | Thread runThread(size_t n){ // damned D lexical capture "semantics" 92 | auto t = new Thread(() => schedulerEntry(n)); 93 | t.start(); 94 | return t; 95 | } 96 | Thread[] threads = new Thread[scheds.length-1]; 97 | foreach (i; 0..threads.length){ 98 | threads[i] = runThread(i+1); 99 | } 100 | schedulerEntry(0); 101 | foreach (t; threads) 102 | t.join(); 103 | version(linux) stoploop(); 104 | } 105 | 106 | /++ 107 | A ref-counted channel that is safe to share between multiple fibers. 108 | In essence it's a multiple producer single consumer queue, that implements 109 | `OutputRange` and `InputRange` concepts. 110 | +/ 111 | struct Channel(T) { 112 | __gshared RingQueue!(T, Event)* buf; 113 | __gshared T item; 114 | bool loaded; 115 | 116 | this(size_t capacity) { 117 | buf = allocRingQueue!T(capacity, Event(false), Event(false)); 118 | } 119 | 120 | this(this) { 121 | buf.retain(); 122 | } 123 | 124 | /// OutputRange contract - puts a new item into the channel. 125 | void put(T value) { 126 | buf.push(move(value)); 127 | } 128 | 129 | void put(T value) shared { 130 | buf.push(move(value)); 131 | } 132 | 133 | void close() shared { 134 | buf.close(); 135 | } 136 | 137 | /++ 138 | Part of InputRange contract - checks if there is an item in the queue. 139 | Returns `true` if channel is closed and its buffer is exhausted. 140 | +/ 141 | bool empty() { 142 | if (loaded) return false; 143 | loaded = buf.tryPop(item); 144 | return !loaded; 145 | } 146 | 147 | bool empty() shared { 148 | if (loaded) return false; 149 | loaded = buf.tryPop(item); 150 | return !loaded; 151 | } 152 | 153 | /++ 154 | Part of InputRange contract - returns an item available in the channel. 155 | +/ 156 | ref T front() { 157 | return cast()item; 158 | } 159 | 160 | ref T front() shared { 161 | return item; 162 | } 163 | 164 | /++ 165 | Part of InputRange contract - advances range forward. 166 | +/ 167 | void popFront() { 168 | loaded = false; 169 | } 170 | 171 | void popFront() shared { 172 | loaded = false; 173 | } 174 | 175 | ~this() { 176 | if (buf) { 177 | if (buf.release) { 178 | disposeRingQueue(buf); 179 | buf = null; 180 | } 181 | } 182 | } 183 | } 184 | 185 | /++ 186 | Create a new shared `Channel` with given capacity. 187 | +/ 188 | auto channel(T)(size_t capacity = 1) { 189 | return cast(shared)Channel!T(capacity); 190 | } 191 | 192 | unittest { 193 | import std.range.primitives, std.traits; 194 | import std.algorithm; 195 | static assert(isInputRange!(Channel!int)); 196 | static assert(isInputRange!(Unqual!(shared Channel!int))); 197 | static assert(isOutputRange!(shared Channel!int, int)); 198 | // 199 | auto ch = channel!int(10); 200 | foreach (i; 0..10){ 201 | ch.put(i); 202 | } 203 | ch.close(); 204 | auto sum = ch.sum; 205 | assert(sum == 45); 206 | } 207 | 208 | 209 | 210 | /++ 211 | Multiplex between multiple channels, executes a lambda attached to the first 212 | channel that becomes ready to read. 213 | +/ 214 | void select(Args...)(auto ref Args args) 215 | if (allSatisfy!(isChannel, Even!Args) && allSatisfy!(isHandler, Odd!Args)) { 216 | void delegate()[args.length/2] handlers = void; 217 | Event*[args.length/2] events = void; 218 | static foreach (i, v; args) { 219 | static if(i % 2 == 0) { 220 | events[i/2] = &v.buf.rtr; 221 | } 222 | else { 223 | handlers[i/2] = v; 224 | } 225 | } 226 | foreach (i, channel; Even!(args)) { 227 | if (channel.buf.readyToRead()) 228 | return handlers[i](); 229 | } 230 | for (;;) { 231 | auto n = awaitAny(events[]); 232 | L_dispatch: 233 | switch(n) { 234 | static foreach (i, channel; Even!(args)) { 235 | case i: 236 | if (channel.buf.readyToRead()) 237 | return handlers[n](); 238 | break L_dispatch; 239 | } 240 | default: 241 | assert(0); 242 | } 243 | } 244 | } 245 | 246 | /// Trait for testing if a type is Channel 247 | enum isChannel(T) = is(T == Channel!(V), V); 248 | 249 | enum isHandler(T) = is(T == void delegate()); 250 | 251 | private template Even(T...) { 252 | static assert(T.length % 2 == 0); 253 | static if (T.length > 0) { 254 | alias Even = AliasSeq!(T[0], Even!(T[2..$])); 255 | } 256 | else { 257 | alias Even = AliasSeq!(); 258 | } 259 | } 260 | 261 | private template Odd(T...) { 262 | static assert(T.length % 2 == 0); 263 | static if (T.length > 0) { 264 | alias Odd = AliasSeq!(T[1], Odd!(T[2..$])); 265 | } 266 | else { 267 | alias Odd = AliasSeq!(); 268 | } 269 | } 270 | 271 | unittest { 272 | static assert(Even!(1, 2, 3, 4) == AliasSeq!(1, 3)); 273 | static assert(Odd!(1, 2, 3, 4) == AliasSeq!(2, 4)); 274 | static assert(isChannel!(Channel!int)); 275 | static assert(isChannel!(shared Channel!int)); 276 | } 277 | 278 | struct PooledEntry(T) { 279 | import std.datetime; 280 | private: 281 | PooledEntry* next; 282 | SysTime lastUsed; 283 | T item; 284 | } 285 | 286 | struct Pooled(T) { 287 | alias get this; 288 | @property ref get() const { return pointer.item; } 289 | private PooledEntry!T* pointer; 290 | } 291 | 292 | /// Generic pool 293 | class Pool(T) { 294 | import std.datetime, photon.ds.common; 295 | private this(size_t size, Duration maxIdle, T delegate() open, void delegate(ref T) close) { 296 | this.size = size; 297 | this.maxIdle = maxIdle; 298 | this.open = open; 299 | this.close = close; 300 | this.allocated = 0; 301 | this.ready = event(0); 302 | this.working = true; 303 | this.lock = SpinLock(SpinLock.Contention.brief); 304 | go({ 305 | while(this.working) { 306 | delay(1.seconds); 307 | auto time = Clock.currTime(); 308 | lock.lock(); 309 | PooledEntry!T* stale; 310 | PooledEntry!T* fresh; 311 | PooledEntry!T* current = pool; 312 | while (current != null) { 313 | if (current.lastUsed + maxIdle < time) { 314 | auto next = current.next; 315 | current.next = stale; 316 | stale = current; 317 | current = next; 318 | } 319 | else { 320 | auto next = current.next; 321 | current.next = fresh; 322 | fresh = current; 323 | current = next; 324 | } 325 | } 326 | pool = fresh; 327 | lock.unlock(); 328 | current = stale; 329 | size_t count = 0; 330 | while (current != null) { 331 | close(current.item); 332 | current = current.next; 333 | count++; 334 | } 335 | atomicFetchSub(allocated, count); 336 | if (count > 0) ready.trigger(); 337 | } 338 | }); 339 | } 340 | 341 | // Acquire resource from the pool 342 | Pooled!T acquire() { 343 | for (;;) { 344 | lock.lock(); 345 | if (pool != null) { 346 | auto next = pool.next; 347 | auto ret = pool; 348 | ret.next = null; 349 | pool = next; 350 | lock.unlock(); 351 | return Pooled!T(ret); 352 | } 353 | lock.unlock(); 354 | if (allocated < size) { 355 | size_t current = allocated; 356 | size_t next = current + 1; 357 | if (cas(&allocated, current, next)) { 358 | // since we are not in the pool yet, lastUsed is ignored 359 | auto item = new PooledEntry!T(null, SysTime.init, open()); 360 | return Pooled!T(item); 361 | } 362 | } 363 | ready.waitAndReset(); 364 | } 365 | } 366 | 367 | Pooled!T acquire() shared { 368 | return this.unshared.acquire(); 369 | } 370 | 371 | /// Put pooled item to reuse 372 | void release(Pooled!T item) { 373 | item.pointer.lastUsed = Clock.currTime(); 374 | lock.lock(); 375 | item.pointer.next = pool; 376 | pool = item.pointer; 377 | lock.unlock(); 378 | ready.trigger(); 379 | } 380 | 381 | void release(Pooled!T item) shared { 382 | this.unshared.release(item); 383 | } 384 | 385 | /// call on items that errored or cannot be reused for some reason 386 | void dispose(Pooled!T item) { 387 | atomicFetchSub(allocated, 1); 388 | close(item.pointer.item); 389 | ready.trigger(); 390 | } 391 | 392 | void dispose(Pooled!T item) shared { 393 | return this.unshared.dispose(item); 394 | } 395 | 396 | void shutdown() { 397 | working = false; 398 | auto current = pool; 399 | while (current != null) { 400 | close(current.item); 401 | current = current.next; 402 | } 403 | } 404 | 405 | void shutdown() shared { 406 | return this.unshared.shutdown(); 407 | } 408 | private: 409 | SpinLock lock; 410 | shared Event ready; 411 | PooledEntry!T* pool; 412 | shared size_t allocated; 413 | size_t size; 414 | Duration maxIdle; 415 | T delegate() open; 416 | void delegate(ref T) close; 417 | shared bool working; 418 | } 419 | 420 | 421 | /// Create generic pool for resources, open creates new resource, close releases the resource. 422 | auto pool(T)(size_t size, Duration maxIdle, T delegate() open, void delegate(ref T) close) { 423 | return cast(shared) new Pool!T(size, maxIdle, open, close); 424 | } -------------------------------------------------------------------------------- /src/photon/support.d: -------------------------------------------------------------------------------- 1 | module photon.support; 2 | 3 | version(linux) public import photon.linux.support; 4 | else version(FreeBSD) public import photon.freebsd.support; 5 | else version(OSX) public import photon.macos.support; 6 | else version(Windows) public import photon.windows.support; 7 | -------------------------------------------------------------------------------- /src/photon/windows/core.d: -------------------------------------------------------------------------------- 1 | module photon.windows.core; 2 | version(Windows): 3 | private: 4 | 5 | import core.sys.windows.core; 6 | import core.sys.windows.winsock2; 7 | import core.atomic; 8 | import core.thread; 9 | import core.internal.spinlock; 10 | import std.exception; 11 | import std.windows.syserror; 12 | import core.stdc.stdlib; 13 | import std.random; 14 | import std.stdio; 15 | import std.traits; 16 | import std.meta; 17 | 18 | import rewind.map; 19 | 20 | import photon.ds.common; 21 | import photon.ds.intrusive_queue; 22 | import photon.windows.support; 23 | 24 | shared struct RawEvent { 25 | nothrow: 26 | this(bool signaled) { 27 | ev = cast(shared(HANDLE))CreateEventA(null, FALSE, signaled, null); 28 | assert(ev != null, "Failed to create RawEvent"); 29 | } 30 | 31 | void waitAndReset() { 32 | auto ret = WaitForSingleObject(cast(HANDLE)ev, INFINITE); 33 | assert(ret == WAIT_OBJECT_0, "Failed while waiting on event"); 34 | } 35 | 36 | void trigger() { 37 | auto ret = SetEvent(cast(HANDLE)ev); 38 | assert(ret != 0); 39 | } 40 | 41 | HANDLE ev; 42 | } 43 | 44 | struct MultiAwait 45 | { 46 | int n; 47 | void delegate() trigger; 48 | MultiAwaitBox* box; 49 | } 50 | 51 | struct MultiAwaitBox { 52 | shared size_t refCount; 53 | shared FiberExt fiber; 54 | } 55 | 56 | extern(Windows) VOID waitCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult) { 57 | auto fiber = cast(FiberExt)Context; 58 | fiber.schedule(); 59 | } 60 | 61 | 62 | extern(Windows) VOID waitAnyCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult) { 63 | auto await = cast(MultiAwait*)Context; 64 | auto fiber = cast()steal(await.box.fiber); 65 | if (fiber) { 66 | logf("AwaitAny callback waking up on %d object", await.n); 67 | fiber.wakeUpObject = await.n; 68 | fiber.schedule(); 69 | } 70 | else { 71 | logf("AwaitAny callback - triggering awaitable again"); 72 | await.trigger(); 73 | } 74 | auto cnt = atomicFetchSub(await.box.refCount, 1); 75 | if (cnt == 1) free(await.box); 76 | free(await); 77 | CloseThreadpoolWait(Wait); 78 | } 79 | 80 | /// Event object 81 | public struct Event { 82 | 83 | @disable this(this); 84 | 85 | this(bool signaled) { 86 | ev = cast(HANDLE)CreateEventA(null, FALSE, signaled, null); 87 | assert(ev != null, "Failed to create Event"); 88 | } 89 | 90 | /// Wait for the event to be triggered, then reset and return atomically 91 | void waitAndReset() { 92 | auto wait = CreateThreadpoolWait(&waitCallback, cast(void*)currentFiber, &environ); 93 | wenforce(wait != null, "Failed to create threadpool wait object"); 94 | SetThreadpoolWait(wait, cast(HANDLE)ev, null); 95 | FiberExt.yield(); 96 | CloseThreadpoolWait(wait); 97 | } 98 | 99 | /// 100 | void waitAndReset() shared { 101 | this.unshared.waitAndReset(); 102 | } 103 | 104 | private void registerForWaitAny(int n, MultiAwaitBox* box) { 105 | auto context = cast(MultiAwait*)calloc(1, MultiAwait.sizeof); 106 | context.box = box; 107 | context.n = n; 108 | context.trigger = cast(void delegate())&this.trigger; 109 | auto wait = CreateThreadpoolWait(&waitAnyCallback, cast(void*)context, &environ); 110 | wenforce(wait != null, "Failed to create threadpool wait object"); 111 | SetThreadpoolWait(wait, cast(HANDLE)ev, null); 112 | } 113 | 114 | private void registerForWaitAny(int n, MultiAwaitBox* box) shared { 115 | this.unshared.registerForWaitAny(n, box); 116 | } 117 | 118 | /// Trigger the event. 119 | void trigger() { 120 | auto ret = SetEvent(cast(HANDLE)ev); 121 | assert(ret != 0); 122 | } 123 | 124 | void trigger() shared { 125 | this.unshared.trigger(); 126 | } 127 | 128 | private: 129 | HANDLE ev; 130 | } 131 | 132 | /// 133 | public auto event(bool signaled) { 134 | return cast(shared)Event(signaled); 135 | } 136 | 137 | /// Semaphore object 138 | public struct Semaphore { 139 | @disable this(this); 140 | 141 | this(int count) { 142 | // set max count to MacOS pipe limit 143 | sem = cast(HANDLE)CreateSemaphoreA(null, count, 4096, null); 144 | assert(sem != null, "Failed to create semaphore"); 145 | } 146 | 147 | this(int count) shared { 148 | // set max count to MacOS pipe limit 149 | sem = cast(shared(HANDLE))CreateSemaphoreA(null, count, 4096, null); 150 | assert(sem != null, "Failed to create semaphore"); 151 | } 152 | 153 | /// 154 | void wait() { 155 | auto wait = CreateThreadpoolWait(&waitCallback, cast(void*)currentFiber, &environ); 156 | wenforce(wait != null, "Failed to create threadpool wait object"); 157 | SetThreadpoolWait(wait, cast(HANDLE)sem, null); 158 | FiberExt.yield(); 159 | CloseThreadpoolWait(wait); 160 | } 161 | 162 | void wait() shared { 163 | this.unshared.wait(); 164 | } 165 | 166 | private void registerForWaitAny(int n, MultiAwaitBox* box) { 167 | auto context = cast(MultiAwait*)calloc(1, MultiAwait.sizeof); 168 | context.box = box; 169 | context.n = n; 170 | context.trigger = { this.trigger(1); }; 171 | auto wait = CreateThreadpoolWait(&waitAnyCallback, cast(void*)context, &environ); 172 | wenforce(wait != null, "Failed to create threadpool wait object"); 173 | SetThreadpoolWait(wait, cast(HANDLE)sem, null); 174 | } 175 | 176 | private void registerForWaitAny(int n, MultiAwaitBox* box) shared { 177 | this.unshared.registerForWaitAny(n, box); 178 | } 179 | 180 | 181 | /// 182 | void trigger(int count) { 183 | auto ret = ReleaseSemaphore(cast(HANDLE)sem, count, null); 184 | assert(ret); 185 | } 186 | 187 | /// 188 | void trigger(int count) shared { 189 | this.unshared.trigger(count); 190 | } 191 | 192 | /// 193 | void dispose() { 194 | CloseHandle(cast(HANDLE)sem); 195 | } 196 | 197 | /// 198 | void dispose() shared { 199 | this.unshared.dispose(); 200 | } 201 | 202 | private: 203 | HANDLE sem; 204 | } 205 | 206 | /// 207 | public auto semaphore(int count) { 208 | return cast(shared)Semaphore(count); 209 | } 210 | 211 | extern(Windows) VOID timerCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer) { 212 | FiberExt fiber = cast(FiberExt)Context; 213 | fiber.schedule(); 214 | } 215 | 216 | /// 217 | struct Timer { 218 | void wait(Duration dur) { 219 | auto timer = CreateThreadpoolTimer(&timerCallback, cast(void*)currentFiber, &environ); 220 | wenforce(timer != null, "Failed to create threadpool timer"); 221 | FILETIME time; 222 | long hnsecs = -dur.total!"hnsecs"; 223 | time.dwHighDateTime = cast(DWORD)(hnsecs >> 32); 224 | time.dwLowDateTime = hnsecs & 0xFFFF_FFFF; 225 | SetThreadpoolTimer(timer, &time, 0, 0); 226 | FiberExt.yield(); 227 | CloseThreadpoolTimer(timer); 228 | } 229 | } 230 | 231 | /// 232 | public auto timer() { 233 | return Timer(); 234 | } 235 | 236 | /// 237 | public void delay(Duration req) { 238 | auto tm = Timer(); // Stateless on Windows 239 | tm.wait(req); 240 | } 241 | 242 | /// 243 | enum isAwaitable(E) = is (E : Event) || is (E : Semaphore) 244 | || is(E : Event*) || is(E : Semaphore*); 245 | 246 | /// 247 | public size_t awaitAny(Awaitable...)(auto ref Awaitable args) 248 | if (allSatisfy!(isAwaitable, Awaitable)) { 249 | auto box = cast(MultiAwaitBox*)calloc(1, MultiAwaitBox.sizeof); 250 | box.refCount = args.length; 251 | box.fiber = cast(shared)currentFiber; 252 | foreach (int i, ref v; args) { 253 | v.registerForWaitAny(i, box); 254 | } 255 | FiberExt.yield(); 256 | return currentFiber.wakeUpObject; 257 | } 258 | 259 | /// 260 | public size_t awaitAny(Awaitable)(Awaitable[] args) 261 | if (allSatisfy!(isAwaitable, Awaitable)) { 262 | auto box = cast(MultiAwaitBox*)calloc(1, MultiAwaitBox.sizeof); 263 | box.refCount = args.length; 264 | box.fiber = cast(shared)currentFiber; 265 | foreach (int i, ref v; args) { 266 | v.registerForWaitAny(i, box); 267 | } 268 | FiberExt.yield(); 269 | return currentFiber.wakeUpObject; 270 | } 271 | 272 | struct SchedulerBlock { 273 | shared IntrusiveQueue!(FiberExt, RawEvent) queue; 274 | shared uint assigned; 275 | size_t[1] padding; 276 | } 277 | static assert(SchedulerBlock.sizeof == 64); 278 | 279 | class FiberExt : Fiber { 280 | FiberExt next; 281 | uint numScheduler; 282 | int bytesTransfered; 283 | int wakeUpObject; 284 | 285 | enum PAGESIZE = 4096; 286 | 287 | this(void function() fn, uint numSched) nothrow { 288 | super(fn); 289 | numScheduler = numSched; 290 | } 291 | 292 | this(void delegate() dg, uint numSched) nothrow { 293 | super(dg); 294 | numScheduler = numSched; 295 | } 296 | 297 | void schedule() nothrow 298 | { 299 | scheds[numScheduler].queue.push(this); 300 | } 301 | } 302 | 303 | package(photon) shared SchedulerBlock[] scheds; 304 | 305 | enum MAX_THREADPOOL_SIZE = 100; 306 | FiberExt currentFiber; 307 | __gshared Map!(SOCKET, FiberExt) ioWaiters = new Map!(SOCKET, FiberExt); // mapping of sockets to awaiting fiber 308 | __gshared RawEvent termination; // termination event, triggered once last fiber exits 309 | __gshared HANDLE iocp; // IO Completion port 310 | __gshared PTP_POOL threadPool; // for synchronious syscalls 311 | __gshared TP_CALLBACK_ENVIRON_V3 environ; // callback environment for the pool 312 | shared int alive; // count of non-terminated Fibers scheduled 313 | 314 | 315 | public void startloop() { 316 | SYSTEM_INFO info; 317 | GetSystemInfo(&info); 318 | // TODO: handle NUMA case 319 | uint threads = info.dwNumberOfProcessors; 320 | debug(photon_single) { 321 | threads = 1; 322 | } 323 | scheds = new SchedulerBlock[threads]; 324 | foreach(ref sched; scheds) { 325 | sched.queue = IntrusiveQueue!(FiberExt, RawEvent)(RawEvent(0)); 326 | } 327 | threadPool = CreateThreadpool(null); 328 | wenforce(threadPool != null, "Failed to create threadpool"); 329 | SetThreadpoolThreadMaximum(threadPool, MAX_THREADPOOL_SIZE); 330 | wenforce(SetThreadpoolThreadMinimum(threadPool, 1) == TRUE, "Failed to set threadpool minimum size"); 331 | InitializeThreadpoolEnvironment(&environ); 332 | SetThreadpoolCallbackPool(&environ, threadPool); 333 | 334 | termination = RawEvent(false); 335 | iocp = CreateIoCompletionPort(cast(HANDLE)INVALID_HANDLE_VALUE, null, 0, 1); 336 | wenforce(iocp != null, "Failed to create IO Completion Port"); 337 | wenforce(CreateThread(null, 0, &eventLoop, null, 0, null) != null, "Failed to start event loop"); 338 | } 339 | 340 | /// Convenience overload for functions 341 | public void go(void function() func) { 342 | go({ func(); }); 343 | } 344 | 345 | /// Setup a fiber task to run on the Photon scheduler. 346 | public void go(void delegate() func) { 347 | import std.random; 348 | uint choice; 349 | if (scheds.length == 1) choice = 0; 350 | else { 351 | uint a = uniform!"[)"(0, cast(uint)scheds.length); 352 | uint b = uniform!"[)"(0, cast(uint)scheds.length-1); 353 | if (a == b) b = cast(uint)scheds.length-1; 354 | uint loadA = scheds[a].assigned; 355 | uint loadB = scheds[b].assigned; 356 | if (loadA < loadB) choice = a; 357 | else choice = b; 358 | } 359 | atomicOp!"+="(scheds[choice].assigned, 1); 360 | atomicOp!"+="(alive, 1); 361 | auto f = new FiberExt(func, choice); 362 | logf("Assigned %x -> %d scheduler", cast(void*)f, choice); 363 | f.schedule(); 364 | } 365 | 366 | package(photon) void schedulerEntry(size_t n) 367 | { 368 | // TODO: handle NUMA case 369 | wenforce(SetThreadAffinityMask(GetCurrentThread(), 1L< 0) { 372 | sched.queue.event.waitAndReset(); 373 | for(;;) { 374 | FiberExt f = sched.queue.drain(); 375 | if (f is null) break; // drained an empty queue, time to sleep 376 | do { 377 | auto next = f.next; //save next, it will be reused on scheduling 378 | currentFiber = f; 379 | logf("Fiber %x started", cast(void*)f); 380 | try { 381 | f.call(); 382 | } 383 | catch (Exception e) { 384 | stderr.writeln(e); 385 | atomicOp!"-="(alive, 1); 386 | } 387 | if (f.state == FiberExt.State.TERM) { 388 | logf("Fiber %s terminated", cast(void*)f); 389 | atomicOp!"-="(alive, 1); 390 | } 391 | f = next; 392 | } while(f !is null); 393 | } 394 | } 395 | termination.trigger(); 396 | foreach (ref s; scheds) { 397 | s.queue.event.trigger(); 398 | } 399 | } 400 | 401 | enum int MAX_COMPLETIONS = 500; 402 | 403 | extern(Windows) uint eventLoop(void* param) { 404 | HANDLE[2] events; 405 | events[0] = iocp; 406 | events[1] = cast(HANDLE)termination.ev; 407 | logf("Started event loop! IOCP = %x termination = %x", iocp, termination.ev); 408 | for (;;) { 409 | auto ret = WaitForMultipleObjects(2, events.ptr, FALSE, INFINITE); 410 | logf("Got signalled in event loop %d", ret); 411 | if (ret == WAIT_OBJECT_0) { // iocp 412 | OVERLAPPED_ENTRY[MAX_COMPLETIONS] entries = void; 413 | uint count = 0; 414 | while(GetQueuedCompletionStatusEx(iocp, entries.ptr, MAX_COMPLETIONS, &count, 0, FALSE)) { 415 | logf("Dequeued I/O events=%d", count); 416 | foreach (e; entries[0..count]) { 417 | SOCKET sock = cast(SOCKET)e.lpCompletionKey; 418 | FiberExt fiber = ioWaiters[sock]; 419 | fiber.bytesTransfered = cast(int)e.dwNumberOfBytesTransferred; 420 | fiber.schedule(); 421 | } 422 | if (count < MAX_COMPLETIONS) break; 423 | } 424 | } 425 | else if (ret == WAIT_OBJECT_0 + 1) { // termination 426 | break; 427 | } 428 | else { 429 | logf("Failed to wait for multiple objects: %x", ret); 430 | break; 431 | } 432 | } 433 | ExitThread(0); 434 | return 0; 435 | } 436 | 437 | 438 | // =========================================================================== 439 | // INTERCEPTS 440 | // =========================================================================== 441 | 442 | extern(Windows) SOCKET socket(int af, int type, int protocol) { 443 | logf("Intercepted socket!"); 444 | SOCKET s = WSASocketW(af, type, protocol, null, 0, WSA_FLAG_OVERLAPPED); 445 | registerSocket(s); 446 | return s; 447 | } 448 | 449 | struct AcceptState { 450 | SOCKET socket; 451 | sockaddr* addr; 452 | LPINT addrlen; 453 | FiberExt fiber; 454 | } 455 | 456 | extern(Windows) VOID acceptJob(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) 457 | { 458 | AcceptState* state = cast(AcceptState*)Context; 459 | logf("Started threadpool job"); 460 | SOCKET resp = WSAAccept(state.socket, state.addr, state.addrlen, null, 0); 461 | if (resp != INVALID_SOCKET) { 462 | registerSocket(resp); 463 | } 464 | state.socket = resp; 465 | state.fiber.schedule(); 466 | } 467 | 468 | extern(Windows) SOCKET accept(SOCKET s, sockaddr* addr, LPINT addrlen) { 469 | logf("Intercepted accept!"); 470 | AcceptState state; 471 | state.socket = s; 472 | state.addr = addr; 473 | state.addrlen = addrlen; 474 | state.fiber = currentFiber; 475 | PTP_WORK work = CreateThreadpoolWork(&acceptJob, &state, &environ); 476 | wenforce(work != null, "Failed to create work for threadpool"); 477 | SubmitThreadpoolWork(work); 478 | FiberExt.yield(); 479 | CloseThreadpoolWork(work); 480 | return state.socket; 481 | } 482 | 483 | void registerSocket(SOCKET s) { 484 | HANDLE port = iocp; 485 | wenforce(CreateIoCompletionPort(cast(void*)s, port, cast(size_t)s, 0) == port, "failed to register I/O completion"); 486 | } 487 | 488 | extern(Windows) int recv(SOCKET s, void* buf, int len, int flags) { 489 | OVERLAPPED overlapped; 490 | WSABUF wsabuf = WSABUF(cast(uint)len, buf); 491 | ioWaiters[s] = currentFiber; 492 | uint received = 0; 493 | int ret = WSARecv(s, &wsabuf, 1, &received, cast(uint*)&flags, cast(LPWSAOVERLAPPED)&overlapped, null); 494 | logf("Got recv %d", ret); 495 | if (ret >= 0) { 496 | FiberExt.yield(); 497 | return received; 498 | } 499 | else { 500 | auto lastError = GetLastError(); 501 | logf("Last error = %d", lastError); 502 | if (lastError == ERROR_IO_PENDING) { 503 | FiberExt.yield(); 504 | return currentFiber.bytesTransfered; 505 | } 506 | else 507 | return ret; 508 | } 509 | } 510 | 511 | extern(Windows) int send(SOCKET s, void* buf, int len, int flags) { 512 | OVERLAPPED overlapped; 513 | WSABUF wsabuf = WSABUF(cast(uint)len, buf); 514 | ioWaiters[s] = currentFiber; 515 | uint sent = 0; 516 | int ret = WSASend(s, &wsabuf, 1, &sent, flags, cast(LPWSAOVERLAPPED)&overlapped, null); 517 | logf("Get send %d", ret); 518 | if (ret >= 0) { 519 | FiberExt.yield(); 520 | return sent; 521 | } 522 | else { 523 | auto lastError = GetLastError(); 524 | logf("Last error = %d", lastError); 525 | if (lastError == ERROR_IO_PENDING) { 526 | FiberExt.yield(); 527 | return currentFiber.bytesTransfered; 528 | } 529 | else 530 | return ret; 531 | } 532 | } 533 | 534 | 535 | extern(Windows) void Sleep(DWORD dwMilliseconds) { 536 | if (currentFiber !is null) { 537 | auto tm = timer(); 538 | tm.wait(dwMilliseconds.msecs); 539 | } else { 540 | SleepEx(dwMilliseconds, FALSE); 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /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 | 7 | struct WSABUF 8 | { 9 | uint length; 10 | void* buf; 11 | } 12 | 13 | extern(Windows) SOCKET WSASocketW( 14 | int af, 15 | int type, 16 | int protocol, 17 | void* lpProtocolInfo, 18 | WORD g, 19 | DWORD dwFlags 20 | ); 21 | 22 | // hackish, we do not use LPCONDITIONPROC 23 | alias LPCONDITIONPROC = void*; 24 | alias LPWSABUF = WSABUF*; 25 | 26 | extern(Windows) SOCKET WSAAccept( 27 | SOCKET s, 28 | sockaddr *addr, 29 | LPINT addrlen, 30 | LPCONDITIONPROC lpfnCondition, 31 | DWORD_PTR dwCallbackData 32 | ); 33 | 34 | extern(Windows) int WSARecv( 35 | SOCKET s, 36 | LPWSABUF lpBuffers, 37 | DWORD dwBufferCount, 38 | LPDWORD lpNumberOfBytesRecvd, 39 | LPDWORD lpFlags, 40 | LPWSAOVERLAPPED lpOverlapped, 41 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 42 | ); 43 | 44 | extern(Windows) int WSASend( 45 | SOCKET s, 46 | LPWSABUF lpBuffers, 47 | DWORD dwBufferCount, 48 | LPDWORD lpNumberOfBytesSent, 49 | DWORD dwFlags, 50 | LPWSAOVERLAPPED lpOverlapped, 51 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 52 | ); 53 | 54 | struct OVERLAPPED_ENTRY { 55 | ULONG_PTR lpCompletionKey; 56 | LPOVERLAPPED lpOverlapped; 57 | ULONG_PTR Internal; 58 | DWORD dwNumberOfBytesTransferred; 59 | } 60 | 61 | alias LPOVERLAPPED_ENTRY = OVERLAPPED_ENTRY*; 62 | 63 | extern(Windows) BOOL GetQueuedCompletionStatusEx( 64 | HANDLE CompletionPort, 65 | LPOVERLAPPED_ENTRY lpCompletionPortEntries, 66 | ULONG ulCount, 67 | PULONG ulNumEntriesRemoved, 68 | DWORD dwMilliseconds, 69 | BOOL fAlertable 70 | ); 71 | 72 | enum WSA_FLAG_OVERLAPPED = 0x01; 73 | 74 | struct TP_POOL; 75 | 76 | alias PTP_POOL = TP_POOL*; 77 | 78 | extern(Windows) PTP_POOL CreateThreadpool( 79 | PVOID reserved 80 | ); 81 | 82 | extern(Windows) void SetThreadpoolThreadMaximum( 83 | PTP_POOL ptpp, 84 | DWORD cthrdMost 85 | ); 86 | 87 | extern(Windows) BOOL SetThreadpoolThreadMinimum( 88 | PTP_POOL ptpp, 89 | DWORD cthrdMic 90 | ); 91 | 92 | alias TP_VERSION = DWORD; 93 | alias PTP_VERSION = TP_VERSION*; 94 | 95 | struct TP_CALLBACK_INSTANCE; 96 | alias PTP_CALLBACK_INSTANCE = TP_CALLBACK_INSTANCE*; 97 | 98 | alias PTP_SIMPLE_CALLBACK = extern(Windows) VOID function(PTP_CALLBACK_INSTANCE, PVOID); 99 | 100 | enum TP_CALLBACK_PRIORITY : int { 101 | TP_CALLBACK_PRIORITY_HIGH, 102 | TP_CALLBACK_PRIORITY_NORMAL, 103 | TP_CALLBACK_PRIORITY_LOW, 104 | TP_CALLBACK_PRIORITY_INVALID, 105 | TP_CALLBACK_PRIORITY_COUNT = TP_CALLBACK_PRIORITY_INVALID 106 | } 107 | 108 | struct TP_POOL_STACK_INFORMATION { 109 | SIZE_T StackReserve; 110 | SIZE_T StackCommit; 111 | } 112 | alias PTP_POOL_STACK_INFORMATION = TP_POOL_STACK_INFORMATION*; 113 | 114 | struct TP_CLEANUP_GROUP; 115 | alias PTP_CLEANUP_GROUP = TP_CLEANUP_GROUP*; 116 | 117 | alias PTP_CLEANUP_GROUP_CANCEL_CALLBACK = extern(Windows) VOID function(PVOID, PVOID); 118 | 119 | struct ACTIVATION_CONTEXT; 120 | 121 | struct TP_CALLBACK_ENVIRON_V3 { 122 | TP_VERSION Version; 123 | PTP_POOL Pool; 124 | PTP_CLEANUP_GROUP CleanupGroup; 125 | PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback; 126 | PVOID RaceDll; 127 | ACTIVATION_CONTEXT* ActivationContext; 128 | PTP_SIMPLE_CALLBACK FinalizationCallback; 129 | DWORD Flags; 130 | TP_CALLBACK_PRIORITY CallbackPriority; 131 | DWORD Size; 132 | } 133 | 134 | alias TP_CALLBACK_ENVIRON = TP_CALLBACK_ENVIRON_V3; 135 | alias PTP_CALLBACK_ENVIRON = TP_CALLBACK_ENVIRON*; 136 | 137 | VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON cbe) { 138 | cbe.Pool = NULL; 139 | cbe.CleanupGroup = NULL; 140 | cbe.CleanupGroupCancelCallback = NULL; 141 | cbe.RaceDll = NULL; 142 | cbe.ActivationContext = NULL; 143 | cbe.FinalizationCallback = NULL; 144 | cbe.Flags = 0; 145 | cbe.Version = 3; 146 | cbe.CallbackPriority = TP_CALLBACK_PRIORITY.TP_CALLBACK_PRIORITY_NORMAL; 147 | cbe.Size = TP_CALLBACK_ENVIRON.sizeof; 148 | } 149 | 150 | extern(Windows) void CloseThreadpool( 151 | PTP_POOL ptpp 152 | ); 153 | 154 | // inline "function" 155 | VOID SetThreadpoolCallbackPool(PTP_CALLBACK_ENVIRON cbe, PTP_POOL pool) { cbe.Pool = pool; } 156 | 157 | struct TP_WORK; 158 | alias PTP_WORK = TP_WORK*; 159 | 160 | struct TP_WAIT; 161 | alias PTP_WAIT = TP_WAIT*; 162 | 163 | struct TP_TIMER; 164 | alias PTP_TIMER = TP_TIMER*; 165 | 166 | alias TP_WAIT_RESULT = DWORD; 167 | 168 | alias PTP_WORK_CALLBACK = extern(Windows) VOID function (PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work); 169 | alias PTP_WAIT_CALLBACK = extern(Windows) VOID function (PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult); 170 | alias PTP_TIMER_CALLBACK = extern(Windows) VOID function(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer); 171 | 172 | extern(Windows) PTP_WORK CreateThreadpoolWork( 173 | PTP_WORK_CALLBACK pfnwk, 174 | PVOID pv, 175 | PTP_CALLBACK_ENVIRON pcbe 176 | ); 177 | 178 | extern(Windows) PTP_WAIT CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe); 179 | 180 | extern(Windows) void SubmitThreadpoolWork( 181 | PTP_WORK pwk 182 | ); 183 | 184 | extern(Windows) void CloseThreadpoolWork( 185 | PTP_WORK pwk 186 | ); 187 | 188 | extern(Windows) void SetThreadpoolWait( 189 | PTP_WAIT pwa, 190 | HANDLE h, 191 | PFILETIME pftTimeout 192 | ); 193 | 194 | extern(Windows) void CloseThreadpoolWait( 195 | PTP_WAIT pwa 196 | ); 197 | 198 | extern(Windows) PTP_TIMER CreateThreadpoolTimer( 199 | PTP_TIMER_CALLBACK pfnti, 200 | PVOID pv, 201 | PTP_CALLBACK_ENVIRON pcbe 202 | ); 203 | 204 | extern(Windows) void SetThreadpoolTimer( 205 | PTP_TIMER pti, 206 | PFILETIME pftDueTime, 207 | DWORD msPeriod, 208 | DWORD msWindowLength 209 | ); 210 | 211 | extern(Windows) void CloseThreadpoolTimer( 212 | PTP_TIMER pti 213 | ); 214 | 215 | 216 | void outputToConsole(const(wchar)[] msg) 217 | { 218 | HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE); 219 | uint size = cast(uint)msg.length; 220 | WriteConsole(output, msg.ptr, size, &size, null); 221 | } 222 | 223 | void logf(T...)(const(wchar)[] fmt, T args) 224 | { 225 | debug(photon) try { 226 | formattedWrite(&outputToConsole, fmt, args); 227 | formattedWrite(&outputToConsole, "\n"); 228 | } 229 | catch (Exception e) { 230 | outputToConsole("ARGH!"w); 231 | } 232 | } 233 | 234 | -------------------------------------------------------------------------------- /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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 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 | import photon.support : logf; 30 | 31 | // telnet localhost 1337 32 | void server_worker(Socket client) { 33 | ubyte[1024] buffer; 34 | scope(exit) { 35 | client.shutdown(SocketShutdown.BOTH); 36 | client.close(); 37 | } 38 | logf("Started server_worker, client = %s", client); 39 | for(;;) { 40 | ptrdiff_t received = client.receive(buffer); 41 | if (received < 0) { 42 | perror("Error while reading from client"); 43 | return; 44 | } 45 | else if (received == 0) { //socket is closed (eof) 46 | return; 47 | } 48 | else { 49 | logf("Server_worker received:\n<%s>", cast(char[])buffer[0.. received]); 50 | } 51 | ptrdiff_t sent; 52 | do { 53 | ptrdiff_t ret = client.send(buffer[sent .. received]); 54 | if (ret < 0) { 55 | perror("Error while writing to client"); 56 | return; 57 | } 58 | sent += ret; 59 | } while(sent < received); 60 | } 61 | } 62 | 63 | void server() { 64 | Socket server = new TcpSocket(); 65 | server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 66 | server.bind(new InternetAddress("127.0.0.1", 1337)); 67 | server.listen(1000); 68 | 69 | logf("Started server"); 70 | void processClient(Socket client) { 71 | go(() => server_worker(client)); 72 | } 73 | while(true) { 74 | logf("Waiting for server.accept()"); 75 | Socket client = server.accept(); 76 | logf("New client accepted %s", client); 77 | processClient(client); 78 | } 79 | } 80 | 81 | void main() { 82 | startloop(); 83 | go(() => server()); 84 | runFibers(); 85 | } 86 | -------------------------------------------------------------------------------- /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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 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 | startloop(); 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 | runFibers(); 69 | 70 | wr.join(); 71 | } 72 | --------------------------------------------------------------------------------