diff --git a/cpp/bmi_grpc_server.cc b/cpp/bmi_grpc_server.cc index 4d8afbd..3a3daae 100644 --- a/cpp/bmi_grpc_server.cc +++ b/cpp/bmi_grpc_server.cc @@ -824,6 +824,7 @@ void run_bmi_server(BmiClass *model, int argc, char *argv[]) grpc::EnableDefaultHealthCheckService(true); grpc::reflection::InitProtoReflectionServerBuilderPlugin(); grpc::ServerBuilder builder; + // builder.SetResourceQuota(grpc::ResourceQuota().SetMaxThreads(2)); builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr server(builder.BuildAndStart()); diff --git a/test/heat-images/c-julia/CMakeLists.txt b/test/heat-images/c-julia/CMakeLists.txt index 1284d9e..ab127cd 100644 --- a/test/heat-images/c-julia/CMakeLists.txt +++ b/test/heat-images/c-julia/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0) -project(run_bmi_server C CXX) +project(run_bmi_server CXX) set(CMAKE_MACOSX_RPATH 1) set(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -7,9 +7,9 @@ set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) set(_cflags "-ansi -Wall -Wundef -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -O2") find_package(PkgConfig REQUIRED) -pkg_check_modules(BMIC REQUIRED IMPORTED_TARGET bmic) -message("-- bmic include - ${BMIC_INCLUDE_DIRS}") -include_directories(${BMIC_INCLUDE_DIRS}) +pkg_check_modules(BMICXX REQUIRED IMPORTED_TARGET bmicxx) +message("-- bmicxx include - ${BMICXX_INCLUDE_DIRS}") +include_directories(${BMICXX_INCLUDE_DIRS}) pkg_check_modules(GRPC4BMI REQUIRED grpc4bmi) # See https://github.com/barche/embedding-julia diff --git a/test/heat-images/c-julia/Dockerfile b/test/heat-images/c-julia/Dockerfile index 04e58c4..a15b0b3 100644 --- a/test/heat-images/c-julia/Dockerfile +++ b/test/heat-images/c-julia/Dockerfile @@ -2,6 +2,8 @@ # # docker build -t heat:c-julia -f test/heat-images/c-julia/Dockerfile . # +# docker run -ti --rm -p 55555:55555 heat:c-julia +# # Run with # # ipython @@ -45,9 +47,11 @@ RUN cmake .. && make install # Install grpc4bmi, use commit c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66 # head of bmi2 branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/124 -ARG GRPC4BMI_VERSION=c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66 -RUN git clone https://github.com/eWaterCycle/grpc4bmi /opt/grpc4bmi \ - && cd /opt/grpc4bmi && git checkout ${GRPC4BMI_VERSION} +# ARG GRPC4BMI_VERSION=c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66 +# RUN git clone https://github.com/eWaterCycle/grpc4bmi /opt/grpc4bmi \ +# && cd /opt/grpc4bmi && git checkout ${GRPC4BMI_VERSION} +COPY cpp /opt/grpc4bmi/cpp +COPY proto /opt/grpc4bmi/proto WORKDIR /opt/grpc4bmi/cpp/build RUN cmake .. && make install @@ -59,6 +63,14 @@ COPY test/heat-images/c-julia/run_bmi_server.cc /opt/grpc4bmiheatc-julia COPY test/heat-images/c-julia/CMakeLists.txt /opt/grpc4bmiheatc-julia RUN cmake .. && make install +FROM julia:bullseye AS jldeps + +# Install heat-julia +RUN julia -e 'using Pkg; Pkg.add(url="https://github.com/csdms/bmi-example-julia.git",rev="80c34b4f2217599e600fe9372b1bae50e1229edf")' && \ + julia -e 'using Pkg; Pkg.add("BasicModelInterface")' + +RUN curl -L https://github.com/csdms/bmi-example-julia/raw/main/example/heat.toml > /usr/local/share/heat.toml + # run container FROM julia:bullseye @@ -68,15 +80,11 @@ RUN apt-get update && apt-get install -qy libssl1.1 && rm -rf /var/lib/apt/lists # Copy compiled and deps COPY --from=builder /usr/local/bin/run_bmi_server /usr/local/bin/run_bmi_server COPY --from=builder /usr/local/lib/ /usr/local/lib/ - -# Install heat-julia -RUN julia -e 'using Pkg; Pkg.add(url="https://github.com/csdms/bmi-example-julia.git",rev="80c34b4f2217599e600fe9372b1bae50e1229edf")' && \ - julia -e 'using Pkg; Pkg.add("BasicModelInterface")' - -RUN curl -L https://github.com/csdms/bmi-example-julia/raw/main/example/heat.toml > /usr/local/share/heat.toml +COPY --from=jldeps /root/.julia/ /root/.julia/ +COPY --from=jldeps /usr/local/share/heat.toml /usr/local/share/heat.toml RUN echo '/usr/local/julia/lib' > /etc/ld.so.conf.d/julia.conf && ldconfig -ENV BMI_PORT=50051 +# TODO run server as non-root user ENTRYPOINT ["/usr/local/bin/run_bmi_server"] diff --git a/test/heat-images/c-julia/demo.ipynb b/test/heat-images/c-julia/demo.ipynb new file mode 100644 index 0000000..c27ba77 --- /dev/null +++ b/test/heat-images/c-julia/demo.ipynb @@ -0,0 +1,175 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import grpc\n", + "from grpc4bmi.bmi_grpc_client import BmiClient\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "mymodel = BmiClient(grpc.insecure_channel(\"localhost:55555\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mymodel.initialize(\"/usr/local/share/heat.toml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The 2D Heat Equation'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mymodel.get_component_name()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "ename": "_InactiveRpcError", + "evalue": "<_InactiveRpcError of RPC that terminated with:\n\tstatus = StatusCode.UNAVAILABLE\n\tdetails = \"Socket closed\"\n\tdebug_error_string = \"UNKNOWN:Error received from peer ipv4:127.0.0.1:55555 {grpc_message:\"Socket closed\", grpc_status:14, created_time:\"2023-10-18T11:32:54.922549729+02:00\"}\"\n>", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31m_InactiveRpcError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/home/verhoes/git/eWaterCycle/grpc4bmi/test/heat-images/c-julia/demo.ipynb Cell 5\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m mymodel\u001b[39m.\u001b[39;49mget_component_name()\n", + "File \u001b[0;32m~/git/eWaterCycle/grpc4bmi/grpc4bmi/bmi_grpc_client.py:125\u001b[0m, in \u001b[0;36mBmiClient.get_component_name\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 123\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mstr\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstub\u001b[39m.\u001b[39mgetComponentName(bmi_pb2\u001b[39m.\u001b[39mEmpty())\u001b[39m.\u001b[39mname)\n\u001b[1;32m 124\u001b[0m \u001b[39mexcept\u001b[39;00m grpc\u001b[39m.\u001b[39mRpcError \u001b[39mas\u001b[39;00m e:\n\u001b[0;32m--> 125\u001b[0m handle_error(e)\n", + "File \u001b[0;32m~/git/eWaterCycle/grpc4bmi/grpc4bmi/bmi_grpc_client.py:123\u001b[0m, in \u001b[0;36mBmiClient.get_component_name\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 121\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mget_component_name\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[1;32m 122\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 123\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mstr\u001b[39m(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mstub\u001b[39m.\u001b[39;49mgetComponentName(bmi_pb2\u001b[39m.\u001b[39;49mEmpty())\u001b[39m.\u001b[39mname)\n\u001b[1;32m 124\u001b[0m \u001b[39mexcept\u001b[39;00m grpc\u001b[39m.\u001b[39mRpcError \u001b[39mas\u001b[39;00m e:\n\u001b[1;32m 125\u001b[0m handle_error(e)\n", + "File \u001b[0;32m~/git/eWaterCycle/grpc4bmi/.venv/lib/python3.10/site-packages/grpc/_channel.py:946\u001b[0m, in \u001b[0;36m_UnaryUnaryMultiCallable.__call__\u001b[0;34m(self, request, timeout, metadata, credentials, wait_for_ready, compression)\u001b[0m\n\u001b[1;32m 937\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__call__\u001b[39m(\u001b[39mself\u001b[39m,\n\u001b[1;32m 938\u001b[0m request,\n\u001b[1;32m 939\u001b[0m timeout\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 942\u001b[0m wait_for_ready\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m,\n\u001b[1;32m 943\u001b[0m compression\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m):\n\u001b[1;32m 944\u001b[0m state, call, \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_blocking(request, timeout, metadata, credentials,\n\u001b[1;32m 945\u001b[0m wait_for_ready, compression)\n\u001b[0;32m--> 946\u001b[0m \u001b[39mreturn\u001b[39;00m _end_unary_response_blocking(state, call, \u001b[39mFalse\u001b[39;49;00m, \u001b[39mNone\u001b[39;49;00m)\n", + "File \u001b[0;32m~/git/eWaterCycle/grpc4bmi/.venv/lib/python3.10/site-packages/grpc/_channel.py:849\u001b[0m, in \u001b[0;36m_end_unary_response_blocking\u001b[0;34m(state, call, with_call, deadline)\u001b[0m\n\u001b[1;32m 847\u001b[0m \u001b[39mreturn\u001b[39;00m state\u001b[39m.\u001b[39mresponse\n\u001b[1;32m 848\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 849\u001b[0m \u001b[39mraise\u001b[39;00m _InactiveRpcError(state)\n", + "\u001b[0;31m_InactiveRpcError\u001b[0m: <_InactiveRpcError of RPC that terminated with:\n\tstatus = StatusCode.UNAVAILABLE\n\tdetails = \"Socket closed\"\n\tdebug_error_string = \"UNKNOWN:Error received from peer ipv4:127.0.0.1:55555 {grpc_message:\"Socket closed\", grpc_status:14, created_time:\"2023-10-18T11:32:54.922549729+02:00\"}\"\n>" + ] + } + ], + "source": [ + "mymodel.get_component_name()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The 2D Heat Equation'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mymodel.get_component_name()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from time import sleep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".\r" + ] + }, + { + "ename": "AttributeError", + "evalue": "module 'os' has no attribute 'sleep'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/home/verhoes/git/eWaterCycle/grpc4bmi/test/heat-images/c-julia/demo.ipynb Cell 8\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 1\u001b[0m \u001b[39mfor\u001b[39;00m _ \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(\u001b[39m100\u001b[39m):\n\u001b[1;32m 2\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m'\u001b[39m\u001b[39m.\u001b[39m\u001b[39m'\u001b[39m, end\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39m\\r\u001b[39;00m\u001b[39m'\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m os\u001b[39m.\u001b[39;49msleep(\u001b[39m0.01\u001b[39m)\n\u001b[1;32m 4\u001b[0m mymodel\u001b[39m.\u001b[39mget_component_name()\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'os' has no attribute 'sleep'" + ] + } + ], + "source": [ + "for _ in range(100):\n", + " print('.', end='\\r')\n", + " sleep(0.01)\n", + " mymodel.get_component_name()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/test/heat-images/c-julia/run_bmi_server.cc b/test/heat-images/c-julia/run_bmi_server.cc index 39c8bf6..5a59d8c 100644 --- a/test/heat-images/c-julia/run_bmi_server.cc +++ b/test/heat-images/c-julia/run_bmi_server.cc @@ -1,21 +1,260 @@ -// #include "bmi_grpc_server.h" -// #include -// #include "bmi_heat.h" +#include +#include #include +#include +#include + +#include #include +#include "bmi_grpc_server.h" using namespace std; +class NotImplemented : public std::logic_error +{ +public: + NotImplemented() : std::logic_error("Not Implemented"){}; +}; + +void handle_julia_exception(void) { + if (jl_value_t *ex = jl_exception_occurred()) { + jl_printf(jl_stderr_stream(), "Exception:\n"); + jl_call2( + jl_get_function(jl_base_module, "showerror"), + jl_stderr_obj(), + ex + ); + jl_printf(jl_stderr_stream(), "\nI quit!\n"); + jl_atexit_hook(1); + exit(1); + } +} + +class BmiJulia : public bmi::Bmi +{ +public: + BmiJulia(std::string package_name, std::string model_name) + { + this->modelt = model_name; + this->package_name = package_name; + } + void Initialize(std::string config_file) + { + jl_init(); + jl_eval_string("import BasicModelInterface as BMI"); + handle_julia_exception(); + std::string import = "import " + this->package_name; + // cout << import << endl; + jl_eval_string(import.c_str()); + handle_julia_exception(); + + std::string cmd = "model = BMI.initialize("; + cmd.append(this->modelt); + cmd.append(", \""); + cmd.append(config_file); + cmd.append("\")"); + // cout << cmd << endl; + jl_eval_string(cmd.c_str()); + handle_julia_exception(); + } + + + void Update() + { + throw NotImplemented(); + } + void UpdateUntil(double time) + { + throw NotImplemented(); + } + void Finalize() + { + throw NotImplemented(); + } + + std::string GetComponentName() + { + cout << "GetComponentName" << jl_is_initialized << endl; + jl_eval_string("print(model)"); + // If we cant get passed line above + // then we are running in another thread + // that initialize has not run in + handle_julia_exception(); + std::string cmd = "BMI.get_component_name(model)"; + cout << cmd << endl; + jl_value_t *jname = jl_eval_string(cmd.c_str()); + handle_julia_exception(); + const char* cname = jl_string_ptr(jname); + handle_julia_exception(); + std::string name(cname); + return name; + } + int GetInputItemCount() + { + throw NotImplemented(); + } + int GetOutputItemCount() + { + throw NotImplemented(); + } + std::vector GetInputVarNames() + { + throw NotImplemented(); + } + std::vector GetOutputVarNames() + { + throw NotImplemented(); + } + + int GetVarGrid(std::string name) + { + throw NotImplemented(); + } + std::string GetVarType(std::string name) + { + throw NotImplemented(); + } + int GetVarItemsize(std::string name) + { + throw NotImplemented(); + } + std::string GetVarUnits(std::string name) + { + throw NotImplemented(); + } + int GetVarNbytes(std::string name) + { + throw NotImplemented(); + } + std::string GetVarLocation(std::string name) + { + throw NotImplemented(); + } + + double GetCurrentTime() + { + throw NotImplemented(); + } + double GetStartTime() + { + throw NotImplemented(); + } + double GetEndTime() + { + throw NotImplemented(); + } + std::string GetTimeUnits() + { + throw NotImplemented(); + } + double GetTimeStep() + { + throw NotImplemented(); + } + + void GetValue(std::string name, void *dest) + { + throw NotImplemented(); + } + void *GetValuePtr(std::string name) + { + throw NotImplemented(); + } + void GetValueAtIndices(std::string name, void *dest, int *inds, int count) + { + throw NotImplemented(); + } + + void SetValue(std::string name, void *src) + { + throw NotImplemented(); + } + void SetValueAtIndices(std::string name, int *inds, int len, void *src) + { + throw NotImplemented(); + } + + int GetGridRank(const int grid) + { + throw NotImplemented(); + } + int GetGridSize(const int grid) + { + throw NotImplemented(); + } + std::string GetGridType(const int grid) + { + throw NotImplemented(); + } + + void GetGridShape(const int grid, int *shape) + { + throw NotImplemented(); + } + void GetGridSpacing(const int grid, double *spacing) + { + throw NotImplemented(); + } + void GetGridOrigin(const int grid, double *origin) + { + throw NotImplemented(); + } + + void GetGridX(const int grid, double *x) + { + throw NotImplemented(); + } + void GetGridY(const int grid, double *y) + { + throw NotImplemented(); + } + void GetGridZ(const int grid, double *z) + { + throw NotImplemented(); + } + + int GetGridNodeCount(const int grid) + { + throw NotImplemented(); + } + int GetGridEdgeCount(const int grid) + { + throw NotImplemented(); + } + int GetGridFaceCount(const int grid) + { + throw NotImplemented(); + } + + void GetGridEdgeNodes(const int grid, int *edge_nodes) + { + throw NotImplemented(); + } + void GetGridFaceEdges(const int grid, int *face_edges) + { + throw NotImplemented(); + } + void GetGridFaceNodes(const int grid, int *face_nodes) + { + throw NotImplemented(); + } + void GetGridNodesPerFace(const int grid, int *nodes_per_face) + { + throw NotImplemented(); + } + +private: + std::string modelt; + std::string package_name; +}; + int main(int argc, char *argv[]) { - jl_init(); { { - // Simple running Julia code - jl_eval_string("x = sqrt(2.0)"); - jl_eval_string("print(x)"); - } - { + // // Simple running Julia code + // jl_eval_string("x = sqrt(2.0)"); + // jl_eval_string("print(x)"); } { @@ -29,24 +268,16 @@ int main(int argc, char *argv[]) BMI.get_component_name(model) */ - - jl_eval_string("import BasicModelInterface as BMI"); - jl_eval_string("import Heat"); - jl_value_t *model = jl_eval_string("BMI.initialize(Heat.Model, \"/usr/local/share/heat.toml\")"); - // jl_function_t *initialize = jl_get_function(jl_main_module, "BMI.initialize"); - // jl_value_t *modelt = jl_eval_string("Heat.Model"); - // jl_value_t *path = jl_cstr_to_string("/usr/local/share/heat.toml"); - // jl_value_t *model = jl_call2(initialize, modelt, path); - jl_function_t *get_component_name = jl_get_function(jl_main_module, "BMI.get_component_name"); - jl_value_t *name = jl_call1(get_component_name, model); - cout << jl_string_ptr(name) << endl; - - // TODO move initialize and get_component_name to a bmi interface } + { + bmi::Bmi* model = new BmiJulia("Heat", "Heat.Model"); + // Calling without grpc works + // model->Initialize("/usr/local/share/heat.toml"); + // cout << model->GetComponentName() << endl; - // Bmi *model = (Bmi *) malloc(sizeof(Bmi)); - - // run_bmi_server(model, argc, argv); + // Calling from grpc client causes segfault when calling jl_eval_string with BMI.initialize() + run_bmi_server(model, argc, argv); + } } int ret = 0; jl_atexit_hook(ret);