Explorar o código

[D, Juptune] Fix: Don't recreate reader & writer each loop - supports pipelining (#10036)

* fix(d,juptune): Don't recreate reader & writer each loop - supports pipelining

* feat(d,juptune): add JSON benchmark
Bradley Chatha hai 1 mes
pai
achega
7e6c49cc7e

+ 1 - 0
frameworks/D/juptune/benchmark_config.json

@@ -4,6 +4,7 @@
     {
       "default": {
         "plaintext_url": "/plaintext",
+        "json_url": "/json",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Platform",

+ 1 - 1
frameworks/D/juptune/juptune.dockerfile

@@ -1,7 +1,7 @@
 FROM debian:bookworm-slim
 
 ARG LDC_VERSION=1.41.0
-ARG JUPTUNE_REF=3a27a36ce2f5f2ff7df7151311e18d9c3660ea8c
+ARG JUPTUNE_REF=52294fa45912dacac24efc80b4a2fad8db958841
 ARG TFB_TEST_NAME
 
 ENV TEST_NAME=${TFB_TEST_NAME}

+ 24 - 7
frameworks/D/juptune/src/main.d

@@ -9,7 +9,7 @@ import  juptune.core.util,
         juptune.http;
 
 import tests.common : log;
-import tests.plaintext;
+import tests.plaintext, tests.json;
 
 /++++ Constant config ++++/
 
@@ -73,6 +73,7 @@ void router() nothrow
         {
             FAILSAFE,
             plaintext,
+            json,
         }
 
         enum Method
@@ -84,6 +85,7 @@ void router() nothrow
         union RouteInput
         {
             PlainTextHeaderInput plaintext;
+            JsonHeaderInput json;
         }
 
         while(!juptuneEventLoopIsThreadCanceled())
@@ -103,17 +105,18 @@ void router() nothrow
                     auto _ = client.close();
 
                 Http1MessageSummary readSummary, writeSummary;
+
+                // Read & Write primitives
+                ubyte[HTTP_READ_BUFFER_BYTES] readBuffer;
+                ubyte[HTTP_WRITE_BUFFER_BYTES] writeBuffer;
+                auto reader = Http1Reader(client, readBuffer, HTTP_CONFIG);
+                auto writer = Http1Writer(client, writeBuffer, HTTP_CONFIG);
+
                 do
                 {
                     if(!client.isOpen)
                         return;
 
-                    // Read & Write primitives
-                    ubyte[HTTP_READ_BUFFER_BYTES] readBuffer;
-                    ubyte[HTTP_WRITE_BUFFER_BYTES] writeBuffer;
-                    auto reader = Http1Reader(client, readBuffer, HTTP_CONFIG);
-                    auto writer = Http1Writer(client, writeBuffer, HTTP_CONFIG);
-
                     // Routing state
                     Route route;
                     Method method;
@@ -158,6 +161,10 @@ void router() nothrow
                                     route = Route.plaintext;
                                     break;
 
+                                case "/json":
+                                    route = Route.json;
+                                    break;
+
                                 default:
                                     setError(404, "Not found");
                                     break;
@@ -195,6 +202,9 @@ void router() nothrow
                                 
                                 case plaintext:
                                     break;
+
+                                case json:
+                                    break;
                             }
                         });
                     }
@@ -218,6 +228,9 @@ void router() nothrow
                                 
                                 case plaintext:
                                     break;
+
+                                case json:
+                                    break;
                             }
                         });
                     } while(chunk.hasDataLeft);
@@ -255,6 +268,10 @@ void router() nothrow
                         case plaintext:
                             handlePlainText(input.plaintext, writer, writeSummary);
                             break;
+
+                        case json:
+                            handleJson(input.json, writer, writeSummary);
+                            break;
                     }
                 } while(!readSummary.connectionClosed && !writeSummary.connectionClosed);
             }, client, &asyncMoveSetter!TcpSocket);

+ 1 - 0
frameworks/D/juptune/src/meson.build

@@ -7,6 +7,7 @@ project('juptune-tfb', 'd')
 srcs = files(
     './main.d',
     './tests/common.d',
+    './tests/json.d',
     './tests/plaintext.d',
 )
 

+ 103 - 0
frameworks/D/juptune/src/tests/json.d

@@ -0,0 +1,103 @@
+module tests.json;
+
+import juptune.core.ds : ArrayNonShrink;
+import juptune.core.util : Result;
+import juptune.http : Http1Writer, Http1Version, Http1MessageSummary;
+
+import tests.common : putServerAndDate, log;
+
+struct JsonHeaderInput
+{
+    // No header input, it's here just so the overarching logic exists.
+}
+
+private struct Message
+{
+    string message;
+}
+
+void handleJson(
+    scope ref JsonHeaderInput input, 
+    scope ref Http1Writer writer,
+    scope ref Http1MessageSummary summary,
+) nothrow
+{
+    import juptune.core.util      : then;
+    import juptune.core.util.conv : IntToCharBuffer, toBase10;
+
+    ArrayNonShrink!char buffer;
+    buffer.reserve(256);
+
+    auto result = serialise(buffer, Message("Hello, World!"));
+    if(result.isError)
+    {
+        result = writer.putResponseLine(Http1Version.http11, 500, "Internal Error").then!(
+            () => writer.putServerAndDate(),
+            () => writer.finishHeaders(),
+            () => writer.finishBody(),
+            () => writer.finishTrailers(),
+            () => writer.finishMessage(summary)
+        );
+        if(result.isError)
+        {
+            log("writing error response [json] failed: ", result);
+            return;
+        }
+
+        log("serialising value failed: ", result);
+        return;
+    }
+
+    IntToCharBuffer contentLengthBuffer;
+    const contentLength = toBase10(buffer.length, contentLengthBuffer);
+
+    result = writer.putResponseLine(Http1Version.http11, 200, "OK").then!(
+        () => writer.putServerAndDate(),
+        () => writer.putHeader("Content-Length", contentLength),
+        () => writer.putHeader("Content-Type", "application/json"),
+        () => writer.finishHeaders(),
+        () => writer.putBody(buffer.slice),
+        () => writer.finishBody(),
+        () => writer.finishTrailers(),
+        () => writer.finishMessage(summary)
+    );
+    if(result.isError)
+    {
+        log("writing response [json] failed: ", result);
+        return;
+    }
+}
+
+// There's currently no built-in serialiser, however because of D's incredibly powerful metaprogramming
+// this watered-down serialiser would likely generate almost the exact same code as a full-blown serialiser
+// in this simple case.
+private Result serialise(T)(scope ref ArrayNonShrink!char buffer, T value)
+if(is(T == struct))
+{
+    import juptune.data.json : JsonBuilder;
+    scope append = (scope const(char)[] text) {
+        buffer.put(text);
+        return Result.noError;
+    };
+
+    ubyte[8] depthBuffer;
+    auto builder = JsonBuilder!(typeof(append))(append, depthBuffer[]);
+
+    auto result = builder.startObject();
+    if(result.isError)
+        return result;
+
+    static foreach(fieldSymbol; value.tupleof)
+    {{
+        immutable FieldName = __traits(identifier, fieldSymbol);
+
+        result = builder.putObjectValue(FieldName, mixin("value."~FieldName));
+        if(result.isError)
+            return result;
+    }}
+
+    result = builder.endObject();
+    if(result.isError)
+        return result;
+    return builder.finish();
+}