Skip to content

Commit 3344bbf

Browse files
committed
impl robust mutex & condition (using alephzero's mtx implementation)
1 parent 96551d5 commit 3344bbf

28 files changed

+1695
-271
lines changed

src/CMakeLists.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
project(ipc)
22

33
if(UNIX)
4-
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux/*.cpp)
4+
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux/*.cpp
5+
${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux/a0/*.c)
56
else()
67
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/win/*.cpp)
78
endif()
@@ -33,18 +34,18 @@ set_target_properties(${PROJECT_NAME}
3334
PROPERTIES
3435
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
3536
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
36-
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" )
37+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
3738

3839
# set version
3940
set_target_properties(${PROJECT_NAME}
4041
PROPERTIES
41-
VERSION 1.1.0
42-
SOVERSION 2)
42+
VERSION 1.2.0
43+
SOVERSION 3)
4344

4445
target_include_directories(${PROJECT_NAME}
4546
PUBLIC ${LIBIPC_PROJECT_DIR}/include
4647
PRIVATE ${LIBIPC_PROJECT_DIR}/src
47-
)
48+
$<$<BOOL:UNIX>:${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux>)
4849

4950
if(NOT MSVC)
5051
target_link_libraries(${PROJECT_NAME} PUBLIC
@@ -56,5 +57,4 @@ install(
5657
TARGETS ${PROJECT_NAME}
5758
RUNTIME DESTINATION bin
5859
LIBRARY DESTINATION lib
59-
ARCHIVE DESTINATION lib
60-
)
60+
ARCHIVE DESTINATION lib)

src/libipc/platform/linux/a0/LICENSE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <http://unlicense.org>
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
```

src/libipc/platform/linux/a0/atomic.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#ifndef A0_SRC_ATOMIC_H
2+
#define A0_SRC_ATOMIC_H
3+
4+
#include "a0/inline.h"
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
A0_STATIC_INLINE
11+
void a0_barrier() {
12+
// 'atomic_thread_fence' is not supported with ‘-fsanitize=thread’
13+
__sync_synchronize();
14+
}
15+
16+
#define a0_atomic_fetch_add(P, V) __atomic_fetch_add((P), (V), __ATOMIC_RELAXED)
17+
#define a0_atomic_add_fetch(P, V) __atomic_add_fetch((P), (V), __ATOMIC_RELAXED)
18+
19+
#define a0_atomic_fetch_and(P, V) __atomic_fetch_and((P), (V), __ATOMIC_RELAXED)
20+
#define a0_atomic_and_fetch(P, V) __atomic_and_fetch((P), (V), __ATOMIC_RELAXED)
21+
22+
#define a0_atomic_fetch_or(P, V) __atomic_fetch_or((P), (V), __ATOMIC_RELAXED)
23+
#define a0_atomic_or_fetch(P, V) __atomic_or_fetch((P), (V), __ATOMIC_RELAXED)
24+
25+
#define a0_atomic_load(P) __atomic_load_n((P), __ATOMIC_RELAXED)
26+
#define a0_atomic_store(P, V) __atomic_store_n((P), (V), __ATOMIC_RELAXED)
27+
28+
// TODO(lshamis): Switch from __sync to __atomic.
29+
#define a0_cas_val(P, OV, NV) __sync_val_compare_and_swap((P), (OV), (NV))
30+
#define a0_cas(P, OV, NV) __sync_bool_compare_and_swap((P), (OV), (NV))
31+
32+
#ifdef __cplusplus
33+
}
34+
#endif
35+
36+
#endif // A0_SRC_ATOMIC_H

src/libipc/platform/linux/a0/clock.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#ifndef A0_SRC_CLOCK_H
2+
#define A0_SRC_CLOCK_H
3+
4+
#include "a0/err.h"
5+
#include "a0/inline.h"
6+
7+
#include <stdint.h>
8+
#include <time.h>
9+
10+
#include "err_macro.h"
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
static const int64_t NS_PER_SEC = 1e9;
17+
18+
typedef struct timespec timespec_t;
19+
20+
A0_STATIC_INLINE
21+
a0_err_t a0_clock_now(clockid_t clk, timespec_t* out) {
22+
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(clk, out));
23+
return A0_OK;
24+
}
25+
26+
A0_STATIC_INLINE
27+
a0_err_t a0_clock_add(timespec_t ts, int64_t add_nsec, timespec_t* out) {
28+
out->tv_sec = ts.tv_sec + add_nsec / NS_PER_SEC;
29+
out->tv_nsec = ts.tv_nsec + add_nsec % NS_PER_SEC;
30+
if (out->tv_nsec >= NS_PER_SEC) {
31+
out->tv_sec++;
32+
out->tv_nsec -= NS_PER_SEC;
33+
} else if (out->tv_nsec < 0) {
34+
out->tv_sec--;
35+
out->tv_nsec += NS_PER_SEC;
36+
}
37+
38+
return A0_OK;
39+
}
40+
41+
A0_STATIC_INLINE
42+
a0_err_t a0_clock_convert(
43+
clockid_t orig_clk,
44+
timespec_t orig_ts,
45+
clockid_t target_clk,
46+
timespec_t* target_ts) {
47+
timespec_t orig_now;
48+
A0_RETURN_ERR_ON_ERR(a0_clock_now(orig_clk, &orig_now));
49+
timespec_t target_now;
50+
A0_RETURN_ERR_ON_ERR(a0_clock_now(target_clk, &target_now));
51+
52+
int64_t add_nsec = (orig_ts.tv_sec - orig_now.tv_sec) * NS_PER_SEC + (orig_ts.tv_nsec - orig_now.tv_nsec);
53+
return a0_clock_add(target_now, add_nsec, target_ts);
54+
}
55+
56+
#ifdef __cplusplus
57+
}
58+
#endif
59+
60+
#endif // A0_SRC_CLOCK_H

src/libipc/platform/linux/a0/empty.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef A0_EMPTY_H
2+
#define A0_EMPTY_H
3+
4+
// Bah. Why is there no consistent way to zero initialize a struct?
5+
#ifdef __cplusplus
6+
#define A0_EMPTY \
7+
{}
8+
#else
9+
#define A0_EMPTY \
10+
{ 0 }
11+
#endif
12+
13+
#endif // A0_EMPTY_H

0 commit comments

Comments
 (0)