diff --git a/frameworks/Dart/dart3/benchmark_config.json b/frameworks/Dart/dart3/benchmark_config.json index 059fb368d0e..a06395953f8 100644 --- a/frameworks/Dart/dart3/benchmark_config.json +++ b/frameworks/Dart/dart3/benchmark_config.json @@ -2,25 +2,32 @@ "framework": "dart3", "tests": [ { - "default": { + "default": { + "display_name": "dart3_native", "json_url": "/json", "plaintext_url": "/plaintext", "port": 8080, "approach": "Stripped", "classification": "Platform", "database": "None", - "framework": "None", "language": "Dart", - "flavor": "None", - "orm": "None", - "platform": "None", - "webserver": "None", "os": "Linux", - "database_os": "Linux", - "display_name": "dart3", - "notes": "", - "versus": "None" + "database_os": "Linux" + } + }, + { + "aot": { + "display_name": "dart3_aot", + "json_url": "/json", + "plaintext_url": "/plaintext", + "port": 8080, + "approach": "Stripped", + "classification": "Platform", + "database": "None", + "language": "Dart", + "os": "Linux", + "database_os": "Linux" } } ] -} +} \ No newline at end of file diff --git a/frameworks/Dart/dart3/bin/server.dart b/frameworks/Dart/dart3/bin/server.dart index 7a512169dd4..2a5b337da5b 100755 --- a/frameworks/Dart/dart3/bin/server.dart +++ b/frameworks/Dart/dart3/bin/server.dart @@ -1,15 +1,62 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; +import 'dart:math' show min; + +/// Environment declarations are evaluated at compile-time. Use 'const' to +/// ensure values are baked into AOT/Native binaries for the benchmark. +/// +/// From https://api.dart.dev/dart-core/int/int.fromEnvironment.html: +/// "This constructor is only guaranteed to work when invoked as const. +/// It may work as a non-constant invocation on some platforms +/// which have access to compiler options at run-time, +/// but most ahead-of-time compiled platforms will not have this information." +const _maxIsolatesfromEnvironment = int.fromEnvironment('MAX_ISOLATES'); void main(List args) async { - /// Isolate count per process, set at build-time via: - /// dart compile exe --define=MAX_ISOLATES=X - /// Defaults to machine core count for dynamic scaling. - final maxIsolates = int.fromEnvironment( - 'MAX_ISOLATES', - defaultValue: Platform.numberOfProcessors, - ); + /// Defines local isolate quota, using MAX_ISOLATES if provided. + /// Falls back to total available cores while respecting hardware limits. + var maxIsolates = _maxIsolatesfromEnvironment > 0 + ? min(_maxIsolatesfromEnvironment, Platform.numberOfProcessors) + : Platform.numberOfProcessors; + + /// Triggers process-level horizontal scaling when running in AOT. + if (Platform.script.toFilePath().endsWith('.aot')) { + /// Internal token used to notify newly spawned processes that they + /// belong to a secondary "worker group". + const workerGroupTag = '--workerGroup'; + + /// Determine if this process instance was initialized as a worker group. + final isWorkerGroup = args.contains(workerGroupTag); + + if (isWorkerGroup) { + /// Sanitize the argument list to ensure the internal token does not + /// interfere with application-level argument parsing. + args.removeAt(args.indexOf(workerGroupTag)); + } + /// Prevents recursive spawning + /// by ensuring only the primary process can spawn worker groups + else { + /// Calculate the number of secondary worker groups required + /// to fully utilize the available hardware capacity. + /// + /// Each group serves as a container for multiple isolates, + /// helping to bypass internal VM scaling bottlenecks. + final workerGroups = Platform.numberOfProcessors ~/ maxIsolates - 1; + + /// Bootstraps independent worker processes via AOT snapshots. + /// Each process initializes its own Isolate Group. + for (var i = 0; i < workerGroups; i++) { + /// [Platform.script] identifies the AOT snapshot or executable. + /// [Isolate.spawnUri] spawns a new process group via [main()]. + Isolate.spawnUri(Platform.script, [workerGroupTag, ...args], null); + } + + /// Updates local isolate limits, assigning the primary group + /// the remaining cores after worker group allocation. + maxIsolates = Platform.numberOfProcessors - workerGroups * maxIsolates; + } + } /// Create an [Isolate] containing an [HttpServer] /// for each processor after the first @@ -37,6 +84,19 @@ Future _startServer(List _) async { /// Handles [HttpRequest]'s from [HttpServer]. await for (final request in server) { + /// Asynchronously processes each request with an 8-second safety deadline + /// to prevent stalled connections from blocking the isolate event loop. + _handleRequest(request).timeout( + const Duration(seconds: 8), + onTimeout: () => _sendResponse(request, HttpStatus.internalServerError), + ); + } +} + +/// Dispatches requests to specific test handlers. Wrapped in a try-catch +/// to ensure stable execution and guaranteed response delivery. +Future _handleRequest(HttpRequest request) async { + try { switch (request.uri.path) { case '/json': _jsonTest(request); @@ -47,6 +107,8 @@ Future _startServer(List _) async { default: _sendResponse(request, HttpStatus.notFound); } + } catch (e) { + _sendResponse(request, HttpStatus.internalServerError); } } diff --git a/frameworks/Dart/dart3/dart3-aot.dockerfile b/frameworks/Dart/dart3/dart3-aot.dockerfile new file mode 100644 index 00000000000..bfe70a492de --- /dev/null +++ b/frameworks/Dart/dart3/dart3-aot.dockerfile @@ -0,0 +1,24 @@ + +FROM dart:3.10.7 AS build +WORKDIR /app + +# Define the build-time argument (Default to 8) +ARG MAX_ISOLATES=8 + +COPY pubspec.yaml . +COPY bin bin + +RUN dart compile aot-snapshot bin/server.dart \ + --define=MAX_ISOLATES=${MAX_ISOLATES} \ + -o server.aot + +FROM gcr.io/distroless/base-debian12 +WORKDIR /app + +COPY --from=build /usr/lib/dart/bin/dartaotruntime /usr/lib/dart/bin/dartaotruntime +COPY --from=build /app/server.aot /app/server.aot + +EXPOSE 8080 + +# Distroless requires absolute paths +ENTRYPOINT ["/usr/lib/dart/bin/dartaotruntime", "/app/server.aot"] diff --git a/frameworks/Dart/dart3/dart3.dockerfile b/frameworks/Dart/dart3/dart3.dockerfile index ed5f060df90..278eedcc0a3 100644 --- a/frameworks/Dart/dart3/dart3.dockerfile +++ b/frameworks/Dart/dart3/dart3.dockerfile @@ -1,25 +1,28 @@ -FROM dart:3.10.7 AS builder +FROM dart:3.10.7 AS build WORKDIR /app # Define the build-time argument (Default to 8) ARG MAX_ISOLATES=8 -COPY . . -RUN mkdir build + +COPY pubspec.yaml . +COPY bin bin + RUN dart compile exe bin/server.dart \ --define=MAX_ISOLATES=${MAX_ISOLATES} \ - -o build/server + -o server FROM busybox:glibc +WORKDIR /app -# Re-declare ARG in the second stage to use it in ENV -# Define the build-time argument (Default to 8) +# Re-declare ARG 'MAX_ISOLATES' in the second stage to use it in ENV ARG MAX_ISOLATES=8 ENV MAX_ISOLATES_PER_PROCESS=${MAX_ISOLATES} -COPY --from=builder /runtime/ / -COPY --from=builder /app/build/server /bin/server +COPY --from=build /runtime/ / +COPY --from=build /app/server /app/server COPY run.sh /bin/run.sh + RUN chmod +x /bin/run.sh EXPOSE 8080 diff --git a/frameworks/Dart/dart3/run.sh b/frameworks/Dart/dart3/run.sh index 767d14c5451..e04fa806800 100644 --- a/frameworks/Dart/dart3/run.sh +++ b/frameworks/Dart/dart3/run.sh @@ -9,10 +9,10 @@ MAX_ISO=${MAX_ISOLATES_PER_PROCESS} NUM_WORKERS=$((TOTAL_CORES / MAX_ISO)) if [ "$NUM_WORKERS" -le 0 ]; then NUM_WORKERS=1; fi -echo "Scaling: $TOTAL_CORES cores / $MAX_ISO isolates = $NUM_WORKERS processes" - +# Bootstrap the calculated number of independent Dart processes. for i in $(seq 1 $NUM_WORKERS); do - /bin/server & + /app/server & done +# Keep the shell alive to manage the backgrounded process group. wait \ No newline at end of file