From 24a94bbc693c9268992c67cd414248d1149798ea Mon Sep 17 00:00:00 2001 From: Gabriel Moise Date: Tue, 21 May 2024 17:13:06 +0100 Subject: [PATCH] RPC_toy: isolated environment to test cohttp functionality --- contrib/RPC_toy/README.md | 66 ++++++++++++++++++++++++++++++++++++++ contrib/RPC_toy/RPC_toy.ml | 49 ++++++++++++++++++++++++++++ contrib/RPC_toy/dune | 20 ++++++++++++ contrib/RPC_toy/leak.sh | 53 ++++++++++++++++++++++++++++++ dune-project | 1 + manifest/product_octez.ml | 14 ++++++++ opam/RPC-toy.opam | 21 ++++++++++++ 7 files changed, 224 insertions(+) create mode 100644 contrib/RPC_toy/README.md create mode 100644 contrib/RPC_toy/RPC_toy.ml create mode 100644 contrib/RPC_toy/dune create mode 100755 contrib/RPC_toy/leak.sh create mode 100644 opam/RPC-toy.opam diff --git a/contrib/RPC_toy/README.md b/contrib/RPC_toy/README.md new file mode 100644 index 000000000000..2b2801051ef8 --- /dev/null +++ b/contrib/RPC_toy/README.md @@ -0,0 +1,66 @@ +# RPC_toy + +The `RPC_toy` tool is used to isolate a resource leak which can appear if there are unclosed connections when running streamed RPCs. + +## Running scenario + +To test if the leak exists, you need to run the `leak.sh` automated script. There are two potential outputs: + +### Leak is present + +```shell +$ cd ~/tezos/contrib/RPC_toy + +$ dune build + +$ ./leak.sh + +Entering directory +Starting node server +Node PID: +[Server response] : Entering directory +[Server response] : Leaving directory +[Server response] : Server running on port 8080 +Make self-loop request to server +[Server response] : Cohttp connection on 1 +[Server response] : %! +[Server response] : I slept +[Server response] : I slept +[Server response] : I slept +[Server response] : I slept +Attempting to cancel the self-loop request +Cancelled the self-loop request +[IMPORTANT] Cohttp connection did NOT CLOSE +Attempting to stop the node server +Stopped the node server +``` + +### Leak is NOT present + +```shell +$ cd ~/tezos/contrib/RPC_toy + +$ dune build + +$ ./leak.sh + +Entering directory +Starting node server +Node PID: +[Server response] : Entering directory +[Server response] : Leaving directory +[Server response] : Server running on port 8080 +Make self-loop request to server +[Server response] : Cohttp connection on 1 +[Server response] : %! +[Server response] : I slept +[Server response] : I slept +[Server response] : I slept +[Server response] : I slept +Attempting to cancel the self-loop request +Cancelled the self-loop request +[Server response] : Cohttp connection closed +[IMPORTANT] Cohttp connection CLOSED +Attempting to stop the node server +Stopped the node server +``` diff --git a/contrib/RPC_toy/RPC_toy.ml b/contrib/RPC_toy/RPC_toy.ml new file mode 100644 index 000000000000..748a03a2a1d3 --- /dev/null +++ b/contrib/RPC_toy/RPC_toy.ml @@ -0,0 +1,49 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 TriliTech *) +(* *) +(*****************************************************************************) + +(* + Server running and accepting requests + Endpoints: + - '/sleep' - sleeps continuously, printing messages at regular intervals +*) + +open Lwt +open Cohttp_lwt_unix + +let port = 8080 + +let callback (_, con) req _body = + (* Record connection established *) + let con_string = Cohttp.Connection.to_string con in + Format.printf "Cohttp connection on %s@." con_string ; + (* Match given endpoint *) + let uri = req |> Request.uri |> Uri.path in + match uri with + | "/sleep" -> + (* Continuous sleep *) + let rec get_busy () = + Lwt_unix.sleep 1.0 >>= fun () -> + Format.printf "I slept @." ; + get_busy () + in + get_busy () + (* Unknown call *) + | _ -> Server.respond_string ~status:`Not_found ~body:"Not found" () + +let start_server () = + let server = + Server.create + ~mode:(`TCP (`Port port)) + (Server.make + ~conn_closed:(fun _ -> Format.printf "Cohttp connection closed\n%!") + ~callback + ()) + in + Printf.printf "Server running on port %d\n%!" port ; + server + +let () = Lwt_main.run (start_server ()) diff --git a/contrib/RPC_toy/dune b/contrib/RPC_toy/dune new file mode 100644 index 000000000000..965c4675f6d8 --- /dev/null +++ b/contrib/RPC_toy/dune @@ -0,0 +1,20 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(executable + (name RPC_toy) + (public_name RPC-toy) + (package RPC-toy) + (instrumentation (backend bisect_ppx)) + (libraries + octez-libs.stdlib-unix + octez-libs.base + cohttp-lwt-unix) + (link_flags + (:standard) + (:include %{workspace_root}/static-link-flags.sexp) + (:include %{workspace_root}/macos-link-flags.sexp) + (-linkall)) + (flags + (:standard) + -open Tezos_base.TzPervasives)) diff --git a/contrib/RPC_toy/leak.sh b/contrib/RPC_toy/leak.sh new file mode 100755 index 000000000000..7d9a28500bfc --- /dev/null +++ b/contrib/RPC_toy/leak.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Run the dune build command in the RPC_toy folder +dune build + +# Run the dune exec RPC_toy.exe executable in the background and capture its output +echo "Starting node server" +PATH_TO_RPC_TOY="./RPC_toy.exe" +output_file=$(mktemp) +dune exec $PATH_TO_RPC_TOY > >(tee -a "$output_file" | while read -r line; do echo "[Server response] : $line"; done) 2>&1 & + +# Save the PID of the node process +NODE_PID=$! +echo "Node PID: $NODE_PID" + +# Sleep for 5 seconds +sleep 5 + +# Run the curl command in the background +echo "Make self-loop request to server" +curl -s 'localhost:8080/sleep' & + +# Save the PID of the curl process +CURL_PID=$! + +# Sleep for 5 more seconds +sleep 5 + +# Kill the curl process +echo "Attempting to cancel the self-loop request" +kill $CURL_PID + +# Wait for the curl process to terminate +wait $CURL_PID 2> /dev/null +echo "Cancelled the self-loop request" + +# Check if the "Cohttp connection closed" message is in the server output +if grep -q "Cohttp connection closed" "$output_file"; then + echo "[IMPORTANT] Cohttp connection CLOSED" +else + echo "[IMPORTANT] Cohttp connection did NOT CLOSE" +fi + +# Clean up the temporary file +rm "$output_file" + +# Terminate the node process +echo "Attempting to stop the node server" +kill $NODE_PID + +# Wait for the node process to terminate +wait $NODE_PID 2> /dev/null +echo "Stopped the node server" diff --git a/dune-project b/dune-project index e17287d99e5b..983ac8a20c8b 100644 --- a/dune-project +++ b/dune-project @@ -3,6 +3,7 @@ (cram enable) (using ctypes 0.3) (using menhir 2.1) +(package (name RPC-toy)) (package (name bls12-381)) (package (name data-encoding)) (package (name gitlab_ci)) diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index 8982fc908471..6e7e3cc86775 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -8096,6 +8096,20 @@ let _octez_injector_server = (* run the protocol registration code *) @ Protocol.(all_optionally [octez_injector])) +let _RPC_toy = + public_exe + "RPC-toy" + ~internal_name:"RPC_toy" + ~path:"contrib/RPC_toy" + ~synopsis:"RPC toy" + ~release_status:Unreleased + ~with_macos_security_framework:true + ~linkall:true + ~deps: + [ + octez_stdlib_unix; octez_base |> open_ ~m:"TzPervasives"; cohttp_lwt_unix; + ] + (* We use Dune's select statement and keep uTop optional *) (* Keeping uTop optional lets `make build` succeed, *) (* which uses tezos/opam-repository to resolve dependencies, *) diff --git a/opam/RPC-toy.opam b/opam/RPC-toy.opam new file mode 100644 index 000000000000..09345292ef7d --- /dev/null +++ b/opam/RPC-toy.opam @@ -0,0 +1,21 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.11.1" } + "ocaml" { >= "4.14" } + "octez-libs" { = version } + "cohttp-lwt-unix" { >= "5.2.0" } +] +build: [ + ["rm" "-r" "vendors" "contrib"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "RPC toy" -- GitLab