Browse Source

Add a benchmark of wizzardo-http with inline-java. (#5383)

* Add a benchmark of wizzardo-http with inline-java.

* Rename wizzardo-http-inline-java to wizzardo-inline.

* Add source code for wizzardo-inline.

* Change vs line for wizzardo-inline.

* Use a newer stack, which should avoid rebuilding Cabal.
Facundo Domínguez 5 years ago
parent
commit
a1ea0626ef

+ 1 - 0
.travis.yml

@@ -42,6 +42,7 @@ env:
     - "TESTDIR=Haskell/servant"
     - "TESTDIR=Haskell/spock"
     - "TESTDIR=Haskell/warp"
+    - "TESTDIR=Haskell/wizzardo-inline"
     - 'TESTDIR="Java/act Java/comsat"'
     - 'TESTDIR="Java/activeweb Java/armeria Java/baratine Java/bayou Java/blade Java/curacao Java/dropwizard Java/firenio Java/voovan"'
     - 'TESTDIR="Java/gemini Java/greenlightning Java/grizzly Java/helidon Java/httpserver Java/jetty Java/jhttp Java/jooby2 Java/wicket"'

+ 33 - 0
frameworks/Haskell/wizzardo-inline/README.md

@@ -0,0 +1,33 @@
+# wizzardo-inline Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](benchmarks/wizzardo-http/src/Main.hs)
+* [PLAINTEXT](benchmarks/wizzardo-http/src/Main.hs)
+* [DB](benchmarks/wizzardo-http/src/DbHandler.hs)
+
+## Important Libraries
+
+These benchmarks measure the [wizzardo-http][wizzardo-http] server when given
+Haskell callbacks using [inline-java][inline-java].
+
+Furtheremore, an experimental interface of inline-java is used, where
+[-XLinearTypes][linear-types] ensures references to Java objects are
+handled correctly on the Haskell side.
+
+[linear-types]: https://github.com/tweag/ghc-proposals/blob/linear-types2/proposals/0000-linear-types.rst
+[inline-java]: https://github.com/tweag/inline-java
+[wizzardo-http]: https://github.com/wizzardo/webery
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db

+ 27 - 0
frameworks/Haskell/wizzardo-inline/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "wizzardo-inline",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "wizzardo-inline",
+        "language": "Haskell",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "wizzardo-inline",
+        "notes": "",
+        "versus": "warp"
+      }
+    }
+  ]
+}

+ 6 - 0
frameworks/Haskell/wizzardo-inline/env-linear.sh

@@ -0,0 +1,6 @@
+. $(pwd)/set-java-home.sh
+
+export C_INCLUDE_PATH="$JAVA_HOME/include:$JAVA_HOME/include/linux"
+export LIBRARY_PATH="$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/lib/server"
+export LD_LIBRARY_PATH="$LIBRARY_PATH"
+export STACK_YAML=stack-linear.yaml

+ 8 - 0
frameworks/Haskell/wizzardo-inline/set-java-home.sh

@@ -0,0 +1,8 @@
+dir="$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | sed 's/[[:space:]]*java\.home = //')"
+
+if [ -d $dir/include ]
+then
+  export JAVA_HOME="$dir"
+else
+  export JAVA_HOME="$(dirname "$dir")"
+fi

+ 39 - 0
frameworks/Haskell/wizzardo-inline/stack-linear.yaml

@@ -0,0 +1,39 @@
+require-stack-version: ">= 1.6"
+
+resolver: lts-14.6
+compiler: ghc-8.9
+skip-ghc-check: true
+system-ghc: true
+allow-newer: true
+
+packages:
+- wizzardo-http-benchmark
+
+flags:
+  jni:
+    linear-types: true
+  jvm:
+    linear-types: true
+  inline-java:
+    linear-types: true
+
+extra-deps:
+- primitive-0.7.0.0
+- parsec-3.1.13.0 # Pinning parsec avoids rebuilding Cabal
+- Cabal-3.0.0.0
+- constraints-0.11
+- inline-c-0.8.0.1
+- git: https://github.com/tweag/inline-java
+  commit: 5b2552fae1beae88bebe2144da317e7182d759fc
+  subdirs:
+  - jni
+  - jvm
+  - .
+- git: https://github.com/facundominguez/malcolm-wallace-universe
+  commit: 2d02e4e2bcb5840152a1daa05a0ecfff089c6426
+  subdirs:
+  - polyparse-1.12
+- git: https://github.com/tweag/distributed-closure
+  commit: 3c9d3e0c9c2ae224ecd84dd4f9a38bedc2cdfd21
+- git: https://github.com/tweag/linear-base
+  commit: 4d43dc099f6f1381b949ee29ac63ce3088597ab0

+ 10 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/README.md

@@ -0,0 +1,10 @@
+This is the implementation of an Http server using
+[wizzardo-http][wizzardo-http] with Haskell callbacks using
+[inline-java][inline-java].
+
+Furtheremore, an experimental interface of inline-java is used, where
+[-XLinearTypes][linear-types] ensures references to Java objects are
+handled correctly on the Haskell side.
+
+Running the benchmark requires the framework from
+https://github.com/TechEmpower/FrameworkBenchmarks/compare/master...facundominguez:fd/wizzardo-http-inline-java

+ 10 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/Setup.hs

@@ -0,0 +1,10 @@
+import Distribution.Simple
+import Language.Java.Inline.Cabal (addJarsToClasspath, gradleHooks)
+import System.Directory (doesFileExist, getCurrentDirectory)
+import System.FilePath ((</>))
+
+main = do
+    here <- getCurrentDirectory
+    defaultMainWithHooks $
+      addJarsToClasspath [here </> "build/libs/wizzardo-http-benchmark.jar"] $
+      gradleHooks simpleUserHooks

+ 17 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/build.gradle

@@ -0,0 +1,17 @@
+apply plugin: 'java'
+
+repositories {
+    jcenter()
+    mavenCentral()
+    maven {
+        url "https://oss.sonatype.org/content/repositories/snapshots/"
+    }
+}
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+dependencies {
+    compile 'com.wizzardo:http:0.3'
+    compile 'com.wizzardo:reactive-pg-client:0.10.2.1'
+}

+ 2 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/settings.gradle

@@ -0,0 +1,2 @@
+
+rootProject.name = 'wizzardo-http-benchmark'

+ 127 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/src/main/haskell/DbHandler.hs

@@ -0,0 +1,127 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+-- The code in Java here has been copied from the benchmark wizzardo-http
+-- in https://github.com/TechEmpower/FrameworkBenchmarks
+module DbHandler (createDbHandler) where
+
+import Control.Monad.IO.Class.Linear (MonadIO)
+import qualified Control.Monad.Linear.Builder as Linear
+import Data.Aeson (ToJSON(..), encode, object, (.=))
+import Data.ByteString.Lazy (toStrict)
+import Data.Int (Int32)
+import Data.String (fromString)
+import Foreign.JNI.Safe (newGlobalRef_)
+import qualified Language.Java as NonLinear
+import Language.Java.Inline.Safe
+import Language.Java.Function (createIntIntToObjFunction)
+import Language.Java.Safe (J, JType(..), type (<>))
+import Wizzardo.Http.Handler (JHandler, createHandler)
+import Prelude (IO, Show, fromInteger, ($))
+import Prelude.Linear (Unrestricted(..))
+import qualified System.IO.Linear as Linear
+
+imports "java.util.concurrent.ThreadLocalRandom"
+imports "com.wizzardo.epoll.*"
+imports "com.wizzardo.http.*"
+imports "com.wizzardo.http.framework.*"
+imports "com.wizzardo.http.request.*"
+imports "com.wizzardo.http.response.*"
+imports "io.reactiverse.pgclient.*"
+imports "io.reactiverse.pgclient.impl.*"
+
+
+createDbHandler :: MonadIO m => m JHandler
+createDbHandler =
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    encodeDbResponse <- createIntIntToObjFunction encodeDbResponseAsJSON
+    Unrestricted jGlobalEncodeDbResponse <- newGlobalRef_ encodeDbResponse
+    byteBufferProviderThreadLocal <- createThreadLocalByteBufferProvider
+    Unrestricted jGlobalByteBufferProviderThreadLocal <-
+      newGlobalRef_ byteBufferProviderThreadLocal
+    poolRef <- createPgPoolRef
+    Unrestricted jGlobalPoolRef <- newGlobalRef_ poolRef
+    createHandler $ \req resp -> Linear.withLinearIO $ do
+      let uPoolRef = Unrestricted jGlobalPoolRef
+          uByteBufferProviderThreadLocal = Unrestricted jGlobalByteBufferProviderThreadLocal
+          uEncodeDbResponse = Unrestricted jGlobalEncodeDbResponse
+      [java| {
+        int genWorldId = 1 + ThreadLocalRandom.current().nextInt(10000);
+        $resp.async();
+        $uPoolRef.get().preparedQuery("SELECT * FROM World WHERE id=$1", Tuple.of(genWorldId), dbRes -> {
+            if (dbRes.succeeded()) {
+                PgIterator resultSet = dbRes.result().iterator();
+                if (!resultSet.hasNext()) {
+                    $resp.status(Status._404);
+                } else {
+                    Tuple row = resultSet.next();
+                    $resp.appendHeader(Header.KV_CONTENT_TYPE_APPLICATION_JSON);
+                    $resp.setBody($uEncodeDbResponse.apply(row.getInteger(0), row.getInteger(1)));
+                }
+            } else {
+                dbRes.cause().printStackTrace();
+                $resp.status(Status._500).body(dbRes.cause().getMessage());
+            }
+            // commit async response
+            ByteBufferProvider bufferProvider = $uByteBufferProviderThreadLocal.get();
+            HttpConnection connection = $req.connection();
+            $resp.commit(connection, bufferProvider);
+            connection.flush(bufferProvider);
+            $resp.reset();
+        });
+       } |]
+      return (Unrestricted ())
+
+data World = World { worldId :: Int32, worldRandomNumber :: Int32 }
+  deriving Show
+
+instance ToJSON World where
+  toJSON w = object ["id" .= worldId w, "randomNumber" .= worldRandomNumber w]
+
+createThreadLocalByteBufferProvider
+  :: MonadIO m
+  => m (J ('Class "java.lang.ThreadLocal" <>
+            '[ 'Iface "com.wizzardo.epoll.ByteBufferProvider"]
+          )
+       )
+createThreadLocalByteBufferProvider =
+  [java| new ThreadLocal<ByteBufferProvider>() {
+     @Override
+     public ByteBufferProvider initialValue() {
+       ByteBufferWrapper wrapper = new ByteBufferWrapper(64 * 1024);
+       return () -> wrapper;
+     }
+   } |]
+
+createPgPoolRef
+ :: MonadIO m
+ => m (J ('Class "java.lang.ThreadLocal" <> '[ 'Class "io.reactiverse.pgclient.PgPool"]))
+createPgPoolRef =
+  [java|
+    new ThreadLocal() {
+      @Override
+      public PgPool initialValue() {
+        WizzardoPgPoolOptions options = new WizzardoPgPoolOptions();
+        options.setDatabase("hello_world");
+        options.setHost("tfb-database");
+        options.setPort(5432);
+        options.setUser("benchmarkdbuser");
+        options.setPassword("benchmarkdbpass");
+        options.setCachePreparedStatements(true);
+        options.setMaxSize(1);
+        return new WizzardoPgPool(options);
+      }
+    }
+   |]
+
+encodeDbResponseAsJSON
+  :: Int32 -> Int32 -> IO (NonLinear.J ('Array ('Prim "byte")))
+encodeDbResponseAsJSON rowId rowRandomInt =
+  NonLinear.reflect $ toStrict $ encode $ World rowId rowRandomInt

+ 237 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/src/main/haskell/Language/Java/Function.hs

@@ -0,0 +1,237 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+module Language.Java.Function
+  ( createBiFunction
+  , createIntIntToObjFunction
+  ) where
+
+import Control.Exception (SomeException, catch)
+import qualified Control.Monad
+import qualified Control.Monad.IO.Class.Linear as Linear
+import qualified Control.Monad.Linear.Builder as Linear
+import qualified Control.Monad.Linear as Linear
+import Data.Int
+import Data.Singletons
+import Data.String (fromString)
+import qualified Data.Text as Text
+import qualified Foreign.JNI as JNI
+import Foreign.JNI.Safe
+import qualified Foreign.JNI.Types as NonLinear
+import Foreign.Ptr
+import GHC.Stable
+import Language.Java.Inline.Safe
+import Language.Java.Safe
+import Prelude
+import Prelude.Linear (Unrestricted(..))
+import System.IO.Unsafe (unsafePerformIO)
+
+imports "io.tweag.inline_java.wizzardo_http_benchmark.*"
+
+type JNIFun f a
+    = NonLinear.JNIEnv -> Ptr NonLinear.JObject -> StablePtrHandle f -> a
+
+type JNIApplyFun
+    = JNIFun
+    (NonLinear.JObject -> NonLinear.JObject -> IO NonLinear.JObject)
+    ( Ptr NonLinear.JObject
+      -> Ptr NonLinear.JObject
+      -> IO (Ptr NonLinear.JObject)
+    )
+
+type JNIIntIntToObjFun
+    = JNIFun
+        (Int32 -> Int32 -> IO NonLinear.JObject)
+        (Int32 -> Int32 -> IO (Ptr NonLinear.JObject))
+
+-- | A representation of a StablePtr that we can pass to Java
+newtype StablePtrHandle a = StablePtrHandle Int64
+  deriving Coercible
+
+foreign import ccall "wrapper" wrapObjectFun
+  :: JNIApplyFun -> IO (FunPtr JNIApplyFun)
+
+foreign import ccall "wrapper" wrapIntIntToObjFun
+  :: JNIIntIntToObjFun -> IO (FunPtr JNIIntIntToObjFun)
+
+-- Export only to get a FunPtr.
+foreign export ccall "wizzardo_http_handler_freeCallbackHandle" freeCallbackHandle
+  :: NonLinear.JNIEnv -> Ptr JObject -> StablePtrHandle a -> IO ()
+foreign import ccall "&wizzardo_http_handler_freeCallbackHandle" freeCallbackHandlePtr
+  :: FunPtr (NonLinear.JNIEnv -> Ptr JObject -> StablePtrHandle a -> IO ())
+
+freeCallbackHandle :: NonLinear.JNIEnv -> Ptr JObject -> StablePtrHandle a -> IO ()
+freeCallbackHandle _ _ = freeStablePtr . handleToStablePtr
+
+-- | Creates a BiFunction from a Haskell function.
+--
+-- The Haskell function must return jnull or a local reference.
+--
+-- TODO Maybe move this to a package to deal with function callbacks.
+createBiFunction
+  :: ( IsReferenceType a
+     , IsReferenceType b
+     , IsReferenceType c
+     , SingI a
+     , SingI b
+     , SingI c
+     , Linear.MonadIO m
+     )
+  => (NonLinear.J a -> NonLinear.J b -> IO (NonLinear.J c))
+  -> m (J ('Class "java.util.function.BiFunction" <> [a, b, c]))
+createBiFunction f =
+    createCallback f registerNativesForBiFunction $ \longFunctionPtr ->
+      unsafeGeneric Linear.<$>
+      [java| new java.util.function.BiFunction() {
+          @Override
+          public Object apply(Object t, Object u) {
+            return hsApply($longFunctionPtr, t, u);
+          }
+
+          private native void hsFinalize(long functionPtr);
+          private native Object hsApply(long functionPtr, Object t, Object u);
+
+          @Override
+          public void finalize() { hsFinalize($longFunctionPtr); }
+        } |]
+
+-- Keep this function at the top level to ensure that no callback-specific state
+-- leaks into the functions to register as native methods for all the instances
+-- of the inner class.
+registerNativesForBiFunction :: NonLinear.JClass -> IO ()
+registerNativesForBiFunction = do
+    let {-# NOINLINE applyPtr #-}
+        applyPtr :: FunPtr JNIApplyFun
+        applyPtr = unsafePerformIO $ wrapObjectFun $ \_jenv _jthis h reqPtr respPtr ->
+          withJNICallbackHandle h nullPtr $ \handleFun ->
+            NonLinear.unsafeObjectToPtr <$> Control.Monad.join
+              (handleFun
+                <$> NonLinear.objectFromPtr reqPtr
+                <*> NonLinear.objectFromPtr respPtr
+              )
+    registerNativesForCallback $ JNI.JNINativeMethod
+          "hsApply"
+          (methodSignature
+            [ SomeSing (sing :: Sing ('Prim "long"))
+            , SomeSing (sing :: Sing ('Class "java.lang.Object"))
+            , SomeSing (sing :: Sing ('Class "java.lang.Object"))
+            ]
+            (sing :: Sing ('Class "java.lang.Object"))
+          )
+          applyPtr
+
+
+-- | Creates an object with a method @Object apply(int, int)@ that
+-- invokes the given callback.
+--
+-- The Haskell callback must return jnull or a local reference.
+--
+createIntIntToObjFunction
+  :: ( IsReferenceType a
+     , SingI a
+     , Linear.MonadIO m
+     )
+  => (Int32 -> Int32 -> IO (NonLinear.J a))
+  -> m (J ('Iface "io.tweag.inline_java.wizzardo_http_benchmark.IntIntToObjFunction" <> '[a]))
+createIntIntToObjFunction f =
+    createCallback f registerNativesForIntIntToObjFunction $ \longFunctionPtr ->
+      unsafeGeneric Linear.<$>
+      [java| new IntIntToObjFunction() {
+          @Override
+          public Object apply(int t, int u) {
+            return hsApply($longFunctionPtr, t, u);
+          }
+
+          private native void hsFinalize(long functionPtr);
+          private native Object hsApply(long functionPtr, int t, int u);
+
+          @Override
+          public void finalize() { hsFinalize($longFunctionPtr); }
+        } |]
+
+-- Keep this function at the top level to ensure that no callback-specific state
+-- leaks into the functions to register as native methods for all the instances
+-- of the inner class.
+registerNativesForIntIntToObjFunction :: NonLinear.JClass -> IO ()
+registerNativesForIntIntToObjFunction = do
+    let {-# NOINLINE applyPtr #-}
+        applyPtr :: FunPtr JNIIntIntToObjFun
+        applyPtr = unsafePerformIO $ wrapIntIntToObjFun $ \_jenv _jthis h t u ->
+          withJNICallbackHandle h nullPtr $ \handleFun ->
+            NonLinear.unsafeObjectToPtr <$> handleFun t u
+    registerNativesForCallback $ JNI.JNINativeMethod
+          "hsApply"
+          (methodSignature
+            [ SomeSing (sing :: Sing ('Prim "long"))
+            , SomeSing (sing :: Sing ('Prim "int"))
+            , SomeSing (sing :: Sing ('Prim "int"))
+            ]
+            (sing :: Sing ('Class "java.lang.Object"))
+          )
+          applyPtr
+
+-- | Creates a Java function object that invokes the given Haskell
+-- callback.
+createCallback
+  :: Linear.MonadIO m
+  => f                                -- ^ Haskell callback
+  -> (NonLinear.JClass -> IO ())      -- ^ Registers native methods for the Java
+                                      -- class of the callback
+  -> (StablePtrHandle f -> m (J ty))  -- ^ Instantiates the java callback which
+                                      -- may have unregistered native methods
+  -> m (J ty)
+createCallback f registerNativesForCallback createJFunction =
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    Unrestricted longFunctionPtr <- Linear.liftIOU (createStablePtrHandle f)
+    jFunction <- createJFunction longFunctionPtr
+    (jFunction, Unrestricted klass) <- getObjectClass jFunction
+    Linear.liftIO (registerNativesForCallback klass)
+    Linear.liftIO (JNI.deleteLocalRef klass)
+    return jFunction
+
+-- | Runs the Haskell callback referred by a 'StablePtrHandle' in the
+-- context of a Java function.
+--
+-- It forwards Haskell exceptions to Java if any occur.
+withJNICallbackHandle :: StablePtrHandle f -> a -> (f -> IO a) -> IO a
+withJNICallbackHandle h valueOnException m =
+    (derefStablePtrHandle h >>= m) `catch` \(e :: SomeException) ->
+    fmap (const valueOnException) $ withLocalFrame_ $
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    jmsg <- reflect (Text.pack $ show e)
+    e <- [java| new RuntimeException($jmsg) |]
+    throw_ (e :: J ('Class "java.lang.RuntimeException"))
+  where
+    derefStablePtrHandle :: StablePtrHandle a -> IO a
+    derefStablePtrHandle = deRefStablePtr . handleToStablePtr
+
+createStablePtrHandle :: a -> IO (StablePtrHandle a)
+createStablePtrHandle a =
+    StablePtrHandle . fromIntegral . ptrToIntPtr . castStablePtrToPtr <$>
+    newStablePtr a
+
+handleToStablePtr :: StablePtrHandle a -> StablePtr a
+handleToStablePtr (StablePtrHandle h) =
+    castPtrToStablePtr $ intPtrToPtr $ fromIntegral h
+
+registerNativesForCallback :: JNI.JNINativeMethod -> NonLinear.JClass -> IO ()
+registerNativesForCallback jniNativeMethod klass = do
+    JNI.registerNatives klass
+      [ jniNativeMethod
+      , JNI.JNINativeMethod
+          "hsFinalize"
+          (methodSignature [SomeSing (sing :: Sing ('Prim "long"))] (sing :: Sing 'Void))
+          freeCallbackHandlePtr
+      ]

+ 103 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/src/main/haskell/Main.hs

@@ -0,0 +1,103 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
+module Main where
+
+import qualified Control.Monad
+import Control.Monad.IO.Class.Linear (MonadIO)
+import qualified Control.Monad.Linear.Builder as Linear
+import Data.Aeson
+import qualified Data.Maybe as Maybe
+import qualified Data.ByteString.Char8 as ByteString.Char8
+import Data.ByteString.Lazy (toStrict)
+import Data.String (fromString)
+import qualified Data.Text as Text
+import DbHandler (createDbHandler)
+import Foreign.JNI.Safe (newGlobalRef_, withJVM, withLocalFrame_)
+import qualified Language.Haskell.TH.Syntax as TH
+import Language.Java.Inline.Safe
+import Language.Java.Safe (reflect)
+import System.Environment (getArgs, lookupEnv)
+import qualified System.IO.Linear as Linear
+import Wizzardo.Http.Handler (JHandler, createHandler)
+import Prelude (IO, (=<<), concat, fromInteger, map, ($), (++))
+import Prelude.Linear (Unrestricted(..))
+import Paths_wizzardo_http_benchmark (getDataFileName)
+
+imports "com.wizzardo.http.*"
+imports "com.wizzardo.http.framework.*"
+imports "com.wizzardo.http.request.*"
+
+main :: IO ()
+main =
+    getDataFileName "build/libs/wizzardo-http-benchmark.jar" Control.Monad.>>= \jar ->
+    getArgs Control.Monad.>>= \args -> do
+    let -- We use the classpath provided at build time.
+        cp = concat $ jar : ":" :
+               Maybe.maybeToList $(TH.lift =<< TH.runIO (lookupEnv "CLASSPATH"))
+        jvmArgs = [ fromString ("-Djava.class.path=" ++ cp) ]
+        otherJVMArgs =
+          [ "-Xmx2G"
+          , "-Xms2G"
+          -- , "-server"
+          , "-XX:+UseNUMA"
+          , "-XX:+UseParallelGC"
+          , "-XX:+AggressiveOpts"
+          ]
+    withJVM (jvmArgs ++ otherJVMArgs) $ withLocalFrame_ $
+      let Linear.Builder{..} = Linear.monadBuilder in do
+      jsonHandler <- createJsonHandler
+      jPlainTextHandler <- createPlainTextHandler
+      jDbHandler <- createDbHandler
+      jargs <- reflect (map Text.pack args)
+      [java| {
+        WebApplication application = new WebApplication($jargs) {
+            @Override
+            protected void initHttpPartsCache() {
+                ByteTree tree = httpStringsCache.getTree();
+                for (Request.Method method : Request.Method.values()) {
+                    tree.append(method.name());
+                }
+                tree.append(HttpConnection.HTTP_1_1);
+            }
+        };
+
+        application.onSetup(app -> {
+          app.getUrlMapping()
+             .append("/json", $jsonHandler)
+             .append("/plaintext", $jPlainTextHandler)
+             .append("/db", $jDbHandler);
+        });
+        application.start();
+       } |]
+
+createJsonHandler :: MonadIO m => m JHandler
+createJsonHandler = createHandler $ \_req resp -> Linear.withLinearIO $
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    jmsg <- reflect (toStrict $ encode $ jsonObject resp)
+    [java| { $resp
+            .setBody($jmsg)
+            .appendHeader(Header.KV_CONTENT_TYPE_APPLICATION_JSON);
+           } |]
+    return (Unrestricted ())
+  where
+    -- Don't inline, so the serialization is not cached.
+    {-# NOINLINE jsonObject #-}
+    jsonObject _ = object ["message" .= Text.pack "Hello, World!"]
+
+createPlainTextHandler :: MonadIO m => m JHandler
+createPlainTextHandler =
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    jmsg <- reflect (ByteString.Char8.pack "Hello, World!")
+    Unrestricted jGlobalMsg <- newGlobalRef_ jmsg
+    createHandler $ \_req resp -> Linear.withLinearIO $ do
+      let ujmsg = Unrestricted jGlobalMsg
+      [java| { $resp
+               .setBody($ujmsg)
+               .appendHeader(Header.KV_CONTENT_TYPE_TEXT_PLAIN);
+             } |]
+      return (Unrestricted ())

+ 58 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/src/main/haskell/Wizzardo/Http/Handler.hs

@@ -0,0 +1,58 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+module Wizzardo.Http.Handler
+  ( JHandler
+  , JRequest
+  , JResponse
+  , createHandler
+  ) where
+
+import qualified Control.Monad
+import qualified Control.Monad.IO.Class.Linear as Linear
+import qualified Control.Monad.Linear.Builder as Linear
+import Data.String (fromString)
+import qualified Foreign.JNI.Types as NonLinear
+import Language.Java.Function (createBiFunction)
+import Language.Java.Inline.Safe
+import Language.Java.Safe
+import Prelude
+import Prelude.Linear (Unrestricted(..))
+
+imports "com.wizzardo.http.*"
+imports "com.wizzardo.http.request.*"
+imports "com.wizzardo.http.response.*"
+
+type JHandler = J ('Class "com.wizzardo.http.Handler")
+type JResponse = NonLinear.J Response
+type JRequest = NonLinear.J Request
+type Response = 'Class "com.wizzardo.http.response.Response"
+type Request = 'Class "com.wizzardo.http.request.Request"
+
+createHandler
+  :: Linear.MonadIO m
+  => (Unrestricted JRequest -> Unrestricted JResponse -> IO ())
+  -> m JHandler
+createHandler handle =
+    let Linear.Builder{..} = Linear.monadBuilder in do
+    f <- createBiFunction $ \req resp ->
+      handle
+        (Unrestricted req)
+        (Unrestricted resp)
+      Control.Monad.>>
+        Control.Monad.return resp
+    [java| new Handler() {
+          @Override
+          public Response handle(Request req, Response resp) {
+            return $f.apply(req, resp);
+          }
+      } |]

+ 6 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/src/main/java/io/tweag/inline_java/wizzardo_http_benchmark/IntIntToObjFunction.java

@@ -0,0 +1,6 @@
+package io.tweag.inline_java.wizzardo_http_benchmark;
+
+@FunctionalInterface
+public interface IntIntToObjFunction<T> {
+    T apply(int a, int b);
+}

+ 32 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-http-benchmark/wizzardo-http-benchmark.cabal

@@ -0,0 +1,32 @@
+name:                wizzardo-http-benchmark
+version:             0.1
+synopsis:            An example application showing how to set the classpath for inline-java.
+author:              EURL Tweag
+maintainer:          [email protected]
+copyright:           2017 EURL Tweag.
+build-type:          Custom
+cabal-version:       >=1.18
+
+custom-setup
+  setup-depends: Cabal>=1.18, base<5, directory, filepath, inline-java
+
+executable wizzardo-http-benchmark
+  main-is:             Main.hs
+  ghc-options: -threaded -rtsopts
+  other-modules:
+    DbHandler
+    Language.Java.Function
+    Paths_wizzardo_http_benchmark
+    Wizzardo.Http.Handler
+  hs-source-dirs:      src/main/haskell
+  build-depends:
+    aeson,
+    base<5,
+    bytestring,
+    inline-java,
+    jni,
+    jvm,
+    linear-base,
+    template-haskell,
+    text
+  default-language:    Haskell2010

+ 18 - 0
frameworks/Haskell/wizzardo-inline/wizzardo-inline.dockerfile

@@ -0,0 +1,18 @@
+FROM tweag/linear-types:1.0.5
+MAINTAINER Facundo Dominguez <[email protected]>
+
+RUN apt-get update && apt-get install -y gradle openjdk-8-jdk
+
+USER root
+WORKDIR /wizzardo-inline
+
+COPY wizzardo-http-benchmark wizzardo-http-benchmark
+COPY stack-linear.yaml stack-linear.yaml
+COPY env-linear.sh env-linear.sh
+COPY set-java-home.sh set-java-home.sh
+
+RUN stack upgrade
+
+RUN bash -c ". env-linear.sh; stack build wizzardo-http-benchmark --no-terminal"
+
+CMD bash -c ". env-linear.sh; stack exec -- wizzardo-http-benchmark env=prod +RTS -A32m -N$(nproc) -RTS"