From e0ef7ba79a06e9251ba456ed05bc9b67eaec48eb Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 1 May 2019 00:29:21 -0400 Subject: [PATCH] Sudden crashes, yay! --- Makefile | 10 ++-- benchmark/empty.dart | 23 ++++++++ benchmark/util.dart | 110 +++++++++++++++++++++++++++++++++++ example/main.dart | 7 ++- example/main.dill | Bin 2011096 -> 0 bytes example/pretty_log.dart | 2 +- example/shared.dart | 22 +++++++ lib/src/bind.cc | 7 ++- lib/src/http.cc | 102 +++++++++++++++++++++++--------- lib/src/libangel_wings.dylib | Bin 90608 -> 100008 bytes lib/src/util.cc | 10 +++- lib/src/wings_driver.dart | 14 ++++- lib/src/wings_request.dart | 105 ++++++++++++++++++++++----------- lib/src/wings_socket.cc | 53 +++++++++++++---- lib/src/wings_socket.dart | 7 ++- lib/src/wings_socket.h | 6 +- libangel_wings.dylib | Bin 90608 -> 100008 bytes 17 files changed, 391 insertions(+), 87 deletions(-) create mode 100644 benchmark/empty.dart create mode 100644 benchmark/util.dart delete mode 100644 example/main.dill create mode 100644 example/shared.dart diff --git a/Makefile b/Makefile index de7fe6ea..edd26fc6 100644 --- a/Makefile +++ b/Makefile @@ -16,12 +16,9 @@ clean: find . -type f -name '*.dylib' -delete find . -type f -name '*.dill' -delete -%-run: % example/main.dart +%-run: % example/main.dill dart example/main.dill -example/main.dill: ./**/*.dart - dart --snapshot="$@" example/main.dart - mac: libangel_wings.dylib linux: lib/src/libangel_wings.so @@ -41,4 +38,7 @@ lib/src/libangel_wings.dylib: $(objects) $(CXX) $(CXXFLAGS) -c -o $@ $< %.o: %.cc lib/src/angel_wings.h %.h - $(CXX) $(CXXFLAGS) -c -o $@ $< \ No newline at end of file + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.dill: %.dart + dart --snapshot="$@" $< \ No newline at end of file diff --git a/benchmark/empty.dart b/benchmark/empty.dart new file mode 100644 index 00000000..dcd5bc0e --- /dev/null +++ b/benchmark/empty.dart @@ -0,0 +1,23 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'util.dart'; + +const AngelBenchmark emptyBenchmark = _EmptyBenchmark(); + +main() => runBenchmarks([emptyBenchmark]); + +class _EmptyBenchmark implements AngelBenchmark { + const _EmptyBenchmark(); + + @override + String get name => 'empty'; + + @override + FutureOr rawHandler(HttpRequest req, HttpResponse res) { + return res.close(); + } + + @override + void setupAngel(Angel app) {} +} diff --git a/benchmark/util.dart b/benchmark/util.dart new file mode 100644 index 00000000..80b03ade --- /dev/null +++ b/benchmark/util.dart @@ -0,0 +1,110 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'package:angel_wings/angel_wings.dart'; +import 'package:io/ansi.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:tuple/tuple.dart'; + +Future _runWrk( + {ProcessStartMode mode = ProcessStartMode.inheritStdio}) async { + return await Process.start('wrk', ['http://localhost:$testPort'], mode: mode); +} + +Future _warmUp() async { + var wrk = await _runWrk(); + await wrk.exitCode; + // await wrk.stderr.drain(); + // await wrk.stdout.drain(); +} + +Future _10s() => Future.delayed(Duration(seconds: 10)); + +const testPort = 8877; + +Future runBenchmarks(Iterable benchmarks, + {Iterable factories = const [ + // 'angel_http', + 'angel_wings', + ]}) async { + for (var benchmark in benchmarks) { + print(magenta.wrap('Entering benchmark: ${benchmark.name}')); + + // // Run dart:io + // print(lightGray.wrap('Booting dart:io server (waiting 10s)...')); + // var isolates = []; + // for (int i = 0; i < Platform.numberOfProcessors; i++) { + // isolates.add(await Isolate.spawn(_httpIsolate, benchmark)); + // } + + // await _10s(); + // print(lightGray.wrap('Warming up dart:io server...')); + // await _warmUp(); + + // stdout + // ..write(lightGray.wrap('Now running `wrk` for ')) + // ..write(cyan.wrap(benchmark.name)) + // ..writeln(lightGray.wrap(' (waiting 10s)...')); + // var wrk = await _runWrk(mode: ProcessStartMode.inheritStdio); + // await wrk.exitCode; + // isolates.forEach((i) => i.kill(priority: Isolate.immediate)); + + // Run Angel HTTP, Wings + for (var fac in factories) { + print(lightGray.wrap('Booting $fac server...')); + + var isolates = []; + for (int i = 0; i < Platform.numberOfProcessors; i++) { + isolates + .add(await Isolate.spawn(_angelIsolate, Tuple2(benchmark, fac))); + } + + await _10s(); + print(lightGray.wrap('Warming up $fac server...')); + await _warmUp(); + stdout + ..write(lightGray.wrap('Now running `wrk` for ')) + ..write(cyan.wrap(benchmark.name)) + ..writeln(lightGray.wrap('...')); + var wrk = await _runWrk(mode: ProcessStartMode.inheritStdio); + await wrk.exitCode; + } + } + + exit(0); +} + +void _httpIsolate(AngelBenchmark benchmark) { + Future(() async { + var raw = await HttpServer.bind(InternetAddress.loopbackIPv4, testPort, + shared: true); + raw.listen((r) => benchmark.rawHandler(r, r.response)); + }); +} + +void _angelIsolate(Tuple2 args) { + Future(() async { + var app = Angel(); + Driver driver; + + if (args.item2 == 'angel_http') + driver = AngelHttp.custom(app, startShared); + else if (args.item2 == 'angel_wings') + driver = AngelWings.custom(app, startSharedWings); + + await app.configure(args.item1.setupAngel); + await driver.startServer(InternetAddress.loopbackIPv4, testPort); + }); +} + +abstract class AngelBenchmark { + const AngelBenchmark(); + + String get name; + + FutureOr setupAngel(Angel app); + + FutureOr rawHandler(HttpRequest req, HttpResponse res); +} diff --git a/example/main.dart b/example/main.dart index c5f4e13d..7f951f99 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_static/angel_static.dart'; import 'package:angel_wings/angel_wings.dart'; @@ -16,9 +17,13 @@ main() async { app.mimeTypeResolver.addExtension('yaml', 'text/x-yaml'); app.get('/', (req, res) => 'WINGS!!!'); + app.post('/', (req, res) async { + await req.parseBody(); + return req.bodyAsMap; + }); app.fallback(vDir.handleRequest); app.fallback((req, res) => throw AngelHttpException.notFound()); - await wings.startServer('127.0.0.1', 3000); + await wings.startServer(InternetAddress.loopbackIPv6, 3000); print('Listening at ${wings.uri}'); } diff --git a/example/main.dill b/example/main.dill deleted file mode 100644 index d3327efd2acab6d6f4ced1a58f350e07ac088901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2011096 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