|
| 1 | +<h1 align="center"> |
| 2 | + <br> |
| 3 | + <img src="https://raw.githubusercontent.com/alephzero/logo/master/rendered/alephzero.svg" width="256px"> |
| 4 | + <br> |
| 5 | + AlephZero |
| 6 | +</h1> |
| 7 | + |
| 8 | +<h3 align="center">Simple, Robust, Fast IPC.</h3> |
| 9 | + |
| 10 | +<p align="center"> |
| 11 | + <a href="https://github.com/alephzero/alephzero/actions?query=workflow%3ACI"><img src="https://github.com/alephzero/alephzero/workflows/CI/badge.svg"></a> |
| 12 | + <a href="https://codecov.io/gh/alephzero/alephzero"><img src="https://codecov.io/gh/alephzero/alephzero/branch/master/graph/badge.svg"></a> |
| 13 | + <a href="https://alephzero.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/alephzero/badge/?version=latest"></a> |
| 14 | + <a href="http://unlicense.org"><img src="https://img.shields.io/badge/license-Unlicense-blue.svg"></a> |
| 15 | +</p> |
| 16 | + |
| 17 | +<p align="center"> |
| 18 | + <a href="#overview">Overview</a> • |
| 19 | + <a href="#transport">Transport</a> • |
| 20 | + <a href="#protocol">Protocol</a> • |
| 21 | + <a href="#examples">Examples</a> • |
| 22 | + <a href="#installation">Installation</a> • |
| 23 | + <a href="#across-dockers">Across Dockers</a> |
| 24 | +</p> |
| 25 | + |
| 26 | +# Overview |
| 27 | + |
| 28 | +[Presentation from March 25, 2020](https://docs.google.com/presentation/d/12KE9UucjZPtpVnM1NljxOqBolBBKECWJdrCoE2yJaBw/edit#slide=id.p) |
| 29 | + |
| 30 | +AlephZero is a library for message based communication between programs running on the same machine. |
| 31 | + |
| 32 | +## Simple |
| 33 | + |
| 34 | +AlephZero's main goal is to be simple to use. Nothing is higher priority. |
| 35 | + |
| 36 | +There is no "master" process in between your nodes that is needed to do handshakes or exchanges of any kind. All you need is the topic name. |
| 37 | + |
| 38 | +See the <a href="#examples">Examples</a>. |
| 39 | + |
| 40 | +## Robust |
| 41 | + |
| 42 | +This is probably the main value of AlephZero, above similar libraries. |
| 43 | + |
| 44 | +AlephZero uses a lot of tricks to ensure the state of all channels is consistent, even when programs die. This includes double-buffering the state of the communication channel and [robustifying](https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html) the locks and notification channels. |
| 45 | + |
| 46 | +## Fast |
| 47 | + |
| 48 | +AlephZero uses shared memory across multiple processes to read and write messages, minimizing the involvement of the kernel. The kernel only really gets involved in notifying a process that a new message exists, and for that we use futex (fast user-space mutex). |
| 49 | + |
| 50 | +TODO: Benchmarks |
| 51 | + |
| 52 | +# Transport |
| 53 | + |
| 54 | +AlephZero, at its core, is a simple allocator on top of a contiguous region of memory. Usually, shared-memory. The allocator of choice is a circular-linked-list, which is fast, simple, and sufficient for the protocol listed below. It also plays well with the robustness requirement. |
| 55 | + |
| 56 | +This has a number of implications. For one, this means that old messages are kept around until the space is needed. The oldest messages are always discarded before any more recent messages. |
| 57 | + |
| 58 | +# Protocol |
| 59 | + |
| 60 | +Rather than exposing the low-level transport directly, AlephZero provides a few higher level protocol: |
| 61 | + |
| 62 | +* <b>PubSub</b>: Broadcast published messages. Subscribers get notified. |
| 63 | +* <b>RPC</b>: Request-response. |
| 64 | +* <b>PRPC (Progressive RPC)</b>: Request-streaming response. |
| 65 | +* <b>Sessions</b>: Bi-directional channel of communication. Not yet implemented. Let me know if you want this. |
| 66 | + |
| 67 | +# Examples |
| 68 | + |
| 69 | +Many more example and an interactive experience can be found at: https://github.com/alephzero/playground |
| 70 | + |
| 71 | +For the curious, here are some simple snippets to get you started: |
| 72 | + |
| 73 | +To begin with, we need to include AlephZero: |
| 74 | +```cc |
| 75 | +#include <a0.h> |
| 76 | +``` |
| 77 | + |
| 78 | +## PubSub |
| 79 | + |
| 80 | +You can have as many publisher and subscribers on the same topic as you wish. They just need to agree on the filename. |
| 81 | + |
| 82 | +```cc |
| 83 | +a0::Publisher p("my_pubsub_topic"); |
| 84 | +p.pub("foo"); |
| 85 | +``` |
| 86 | +
|
| 87 | +You just published `"foo"` to the `"my_pubsub_topic"`. |
| 88 | +
|
| 89 | +To read those message, you can create a subscriber on the same topic: |
| 90 | +```cc |
| 91 | +a0::Subscriber sub( |
| 92 | + "my_pubsub_topic", |
| 93 | + A0_INIT_AWAIT_NEW, // or MOST_RECENT or OLDEST |
| 94 | + A0_ITER_NEWEST, // or NEXT |
| 95 | + [](a0::PacketView pkt_view) { |
| 96 | + std::cout << "Got: " << pkt_view.payload() << std::endl; |
| 97 | + }); |
| 98 | +``` |
| 99 | +The callback will trigger whenever a message is published. |
| 100 | + |
| 101 | +The `Subscriber` object spawns a thread that will read the topic and call the callback. |
| 102 | + |
| 103 | +The `A0_INIT` tells the subscriber where to start reading. |
| 104 | +* `A0_INIT_AWAIT_NEW`: Start with messages published after the creation of the subscriber. |
| 105 | +* `A0_INIT_MOST_RECENT`: Start with the most recently published message. Useful for state and configuration. But be careful, this can be quite old! |
| 106 | +* `A0_INIT_OLDEST`: Topics keep a history of 16MB (unless configures otherwise). Start with the oldest thing still in there. |
| 107 | + |
| 108 | +The `A0_ITER` tells the subscriber how to continue reading messages. After each callback: |
| 109 | +* `A0_ITER_NEXT`: grab the sequentially next message. When you don't want to miss a thing. |
| 110 | +* `A0_ITER_NEWEST`: grab the newest available unread message. When you want to keep up with the firehose. |
| 111 | + |
| 112 | +```cc |
| 113 | +a0::SubscriberSync sub_sync( |
| 114 | + "my_pubsub_topic", |
| 115 | + A0_INIT_OLDEST, A0_ITER_NEXT); |
| 116 | +while (sub_sync.has_next()) { |
| 117 | + auto pkt = sub_sync.next(); |
| 118 | + std::cout << "Got: " << pkt.payload() << std::endl; |
| 119 | +} |
| 120 | +``` |
| 121 | +
|
| 122 | +## RPC |
| 123 | +
|
| 124 | +Create an `RpcServer`: |
| 125 | +
|
| 126 | +```cc |
| 127 | +a0::RpcServer server( |
| 128 | + "my_rpc_topic", |
| 129 | + /* onrequest = */ [](a0::RpcRequest req) { |
| 130 | + std::cout << "Got: " << req.pkt().payload() << std::endl; |
| 131 | + req.reply("echo " + std::string(req.pkt().payload())); |
| 132 | + }, |
| 133 | + /* oncancel = */ nullptr); |
| 134 | +``` |
| 135 | + |
| 136 | +Create an `RpcClient`: |
| 137 | + |
| 138 | +```cc |
| 139 | +a0::RpcClient client("my_rpc_topic"); |
| 140 | +client.send("client msg", [](a0::PacketView reply) { |
| 141 | + std::cout << "Got: " << reply.payload() << std::endl; |
| 142 | +}); |
| 143 | +``` |
| 144 | +
|
| 145 | +# Installation |
| 146 | +
|
| 147 | +## Install From Source |
| 148 | +
|
| 149 | +### Ubuntu Dependencies |
| 150 | +
|
| 151 | +```sh |
| 152 | +apt install g++ make |
| 153 | +``` |
| 154 | + |
| 155 | +### Alpine Dependencies |
| 156 | + |
| 157 | +```sh |
| 158 | +apk add g++ linux-headers make |
| 159 | +``` |
| 160 | + |
| 161 | +### Download And Install |
| 162 | + |
| 163 | +```sh |
| 164 | +git clone https://github.com/alephzero/alephzero.git |
| 165 | +cd alephzero |
| 166 | +make install -j |
| 167 | +``` |
| 168 | + |
| 169 | +## Install From Package |
| 170 | + |
| 171 | +Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated. |
| 172 | + |
| 173 | +## Integration |
| 174 | + |
| 175 | +### Command Line |
| 176 | + |
| 177 | +Add the following to g++ / clang commands. |
| 178 | +```sh |
| 179 | +-L${libdir} -lalephzero -lpthread |
| 180 | +``` |
| 181 | + |
| 182 | +### Package-cfg |
| 183 | + |
| 184 | +```sh |
| 185 | +pkg-config --cflags --libs alephzero |
| 186 | +``` |
| 187 | + |
| 188 | +### CMake |
| 189 | + |
| 190 | +Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated. |
| 191 | + |
| 192 | +### Bazel |
| 193 | + |
| 194 | +Coming soon-ish. Let me know if you want this and I'll prioritize it. |
| 195 | + |
| 196 | +# Across Dockers |
| 197 | + |
| 198 | +For programs running across different dockers to be able to communicate, we need to have them match up on two flags: `--ipc` and `--pid`. |
| 199 | + |
| 200 | +* `--ipc` shares the `/dev/shm` filesystem. This is necessary to open the same file topics. |
| 201 | +* `--pid` shares the process id namespace. This is necessary for the locking and notification systems. |
| 202 | + |
| 203 | +In the simplest case, you can set them both to `host` and talk through the system's global `/dev/shm` and process id namespace. |
| 204 | +```sh |
| 205 | +docker run --ipc=host --pid=host --name=foo foo_image |
| 206 | +docker run --ipc=host --pid=host --name=bar bar_image |
| 207 | +``` |
| 208 | + |
| 209 | +Or, you can mark one as `shareable` and have the others connect to it: |
| 210 | +```sh |
| 211 | +docker run --ipc=shareable --pid=shareable --name=foo foo_image |
| 212 | +docker run --ipc=container:foo --pid=container:foo --name=bar bar_image |
| 213 | +``` |
0 commit comments