Skip to content

Commit

Permalink
Merge #49: Add standalone example with 3 processes
Browse files Browse the repository at this point in the history
4b45e14 Add standalone example with 3 processes (Russell Yanofsky)

Pull request description:

  Added build instructions to README, but could use some more
  documentation and CMakeLists.txt file could be made less repetitive
  maybe introducing a mp_generate_cpp function that works like the
  capnp_generate_cpp function.

Top commit has no ACKs.

Tree-SHA512: 3dee91a79f794ec7b36cffbdd7ef956b3c16e9e01430837fa383453cad00ca856879a2bd00c842ea607e64b8545bf54ff061a53d7d5727761b9f4a73cc0cb8bd
  • Loading branch information
ryanofsky committed Mar 17, 2021
2 parents 17bfda9 + 4b45e14 commit 805eb73
Show file tree
Hide file tree
Showing 12 changed files with 440 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@ install(FILES "include/mpgen.mk" DESTINATION "include")

install(EXPORT Multiprocess DESTINATION lib/cmake/Multiprocess)

add_subdirectory(example EXCLUDE_FROM_ALL)
add_subdirectory(test EXCLUDE_FROM_ALL)
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ basis in this library to construct the event-loop necessary to service IPC reque

A simple interface description can be found at [test/mp/test/foo.capnp](test/mp/test/foo.capnp), implementation in [test/mp/test/foo.h](test/mp/test/foo.h), and usage in [test/mp/test/test.cpp](test/mp/test/test.cpp).

A more complete example can be found in [example](example/) and run with:

```sh
make -C build example
build/example/mpexample
```

## Future directions

_libmultiprocess_ uses the [Cap'n Proto](https://capnproto.org) interface description language and protocol, but it could be extended or changed to use a different IDL/protocol like [gRPC](https://grpc.io). The nice thing about _Cap'n Proto_ compared to _gRPC_ and most other lower level protocols is that it allows interface pointers (_Services_ in gRPC parlance) to be passed as method arguments and return values, so object references and bidirectional requests work out of the box. Supporting a lower-level protocol would require writing adding maps and tracking code to proxy objects.
Expand Down
164 changes: 164 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

add_custom_command(
OUTPUT
init.capnp.h
init.capnp.c++
init.capnp.proxy.h
init.capnp.proxy-server.c++
init.capnp.proxy-client.c++
init.capnp.proxy-types.c++
init.capnp.proxy-types.h
COMMAND mpgen "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/init.capnp" "${CMAKE_SOURCE_DIR}/include" "${capnp_PREFIX}/include"
DEPENDS init.capnp mpgen
)

add_custom_command(
OUTPUT
calculator.capnp.h
calculator.capnp.c++
calculator.capnp.proxy.h
calculator.capnp.proxy-server.c++
calculator.capnp.proxy-client.c++
calculator.capnp.proxy-types.c++
calculator.capnp.proxy-types.h
COMMAND mpgen "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/calculator.capnp" "${CMAKE_SOURCE_DIR}/include" "${capnp_PREFIX}/include"
DEPENDS calculator.capnp mpgen
)

add_executable(mpcalculator
calculator.capnp.h
calculator.capnp.c++
calculator.capnp.proxy.h
calculator.capnp.proxy-server.c++
calculator.capnp.proxy-client.c++
calculator.capnp.proxy-types.c++
calculator.capnp.proxy-types.h
calculator.cpp
init.capnp.h
init.capnp.c++
init.capnp.proxy.h
init.capnp.proxy-server.c++
init.capnp.proxy-client.c++
init.capnp.proxy-types.c++
init.capnp.proxy-types.h
printer.capnp.h
printer.capnp.c++
printer.capnp.proxy.h
printer.capnp.proxy-server.c++
printer.capnp.proxy-client.c++
printer.capnp.proxy-types.c++
printer.capnp.proxy-types.h
)
target_include_directories(mpcalculator PUBLIC
${CAPNP_INCLUDE_DIRECTORY}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(mpcalculator PRIVATE CapnProto::capnp)
target_link_libraries(mpcalculator PRIVATE CapnProto::capnp-rpc)
target_link_libraries(mpcalculator PRIVATE CapnProto::kj)
target_link_libraries(mpcalculator PRIVATE CapnProto::kj-async)
target_link_libraries(mpcalculator PRIVATE Threads::Threads)
target_link_libraries(mpcalculator PRIVATE multiprocess)
set_target_properties(mpcalculator PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES)

add_custom_command(
OUTPUT
printer.capnp.h
printer.capnp.c++
printer.capnp.proxy.h
printer.capnp.proxy-server.c++
printer.capnp.proxy-client.c++
printer.capnp.proxy-types.c++
printer.capnp.proxy-types.h
COMMAND mpgen "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/printer.capnp" "${CMAKE_SOURCE_DIR}/include" "${capnp_PREFIX}/include"
DEPENDS printer.capnp mpgen
)

add_executable(mpprinter
calculator.capnp.c++
calculator.capnp.h
calculator.capnp.proxy-client.c++
calculator.capnp.proxy-server.c++
calculator.capnp.proxy-types.c++
calculator.capnp.proxy-types.h
calculator.capnp.proxy.h
init.capnp.h
init.capnp.c++
init.capnp.proxy.h
init.capnp.proxy-server.c++
init.capnp.proxy-client.c++
init.capnp.proxy-types.c++
init.capnp.proxy-types.h
printer.capnp.h
printer.capnp.c++
printer.capnp.proxy.h
printer.capnp.proxy-server.c++
printer.capnp.proxy-client.c++
printer.capnp.proxy-types.c++
printer.capnp.proxy-types.h
printer.cpp
)
target_include_directories(mpprinter PUBLIC
${CAPNP_INCLUDE_DIRECTORY}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(mpprinter PRIVATE CapnProto::capnp)
target_link_libraries(mpprinter PRIVATE CapnProto::capnp-rpc)
target_link_libraries(mpprinter PRIVATE CapnProto::kj)
target_link_libraries(mpprinter PRIVATE CapnProto::kj-async)
target_link_libraries(mpprinter PRIVATE Threads::Threads)
target_link_libraries(mpprinter PRIVATE multiprocess)
set_target_properties(mpprinter PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES)

add_executable(mpexample
calculator.capnp.c++
calculator.capnp.h
calculator.capnp.proxy-client.c++
calculator.capnp.proxy-server.c++
calculator.capnp.proxy-types.c++
calculator.capnp.proxy-types.h
calculator.capnp.proxy.h
init.capnp.c++
init.capnp.h
init.capnp.proxy-client.c++
init.capnp.proxy-server.c++
init.capnp.proxy-types.c++
init.capnp.proxy-types.h
init.capnp.proxy.h
printer.capnp.h
printer.capnp.c++
printer.capnp.proxy.h
printer.capnp.proxy-server.c++
printer.capnp.proxy-client.c++
printer.capnp.proxy-types.c++
printer.capnp.proxy-types.h
printer.h
calculator.h
example.cpp
)
target_include_directories(mpexample PUBLIC
${CAPNP_INCLUDE_DIRECTORY}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(mpexample PRIVATE CapnProto::capnp)
target_link_libraries(mpexample PRIVATE CapnProto::capnp-rpc)
target_link_libraries(mpexample PRIVATE CapnProto::kj)
target_link_libraries(mpexample PRIVATE CapnProto::kj-async)
target_link_libraries(mpexample PRIVATE Threads::Threads)
target_link_libraries(mpexample PRIVATE multiprocess)
target_link_libraries(mpexample PRIVATE stdc++fs)
set_target_properties(mpexample PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES)

add_custom_target(example DEPENDS mpexample mpcalculator mpprinter)
15 changes: 15 additions & 0 deletions example/calculator.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0xb67dbf34061180a9;

using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";

$Proxy.include("calculator.h");

interface CalculatorInterface $Proxy.wrap("Calculator") {
destroy @0 (context :Proxy.Context) -> ();
solveEquation @1 (context :Proxy.Context, eqn: Text) -> ();
}
51 changes: 51 additions & 0 deletions example/calculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <calculator.h>
#include <fstream>
#include <init.capnp.h>
#include <init.capnp.proxy-types.h>
#include <init.h>
#include <iostream>
#include <memory>
#include <mp/proxy-io.h>
#include <printer.h>
#include <stdexcept>

class CalculatorImpl : public Calculator
{
public:
CalculatorImpl(std::unique_ptr<Printer> printer) : m_printer(std::move(printer)) {}
void solveEquation(const std::string& eqn) override { m_printer->print("Wow " + eqn + ", that's a tough one.\n"); }
std::unique_ptr<Printer> m_printer;
};

class InitImpl : public Init
{
public:
std::unique_ptr<Calculator> makeCalculator(std::unique_ptr<Printer> printer) override
{
return std::make_unique<CalculatorImpl>(std::move(printer));
}
};

void LogPrint(bool raise, std::string message)
{
if (raise) throw std::runtime_error(std::move(message));
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
}

int main(int argc, char** argv)
{
if (argc != 2) {
std::cout << "Usage: mpcalculator <fd>\n";
return 1;
}
mp::EventLoop loop("mpcalculator", LogPrint);
int fd = std::stoi(argv[1]);
std::unique_ptr<Init> init = std::make_unique<InitImpl>();
mp::ServeStream<InitInterface>(loop, fd, *init);
loop.loop();
return 0;
}
17 changes: 17 additions & 0 deletions example/calculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef EXAMPLE_CALCULATOR_H
#define EXAMPLE_CALCULATOR_H

#include <string>

class Calculator
{
public:
virtual ~Calculator() = default;
virtual void solveEquation(const std::string& eqn) = 0;
};

#endif // EXAMPLE_CALCULATOR_H
66 changes: 66 additions & 0 deletions example/example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <filesystem>
#include <fstream>
#include <init.capnp.h>
#include <init.capnp.proxy-types.h>
#include <iostream>
#include <mp/proxy-io.h>

namespace fs = std::filesystem;

auto Spawn(mp::EventLoop& loop, const std::string& process_argv0, const std::string& new_exe_name)
{
int pid;
int fd = mp::SpawnProcess(pid, [&](int fd) -> std::vector<std::string> {
fs::path path = process_argv0;
path.remove_filename();
path.append(new_exe_name);
return {path.string(), std::to_string(fd)};
});
return std::make_tuple(mp::ConnectStream<InitInterface>(loop, fd), pid);
}

void LogPrint(bool raise, std::string message)
{
if (raise) throw std::runtime_error(std::move(message));
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
}

int main(int argc, char** argv)
{
if (argc != 1) {
std::cout << "Usage: mpexample\n";
return 1;
}

std::promise<mp::EventLoop*> promise;
std::thread loop_thread([&] {
mp::EventLoop loop("mpexample", LogPrint);
{
std::unique_lock<std::mutex> lock(loop.m_mutex);
loop.addClient(lock);
}
promise.set_value(&loop);
loop.loop();
});
mp::EventLoop* loop = promise.get_future().get();

auto [printer_init, printer_pid] = Spawn(*loop, argv[0], "mpprinter");
auto [calc_init, calc_pid] = Spawn(*loop, argv[0], "mpcalculator");
auto calc = calc_init->makeCalculator(printer_init->makePrinter());
while (true) {
std::string eqn;
std::cout << "Enter the equation: ";
std::getline(std::cin, eqn);
calc->solveEquation(eqn);
}
calc.reset();
calc_init.reset();
mp::WaitProcess(calc_pid);
printer_init.reset();
mp::WaitProcess(printer_pid);
return 0;
}
22 changes: 22 additions & 0 deletions example/init.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0xba5a7448664901b1;

using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";
using Calculator = import "calculator.capnp";
using Printer = import "printer.capnp";

$Proxy.include("calculator.h");
$Proxy.include("init.h");
$Proxy.include("printer.h");
$Proxy.includeTypes("calculator.capnp.proxy-types.h");
$Proxy.includeTypes("printer.capnp.proxy-types.h");

interface InitInterface $Proxy.wrap("Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeCalculator @1 (context :Proxy.Context, print :Printer.PrinterInterface) -> (result :Calculator.CalculatorInterface);
makePrinter @2 (context :Proxy.Context) -> (result :Printer.PrinterInterface);
}
20 changes: 20 additions & 0 deletions example/init.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef EXAMPLE_INIT_H
#define EXAMPLE_INIT_H

#include <calculator.h>
#include <memory>
#include <printer.h>

class Init
{
public:
virtual ~Init() = default;
virtual std::unique_ptr<Printer> makePrinter() { return nullptr; }
virtual std::unique_ptr<Calculator> makeCalculator(std::unique_ptr<Printer> printer) { return nullptr; }
};

#endif // EXAMPLE_INIT_H
15 changes: 15 additions & 0 deletions example/printer.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0x893db95f456ed0e3;

using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";

$Proxy.include("printer.h");

interface PrinterInterface $Proxy.wrap("Printer") {
destroy @0 (context :Proxy.Context) -> ();
print @1 (context :Proxy.Context, text: Text) -> ();
}
Loading

0 comments on commit 805eb73

Please sign in to comment.