Compare commits

..

No commits in common. "main" and "original" have entirely different histories.

127 changed files with 2765 additions and 14910 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target
**/*.rs.bk
localdev/
scratch/

2056
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,49 @@
cargo-features = ["strip"]
[package]
name = "syndicate-rs"
version = "0.1.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
[workspace]
members = [
"syndicate",
"syndicate-macros",
"syndicate-schema-plugin",
"syndicate-server",
"syndicate-tools",
]
[patch.crates-io]
preserves = { path = "/home/tonyg/src/preserves/implementations/rust/preserves" }
# [patch.crates-io]
# #
# # Use a bind mount for localdev:
# #
# # mkdir localdev
# # sudo mount --bind /home/tonyg/src localdev
# #
# preserves = { path = "localdev/preserves/implementations/rust/preserves" }
# preserves-schema = { path = "localdev/preserves/implementations/rust/preserves-schema" }
[features]
vendored-openssl = ["openssl/vendored"]
[profile.release]
strip = true
# debug = true
# lto = true
[profile.bench]
debug = true
lto = true
# [patch.crates-io]
# # Unfortunately, until [1] is fixed (perhaps via [2]), we have to use a patched proc-macro2.
# # [1]: https://github.com/dtolnay/proc-macro2/issues/402
# # [2]: https://github.com/dtolnay/proc-macro2/pull/407
# proc-macro2 = { git = "https://github.com/tonyg/proc-macro2", branch = "repair_span_start_end" }
[lib]
name = "syndicate"
[dependencies]
preserves = "0.13.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_bytes = "0.11"
tokio = { version = "0.2.21", features = ["macros", "rt-threaded", "sync", "dns", "tcp", "time", "stream"] }
tokio-util = { version = "0.3.1", features = ["codec"] }
bytes = "0.5.4"
futures = "0.3.5"
structopt = "0.3.14"
tungstenite = "0.10.1"
tokio-tungstenite = "0.10.1"
tracing = "0.1.14"
tracing-subscriber = "0.2.5"
tracing-futures = "0.2.4"
# Only used for vendored-openssl, which in turn is being used for cross-builds
openssl = { version = "0.10", optional = true }
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "bench_dataspace"
harness = false

View File

@ -1,6 +0,0 @@
[build.env]
# Both of these are needed to workaround https://github.com/rust-embedded/cross/issues/598
passthrough = [
"RUSTFLAGS",
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS",
]

View File

@ -1,41 +1,24 @@
# Use cargo release to manage publication and versions etc.
#
# cargo install cargo-release
# cargo install cargo-watch
watch:
cargo watch -c -x check -x 'test -- --nocapture'
all:
run-watch:
RUST_BACKTRACE=1 cargo watch -c -x 'build --all-targets' -x 'run'
clippy-watch:
cargo watch -c -x clippy
inotifytest:
inotifytest sh -c 'reset; cargo build && RUST_BACKTRACE=1 cargo test -- --nocapture'
binary: binary-release
binary-release:
cargo build --release --all-targets
binary-debug:
cargo build --all-targets
test:
cargo test
test-all:
cargo test --all-targets
ws-bump:
cargo workspaces version \
--no-global-tag \
--individual-tag-prefix '%n-v' \
--allow-branch 'main' \
$(BUMP_ARGS)
ws-publish:
cargo workspaces publish \
--from-git
PROTOCOLS_BRANCH=main
pull-protocols:
git subtree pull -P syndicate/protocols \
-m 'Merge latest changes from the syndicate-protocols repository' \
git@git.syndicate-lang.org:syndicate-lang/syndicate-protocols \
$(PROTOCOLS_BRANCH)
static: static-x86_64
static-%:
CARGO_TARGET_DIR=target/target.$* cross build --target $*-unknown-linux-musl --features vendored-openssl,jemalloc
###########################################################################
# OK, rather than doing it myself (per
# https://eighty-twenty.org/2019/10/15/cross-compiling-rust), it turns
# out past a certain level of complexity we need more than just a
@ -51,30 +34,10 @@ static-%:
# etc, ready on my system despite being otherwise able to rely on
# cross. I think. It's a bit confusing.
x86_64-binary: x86_64-binary-release
arm-binary: arm-binary-release
x86_64-binary-release:
CARGO_TARGET_DIR=target/target.x86_64 cross build --target x86_64-unknown-linux-musl --release --all-targets --features vendored-openssl,jemalloc
arm-binary-release:
cross build --target=armv7-unknown-linux-musleabihf --release --all-targets --features vendored-openssl
x86_64-binary-debug:
CARGO_TARGET_DIR=target/target.x86_64 cross build --target x86_64-unknown-linux-musl --all-targets --features vendored-openssl
armv7-binary: armv7-binary-release
armv7-binary-release:
CARGO_TARGET_DIR=target/target.armv7 cross build --target=armv7-unknown-linux-musleabihf --release --all-targets --features vendored-openssl
armv7-binary-debug:
CARGO_TARGET_DIR=target/target.armv7 cross build --target=armv7-unknown-linux-musleabihf --all-targets --features vendored-openssl
# As of 2023-05-12 (and probably earlier!) this is no longer required with current Rust nightlies
# # Hack to workaround https://github.com/rust-embedded/cross/issues/598
# HACK_WORKAROUND_ISSUE_598=CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C link-arg=/usr/local/aarch64-linux-musl/lib/libc.a"
aarch64-binary: aarch64-binary-release
aarch64-binary-release:
CARGO_TARGET_DIR=target/target.aarch64 cross build --target=aarch64-unknown-linux-musl --release --all-targets --features vendored-openssl,jemalloc
aarch64-binary-debug:
CARGO_TARGET_DIR=target/target.aarch64 cross build --target=aarch64-unknown-linux-musl --all-targets --features vendored-openssl
arm-binary-debug:
cross build --target=armv7-unknown-linux-musleabihf --all-targets --features vendored-openssl

View File

@ -2,51 +2,28 @@
A Rust implementation of:
- the Syndicated Actor model, including assertion-based
communication, failure-handling, capability-style security,
dataspace entities, and facets as a structuring principle;
- the Syndicate network protocol, including
- a high-speed Dataspace indexing structure
([`skeleton.rs`](syndicate/src/skeleton.rs); see also
[HOWITWORKS.md](https://git.syndicate-lang.org/syndicate-lang/syndicate-rkt/src/commit/90c4c60699069b496491b81ee63b5a45ffd638cb/syndicate/HOWITWORKS.md)
from `syndicate-rkt`) and
- a standalone Syndicate protocol "broker" service (roughly
comparable in scope and intent to D-Bus); and
- a handful of [example programs](syndicate-server/examples/).
- a high-speed Dataspace indexing structure (see
[HOWITWORKS.md](https://git.syndicate-lang.org/syndicate-lang/syndicate-rkt/src/commit/90c4c60699069b496491b81ee63b5a45ffd638cb/syndicate/HOWITWORKS.md)
from `syndicate-rkt`),
- a standalone Syndicate protocol *broker* service, and
- a handful of [examples](examples/).
![The Syndicate/rs server running.](syndicate-rs-server.png)
*The Syndicate/rs server running.*
## Quickstart
From docker or podman:
docker run -it --rm leastfixedpoint/syndicate-server /syndicate-server -p 8001
Build and run from source:
git clone https://git.syndicate-lang.org/syndicate-lang/syndicate-rs
cd syndicate-rs
cargo build --release
./target/release/syndicate-server -p 8001
If you have [`mold`](https://github.com/rui314/mold) available (`apt install mold`), you may be
able to get faster linking by creating `.cargo/config.toml` as follows:
[build]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
Enabling the `jemalloc` feature can get a *substantial* (~20%-50%) improvement in throughput.
./target/release/syndicate-server
## Running the examples
In one window, start the server with a basic configuration:
In one window, start the server:
./target/release/syndicate-server -c dev-scripts/benchmark-config.pr
./target/release/syndicate-server
Then, choose one of the examples below.
@ -84,7 +61,7 @@ about who kicks off the pingpong session.
You may find better performance by restricting the server to fewer
cores than you have available. For example, for me, running
taskset -c 0,1 ./target/release/syndicate-server -c dev-scripts/benchmark-config.pr
taskset -c 0,1 ./target/release/syndicate-server
roughly *doubles* throughput for a single producer/consumer pair,
roughly *quadruples* throughput for a single producer/consumer pair,
on my 48-core AMD CPU.

184
benches/bench_dataspace.rs Normal file
View File

@ -0,0 +1,184 @@
use criterion::{criterion_group, criterion_main, Criterion};
use futures::Sink;
use std::mem::drop;
use std::pin::Pin;
use std::sync::{Arc, Mutex, atomic::{AtomicU64, Ordering}};
use std::task::{Context, Poll};
use std::thread;
use std::time::Instant;
use structopt::StructOpt;
use syndicate::peer::Peer;
use syndicate::{config, spaces, packets, value::{Value, IOValue}};
use tokio::runtime::Runtime;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tracing::Level;
struct SinkTx<T> {
tx: Option<UnboundedSender<T>>,
}
impl<T> SinkTx<T> {
fn new(tx: UnboundedSender<T>) -> Self {
SinkTx { tx: Some(tx) }
}
}
impl<T> Sink<T> for SinkTx<T> {
type Error = packets::Error;
fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Result<(), packets::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, v: T) -> Result<(), packets::Error> {
self.tx.as_ref().unwrap().send(v).map_err(|e| packets::Error::Message(e.to_string()))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Result<(), packets::Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Result<(), packets::Error>> {
(&mut self).tx = None;
Poll::Ready(Ok(()))
}
}
#[inline]
fn says(who: IOValue, what: IOValue) -> IOValue {
let mut r = Value::simple_record("Says", 2);
r.fields_vec_mut().push(who);
r.fields_vec_mut().push(what);
r.finish().wrap()
}
pub fn bench_pub(c: &mut Criterion) {
let filter = tracing_subscriber::filter::EnvFilter::from_default_env()
.add_directive(tracing_subscriber::filter::LevelFilter::INFO.into());
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_ansi(true)
.with_max_level(Level::TRACE)
.with_env_filter(filter)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Could not set tracing global subscriber");
c.bench_function("publication alone", |b| {
b.iter_custom(|iters| {
let no_args: Vec<String> = vec![];
let config = Arc::new(config::ServerConfig::from_iter(no_args.iter()));
let spaces = Arc::new(Mutex::new(spaces::Spaces::new()));
let (c2s_tx, c2s_rx) = unbounded_channel();
let (s2c_tx, _s2c_rx) = unbounded_channel();
let runtime_handle = thread::spawn(move || {
let mut rt = Runtime::new().unwrap();
rt.block_on(async {
Peer::new(0, c2s_rx, SinkTx::new(s2c_tx)).run(spaces, &config).await.unwrap();
})
});
c2s_tx.send(Ok(packets::C2S::Connect(Value::from("bench_pub").wrap()))).unwrap();
let turn = packets::C2S::Turn(vec![
packets::Action::Message(says(Value::from("bench_pub").wrap(),
Value::ByteString(vec![]).wrap()))]);
let start = Instant::now();
for _ in 0..iters {
c2s_tx.send(Ok(turn.clone())).unwrap();
}
drop(c2s_tx);
runtime_handle.join().unwrap();
start.elapsed()
})
});
c.bench_function("publication and subscription", |b| {
b.iter_custom(|iters| {
let no_args: Vec<String> = vec![];
let config = Arc::new(config::ServerConfig::from_iter(no_args.iter()));
let spaces = Arc::new(Mutex::new(spaces::Spaces::new()));
let turn_count = Arc::new(AtomicU64::new(0));
let (c2s_tx, c2s_rx) = unbounded_channel();
let c2s_tx = Arc::new(c2s_tx);
{
let c2s_tx = c2s_tx.clone();
c2s_tx.send(Ok(packets::C2S::Connect(Value::from("bench_pub").wrap()))).unwrap();
let discard: IOValue = Value::simple_record0("discard").wrap();
let capture: IOValue = Value::simple_record1("capture", discard).wrap();
c2s_tx.send(Ok(packets::C2S::Turn(vec![
packets::Action::Assert(Value::from(0).wrap(),
Value::simple_record1(
"observe",
says(Value::from("bench_pub").wrap(),
capture)).wrap())]))).unwrap();
// tracing::info!("Sending {} messages", iters);
let turn = packets::C2S::Turn(vec![
packets::Action::Message(says(Value::from("bench_pub").wrap(),
Value::ByteString(vec![]).wrap()))]);
for _ in 0..iters {
c2s_tx.send(Ok(turn.clone())).unwrap();
}
c2s_tx.send(Ok(packets::C2S::Turn(vec![
packets::Action::Clear(Value::from(0).wrap())]))).unwrap();
}
let start = Instant::now();
let runtime_handle = {
let turn_count = turn_count.clone();
let mut c2s_tx = Some(c2s_tx.clone());
thread::spawn(move || {
let mut rt = Runtime::new().unwrap();
rt.block_on(async move {
let (s2c_tx, mut s2c_rx) = unbounded_channel();
let consumer_handle = tokio::spawn(async move {
while let Some(p) = s2c_rx.recv().await {
// tracing::info!("Consumer got {:?}", &p);
match p {
packets::S2C::Ping() => (),
packets::S2C::Turn(actions) => {
for a in actions {
match a {
packets::Event::Msg(_, _) => {
turn_count.fetch_add(1, Ordering::Relaxed);
},
packets::Event::End(_) => {
c2s_tx.take();
}
_ => panic!("Unexpected action: {:?}", a),
}
}
},
_ => panic!("Unexpected packet: {:?}", p),
}
}
// tracing::info!("Consumer terminating");
});
Peer::new(0, c2s_rx, SinkTx::new(s2c_tx)).run(spaces, &config).await.unwrap();
consumer_handle.await.unwrap();
})
})
};
drop(c2s_tx);
runtime_handle.join().unwrap();
let elapsed = start.elapsed();
let actual_turns = turn_count.load(Ordering::SeqCst);
if actual_turns != iters {
panic!("Expected {}, got {} messages", iters, actual_turns);
}
elapsed
})
});
}
criterion_group!(publish, bench_pub);
criterion_main!(publish);

View File

@ -1,3 +0,0 @@
let ?root_ds = dataspace
<require-service <relay-listener <tcp "0.0.0.0" 9001> $gatekeeper>>
<bind <ref { oid: "syndicate" key: #x"" }> $root_ds #f>

View File

@ -1,2 +0,0 @@
#!/bin/sh
while true; do ../target/release/examples/dirty-consumer "$@"; sleep 2; done

View File

@ -1,2 +0,0 @@
#!/bin/sh
while true; do ../target/release/examples/dirty-producer "$@"; sleep 2; done

View File

@ -1,2 +0,0 @@
#!/bin/sh
while true; do ../target/release/examples/state-consumer "$@"; sleep 2; done

View File

@ -1,2 +0,0 @@
#!/bin/sh
while true; do ../target/release/examples/state-producer "$@"; sleep 2; done

View File

@ -1,7 +0,0 @@
#!/bin/sh
TASKSET='taskset -c 0,1'
if [ $(uname -s) = 'Darwin' ]
then
TASKSET=
fi
make -C ../syndicate-server binary && exec $TASKSET ../target/release/syndicate-server -c benchmark-config.pr "$@"

1
docker/.gitignore vendored
View File

@ -1 +0,0 @@
syndicate-server.*

View File

@ -1,6 +0,0 @@
FROM busybox
RUN mkdir /data
ARG TARGETARCH
COPY ./syndicate-server.$TARGETARCH /syndicate-server
EXPOSE 1
CMD ["/syndicate-server", "-c", "/data", "-p", "1"]

View File

@ -1,37 +0,0 @@
U=leastfixedpoint
I=syndicate-server
ARCHITECTURES:=amd64 arm arm64
SERVERS:=$(patsubst %,syndicate-server.%,$(ARCHITECTURES))
VERSION=$(shell ./syndicate-server.$(shell ./docker-architecture $$(uname -m)) --version | cut -d' ' -f2)
all:
.PHONY: all clean image push push-only
clean:
rm -f syndicate-server.*
-podman images -q $(U)/$(I) | sort -u | xargs podman rmi -f
image: $(SERVERS)
for A in $(ARCHITECTURES); do set -x; \
podman build --platform=linux/$$A \
-t $(U)/$(I):$(VERSION)-$$A \
-t $(U)/$(I):latest-$$A \
.; \
done
rm -f tmp.image
push: image push-only
push-only:
$(patsubst %,podman push $(U)/$(I):$(VERSION)-%;,$(ARCHITECTURES))
$(patsubst %,podman push $(U)/$(I):latest-%;,$(ARCHITECTURES))
podman rmi -f $(U)/$(I):$(VERSION) $(U)/$(I):latest
podman manifest create $(U)/$(I):$(VERSION) $(patsubst %,$(U)/$(I):$(VERSION)-%,$(ARCHITECTURES))
podman manifest create $(U)/$(I):latest $(patsubst %,$(U)/$(I):latest-%,$(ARCHITECTURES))
podman manifest push $(U)/$(I):$(VERSION)
podman manifest push $(U)/$(I):latest
syndicate-server.%:
make -C .. $$(./alpine-architecture $*)-binary-release
cp -a ../target/target.$$(./alpine-architecture $*)/$$(./alpine-architecture $*)-unknown-linux-musl*/release/syndicate-server $@

View File

@ -1,9 +0,0 @@
# Docker images for syndicate-server
Build using podman:
apt install podman
and at least until the dependencies are fixed (?),
apt install uidmap slirp4netns

View File

@ -1,6 +0,0 @@
#!/bin/sh
case $1 in
amd64) echo x86_64;;
arm) echo armv7;;
arm64) echo aarch64;;
esac

View File

@ -1,6 +0,0 @@
#!/bin/sh
case $1 in
x86_64) echo amd64;;
armv7) echo arm;;
aarch64) echo arm64;;
esac

View File

@ -1,9 +0,0 @@
version: "3"
services:
syndicate:
image: leastfixedpoint/syndicate-server
ports:
- "1:1"
volumes:
- "/etc/syndicate:/data"

61
examples/consumer.rs Normal file
View File

@ -0,0 +1,61 @@
#![recursion_limit = "256"]
use syndicate::{V, value::Value};
use syndicate::packets::{ClientCodec, C2S, S2C, Action};
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use futures::SinkExt;
use futures::StreamExt;
use futures::FutureExt;
use futures::select;
use core::time::Duration;
use tokio::time::interval;
#[inline]
fn says(who: V, what: V) -> V {
let mut r = Value::simple_record("Says", 2);
r.fields_vec_mut().push(who);
r.fields_vec_mut().push(what);
r.finish().wrap()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let discard: V = Value::simple_record0("discard").wrap();
let capture: V = Value::simple_record1("capture", discard).wrap();
let mut frames = Framed::new(TcpStream::connect("127.0.0.1:8001").await?, ClientCodec::new());
frames.send(C2S::Connect(Value::from("chat").wrap())).await?;
frames.send(
C2S::Turn(vec![Action::Assert(
Value::from(0).wrap(),
Value::simple_record1("observe", says(capture.clone(), capture)).wrap())]))
.await?;
let mut stats_timer = interval(Duration::from_secs(1));
let mut turn_counter = 0;
let mut event_counter = 0;
loop {
select! {
_instant = stats_timer.next().boxed().fuse() => {
print!("{:?} turns, {:?} events in the last second\n", turn_counter, event_counter);
turn_counter = 0;
event_counter = 0;
},
frame = frames.next().boxed().fuse() => match frame {
None => return Ok(()),
Some(res) => match res? {
S2C::Err(msg, _) => return Err(msg.into()),
S2C::Turn(es) => {
// print!("{:?}\n", es);
turn_counter = turn_counter + 1;
event_counter = event_counter + es.len();
},
S2C::Ping() => frames.send(C2S::Pong()).await?,
S2C::Pong() => (),
}
},
}
}
}

193
examples/pingpong.rs Normal file
View File

@ -0,0 +1,193 @@
#![recursion_limit = "512"]
use core::time::Duration;
use futures::FutureExt;
use futures::SinkExt;
use futures::StreamExt;
use futures::select;
use std::time::{SystemTime, SystemTimeError};
use structopt::StructOpt;
use tokio::net::TcpStream;
use tokio::time::interval;
use tokio_util::codec::Framed;
use syndicate::packets::{ClientCodec, C2S, S2C, Action, Event};
use syndicate::value::{NestedValue, Value, IOValue};
#[derive(Clone, Debug, StructOpt)]
pub struct PingConfig {
#[structopt(short = "t", default_value = "1")]
turn_count: u32,
#[structopt(short = "a", default_value = "1")]
action_count: u32,
#[structopt(short = "l", default_value = "0")]
report_latency_every: usize,
#[structopt(short = "b", default_value = "0")]
bytes_padding: usize,
}
#[derive(Clone, Debug, StructOpt)]
pub enum PingPongMode {
Ping(PingConfig),
Pong,
}
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(subcommand)]
mode: PingPongMode,
#[structopt(default_value = "pingpong")]
dataspace: String,
}
fn now() -> Result<u64, SystemTimeError> {
Ok(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_nanos() as u64)
}
fn simple_record2(label: &str, v1: IOValue, v2: IOValue) -> IOValue {
let mut r = Value::simple_record(label, 2);
r.fields_vec_mut().push(v1);
r.fields_vec_mut().push(v2);
r.finish().wrap()
}
fn report_latencies(rtt_ns_samples: &Vec<u64>) {
let n = rtt_ns_samples.len();
let rtt_0 = rtt_ns_samples[0];
let rtt_50 = rtt_ns_samples[n * 1 / 2];
let rtt_90 = rtt_ns_samples[n * 90 / 100];
let rtt_95 = rtt_ns_samples[n * 95 / 100];
let rtt_99 = rtt_ns_samples[n * 99 / 100];
let rtt_99_9 = rtt_ns_samples[n * 999 / 1000];
let rtt_99_99 = rtt_ns_samples[n * 9999 / 10000];
let rtt_max = rtt_ns_samples[n - 1];
println!("rtt: 0% {:05.5}ms, 50% {:05.5}ms, 90% {:05.5}ms, 95% {:05.5}ms, 99% {:05.5}ms, 99.9% {:05.5}ms, 99.99% {:05.5}ms, max {:05.5}ms",
rtt_0 as f64 / 1000000.0,
rtt_50 as f64 / 1000000.0,
rtt_90 as f64 / 1000000.0,
rtt_95 as f64 / 1000000.0,
rtt_99 as f64 / 1000000.0,
rtt_99_9 as f64 / 1000000.0,
rtt_99_99 as f64 / 1000000.0,
rtt_max as f64 / 1000000.0);
println!("msg: 0% {:05.5}ms, 50% {:05.5}ms, 90% {:05.5}ms, 95% {:05.5}ms, 99% {:05.5}ms, 99.9% {:05.5}ms, 99.99% {:05.5}ms, max {:05.5}ms",
rtt_0 as f64 / 2000000.0,
rtt_50 as f64 / 2000000.0,
rtt_90 as f64 / 2000000.0,
rtt_95 as f64 / 2000000.0,
rtt_99 as f64 / 2000000.0,
rtt_99_9 as f64 / 2000000.0,
rtt_99_99 as f64 / 2000000.0,
rtt_max as f64 / 2000000.0);
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_args();
let (send_label, recv_label, report_latency_every, should_echo, bytes_padding) =
match config.mode {
PingPongMode::Ping(ref c) =>
("Ping", "Pong", c.report_latency_every, false, c.bytes_padding),
PingPongMode::Pong =>
("Pong", "Ping", 0, true, 0),
};
let mut frames = Framed::new(TcpStream::connect("127.0.0.1:8001").await?, ClientCodec::new());
frames.send(C2S::Connect(Value::from(config.dataspace).wrap())).await?;
let discard: IOValue = Value::simple_record0("discard").wrap();
let capture: IOValue = Value::simple_record1("capture", discard).wrap();
let pat: IOValue = simple_record2(recv_label, capture.clone(), capture);
frames.send(
C2S::Turn(vec![Action::Assert(
Value::from(0).wrap(),
Value::simple_record1("observe", pat).wrap())]))
.await?;
let padding: IOValue = Value::ByteString(vec![0; bytes_padding]).wrap();
let mut stats_timer = interval(Duration::from_secs(1));
let mut turn_counter = 0;
let mut event_counter = 0;
let mut current_rec: IOValue = simple_record2(send_label,
Value::from(0).wrap(),
padding.clone());
if let PingPongMode::Ping(ref c) = config.mode {
for _ in 0..c.turn_count {
let mut actions = vec![];
current_rec = simple_record2(send_label,
Value::from(now()?).wrap(),
padding.clone());
for _ in 0..c.action_count {
actions.push(Action::Message(current_rec.clone()));
}
frames.send(C2S::Turn(actions)).await?;
}
}
let mut rtt_ns_samples: Vec<u64> = vec![0; report_latency_every];
let mut rtt_batch_count = 0;
loop {
select! {
_instant = stats_timer.next().boxed().fuse() => {
print!("{:?} turns, {:?} events in the last second\n", turn_counter, event_counter);
turn_counter = 0;
event_counter = 0;
},
frame = frames.next().boxed().fuse() => match frame {
None => return Ok(()),
Some(res) => match res? {
S2C::Err(msg, _) => return Err(msg.into()),
S2C::Turn(events) => {
turn_counter = turn_counter + 1;
event_counter = event_counter + events.len();
let mut actions = vec![];
let mut have_sample = false;
for e in events {
match e {
Event::Msg(_, captures) => {
if should_echo || (report_latency_every == 0) {
actions.push(Action::Message(
simple_record2(send_label,
captures[0].clone(),
captures[1].clone())));
} else {
if !have_sample {
let rtt_ns = now()? - captures[0].value().to_u64()?;
rtt_ns_samples[rtt_batch_count] = rtt_ns;
rtt_batch_count = rtt_batch_count + 1;
if rtt_batch_count == report_latency_every {
rtt_ns_samples.sort();
report_latencies(&rtt_ns_samples);
rtt_batch_count = 0;
}
have_sample = true;
current_rec = simple_record2(send_label,
Value::from(now()?).wrap(),
padding.clone());
}
actions.push(Action::Message(current_rec.clone()));
}
}
_ =>
()
}
}
frames.send(C2S::Turn(actions)).await?;
},
S2C::Ping() => frames.send(C2S::Pong()).await?,
S2C::Pong() => (),
}
},
}
}
}

59
examples/producer.rs Normal file
View File

@ -0,0 +1,59 @@
use futures::{SinkExt, StreamExt, poll};
use std::task::Poll;
use structopt::StructOpt;
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use syndicate::packets::{ClientCodec, C2S, S2C, Action};
use syndicate::value::{Value, IOValue};
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "a", default_value = "1")]
action_count: u32,
#[structopt(short = "b", default_value = "0")]
bytes_padding: usize,
}
#[inline]
fn says(who: IOValue, what: IOValue) -> IOValue {
let mut r = Value::simple_record("Says", 2);
r.fields_vec_mut().push(who);
r.fields_vec_mut().push(what);
r.finish().wrap()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_args();
let mut frames = Framed::new(TcpStream::connect("127.0.0.1:8001").await?, ClientCodec::new());
frames.send(C2S::Connect(Value::from("chat").wrap())).await?;
let padding: IOValue = Value::ByteString(vec![0; config.bytes_padding]).wrap();
loop {
let mut actions = vec![];
for _ in 0..config.action_count {
actions.push(Action::Message(says(Value::from("producer").wrap(),
padding.clone())));
}
frames.send(C2S::Turn(actions)).await?;
loop {
match poll!(frames.next()) {
Poll::Pending => break,
Poll::Ready(None) => {
print!("Server closed connection");
return Ok(());
}
Poll::Ready(Some(res)) => {
let p = res?;
print!("{:?}\n", p);
if let S2C::Ping() = p { frames.send(C2S::Pong()).await? }
}
}
}
}
}

View File

@ -0,0 +1,76 @@
#![recursion_limit = "256"]
use syndicate::{V, value::Value};
use syndicate::packets::{ClientCodec, C2S, S2C, Action, Event};
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use futures::SinkExt;
use futures::StreamExt;
use futures::FutureExt;
use futures::select;
use core::time::Duration;
use tokio::time::interval;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let discard: V = Value::simple_record0("discard").wrap();
let capture: V = Value::simple_record1("capture", discard).wrap();
let mut frames = Framed::new(TcpStream::connect("127.0.0.1:8001").await?, ClientCodec::new());
frames.send(C2S::Connect(Value::from("chat").wrap())).await?;
frames.send(
C2S::Turn(vec![Action::Assert(
Value::from(0).wrap(),
Value::simple_record1("observe",
Value::simple_record1("Present", capture).wrap()).wrap())]))
.await?;
let mut stats_timer = interval(Duration::from_secs(1));
let mut turn_counter = 0;
let mut event_counter = 0;
let mut arrival_counter = 0;
let mut departure_counter = 0;
let mut occupancy = 0;
loop {
select! {
_instant = stats_timer.next().boxed().fuse() => {
print!("{:?} turns, {:?} events, {:?} arrivals, {:?} departures, {:?} present in the last second\n",
turn_counter,
event_counter,
arrival_counter,
departure_counter,
occupancy);
turn_counter = 0;
event_counter = 0;
arrival_counter = 0;
departure_counter = 0;
},
frame = frames.next().boxed().fuse() => match frame {
None => return Ok(()),
Some(res) => match res? {
S2C::Err(msg, _) => return Err(msg.into()),
S2C::Turn(events) => {
turn_counter = turn_counter + 1;
event_counter = event_counter + events.len();
for e in events {
match e {
Event::Add(_, _) => {
arrival_counter = arrival_counter + 1;
occupancy = occupancy + 1;
},
Event::Del(_, _) => {
departure_counter = departure_counter + 1;
occupancy = occupancy - 1;
},
_ => ()
}
}
},
S2C::Ping() => frames.send(C2S::Pong()).await?,
S2C::Pong() => (),
}
},
}
}
}

View File

@ -0,0 +1,49 @@
use futures::{SinkExt, StreamExt, poll};
use std::task::Poll;
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use syndicate::packets::{ClientCodec, C2S, S2C, Action, Event};
use syndicate::value::Value;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut frames = Framed::new(TcpStream::connect("127.0.0.1:8001").await?, ClientCodec::new());
frames.send(C2S::Connect(Value::from("chat").wrap())).await?;
let present_action = Action::Assert(
Value::from(0).wrap(),
Value::simple_record1("Present", Value::from(std::process::id()).wrap()).wrap());
let absent_action = Action::Clear(
Value::from(0).wrap());
frames.send(C2S::Turn(vec![present_action.clone()])).await?;
loop {
frames.send(C2S::Turn(vec![absent_action.clone()])).await?;
frames.send(C2S::Turn(vec![present_action.clone()])).await?;
loop {
match poll!(frames.next()) {
Poll::Pending => break,
Poll::Ready(None) => {
print!("Server closed connection");
return Ok(());
}
Poll::Ready(Some(res)) => {
match res? {
S2C::Turn(events) => {
for e in events {
match e {
Event::End(_) => (),
_ => println!("{:?}", e),
}
}
}
S2C::Ping() => frames.send(C2S::Pong()).await?,
p => println!("{:?}", p),
}
}
}
}
}
}

View File

@ -1,12 +0,0 @@
#!/bin/sh
buildtag() {
name=$(grep '^name' "$1" | head -1 | sed -e 's:^.*"\([^"]*\)":\1:')
version=$(grep '^version' "$1" | head -1 | sed -e 's:^.*"\([^"]*\)":\1:')
echo "$name-v$version"
}
git tag "$(buildtag syndicate/Cargo.toml)"
git tag "$(buildtag syndicate-macros/Cargo.toml)"
git tag "$(buildtag syndicate-server/Cargo.toml)"
git tag "$(buildtag syndicate-tools/Cargo.toml)"

View File

@ -1,152 +0,0 @@
# We will create a TCP listener on port 9222, which speaks unencrypted
# protocol and allows interaction with the default/system gatekeeper, which
# has a single noise binding for introducing encrypted interaction with a
# *second* gatekeeper, which finally allows resolution of references to
# other objects.
# First, build a space where we place bindings for the inner gatekeeper to
# expose.
let ?inner-bindings = dataspace
# Next, start the inner gatekeeper.
<require-service <gatekeeper $inner-bindings>>
? <service-object <gatekeeper $inner-bindings> ?inner-gatekeeper> [
# Expose it via a noise binding at the outer/system gatekeeper.
<bind <noise { key: #[z1w/OLy0wi3Veyk8/D+2182YxcrKpgc8y0ZJEBDrmWs],
secretKey: #[qLkyuJw/K4yobr4XVKExbinDwEx9QTt9PfDWyx14/kg],
service: world }>
$inner-gatekeeper #f>
]
# Now, expose the outer gatekeeper to the world, via TCP. The system
# gatekeeper is a primordial syndicate-server object bound to $gatekeeper.
<require-service <relay-listener <tcp "0.0.0.0" 9222> $gatekeeper>>
# Finally, let's expose some behaviour accessible via the inner gatekeeper.
#
# We will create a service dataspace called $world.
let ?world = dataspace
# Running `syndicate-macaroon mint --oid a-service --phrase hello` yields:
#
# <ref {oid: a-service, sig: #[JTTGQeYCgohMXW/2S2XH8g]}>
#
# That's a root capability for the service. We use the corresponding
# sturdy.SturdyDescriptionDetail to bind it to $world.
#
$inner-bindings += <bind <ref {oid: a-service, key: #"hello"}>
$world #f>
# Now, we can hand out paths to our services involving an initial noise
# step and a subsequent sturdyref/macaroon step.
#
# For example, running `syndicate-macaroon` like this:
#
# syndicate-macaroon mint --oid a-service --phrase hello \
# --caveat '<rewrite <bind <_>> <rec labelled [<lit "alice"> <ref 0>]>>'
#
# generates
#
# <ref {caveats: [<rewrite <bind <_>> <rec labelled [<lit "alice">, <ref 0>]>>],
# oid: a-service,
# sig: #[CXn7+rAoO3Xr6Y6Laap3OA]}>
#
# which is an attenuation of the root capability we bound that wraps all
# assertions and messages in a `<labelled "alice" _>` wrapper.
#
# All together, the `gatekeeper.Route` that Alice would use would be
# something like:
#
# <route [<ws "wss://generic-dataspace.demo.leastfixedpoint.com/">]
# <noise { key: #[z1w/OLy0wi3Veyk8/D+2182YxcrKpgc8y0ZJEBDrmWs],
# service: world }>
# <ref { caveats: [<rewrite <bind <_>> <rec labelled [<lit "alice">, <ref 0>]>>],
# oid: a-service,
# sig: #[CXn7+rAoO3Xr6Y6Laap3OA] }>>
#
# Here's one for "bob":
#
# syndicate-macaroon mint --oid a-service --phrase hello \
# --caveat '<rewrite <bind <_>> <rec labelled [<lit "bob"> <ref 0>]>>'
#
# <ref {caveats: [<rewrite <bind <_>> <rec labelled [<lit "bob">, <ref 0>]>>],
# oid: a-service,
# sig: #[/75BbF77LOiqNcvpzNHf0g]}>
#
# <route [<ws "wss://generic-dataspace.demo.leastfixedpoint.com/">]
# <noise { key: #[z1w/OLy0wi3Veyk8/D+2182YxcrKpgc8y0ZJEBDrmWs],
# service: world }>
# <ref { caveats: [<rewrite <bind <_>> <rec labelled [<lit "bob">, <ref 0>]>>],
# oid: a-service,
# sig: #[/75BbF77LOiqNcvpzNHf0g] }>>
#
# We relay labelled to unlabelled information, enacting a chat protocol
# that enforces usernames.
$world [
# Assertions of presence have the username wiped out and replaced with the label.
? <labelled ?who <Present _>> <Present $who>
# Likewise utterance messages.
?? <labelled ?who <Says _ ?what>> ! <Says $who $what>
# We allow anyone to subscribe to presence and utterances.
? <labelled _ <Observe <rec Present ?p> ?o>> <Observe <rec Present $p> $o>
? <labelled _ <Observe <rec Says ?p> ?o>> <Observe <rec Says $p> $o>
]
# We can also use sturdyref rewrites to directly handle `Says` and
# `Present` values, rather than wrapping with `<labelled ...>` and
# unwrapping using the script fragment just above.
#
# The multiply-quoted patterns in the `Observe` cases start to get unwieldy
# at this point!
#
# For Alice:
#
# syndicate-macaroon mint --oid a-service --phrase hello --caveat '<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "alice">]>>
# <rewrite <rec Says [<_> <bind String>]> <rec Says [<lit "alice"> <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present> <_>]> <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says> <_>]> <_>]>> <ref 0>>
# ]>'
#
# <ref { oid: a-service sig: #[s918Jk6As8AWJ9rtozOTlg] caveats: [<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "alice">]>>
# <rewrite <rec Says [<_>, <bind String>]> <rec Says [<lit "alice">, <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present>, <_>]>, <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says>, <_>]>, <_>]>> <ref 0>> ]>]}>
#
# <route [<ws "wss://generic-dataspace.demo.leastfixedpoint.com/">]
# <noise { key: #[z1w/OLy0wi3Veyk8/D+2182YxcrKpgc8y0ZJEBDrmWs],
# service: world }>
# <ref { oid: a-service sig: #[s918Jk6As8AWJ9rtozOTlg] caveats: [<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "alice">]>>
# <rewrite <rec Says [<_>, <bind String>]> <rec Says [<lit "alice">, <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present>, <_>]>, <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says>, <_>]>, <_>]>> <ref 0>> ]>]}>>
#
# For Bob:
#
# syndicate-macaroon mint --oid a-service --phrase hello --caveat '<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "bob">]>>
# <rewrite <rec Says [<_> <bind String>]> <rec Says [<lit "bob"> <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present> <_>]> <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says> <_>]> <_>]>> <ref 0>>
# ]>'
#
# <ref { oid: a-service sig: #[QBbV4LrS0i3BG6OyCPJl+A] caveats: [<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "bob">]>>
# <rewrite <rec Says [<_>, <bind String>]> <rec Says [<lit "bob">, <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present>, <_>]>, <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says>, <_>]>, <_>]>> <ref 0>> ]>]}>
#
# <route [<ws "wss://generic-dataspace.demo.leastfixedpoint.com/">]
# <noise { key: #[z1w/OLy0wi3Veyk8/D+2182YxcrKpgc8y0ZJEBDrmWs],
# service: world }>
# <ref { oid: a-service sig: #[QBbV4LrS0i3BG6OyCPJl+A] caveats: [<or [
# <rewrite <rec Present [<_>]> <rec Present [<lit "bob">]>>
# <rewrite <rec Says [<_>, <bind String>]> <rec Says [<lit "bob">, <ref 0>]>>
# <rewrite <bind <rec Observe [<rec rec [<lit Present>, <_>]>, <_>]>> <ref 0>>
# <rewrite <bind <rec Observe [<rec rec [<lit Says>, <_>]>, <_>]>> <ref 0>> ]>]}>>

View File

@ -1,65 +0,0 @@
# We use $root_ds as the httpd space.
let ?root_ds = dataspace
# Supplying $root_ds as the last parameter in this relay-listener enables httpd service.
<require-service <relay-listener <tcp "0.0.0.0" 9001> $gatekeeper $root_ds>>
# Regular gatekeeper stuff works too.
<bind <ref { oid: "syndicate" key: #x"" }> $root_ds #f>
# Create an httpd router monitoring $root_ds for requests and bind requests.
<require-service <http-router $root_ds>>
# Create a static file server. When it gets a request, it ignores the first n (here, 1)
# elements of the path, and takes the remainder as relative to its configured directory (here,
# ".").
#
<require-service <http-static-files "." 1>>
#
# It publishes a service object: requests should be asserted to this.
# The http-bind record establishes this mapping.
#
? <service-object <http-static-files "." 1> ?handler> [
$root_ds += <http-bind #f 9001 get ["files" ...] $handler>
]
# Separately, bind path /d to $index, and respond there.
#
let ?index = dataspace
$root_ds += <http-bind #f 9001 get ["d"] $index>
$index ? <request _ ?k> [
$k ! <status 200 "OK">
$k ! <header content-type "text/html">
$k ! <chunk "<!DOCTYPE html>">
$k ! <done "<html><body>D</body></html>">
]
# Similarly, bind three paths, /d, /e and /t to $index2
# Because /d doubles up, the httpd router gives a warning when it is accessed.
# Accessing /e works fine.
# Accessing /t results in wasted work because of the hijacking listeners below.
#
let ?index2 = dataspace
$root_ds += <http-bind #f 9001 get ["d"] $index2>
$root_ds += <http-bind #f 9001 get ["e"] $index2>
$root_ds += <http-bind #f 9001 get ["t"] $index2>
$index2 ? <request _ ?k> [
$k ! <status 200 "OK">
$k ! <header content-type "text/html">
$k ! <chunk "<!DOCTYPE html>">
$k ! <done "<html><body>D2</body></html>">
]
# These two hijack /t by listening for raw incoming requests the same way the httpd router
# does. They respond quicker and so win the race. The httpd router's responses are lost.
#
$root_ds ? <request <http-request _ _ _ get ["t"] _ _ _> ?k> [
$k ! <status 200 "OK">
$k ! <header content-type "text/html">
$k ! <done "<html><body>T</body></html>">
]
$root_ds ? <request <http-request _ _ _ get ["t"] _ _ _> ?k> [
$k ! <status 200 "OK">
$k ! <header content-type "text/html">
$k ! <done "<html><body>T2</body></html>">
]

View File

@ -1,41 +1,19 @@
//! A "bag" data structure (based on
//! [`BTreeMap`][std::collections::BTreeMap]), used in
//! [dataspace][super::dataspace] indexing.
use std::collections::BTreeMap;
use std::collections::btree_map::{Iter, Keys, Entry};
use std::iter::FromIterator;
use std::iter::{FromIterator, IntoIterator};
/// Element counts in [`BTreeBag`]s are 32-bit signed integers.
pub type Count = i32;
/// Represents the "net change" to the count of a given `key` after a
/// change-in-count has been applied via [`BTreeBag::change`] and
/// friends.
#[derive(Debug, PartialEq, Eq)]
pub enum Net {
/// The key previously had non-zero count, now has zero count.
PresentToAbsent,
/// The key's count stayed the same, at zero.
AbsentToAbsent,
/// The key previously had zero count, now has non-zero count.
AbsentToPresent,
/// The key's count was previously, and is now also, non-zero.
PresentToPresent,
}
/// A bag datastructure (mapping from values `V` to [`Count`]s
/// internally).
///
/// A bag may have a *negative* count against a particular element.
/// Such a bag is called a "delta", and can be used to represent a
/// *change* against a bag containing only positive counts.
///
/// Every value of type `V` notionally has a zero count associated
/// with it at the start of the bag's lifetime; so a bag is a *total*
/// mapping from elements of `V` to counts.
///
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
// Allows negative counts - a "delta"
pub struct BTreeBag<V: std::cmp::Ord> {
counts: BTreeMap<V, Count>,
total: isize,
@ -48,26 +26,13 @@ impl<V: std::cmp::Ord> std::default::Default for BTreeBag<V> {
}
impl<V: std::cmp::Ord> BTreeBag<V> {
/// Construct a new, empty bag.
pub fn new() -> BTreeBag<V> {
BTreeBag { counts: BTreeMap::new(), total: 0 }
}
/// Apply a change-in-count (`delta`) against `key`, allowing the
/// resulting count to be negative if needed.
pub fn change(&mut self, key: V, delta: Count) -> Net {
self._change(key, delta, false)
}
pub fn change(&mut self, key: V, delta: Count) -> Net { self._change(key, delta, false) }
pub fn change_clamped(&mut self, key: V, delta: Count) -> Net { self._change(key, delta, true) }
/// Apply a change-in-count (`delta`) against `key`, preventing
/// the resulting count from dropping below zero (by clamping it
/// at zero in that case).
pub fn change_clamped(&mut self, key: V, delta: Count) -> Net {
self._change(key, delta, true)
}
/// Apply a change-in-count (`delta`) against `key`, clamping the
/// resulting count at zero iff `clamp` is `true`.
pub fn _change(&mut self, key: V, delta: Count, clamp: bool) -> Net {
let old_count = self[&key];
let mut new_count = old_count + delta;
@ -82,47 +47,31 @@ impl<V: std::cmp::Ord> BTreeBag<V> {
}
}
/// Removes all elements from the bag, leaving it empty with a zero total count.
pub fn clear(&mut self) {
self.counts.clear();
self.total = 0;
}
/// `true` iff `key` has a non-zero count associated with it.
///
/// Note that `true` will be returned even when the count is *negative*.
///
pub fn contains_key(&self, key: &V) -> bool {
self.counts.contains_key(key)
}
/// `true` iff no element of the bag has a non-zero count.
pub fn is_empty(&self) -> bool {
self.counts.is_empty()
}
/// Yields the number of elements having a non-zero count.
pub fn len(&self) -> usize {
self.counts.len()
}
/// Answers the sum of all counts in the bag.
///
/// For bags with no negative counts, this is the same as the
/// number of "distinct copies" of elements in the bag.
pub fn total(&self) -> isize {
self.total
}
/// Iterates over elements in the bag with non-zero counts.
pub fn keys(&self) -> Keys<V, Count> {
self.counts.keys()
}
/// Retrieves an [`Entry`] for `key`.
///
/// Note that the `Entry` will be "absent" when `key` has a zero
/// associated count.
pub fn entry(&mut self, key: V) -> Entry<V, Count> {
self.counts.entry(key)
}

204
src/bin/syndicate-server.rs Normal file
View File

@ -0,0 +1,204 @@
use syndicate::{config, spaces, packets, ConnId};
use syndicate::peer::Peer;
use std::sync::{Mutex, Arc};
use futures::{SinkExt, StreamExt};
use tracing::{Level, error, info, trace};
use tracing_futures::Instrument;
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use tungstenite::Message;
use structopt::StructOpt; // for from_args in main
type UnitAsyncResult = Result<(), std::io::Error>;
fn message_error<E: std::fmt::Display>(e: E) -> packets::Error {
packets::Error::Message(e.to_string())
}
fn encode_message(p: packets::S2C) ->
Result<Message, packets::Error>
{
let mut bs = Vec::with_capacity(128);
preserves::ser::to_writer(&mut preserves::value::PackedWriter::new(&mut bs), &p)?;
Ok(Message::Binary(bs))
}
fn message_encoder(p: packets::S2C) -> futures::future::Ready<Result<Message, packets::Error>>
{
futures::future::ready(encode_message(p))
}
async fn message_decoder(r: Result<Message, tungstenite::Error>) -> Option<Result<packets::C2S, packets::Error>>
{
match r {
Ok(ref m) => match m {
Message::Text(_) =>
Some(Err(preserves::error::syntax_error("Text websocket frames are not accepted"))),
Message::Binary(ref bs) =>
match preserves::de::from_bytes(bs) {
Ok(p) => Some(Ok(p)),
Err(e) => Some(Err(e.into())),
},
Message::Ping(_) =>
None, // pings are handled by tungstenite before we see them
Message::Pong(_) =>
None, // unsolicited pongs are to be ignored
Message::Close(_) =>
Some(Err(preserves::error::eof())),
}
Err(tungstenite::Error::Io(e)) =>
Some(Err(e.into())),
Err(e) =>
Some(Err(message_error(e))),
}
}
async fn run_connection(connid: ConnId,
mut stream: TcpStream,
spaces: Arc<Mutex<spaces::Spaces>>,
addr: std::net::SocketAddr,
config: config::ServerConfigRef) ->
UnitAsyncResult
{
let mut buf = [0; 1]; // peek at the first byte to see what kind of connection to expect
match stream.peek(&mut buf).await? {
1 => match buf[0] {
71 /* ASCII 'G' for "GET" */ => {
info!(protocol = display("websocket"), peer = debug(addr));
let s = tokio_tungstenite::accept_async(stream).await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let (o, i) = s.split();
let i = i.filter_map(message_decoder);
let o = o.sink_map_err(message_error).with(message_encoder);
let mut p = Peer::new(connid, i, o);
p.run(spaces, &config).await?
},
_ => {
info!(protocol = display("raw"), peer = debug(addr));
let (o, i) = Framed::new(stream, packets::Codec::new()).split();
let mut p = Peer::new(connid, i, o);
p.run(spaces, &config).await?
}
}
0 => return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof,
"closed before starting")),
_ => unreachable!()
}
Ok(())
}
static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
async fn run_listener(spaces: Arc<Mutex<spaces::Spaces>>, port: u16, config: config::ServerConfigRef) ->
UnitAsyncResult
{
let mut listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
loop {
let (stream, addr) = listener.accept().await?;
let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let spaces = Arc::clone(&spaces);
let config = Arc::clone(&config);
if let Some(n) = config.recv_buffer_size { stream.set_recv_buffer_size(n)?; }
if let Some(n) = config.send_buffer_size { stream.set_send_buffer_size(n)?; }
tokio::spawn(async move {
match run_connection(id, stream, spaces, addr, config).await {
Ok(()) => info!("closed"),
Err(e) => info!(error = display(e), "closed"),
}
}.instrument(tracing::info_span!("connection", id)));
}
}
async fn periodic_tasks(spaces: Arc<Mutex<spaces::Spaces>>) -> UnitAsyncResult {
let interval = core::time::Duration::from_secs(10);
let mut delay = tokio::time::interval(interval);
loop {
delay.next().await.unwrap();
{
let mut spaces = spaces.lock().unwrap();
spaces.cleanup();
spaces.dump_stats(interval);
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let filter = tracing_subscriber::filter::EnvFilter::from_default_env()
.add_directive(tracing_subscriber::filter::LevelFilter::INFO.into());
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_ansi(true)
.with_max_level(Level::TRACE)
.with_env_filter(filter)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Could not set tracing global subscriber");
{
const BRIGHT_GREEN: &str = "\x1b[92m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const NORMAL: &str = "\x1b[0m";
const BRIGHT_YELLOW: &str = "\x1b[93m";
info!(r"{} ______ {}", GREEN, NORMAL);
info!(r"{} / {}\_{}\{} ", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
info!(r"{} / {},{}__/{} \ {} ____ __", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
info!(r"{} /{}\__/ \{},{} \{} _______ ______ ____/ /_/________ / /____", GREEN, BRIGHT_GREEN, RED, GREEN, NORMAL);
info!(r"{} \{}/ \__/ {}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
info!(r"{} \ {}'{} \__{}/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
info!(r"{} \____{}/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
info!(r" /____/");
// info!(r" {} __{}__{}__ {}", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// info!(r" {} /{}_/ \_{}\ {}", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// info!(r" {} / \__/ \ {} __ __", BRIGHT_GREEN, NORMAL);
// info!(r" {}/{}\__/ \__/{}\{} _______ ______ ____/ /__________ / /____", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// info!(r" {}\{}/ \__/ \{}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// info!(r" {} \__/ \__/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", BRIGHT_GREEN, NORMAL);
// info!(r" {} \_{}\__/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// info!(r" /____/");
info!(r"");
info!(r" {}version {}{}", BRIGHT_YELLOW, env!("CARGO_PKG_VERSION"), NORMAL);
info!(r"");
info!(r" documentation & reference material: https://syndicate-lang.org/");
info!(r" source code & bugs: https://git.syndicate-lang.org/syndicate-lang/syndicate-rs");
info!(r"");
}
let config = Arc::new(config::ServerConfig::from_args());
let spaces = Arc::new(Mutex::new(spaces::Spaces::new()));
let mut daemons = Vec::new();
{
let spaces = Arc::clone(&spaces);
tokio::spawn(async move {
periodic_tasks(spaces).await
});
}
trace!("startup");
for port in config.ports.clone() {
let spaces = Arc::clone(&spaces);
let config = Arc::clone(&config);
daemons.push(tokio::spawn(async move {
info!(port, "listening");
match run_listener(spaces, port, config).await {
Ok(()) => (),
Err(e) => error!("{}", e),
}
}.instrument(tracing::info_span!("listener", port))));
}
futures::future::join_all(daemons).await;
Ok(())
}

19
src/config.rs Normal file
View File

@ -0,0 +1,19 @@
use structopt::StructOpt;
#[derive(Clone, StructOpt)]
pub struct ServerConfig {
#[structopt(short = "p", long = "port", default_value = "8001")]
pub ports: Vec<u16>,
#[structopt(long)]
pub recv_buffer_size: Option<usize>,
#[structopt(long)]
pub send_buffer_size: Option<usize>,
#[structopt(long, default_value = "10000")]
pub overload_threshold: usize,
#[structopt(long, default_value = "5")]
pub overload_turn_limit: usize,
}
pub type ServerConfigRef = std::sync::Arc<ServerConfig>;

209
src/dataspace.rs Normal file
View File

@ -0,0 +1,209 @@
use super::V;
use super::ConnId;
use super::packets::{self, Assertion, EndpointName};
use super::skeleton;
use preserves::value::{self, Map, NestedValue};
use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering}};
use tokio::sync::mpsc::UnboundedSender;
pub type DataspaceRef = Arc<RwLock<Dataspace>>;
pub type DataspaceError = (String, V);
#[derive(Debug)]
struct Actor {
tx: UnboundedSender<packets::S2C>,
queue_depth: Arc<AtomicUsize>,
endpoints: Map<EndpointName, ActorEndpoint>,
}
#[derive(Debug)]
struct ActorEndpoint {
analysis_results: Option<skeleton::AnalysisResults>,
assertion: Assertion,
}
#[derive(Debug)]
pub struct Churn {
pub peers_added: usize,
pub peers_removed: usize,
pub assertions_added: usize,
pub assertions_removed: usize,
pub endpoints_added: usize,
pub endpoints_removed: usize,
pub messages_injected: usize,
pub messages_delivered: usize,
}
impl Churn {
pub fn new() -> Self {
Self {
peers_added: 0,
peers_removed: 0,
assertions_added: 0,
assertions_removed: 0,
endpoints_added: 0,
endpoints_removed: 0,
messages_injected: 0,
messages_delivered: 0,
}
}
pub fn reset(&mut self) {
self.peers_added = 0;
self.peers_removed = 0;
self.assertions_added = 0;
self.assertions_removed = 0;
self.endpoints_added = 0;
self.endpoints_removed = 0;
self.messages_injected = 0;
self.messages_delivered = 0;
}
}
#[derive(Debug)]
pub struct Dataspace {
name: V,
peers: Map<ConnId, Actor>,
index: skeleton::Index,
pub churn: Churn,
}
impl Dataspace {
pub fn new(name: &V) -> Self {
Self {
name: name.clone(),
peers: Map::new(),
index: skeleton::Index::new(),
churn: Churn::new(),
}
}
pub fn new_ref(name: &V) -> DataspaceRef {
Arc::new(RwLock::new(Self::new(name)))
}
pub fn register(&mut self, id: ConnId,
tx: UnboundedSender<packets::S2C>,
queue_depth: Arc<AtomicUsize>)
{
assert!(!self.peers.contains_key(&id));
self.peers.insert(id, Actor {
tx,
queue_depth,
endpoints: Map::new(),
});
self.churn.peers_added += 1;
}
pub fn deregister(&mut self, id: ConnId) {
let ac = self.peers.remove(&id).unwrap();
self.churn.peers_removed += 1;
let mut outbound_turns: Map<ConnId, Vec<packets::Event>> = Map::new();
for (epname, ep) in ac.endpoints {
self.remove_endpoint(&mut outbound_turns, id, &epname, ep);
}
outbound_turns.remove(&id);
self.deliver_outbound_turns(outbound_turns);
}
fn remove_endpoint(&mut self,
mut outbound_turns: &mut Map<ConnId, Vec<packets::Event>>,
id: ConnId,
epname: &EndpointName,
ep: ActorEndpoint)
{
let ActorEndpoint{ analysis_results, assertion } = ep;
if let Some(ar) = analysis_results {
self.index.remove_endpoint(&ar, skeleton::Endpoint {
connection: id,
name: epname.clone(),
});
}
let old_assertions = self.index.assertion_count();
self.index.remove((&assertion).into(), &mut outbound_turns);
self.churn.assertions_removed += old_assertions - self.index.assertion_count();
self.churn.endpoints_removed += 1;
}
pub fn turn(&mut self, id: ConnId, actions: Vec<packets::Action>) ->
Result<(), DataspaceError>
{
let mut outbound_turns: Map<ConnId, Vec<packets::Event>> = Map::new();
for a in actions {
tracing::trace!(action = debug(&a), "turn");
match a {
packets::Action::Assert(ref epname, ref assertion) => {
let ac = self.peers.get_mut(&id).unwrap();
if ac.endpoints.contains_key(&epname) {
return Err(("Duplicate endpoint name".to_string(), value::to_value(a)));
}
let ar =
if let Some(fs) = assertion.value().as_simple_record("observe", Some(1)) {
let ar = skeleton::analyze(&fs[0]);
let events = self.index.add_endpoint(&ar, skeleton::Endpoint {
connection: id,
name: epname.clone(),
});
outbound_turns.entry(id).or_insert_with(Vec::new).extend(events);
Some(ar)
} else {
None
};
let old_assertions = self.index.assertion_count();
self.index.insert(assertion.into(), &mut outbound_turns);
self.churn.assertions_added += self.index.assertion_count() - old_assertions;
self.churn.endpoints_added += 1;
ac.endpoints.insert(epname.clone(), ActorEndpoint {
analysis_results: ar,
assertion: assertion.clone()
});
}
packets::Action::Clear(ref epname) => {
let ac = self.peers.get_mut(&id).unwrap();
match ac.endpoints.remove(epname) {
None => {
return Err(("Nonexistent endpoint name".to_string(), value::to_value(a)));
}
Some(ep) => {
self.remove_endpoint(&mut outbound_turns, id, epname, ep);
outbound_turns.entry(id).or_insert_with(Vec::new)
.push(packets::Event::End(epname.clone()));
}
}
}
packets::Action::Message(ref assertion) => {
self.index.send(assertion.into(),
&mut outbound_turns,
&mut self.churn.messages_delivered);
self.churn.messages_injected += 1;
}
}
}
self.deliver_outbound_turns(outbound_turns);
Ok(())
}
fn deliver_outbound_turns(&mut self, outbound_turns: Map<ConnId, Vec<packets::Event>>) {
for (target, events) in outbound_turns {
let actor = self.peers.get_mut(&target).unwrap();
let _ = actor.tx.send(packets::S2C::Turn(events));
actor.queue_depth.fetch_add(1, Ordering::Relaxed);
}
}
pub fn peer_count(&self) -> usize {
self.peers.len()
}
pub fn assertion_count(&self) -> usize {
self.index.assertion_count()
}
pub fn endpoint_count(&self) -> isize {
self.index.endpoint_count()
}
}

30
src/lib.rs Normal file
View File

@ -0,0 +1,30 @@
#![recursion_limit="512"]
pub mod bag;
pub mod config;
pub mod dataspace;
pub mod packets;
pub mod peer;
pub mod skeleton;
pub mod spaces;
pub use preserves::value;
// use std::sync::atomic::{AtomicUsize, Ordering};
//
// #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
// pub enum Syndicate {
// Placeholder(usize),
// }
//
// impl value::Domain for Syndicate {}
//
// static NEXT_PLACEHOLDER: AtomicUsize = AtomicUsize::new(0);
// impl Syndicate {
// pub fn new_placeholder() -> Self {
// Self::Placeholder(NEXT_PLACEHOLDER.fetch_add(1, Ordering::SeqCst))
// }
// }
pub type ConnId = u64;
pub type V = value::IOValue; // value::ArcValue<Syndicate>;

91
src/packets.rs Normal file
View File

@ -0,0 +1,91 @@
use super::V;
use bytes::{Buf, buf::BufMutExt, BytesMut};
use std::sync::Arc;
use std::marker::PhantomData;
use preserves::{
de::Deserializer,
error,
ser::to_writer,
value::{PackedReader, PackedWriter},
};
pub type EndpointName = V;
pub type Assertion = V;
pub type Captures = Arc<Vec<Assertion>>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum Action {
Assert(EndpointName, Assertion),
Clear(EndpointName),
Message(Assertion),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum Event {
Add(EndpointName, Captures),
Del(EndpointName, Captures),
Msg(EndpointName, Captures),
End(EndpointName),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum C2S {
Connect(V),
Turn(Vec<Action>),
Ping(),
Pong(),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum S2C {
Err(String, V),
Turn(Vec<Event>),
Ping(),
Pong(),
}
//---------------------------------------------------------------------------
pub type Error = error::Error;
pub struct Codec<InT, OutT> {
ph_in: PhantomData<InT>,
ph_out: PhantomData<OutT>,
}
pub type ServerCodec = Codec<C2S, S2C>;
pub type ClientCodec = Codec<S2C, C2S>;
impl<InT, OutT> Codec<InT, OutT> {
pub fn new() -> Self {
Codec { ph_in: PhantomData, ph_out: PhantomData }
}
}
impl<InT: serde::de::DeserializeOwned, OutT> tokio_util::codec::Decoder for Codec<InT, OutT> {
type Item = InT;
type Error = Error;
fn decode(&mut self, bs: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
let mut r = PackedReader::decode_bytes(bs);
let mut d = Deserializer::from_reader(&mut r);
match Self::Item::deserialize(&mut d) {
Err(e) if error::is_eof_error(&e) => Ok(None),
Err(e) => Err(e),
Ok(item) => {
let count = d.read.source.index;
bs.advance(count);
Ok(Some(item))
}
}
}
}
impl<InT, OutT: serde::Serialize> tokio_util::codec::Encoder<OutT> for Codec<InT, OutT>
{
type Error = Error;
fn encode(&mut self, item: OutT, bs: &mut BytesMut) -> Result<(), Self::Error> {
to_writer(&mut PackedWriter::new(&mut bs.writer()), &item)
}
}

198
src/peer.rs Normal file
View File

@ -0,0 +1,198 @@
use super::V;
use super::ConnId;
use super::dataspace;
use super::packets;
use super::spaces;
use super::config;
use core::time::Duration;
use futures::{Sink, SinkExt, Stream};
use futures::FutureExt;
use futures::select;
use preserves::value;
use std::pin::Pin;
use std::sync::{Mutex, Arc, atomic::{AtomicUsize, Ordering}};
use tokio::stream::StreamExt;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, UnboundedReceiver, error::TryRecvError};
use tokio::time::interval;
pub type ResultC2S = Result<packets::C2S, packets::Error>;
pub struct Peer<I, O>
where I: Stream<Item = ResultC2S> + Send,
O: Sink<packets::S2C, Error = packets::Error>,
{
id: ConnId,
tx: UnboundedSender<packets::S2C>,
rx: UnboundedReceiver<packets::S2C>,
i: Pin<Box<I>>,
o: Pin<Box<O>>,
space: Option<dataspace::DataspaceRef>,
}
fn err(s: &str, ctx: V) -> packets::S2C {
packets::S2C::Err(s.into(), ctx)
}
impl<I, O> Peer<I, O>
where I: Stream<Item = ResultC2S> + Send,
O: Sink<packets::S2C, Error = packets::Error>,
{
pub fn new(id: ConnId, i: I, o: O) -> Self {
let (tx, rx) = unbounded_channel();
Peer{ id, tx, rx, i: Box::pin(i), o: Box::pin(o), space: None }
}
pub async fn run(&mut self, spaces: Arc<Mutex<spaces::Spaces>>, config: &config::ServerConfig) ->
Result<(), packets::Error>
{
let firstpacket = self.i.next().await;
let dsname = if let Some(Ok(packets::C2S::Connect(dsname))) = firstpacket {
dsname
} else {
let e = format!("Expected initial Connect, got {:?}", firstpacket);
self.o.send(err(&e, value::FALSE.clone())).await?;
return Err(preserves::error::syntax_error(&e))
};
self.space = Some(spaces.lock().unwrap().lookup(&dsname));
let queue_depth = Arc::new(AtomicUsize::new(0));
self.space.as_ref().unwrap().write().unwrap().register(
self.id,
self.tx.clone(),
Arc::clone(&queue_depth));
let mut ping_timer = interval(Duration::from_secs(60));
let mut running = true;
let mut overloaded = None;
let mut previous_sample = None;
while running {
let mut to_send = Vec::new();
let queue_depth_sample = queue_depth.load(Ordering::Relaxed);
if queue_depth_sample > config.overload_threshold {
let n = overloaded.unwrap_or(0);
tracing::warn!(turns=n, queue_depth=queue_depth_sample, "overloaded");
if n == config.overload_turn_limit {
to_send.push(err("Overloaded",
value::Value::from(queue_depth_sample as u64).wrap()));
running = false;
} else {
if queue_depth_sample > previous_sample.unwrap_or(0) {
overloaded = Some(n + 1)
} else {
overloaded = Some(0)
}
}
} else {
if let Some(_) = overloaded {
tracing::info!(queue_depth=queue_depth_sample, "recovered");
}
overloaded = None;
}
previous_sample = Some(queue_depth_sample);
select! {
_instant = ping_timer.next().boxed().fuse() => to_send.push(packets::S2C::Ping()),
frame = self.i.next().fuse() => match frame {
Some(res) => match res {
Ok(p) => {
tracing::trace!(packet = debug(&p), "input");
match p {
packets::C2S::Turn(actions) => {
match self.space.as_ref().unwrap().write().unwrap()
.turn(self.id, actions)
{
Ok(()) => (),
Err((msg, ctx)) => {
to_send.push(err(&msg, ctx));
running = false;
}
}
}
packets::C2S::Ping() =>
to_send.push(packets::S2C::Pong()),
packets::C2S::Pong() =>
(),
packets::C2S::Connect(_) => {
to_send.push(err("Unexpected Connect", value::to_value(p)));
running = false;
}
}
}
Err(e) if preserves::error::is_eof_error(&e) => {
tracing::trace!("eof");
running = false;
}
Err(e) if preserves::error::is_syntax_error(&e) => {
to_send.push(err(&e.to_string(), value::FALSE.clone()));
running = false;
}
Err(e) => {
if preserves::error::is_io_error(&e) {
return Err(e);
} else {
to_send.push(err(&format!("Packet deserialization error: {}", e),
value::FALSE.clone()));
running = false;
}
}
}
None => {
tracing::trace!("remote has closed");
running = false;
}
},
msgopt = self.rx.recv().boxed().fuse() => {
let mut ok = true;
match msgopt {
Some(msg) => {
to_send.push(msg);
loop {
match self.rx.try_recv() {
Ok(m) => to_send.push(m),
Err(TryRecvError::Empty) => {
queue_depth.store(0, Ordering::Relaxed);
break;
}
Err(TryRecvError::Closed) => {
ok = false;
break;
}
}
}
}
None => ok = false,
}
if !ok {
/* weird. */
to_send.push(err("Outbound channel closed unexpectedly", value::FALSE.clone()));
running = false;
}
},
}
for v in to_send {
if let packets::S2C::Err(ref msg, ref ctx) = v {
tracing::error!(context = debug(ctx), msg = display(msg), "error");
} else {
tracing::trace!(packet = debug(&v), "output");
}
self.o.send(v).await?;
}
tokio::task::yield_now().await;
}
Ok(())
}
}
impl<I, O> Drop for Peer<I, O>
where I: Stream<Item = ResultC2S> + Send,
O: Sink<packets::S2C, Error = packets::Error>,
{
fn drop(&mut self) {
if let Some(ref s) = self.space {
s.write().unwrap().deregister(self.id);
}
}
}

609
src/skeleton.rs Normal file
View File

@ -0,0 +1,609 @@
use super::ConnId;
use super::bag;
use super::packets::Assertion;
use super::packets::Captures;
use super::packets::EndpointName;
use super::packets::Event;
use preserves::value::{Map, Set, Value, NestedValue};
use std::cmp::Ordering;
use std::collections::btree_map::Entry;
use std::sync::Arc;
type Bag<A> = bag::BTreeBag<A>;
pub type Path = Vec<usize>;
pub type Paths = Vec<Path>;
pub type Events = Vec<Event>;
pub type TurnMap = Map<ConnId, Events>;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Endpoint {
pub connection: ConnId,
pub name: EndpointName,
}
#[derive(Debug)]
pub enum Skeleton {
Blank,
Guarded(Guard, Vec<Skeleton>)
}
#[derive(Debug)]
pub struct AnalysisResults {
pub skeleton: Skeleton,
pub const_paths: Paths,
pub const_vals: Captures,
pub capture_paths: Paths,
pub assertion: Assertion,
}
#[derive(Debug)]
pub struct Index {
all_assertions: Bag<CachedAssertion>,
root: Node,
}
impl Index {
pub fn new() -> Self {
Index{ all_assertions: Bag::new(), root: Node::new(Continuation::new(Set::new())) }
}
pub fn add_endpoint(&mut self, analysis_results: &AnalysisResults, endpoint: Endpoint) -> Events
{
let continuation = self.root.extend(&analysis_results.skeleton);
let continuation_cached_assertions = &continuation.cached_assertions;
let const_val_map =
continuation.leaf_map.entry(analysis_results.const_paths.clone()).or_insert_with(|| {
let mut cvm = Map::new();
for a in continuation_cached_assertions {
let key = project_paths(a.unscope(), &analysis_results.const_paths);
cvm.entry(key).or_insert_with(Leaf::new).cached_assertions.insert(a.clone());
}
cvm
});
let capture_paths = &analysis_results.capture_paths;
let leaf = const_val_map.entry(analysis_results.const_vals.clone()).or_insert_with(Leaf::new);
let leaf_cached_assertions = &leaf.cached_assertions;
let endpoints = leaf.endpoints_map.entry(capture_paths.clone()).or_insert_with(|| {
let mut b = Bag::new();
for a in leaf_cached_assertions {
let (restriction_paths, term) = a.unpack();
if is_unrestricted(&capture_paths, restriction_paths) {
let captures = project_paths(term, &capture_paths);
*b.entry(captures).or_insert(0) += 1;
}
}
Endpoints::new(b)
});
let endpoint_name = endpoint.name.clone();
endpoints.endpoints.insert(endpoint);
endpoints.cached_captures.into_iter()
.map(|(cs,_)| Event::Add(endpoint_name.clone(), cs.clone()))
.collect()
}
pub fn remove_endpoint(&mut self, analysis_results: &AnalysisResults, endpoint: Endpoint) {
let continuation = self.root.extend(&analysis_results.skeleton);
if let Entry::Occupied(mut const_val_map_entry)
= continuation.leaf_map.entry(analysis_results.const_paths.clone())
{
let const_val_map = const_val_map_entry.get_mut();
if let Entry::Occupied(mut leaf_entry)
= const_val_map.entry(analysis_results.const_vals.clone())
{
let leaf = leaf_entry.get_mut();
if let Entry::Occupied(mut endpoints_entry)
= leaf.endpoints_map.entry(analysis_results.capture_paths.clone())
{
let endpoints = endpoints_entry.get_mut();
endpoints.endpoints.remove(&endpoint);
if endpoints.endpoints.is_empty() {
endpoints_entry.remove_entry();
}
}
if leaf.is_empty() {
leaf_entry.remove_entry();
}
}
if const_val_map.is_empty() {
const_val_map_entry.remove_entry();
}
}
}
pub fn insert(&mut self, outer_value: CachedAssertion, outputs: &mut TurnMap) {
let net = self.all_assertions.change(outer_value.clone(), 1);
match net {
bag::Net::AbsentToPresent => {
Modification::new(
true,
&outer_value,
|c, v| { c.cached_assertions.insert(v.clone()); },
|l, v| { l.cached_assertions.insert(v.clone()); },
|es, cs| {
if es.cached_captures.change(cs.clone(), 1) == bag::Net::AbsentToPresent {
for ep in &es.endpoints {
outputs.entry(ep.connection).or_insert_with(Vec::new)
.push(Event::Add(ep.name.clone(), cs.clone()))
}
}
})
.perform(&mut self.root);
}
bag::Net::PresentToPresent => (),
_ => unreachable!(),
}
}
pub fn remove(&mut self, outer_value: CachedAssertion, outputs: &mut TurnMap) {
let net = self.all_assertions.change(outer_value.clone(), -1);
match net {
bag::Net::PresentToAbsent => {
Modification::new(
false,
&outer_value,
|c, v| { c.cached_assertions.remove(v); },
|l, v| { l.cached_assertions.remove(v); },
|es, cs| {
if es.cached_captures.change(cs.clone(), -1) == bag::Net::PresentToAbsent {
for ep in &es.endpoints {
outputs.entry(ep.connection).or_insert_with(Vec::new)
.push(Event::Del(ep.name.clone(), cs.clone()))
}
}
})
.perform(&mut self.root);
}
bag::Net::PresentToPresent => (),
_ => unreachable!(),
}
}
pub fn send(&mut self,
outer_value: CachedAssertion,
outputs: &mut TurnMap,
delivery_count: &mut usize)
{
Modification::new(
false,
&outer_value,
|_c, _v| (),
|_l, _v| (),
|es, cs| {
*delivery_count += es.endpoints.len();
for ep in &es.endpoints {
outputs.entry(ep.connection).or_insert_with(Vec::new)
.push(Event::Msg(ep.name.clone(), cs.clone()))
}
}).perform(&mut self.root);
}
pub fn assertion_count(&self) -> usize {
return self.all_assertions.len()
}
pub fn endpoint_count(&self) -> isize {
return self.all_assertions.total()
}
}
#[derive(Debug)]
struct Node {
continuation: Continuation,
edges: Map<Selector, Map<Guard, Node>>,
}
impl Node {
fn new(continuation: Continuation) -> Self {
Node { continuation, edges: Map::new() }
}
fn extend(&mut self, skeleton: &Skeleton) -> &mut Continuation {
let (_pop_count, final_node) = self.extend_walk(&mut Vec::new(), 0, 0, skeleton);
&mut final_node.continuation
}
fn extend_walk(&mut self, path: &mut Path, pop_count: usize, index: usize, skeleton: &Skeleton)
-> (usize, &mut Node) {
match skeleton {
Skeleton::Blank => (pop_count, self),
Skeleton::Guarded(cls, kids) => {
let selector = Selector { pop_count, index };
let continuation = &self.continuation;
let table = self.edges.entry(selector).or_insert_with(Map::new);
let mut next_node = table.entry(cls.clone()).or_insert_with(|| {
Self::new(Continuation::new(
continuation.cached_assertions.iter()
.filter(|a| {
Some(cls) == class_of(project_path(a.unscope(), path)).as_ref() })
.cloned()
.collect()))
});
let mut pop_count = 0;
for (index, kid) in kids.iter().enumerate() {
path.push(index);
let (pc, nn) = next_node.extend_walk(path, pop_count, index, kid);
pop_count = pc;
next_node = nn;
path.pop();
}
(pop_count + 1, next_node)
}
}
}
}
#[derive(Debug)]
pub enum Stack<'a, T> {
Empty,
Item(T, &'a Stack<'a, T>)
}
impl<'a, T> Stack<'a, T> {
fn pop(&self) -> &Self {
match self {
Stack::Empty => panic!("Internal error: pop: Incorrect pop_count computation"),
Stack::Item(_, tail) => tail
}
}
fn top(&self) -> &T {
match self {
Stack::Empty => panic!("Internal error: top: Incorrect pop_count computation"),
Stack::Item(item, _) => item
}
}
}
struct Modification<'op, FCont, FLeaf, FEndpoints>
where FCont: FnMut(&mut Continuation, &CachedAssertion) -> (),
FLeaf: FnMut(&mut Leaf, &CachedAssertion) -> (),
FEndpoints: FnMut(&mut Endpoints, Captures) -> ()
{
create_leaf_if_absent: bool,
outer_value: &'op CachedAssertion,
restriction_paths: Option<&'op Paths>,
outer_value_term: &'op Assertion,
m_cont: FCont,
m_leaf: FLeaf,
m_endpoints: FEndpoints,
}
impl<'op, FCont, FLeaf, FEndpoints> Modification<'op, FCont, FLeaf, FEndpoints>
where FCont: FnMut(&mut Continuation, &CachedAssertion) -> (),
FLeaf: FnMut(&mut Leaf, &CachedAssertion) -> (),
FEndpoints: FnMut(&mut Endpoints, Captures) -> ()
{
fn new(create_leaf_if_absent: bool,
outer_value: &'op CachedAssertion,
m_cont: FCont,
m_leaf: FLeaf,
m_endpoints: FEndpoints) -> Self {
let (restriction_paths, outer_value_term) = outer_value.unpack();
Modification {
create_leaf_if_absent,
outer_value,
restriction_paths,
outer_value_term,
m_cont,
m_leaf,
m_endpoints,
}
}
fn perform(&mut self, n: &mut Node) {
self.node(n, &Stack::Item(&Value::from(vec![self.outer_value_term.clone()]).wrap(), &Stack::Empty))
}
fn node(&mut self, n: &mut Node, term_stack: &Stack<&Assertion>) {
self.continuation(&mut n.continuation);
for (selector, table) in &mut n.edges {
let mut next_stack = term_stack;
for _ in 0..selector.pop_count { next_stack = next_stack.pop() }
let next_value = step(next_stack.top(), selector.index);
if let Some(next_class) = class_of(next_value) {
if let Some(next_node) = table.get_mut(&next_class) {
self.node(next_node, &Stack::Item(next_value, next_stack))
}
}
}
}
fn continuation(&mut self, c: &mut Continuation) {
(self.m_cont)(c, self.outer_value);
let mut empty_const_paths = Vec::new();
for (const_paths, const_val_map) in &mut c.leaf_map {
let const_vals = project_paths(self.outer_value_term, const_paths);
let leaf_opt = if self.create_leaf_if_absent {
Some(const_val_map.entry(const_vals.clone()).or_insert_with(Leaf::new))
} else {
const_val_map.get_mut(&const_vals)
};
if let Some(leaf) = leaf_opt {
(self.m_leaf)(leaf, self.outer_value);
for (capture_paths, endpoints) in &mut leaf.endpoints_map {
if is_unrestricted(&capture_paths, self.restriction_paths) {
(self.m_endpoints)(endpoints,
project_paths(self.outer_value_term, &capture_paths));
}
}
if leaf.is_empty() {
const_val_map.remove(&const_vals);
if const_val_map.is_empty() {
empty_const_paths.push(const_paths.clone());
}
}
}
}
for const_paths in empty_const_paths {
c.leaf_map.remove(&const_paths);
}
}
}
fn class_of(v: &Assertion) -> Option<Guard> {
match v.value() {
Value::Sequence(ref vs) => Some(Guard::Seq(vs.len())),
Value::Record(ref r) => Some(Guard::Rec(r.label().clone(), r.arity())),
_ => None,
}
}
fn project_path<'a>(v: &'a Assertion, p: &Path) -> &'a Assertion {
let mut v = v;
for i in p {
v = step(v, *i);
}
v
}
fn project_paths<'a>(v: &'a Assertion, ps: &Paths) -> Captures {
Arc::new(ps.iter().map(|p| project_path(v, p)).cloned().collect())
}
fn step(v: &Assertion, i: usize) -> &Assertion {
match v.value() {
Value::Sequence(ref vs) => &vs[i],
Value::Record(ref r) => &r.fields()[i],
_ => panic!("step: non-sequence, non-record {:?}", v)
}
}
#[derive(Debug)]
struct Continuation {
cached_assertions: Set<CachedAssertion>,
leaf_map: Map<Paths, Map<Captures, Leaf>>,
}
impl Continuation {
fn new(cached_assertions: Set<CachedAssertion>) -> Self {
Continuation { cached_assertions, leaf_map: Map::new() }
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Selector {
pop_count: usize,
index: usize,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum Guard {
Rec(Assertion, usize),
Seq(usize),
}
impl Guard {
fn arity(&self) -> usize {
match self {
Guard::Rec(_, s) => *s,
Guard::Seq(s) => *s
}
}
}
#[derive(Debug)]
struct Leaf { // aka Topic
cached_assertions: Set<CachedAssertion>,
endpoints_map: Map<Paths, Endpoints>,
}
impl Leaf {
fn new() -> Self {
Leaf { cached_assertions: Set::new(), endpoints_map: Map::new() }
}
fn is_empty(&self) -> bool {
self.cached_assertions.is_empty() && self.endpoints_map.is_empty()
}
}
#[derive(Debug)]
struct Endpoints {
cached_captures: Bag<Captures>,
endpoints: Set<Endpoint>,
}
impl Endpoints {
fn new(cached_captures: Bag<Captures>) -> Self {
Endpoints { cached_captures, endpoints: Set::new() }
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum CachedAssertion {
VisibilityRestricted(Paths, Assertion),
Unrestricted(Assertion),
}
impl From<&Assertion> for CachedAssertion {
fn from(a: &Assertion) -> Self {
CachedAssertion::Unrestricted(a.clone())
}
}
impl CachedAssertion {
fn unscope(&self) -> &Assertion {
match self {
CachedAssertion::VisibilityRestricted(_, a) => a,
CachedAssertion::Unrestricted(a) => a,
}
}
fn unpack(&self) -> (Option<&Paths>, &Assertion) {
match self {
CachedAssertion::VisibilityRestricted(ps, a) => (Some(ps), a),
CachedAssertion::Unrestricted(a) => (None, a),
}
}
}
fn is_unrestricted(capture_paths: &Paths, restriction_paths: Option<&Paths>) -> bool {
// We are "unrestricted" if Set(capture_paths) ⊆ Set(restriction_paths). Since both
// variables really hold lists, we operate with awareness of the order the lists are
// built here. We know that the lists are built in fringe order; that is, they are
// sorted wrt `pathCmp`.
match restriction_paths {
None => true, // not visibility-restricted in the first place
Some(rpaths) => {
let mut rpi = rpaths.iter();
'outer: for c in capture_paths {
'inner: loop {
match rpi.next() {
None => {
// there's at least one capture_paths entry (`c`) that does
// not appear in restriction_paths, so we are restricted
return false;
}
Some(r) => match c.cmp(r) {
Ordering::Less => {
// `c` is less than `r`, but restriction_paths is sorted,
// so `c` does not appear in restriction_paths, and we are
// thus restricted.
return false;
}
Ordering::Equal => {
// `c` is equal to `r`, so we may yet be unrestricted.
// Discard both `c` and `r` and continue.
continue 'outer;
}
Ordering::Greater => {
// `c` is greater than `r`, but capture_paths and
// restriction_paths are sorted, so while we might yet
// come to an `r` that is equal to `c`, we will never find
// another `c` that is less than this `c`. Discard this
// `r` then, keeping the `c`, and compare against the next
// `r`.
continue 'inner;
}
}
}
}
}
// We went all the way through capture_paths without finding any `c` not in
// restriction_paths.
true
}
}
}
pub struct Analyzer {
const_paths: Paths,
const_vals: Vec<Assertion>,
capture_paths: Paths,
path: Path,
}
impl Analyzer {
fn walk(&mut self, mut a: &Assertion) -> Skeleton {
while let Some(fields) = a.value().as_simple_record("capture", Some(1)) {
self.capture_paths.push(self.path.clone());
a = &fields[0];
}
if a.value().is_simple_record("discard", Some(0)) {
Skeleton::Blank
} else {
match class_of(a) {
Some(cls) => {
let arity = cls.arity();
Skeleton::Guarded(cls,
(0..arity).map(|i| {
self.path.push(i);
let s = self.walk(step(a, i));
self.path.pop();
s
}).collect())
}
None => {
self.const_paths.push(self.path.clone());
self.const_vals.push(a.clone());
Skeleton::Blank
}
}
}
}
}
pub fn analyze(a: &Assertion) -> AnalysisResults {
let mut z = Analyzer {
const_paths: Vec::new(),
const_vals: Vec::new(),
capture_paths: Vec::new(),
path: Vec::new(),
};
let skeleton = z.walk(a);
AnalysisResults {
skeleton,
const_paths: z.const_paths,
const_vals: Arc::new(z.const_vals),
capture_paths: z.capture_paths,
assertion: a.clone(),
}
}
// pub fn instantiate_assertion(a: &Assertion, cs: Captures) -> CachedAssertion {
// let mut capture_paths = Vec::new();
// let mut path = Vec::new();
// let mut vs: Vec<Assertion> = (*cs).clone();
// vs.reverse();
// let instantiated = instantiate_assertion_walk(&mut capture_paths, &mut path, &mut vs, a);
// CachedAssertion::VisibilityRestricted(capture_paths, instantiated)
// }
// fn instantiate_assertion_walk(capture_paths: &mut Paths,
// path: &mut Path,
// vs: &mut Vec<Assertion>,
// a: &Assertion) -> Assertion {
// if let Some(fields) = a.value().as_simple_record("capture", Some(1)) {
// capture_paths.push(path.clone());
// let v = vs.pop().unwrap();
// instantiate_assertion_walk(capture_paths, path, vs, &fields[0]);
// v
// } else if a.value().is_simple_record("discard", Some(0)) {
// Value::Domain(Syndicate::new_placeholder()).wrap()
// } else {
// let f = |(i, aa)| {
// path.push(i);
// let vv = instantiate_assertion_walk(capture_paths,
// path,
// vs,
// aa);
// path.pop();
// vv
// };
// match class_of(a) {
// Some(Guard::Seq(_)) =>
// Value::from(Vec::from_iter(a.value().as_sequence().unwrap()
// .iter().enumerate().map(f)))
// .wrap(),
// Some(Guard::Rec(l, fieldcount)) =>
// Value::record(l, a.value().as_record(Some(fieldcount)).unwrap().1
// .iter().enumerate().map(f).collect())
// .wrap(),
// None =>
// a.clone(),
// }
// }
// }

54
src/spaces.rs Normal file
View File

@ -0,0 +1,54 @@
use super::V;
use super::dataspace;
use std::sync::Arc;
use tracing::{info, debug};
use preserves::value::Map;
pub struct Spaces {
index: Map<V, dataspace::DataspaceRef>,
}
impl Spaces {
pub fn new() -> Self {
Self { index: Map::new() }
}
pub fn lookup(&mut self, name: &V) -> dataspace::DataspaceRef {
let (is_new, space) = match self.index.get(name) {
Some(s) => (false, s.clone()),
None => {
let s = dataspace::Dataspace::new_ref(name);
self.index.insert(name.clone(), s.clone());
(true, s)
}
};
debug!(name = debug(name),
action = display(if is_new { "created" } else { "accessed" }));
space
}
pub fn cleanup(&mut self) {
self.index = self.index.iter()
.filter(|s| s.1.read().unwrap().peer_count() > 0)
.map(|(k,v)| (k.clone(), Arc::clone(v)))
.collect();
}
pub fn dump_stats(&self, delta: core::time::Duration) {
for (dsname, dsref) in &self.index {
let mut ds = dsref.write().unwrap();
info!(name = debug(dsname),
connections = display(format!("{} (+{}/-{})", ds.peer_count(), ds.churn.peers_added, ds.churn.peers_removed)),
assertions = display(format!("{} (+{}/-{})", ds.assertion_count(), ds.churn.assertions_added, ds.churn.assertions_removed)),
endpoints = display(format!("{} (+{}/-{})", ds.endpoint_count(), ds.churn.endpoints_added, ds.churn.endpoints_removed)),
msg_in_rate = display(ds.churn.messages_injected as f32 / delta.as_secs() as f32),
msg_out_rate = display(ds.churn.messages_delivered as f32 / delta.as_secs() as f32));
ds.churn.reset();
}
}
}

View File

@ -1,27 +0,0 @@
[package]
name = "syndicate-macros"
version = "0.32.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
description = "Support macros for programming with the Syndicated Actor model and Dataspaces."
homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0"
[lib]
proc-macro = true
[dependencies]
syndicate = { path = "../syndicate", version = "0.40.0"}
proc-macro2 = { version = "^1.0", features = ["span-locations"] }
quote = "^1.0"
syn = { version = "^1.0", features = ["extra-traits"] } # for impl Debug for syn::Expr
[dev-dependencies]
tokio = { version = "1.10", features = ["io-std"] }
tracing = "0.1"
[package.metadata.workspaces]
independent = true

View File

@ -1,82 +0,0 @@
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::dataspace::Dataspace;
use syndicate::language;
use syndicate::schemas::dataspace::Observe;
use syndicate::value::NestedValue;
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
Actor::top(None, |t| {
let ds = Cap::new(&t.create(Dataspace::new(None)));
let _ = t.prevent_inert_check();
t.spawn(Some(AnyValue::symbol("box")), enclose!((ds) move |t| {
let current_value = t.named_field("current_value", 0u64);
t.dataflow({
let mut state_assertion_handle = None;
enclose!((ds, current_value) move |t| {
let v = AnyValue::new(*t.get(&current_value));
tracing::info!(?v, "asserting");
ds.update(t, &mut state_assertion_handle, &(),
Some(&syndicate_macros::template!("<box-state =v>")));
Ok(())
})
})?;
let set_box_handler = syndicate::entity(())
.on_message(enclose!((current_value) move |(), t, captures: AnyValue| {
let v = captures.value().to_sequence()?[0].value().to_u64()?;
tracing::info!(?v, "from set-box");
t.set(&current_value, v);
Ok(())
}))
.create_cap(t);
ds.assert(t, language(), &Observe {
pattern: syndicate_macros::pattern!{<set-box $>},
observer: set_box_handler,
});
t.dataflow(enclose!((current_value) move |t| {
if *t.get(&current_value) == 1000000 {
t.stop();
}
Ok(())
}))?;
Ok(())
}));
t.spawn(Some(AnyValue::symbol("client")), enclose!((ds) move |t| {
let box_state_handler = syndicate::entity(0u32)
.on_asserted(enclose!((ds) move |count, t, captures: AnyValue| {
*count = *count + 1;
let value = captures.value().to_sequence()?[0].value().to_u64()?;
tracing::info!(?value);
let next = AnyValue::new(value + 1);
tracing::info!(?next, "sending");
ds.message(t, &(), &syndicate_macros::template!("<set-box =next>"));
Ok(Some(Box::new(|count, t| {
*count = *count - 1;
if *count == 0 {
tracing::info!("box state retracted");
t.stop();
}
Ok(())
})))
}))
.create_cap(t);
ds.assert(t, language(), &Observe {
pattern: syndicate_macros::pattern!{<box-state $>},
observer: box_state_handler,
});
Ok(())
}));
Ok(())
}).await??;
Ok(())
}

View File

@ -1,133 +0,0 @@
use syndicate::actor::*;
use std::env;
use std::sync::Arc;
#[derive(Debug)]
enum Instruction {
SetPeer(Arc<Ref<Instruction>>),
HandleMessage(u64),
}
struct Forwarder {
hop_limit: u64,
supervisor: Arc<Ref<Instruction>>,
peer: Option<Arc<Ref<Instruction>>>,
}
impl Drop for Forwarder {
fn drop(&mut self) {
let r = self.peer.take();
let _ = tokio::spawn(async move {
drop(r);
});
}
}
impl Entity<Instruction> for Forwarder {
fn message(&mut self, turn: &mut Activation, message: Instruction) -> ActorResult {
match message {
Instruction::SetPeer(r) => {
tracing::info!("Setting peer {:?}", r);
self.peer = Some(r);
}
Instruction::HandleMessage(n) => {
let target = if n >= self.hop_limit { &self.supervisor } else { self.peer.as_ref().expect("peer") };
turn.message(target, Instruction::HandleMessage(n + 1));
}
}
Ok(())
}
}
struct Supervisor {
latency_mode: bool,
total_transfers: u64,
remaining_to_receive: u32,
start_time: Option<std::time::Instant>,
}
impl Entity<Instruction> for Supervisor {
fn message(&mut self, turn: &mut Activation, message: Instruction) -> ActorResult {
match message {
Instruction::SetPeer(_) => {
tracing::info!("Start");
self.start_time = Some(std::time::Instant::now());
},
Instruction::HandleMessage(_n) => {
self.remaining_to_receive -= 1;
if self.remaining_to_receive == 0 {
let stop_time = std::time::Instant::now();
let duration = stop_time - self.start_time.unwrap();
tracing::info!("Stop after {:?}; {:?} messages, so {:?} Hz ({} mode)",
duration,
self.total_transfers,
(1000.0 * self.total_transfers as f64) / duration.as_millis() as f64,
if self.latency_mode { "latency" } else { "throughput" });
turn.stop_root();
}
},
}
Ok(())
}
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
Actor::top(None, |t| {
let args: Vec<String> = env::args().collect();
let n_actors: u32 = args.get(1).unwrap_or(&"1000000".to_string()).parse()?;
let n_rounds: u32 = args.get(2).unwrap_or(&"200".to_string()).parse()?;
let latency_mode: bool = match args.get(3).unwrap_or(&"throughput".to_string()).as_str() {
"latency" => true,
"throughput" => false,
_other => return Err("Invalid throughput/latency mode".into()),
};
tracing::info!("Will run {:?} actors for {:?} rounds", n_actors, n_rounds);
let total_transfers: u64 = n_actors as u64 * n_rounds as u64;
let (hop_limit, injection_count) = if latency_mode {
(total_transfers, 1)
} else {
(n_rounds as u64, n_actors)
};
let me = t.create(Supervisor {
latency_mode,
total_transfers,
remaining_to_receive: injection_count,
start_time: None,
});
let mut forwarders: Vec<Arc<Ref<Instruction>>> = Vec::new();
for _i in 0 .. n_actors {
if _i % 10000 == 0 { tracing::info!("Actor {:?}", _i); }
forwarders.push(
t.spawn_for_entity(None, true, Box::new(
Forwarder {
hop_limit,
supervisor: me.clone(),
peer: forwarders.last().cloned(),
}))
.0.expect("an entity"));
}
t.message(&forwarders[0], Instruction::SetPeer(forwarders.last().expect("an entity").clone()));
t.later(move |t| {
t.message(&me, Instruction::SetPeer(me.clone()));
t.later(move |t| {
let mut injected: u32 = 0;
for f in forwarders.into_iter() {
if injected >= injection_count {
break;
}
t.message(&f, Instruction::HandleMessage(0));
injected += 1;
}
Ok(())
});
Ok(())
});
Ok(())
}).await??;
Ok(())
}

View File

@ -1,175 +0,0 @@
use std::env;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
type Ref<T> = UnboundedSender<Box<T>>;
#[derive(Debug)]
enum Instruction {
SetPeer(Arc<Ref<Instruction>>),
HandleMessage(u64),
}
struct Forwarder {
hop_limit: u64,
supervisor: Arc<Ref<Instruction>>,
peer: Option<Arc<Ref<Instruction>>>,
}
impl Drop for Forwarder {
fn drop(&mut self) {
let r = self.peer.take();
let _ = tokio::spawn(async move {
drop(r);
});
}
}
enum Action { Continue, Stop }
trait Actor<T> {
fn message(&mut self, message: T) -> Action;
}
fn send<T: std::marker::Send + 'static>(ch: &Arc<Ref<T>>, message: T) -> () {
match ch.send(Box::new(message)) {
Ok(()) => (),
Err(v) => panic!("Aiee! Could not send {:?}", v),
}
}
fn spawn<T: std::marker::Send + 'static, R: Actor<T> + std::marker::Send + 'static>(rt: Option<Arc<AtomicU64>>, mut ac: R) -> Arc<Ref<T>> {
let (tx, mut rx) = unbounded_channel::<Box<T>>();
if let Some(ref c) = rt {
c.fetch_add(1, Ordering::SeqCst);
}
tokio::spawn(async move {
loop {
match rx.recv().await {
None => break,
Some(message) => {
match ac.message(*message) {
Action::Continue => continue,
Action::Stop => break,
}
}
}
}
if let Some(c) = rt {
c.fetch_sub(1, Ordering::SeqCst);
}
});
Arc::new(tx)
}
impl Actor<Instruction> for Forwarder {
fn message(&mut self, message: Instruction) -> Action {
match message {
Instruction::SetPeer(r) => {
tracing::info!("Setting peer {:?}", r);
self.peer = Some(r);
}
Instruction::HandleMessage(n) => {
let target = if n >= self.hop_limit { &self.supervisor } else { self.peer.as_ref().expect("peer") };
send(target, Instruction::HandleMessage(n + 1));
}
}
Action::Continue
}
}
struct Supervisor {
latency_mode: bool,
total_transfers: u64,
remaining_to_receive: u32,
start_time: Option<std::time::Instant>,
}
impl Actor<Instruction> for Supervisor {
fn message(&mut self, message: Instruction) -> Action {
match message {
Instruction::SetPeer(_) => {
tracing::info!("Start");
self.start_time = Some(std::time::Instant::now());
},
Instruction::HandleMessage(_n) => {
self.remaining_to_receive -= 1;
if self.remaining_to_receive == 0 {
let stop_time = std::time::Instant::now();
let duration = stop_time - self.start_time.unwrap();
tracing::info!("Stop after {:?}; {:?} messages, so {:?} Hz ({} mode)",
duration,
self.total_transfers,
(1000.0 * self.total_transfers as f64) / duration.as_millis() as f64,
if self.latency_mode { "latency" } else { "throughput" });
return Action::Stop;
}
},
}
Action::Continue
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>> {
syndicate::convenient_logging()?;
let args: Vec<String> = env::args().collect();
let n_actors: u32 = args.get(1).unwrap_or(&"1000000".to_string()).parse()?;
let n_rounds: u32 = args.get(2).unwrap_or(&"200".to_string()).parse()?;
let latency_mode: bool = match args.get(3).unwrap_or(&"throughput".to_string()).as_str() {
"latency" => true,
"throughput" => false,
_other => return Err("Invalid throughput/latency mode".into()),
};
tracing::info!("Will run {:?} actors for {:?} rounds", n_actors, n_rounds);
let count = Arc::new(AtomicU64::new(0));
let total_transfers: u64 = n_actors as u64 * n_rounds as u64;
let (hop_limit, injection_count) = if latency_mode {
(total_transfers, 1)
} else {
(n_rounds as u64, n_actors)
};
let me = spawn(Some(count.clone()), Supervisor {
latency_mode,
total_transfers,
remaining_to_receive: injection_count,
start_time: None,
});
let mut forwarders: Vec<Arc<Ref<Instruction>>> = Vec::new();
for _i in 0 .. n_actors {
if _i % 10000 == 0 { tracing::info!("Actor {:?}", _i); }
forwarders.push(spawn(None, Forwarder {
hop_limit,
supervisor: me.clone(),
peer: forwarders.last().cloned(),
}));
}
send(&forwarders[0], Instruction::SetPeer(forwarders.last().expect("an entity").clone()));
send(&me, Instruction::SetPeer(me.clone()));
let mut injected: u32 = 0;
for f in forwarders.into_iter() {
if injected >= injection_count {
break;
}
send(&f, Instruction::HandleMessage(0));
injected += 1;
}
loop {
if count.load(Ordering::SeqCst) == 0 {
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
Ok(())
}

View File

@ -1,138 +0,0 @@
use proc_macro2::Span;
use quote::quote_spanned;
use syn::parse_macro_input;
use syn::Expr;
use syn::Ident;
use syn::LitInt;
use syn::Token;
use syn::Type;
use syn::parse::Error;
use syn::parse::Parse;
use syn::parse::ParseStream;
use crate::stx::Stx;
use crate::pat;
#[derive(Debug)]
struct During {
turn_stx: Expr,
ds_stx: Expr,
lang_stx: Expr,
pat_stx: Stx,
body_stx: Expr,
}
fn comma_parse<T: Parse>(input: ParseStream) -> syn::parse::Result<T> {
let _: Token![,] = input.parse()?;
input.parse()
}
impl Parse for During {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
Ok(During {
turn_stx: input.parse()?,
ds_stx: comma_parse(input)?,
lang_stx: comma_parse(input)?,
pat_stx: comma_parse(input)?,
body_stx: comma_parse(input)?,
})
}
}
impl During {
fn bindings(&self) -> (Vec<Ident>, Vec<Type>, Vec<LitInt>) {
let mut ids = vec![];
let mut tys = vec![];
let mut indexes = vec![];
for (i, (maybe_id, ty)) in self.pat_stx.bindings().into_iter().enumerate() {
if let Some(id) = maybe_id {
indexes.push(LitInt::new(&i.to_string(), id.span()));
ids.push(id);
tys.push(ty);
}
}
(ids, tys, indexes)
}
}
pub fn during(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
let d = parse_macro_input!(src as During);
let During { turn_stx, ds_stx, lang_stx, pat_stx, body_stx } = &d;
let (varname_stx, type_stx, index_stx) = d.bindings();
let binding_count = varname_stx.len();
let pat_stx_expr = match pat::to_pattern_expr(pat_stx) {
Ok(e) => e,
Err(e) => return Error::new(Span::call_site(), e).to_compile_error().into(),
};
(quote_spanned!{Span::mixed_site()=> {
let __ds = #ds_stx.clone();
let __lang = #lang_stx;
let monitor = syndicate::during::entity(())
.on_asserted_facet(move |_, t, captures: syndicate::actor::AnyValue| {
if let Some(captures) = {
use syndicate::value::NestedValue;
use syndicate::value::Value;
captures.value().as_sequence()
}{
if captures.len() == #binding_count {
#(let #varname_stx: #type_stx = match {
use syndicate::preserves_schema::Codec;
__lang.parse(&captures[#index_stx])
} {
Ok(v) => v,
Err(_) => return Ok(()),
};)*
return (#body_stx)(t);
}
}
Ok(())
})
.create_cap(#turn_stx);
__ds.assert(#turn_stx, __lang, &syndicate::schemas::dataspace::Observe {
pattern: #pat_stx_expr,
observer: monitor,
});
}}).into()
}
pub fn on_message(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
let d = parse_macro_input!(src as During);
let During { turn_stx, ds_stx, lang_stx, pat_stx, body_stx } = &d;
let (varname_stx, type_stx, index_stx) = d.bindings();
let binding_count = varname_stx.len();
let pat_stx_expr = match pat::to_pattern_expr(pat_stx) {
Ok(e) => e,
Err(e) => return Error::new(Span::call_site(), e).to_compile_error().into(),
};
(quote_spanned!{Span::mixed_site()=> {
let __ds = #ds_stx.clone();
let __lang = #lang_stx;
let monitor = syndicate::during::entity(())
.on_message(move |_, t, captures: syndicate::actor::AnyValue| {
if let Some(captures) = {
use syndicate::value::NestedValue;
use syndicate::value::Value;
captures.value().as_sequence()
}{
if captures.len() == #binding_count {
#(let #varname_stx: #type_stx = match {
use syndicate::preserves_schema::Codec;
__lang.parse(&captures[#index_stx])
} {
Ok(v) => v,
Err(_) => return Ok(()),
};)*
return (#body_stx)(t);
}
}
Ok(())
})
.create_cap(#turn_stx);
__ds.assert(#turn_stx, __lang, &syndicate::schemas::dataspace::Observe {
pattern: #pat_stx_expr,
observer: monitor,
});
}}).into()
}

View File

@ -1,262 +0,0 @@
#![feature(proc_macro_span)]
use syndicate::value::IOValue;
use syndicate::value::NestedValue;
use syndicate::value::Value;
use syndicate::value::text::iovalue_from_str;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::quote;
use std::convert::TryFrom;
use syn::parse_macro_input;
use syn::ExprLit;
use syn::Ident;
use syn::Lit;
use syn::LitByteStr;
mod dur;
mod pat;
mod stx;
mod val;
use pat::lit;
enum SymbolVariant<'a> {
Normal(&'a str),
#[allow(dead_code)] // otherwise we get 'warning: field `0` is never read'
Binder(&'a str),
Substitution(&'a str),
Discard,
}
fn compile_sequence_members(vs: &[IOValue]) -> Vec<TokenStream> {
vs.iter().enumerate().map(|(i, f)| {
let p = compile_pattern(f);
quote!((syndicate::value::Value::from(#i).wrap(), #p))
}).collect::<Vec<_>>()
}
fn analyze_symbol(s: &str, allow_binding_and_substitution: bool) -> SymbolVariant {
if !allow_binding_and_substitution {
SymbolVariant::Normal(s)
} else if s.starts_with("$") {
SymbolVariant::Binder(&s[1..])
} else if s.starts_with("=") {
SymbolVariant::Substitution(&s[1..])
} else if s == "_" {
SymbolVariant::Discard
} else {
SymbolVariant::Normal(s)
}
}
struct ValueCompiler {
allow_binding_and_substitution: bool,
}
impl ValueCompiler {
fn for_patterns() -> Self {
ValueCompiler {
allow_binding_and_substitution: false,
}
}
fn for_templates() -> Self {
ValueCompiler {
allow_binding_and_substitution: true,
}
}
fn compile(&self, v: &IOValue) -> TokenStream {
#[allow(non_snake_case)]
let V_: TokenStream = quote!(syndicate::value);
let walk = |w| self.compile(w);
match v.value() {
Value::Boolean(b) =>
quote!(#V_::Value::from(#b).wrap()),
Value::Double(d) => {
let d = d.0;
quote!(#V_::Value::from(#d).wrap())
}
Value::SignedInteger(i) => {
let i = i128::try_from(i).expect("Literal integer out-of-range");
quote!(#V_::Value::from(#i).wrap())
}
Value::String(s) =>
quote!(#V_::Value::from(#s).wrap()),
Value::ByteString(bs) => {
let bs = LitByteStr::new(bs, Span::call_site());
quote!(#V_::Value::from(#bs).wrap())
}
Value::Symbol(s) => match analyze_symbol(&s, self.allow_binding_and_substitution) {
SymbolVariant::Normal(s) =>
quote!(#V_::Value::symbol(#s).wrap()),
SymbolVariant::Binder(_) |
SymbolVariant::Discard =>
panic!("Binding/Discard not supported here"),
SymbolVariant::Substitution(s) => {
let i = Ident::new(s, Span::call_site());
quote!(#i)
}
}
Value::Record(r) => {
let arity = r.arity();
let label = walk(r.label());
let fs: Vec<_> = r.fields().iter().map(walk).collect();
quote!({
let mut ___r = #V_::Value::record(#label, #arity);
#(___r.fields_vec_mut().push(#fs);)*
___r.finish().wrap()
})
}
Value::Sequence(vs) => {
let vs: Vec<_> = vs.iter().map(walk).collect();
quote!(#V_::Value::from(vec![#(#vs),*]).wrap())
}
Value::Set(vs) => {
let vs: Vec<_> = vs.iter().map(walk).collect();
quote!({
let mut ___s = #V_::Set::new();
#(___s.insert(#vs);)*
#V_::Value::from(___s).wrap()
})
}
Value::Dictionary(d) => {
let members: Vec<_> = d.iter().map(|(k, v)| {
let k = walk(k);
let v = walk(v);
quote!(___d.insert(#k, #v))
}).collect();
quote!({
let mut ___d = #V_::Map::new();
#(#members;)*
#V_::Value::from(___d).wrap()
})
}
Value::Embedded(_) =>
panic!("Embedded values in compile-time Preserves templates not (yet?) supported"),
}
}
}
fn compile_pattern(v: &IOValue) -> TokenStream {
#[allow(non_snake_case)]
let P_: TokenStream = quote!(syndicate::schemas::dataspace_patterns);
#[allow(non_snake_case)]
let V_: TokenStream = quote!(syndicate::value);
#[allow(non_snake_case)]
let MapFrom_: TokenStream = quote!(<#V_::Map<_, _>>::from);
match v.value() {
Value::Symbol(s) => match analyze_symbol(&s, true) {
SymbolVariant::Binder(_) =>
quote!(#P_::Pattern::Bind{ pattern: Box::new(#P_::Pattern::Discard) }),
SymbolVariant::Discard =>
quote!(#P_::Pattern::Discard),
SymbolVariant::Substitution(s) =>
lit(Ident::new(s, Span::call_site())),
SymbolVariant::Normal(_) =>
lit(ValueCompiler::for_patterns().compile(v)),
}
Value::Record(r) => {
match r.label().value().as_symbol() {
None => panic!("Record labels in patterns must be symbols"),
Some(label) =>
if label.starts_with("$") && r.arity() == 1 {
let nested = compile_pattern(&r.fields()[0]);
quote!(#P_::Pattern::Bind{ pattern: Box::new(#nested) })
} else {
let label_stx = if label.starts_with("=") {
let id = Ident::new(&label[1..], Span::call_site());
quote!(#id)
} else {
quote!(#V_::Value::symbol(#label).wrap())
};
let members = compile_sequence_members(r.fields());
quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Rec { label: #label_stx }),
entries: #MapFrom_([#(#members),*]),
})
}
}
}
Value::Sequence(vs) => {
let members = compile_sequence_members(vs);
quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Arr),
entries: #MapFrom_([#(#members),*]),
})
}
Value::Set(_) =>
panic!("Cannot match sets in patterns"),
Value::Dictionary(d) => {
let members = d.iter().map(|(k, v)| {
let k = ValueCompiler::for_patterns().compile(k);
let v = compile_pattern(v);
quote!((#k, #v))
}).collect::<Vec<_>>();
quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Dict),
entries: #MapFrom_([#(#members),*]),
})
}
_ => lit(ValueCompiler::for_patterns().compile(v)),
}
}
#[proc_macro]
pub fn pattern_str(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
if let Lit::Str(s) = parse_macro_input!(src as ExprLit).lit {
match iovalue_from_str(&s.value()) {
Ok(v) => {
let e = compile_pattern(&v);
// println!("{:#}", &e);
return e.into();
}
Err(_) => (),
}
}
panic!("Expected literal string containing the pattern and no more");
}
#[proc_macro]
pub fn template(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
if let Lit::Str(s) = parse_macro_input!(src as ExprLit).lit {
match iovalue_from_str(&s.value()) {
Ok(v) => {
let e = ValueCompiler::for_templates().compile(&v);
// println!("{:#}", &e);
return e.into();
}
Err(_) => (),
}
}
panic!("Expected literal string containing the template and no more");
}
//---------------------------------------------------------------------------
#[proc_macro]
pub fn pattern(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
pat::pattern(src)
}
//---------------------------------------------------------------------------
#[proc_macro]
pub fn during(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
dur::during(src)
}
#[proc_macro]
pub fn on_message(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
dur::on_message(src)
}

View File

@ -1,87 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use quote::quote;
use syn::parse_macro_input;
use crate::stx::Stx;
use crate::val::to_value_expr;
use crate::val::value_to_value_expr;
pub fn lit<T: ToTokens>(e: T) -> TokenStream2 {
quote!(syndicate::pattern::lift_literal(#e))
}
fn compile_sequence_members(stxs: &Vec<Stx>) -> Result<Vec<TokenStream2>, &'static str> {
stxs.iter().enumerate().map(|(i, stx)| {
let p = to_pattern_expr(stx)?;
Ok(quote!((syndicate::value::Value::from(#i).wrap(), #p)))
}).collect()
}
pub fn to_pattern_expr(stx: &Stx) -> Result<TokenStream2, &'static str> {
#[allow(non_snake_case)]
let P_: TokenStream2 = quote!(syndicate::schemas::dataspace_patterns);
#[allow(non_snake_case)]
let V_: TokenStream2 = quote!(syndicate::value);
#[allow(non_snake_case)]
let MapFrom_: TokenStream2 = quote!(<#V_::Map<_, _>>::from);
match stx {
Stx::Atom(v) =>
Ok(lit(value_to_value_expr(&v))),
Stx::Binder(_, maybe_ty, maybe_pat) => {
let inner_pat_expr = match maybe_pat {
Some(p) => to_pattern_expr(&*p)?,
None => match maybe_ty {
Some(ty) => quote!(#ty::wildcard_dataspace_pattern()),
None => to_pattern_expr(&Stx::Discard)?,
}
};
Ok(quote!(#P_::Pattern::Bind { pattern: Box::new(#inner_pat_expr) }))
}
Stx::Subst(e) =>
Ok(lit(e)),
Stx::Discard =>
Ok(quote!(#P_::Pattern::Discard)),
Stx::Rec(l, fs) => {
let label = to_value_expr(&*l)?;
let members = compile_sequence_members(fs)?;
Ok(quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Rec { label: #label }),
entries: #MapFrom_([#(#members),*]),
}))
},
Stx::Seq(stxs) => {
let members = compile_sequence_members(stxs)?;
Ok(quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Arr),
entries: #MapFrom_([#(#members),*]),
}))
}
Stx::Set(_stxs) =>
Err("Set literals not supported in patterns"),
Stx::Dict(d) => {
let members = d.iter().map(|(k, v)| {
let k = to_value_expr(k)?;
let v = to_pattern_expr(v)?;
Ok(quote!((#k, #v)))
}).collect::<Result<Vec<_>, &'static str>>()?;
Ok(quote!(#P_::Pattern::Group {
type_: Box::new(#P_::GroupType::Dict),
entries: #MapFrom_([#(#members),*])
}))
}
}
}
pub fn pattern(src: TokenStream) -> TokenStream {
let src2 = src.clone();
let e = to_pattern_expr(&parse_macro_input!(src2 as Stx))
.expect("Cannot compile pattern").into();
// println!("\n{:#} -->\n{:#}\n", &src, &e);
e
}

View File

@ -1,283 +0,0 @@
use proc_macro2::Delimiter;
use proc_macro2::LineColumn;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use syn::ExprLit;
use syn::Ident;
use syn::Lit;
use syn::Result;
use syn::Type;
use syn::buffer::Cursor;
use syn::parse::Error;
use syn::parse::Parse;
use syn::parse::Parser;
use syn::parse::ParseStream;
use syn::parse_str;
use syndicate::value::Double;
use syndicate::value::IOValue;
use syndicate::value::NestedValue;
#[derive(Debug, Clone)]
pub enum Stx {
Atom(IOValue),
Binder(Option<Ident>, Option<Type>, Option<Box<Stx>>),
Discard,
Subst(TokenStream),
Rec(Box<Stx>, Vec<Stx>),
Seq(Vec<Stx>),
Set(Vec<Stx>),
Dict(Vec<(Stx, Stx)>),
}
impl Parse for Stx {
fn parse(input: ParseStream) -> Result<Self> {
input.step(|c| parse1(*c))
}
}
impl Stx {
pub fn bindings(&self) -> Vec<(Option<Ident>, Type)> {
let mut bs = vec![];
self._bindings(&mut bs);
bs
}
fn _bindings(&self, bs: &mut Vec<(Option<Ident>, Type)>) {
match self {
Stx::Atom(_) | Stx::Discard | Stx::Subst(_) => (),
Stx::Binder(id, ty, pat) => {
bs.push((id.clone(),
ty.clone().unwrap_or_else(
|| parse_str("syndicate::actor::AnyValue").unwrap())));
if let Some(p) = pat {
p._bindings(bs);
}
},
Stx::Rec(l, fs) => {
l._bindings(bs);
fs.iter().for_each(|f| f._bindings(bs));
},
Stx::Seq(vs) => vs.iter().for_each(|v| v._bindings(bs)),
Stx::Set(vs) => vs.iter().for_each(|v| v._bindings(bs)),
Stx::Dict(kvs) => kvs.iter().for_each(|(_k, v)| v._bindings(bs)),
}
}
}
fn punct_char(c: Cursor) -> Option<(char, Cursor)> {
c.punct().map(|(p, c)| (p.as_char(), c))
}
fn start_pos(s: Span) -> LineColumn {
// We would like to write
// s.start()
// here, but until [1] is fixed (perhaps via [2]), we have to go the unsafe route
// and assume we are in procedural macro context.
// [1]: https://github.com/dtolnay/proc-macro2/issues/402
// [2]: https://github.com/dtolnay/proc-macro2/pull/407
let u = s.unwrap().start();
LineColumn { column: u.column(), line: u.line() }
}
fn end_pos(s: Span) -> LineColumn {
// See start_pos
let u = s.unwrap().end();
LineColumn { column: u.column(), line: u.line() }
}
fn parse_id(mut c: Cursor) -> Result<(String, Cursor)> {
let mut id = String::new();
let mut prev_pos = start_pos(c.span());
loop {
if c.eof() || start_pos(c.span()) != prev_pos {
return Ok((id, c));
} else if let Some((p, next)) = c.punct() {
match p.as_char() {
'<' | '>' | '(' | ')' | '{' | '}' | '[' | ']' | ',' | ':' => return Ok((id, c)),
ch => {
id.push(ch);
prev_pos = end_pos(c.span());
c = next;
}
}
} else if let Some((i, next)) = c.ident() {
id.push_str(&i.to_string());
prev_pos = end_pos(i.span());
c = next;
} else {
return Ok((id, c));
}
}
}
fn parse_seq(delim_ch: char, mut c: Cursor) -> Result<(Vec<Stx>, Cursor)> {
let mut stxs = Vec::new();
loop {
c = skip_commas(c);
if c.eof() {
return Err(Error::new(c.span(), &format!("Expected {:?}", delim_ch)));
}
if let Some((p, next)) = c.punct() {
if p.as_char() == delim_ch {
return Ok((stxs, next));
}
}
let (stx, next) = parse1(c)?;
stxs.push(stx);
c = next;
}
}
fn skip_commas(mut c: Cursor) -> Cursor {
loop {
if let Some((',', next)) = punct_char(c) {
c = next;
continue;
}
return c;
}
}
fn parse_group<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>(
mut c: Cursor<'c>,
f: F,
after: Cursor<'c>,
) -> Result<(Vec<R>, Cursor<'c>)> {
let mut stxs = Vec::new();
loop {
c = skip_commas(c);
if c.eof() {
return Ok((stxs, after));
}
let (stx, next) = f(c)?;
stxs.push(stx);
c = next;
}
}
fn parse_kv(c: Cursor) -> Result<((Stx, Stx), Cursor)> {
let (k, c) = parse1(c)?;
if let Some((':', c)) = punct_char(c) {
let (v, c) = parse1(c)?;
return Ok(((k, v), c));
}
Err(Error::new(c.span(), "Expected ':'"))
}
fn adjacent_ident(pos: LineColumn, c: Cursor) -> (Option<Ident>, Cursor) {
if start_pos(c.span()) != pos {
(None, c)
} else if let Some((id, next)) = c.ident() {
(Some(id), next)
} else {
(None, c)
}
}
fn parse_exactly_one<'c>(c: Cursor<'c>) -> Result<Stx> {
parse1(c).and_then(|(q, c)| if c.eof() {
Ok(q)
} else {
Err(Error::new(c.span(), "No more input expected"))
})
}
fn parse_generic<T: Parse>(mut c: Cursor) -> Option<(T, Cursor)> {
match T::parse.parse2(c.token_stream()) {
Ok(t) => Some((t, Cursor::empty())), // because parse2 checks for end-of-stream!
Err(e) => {
// OK, because parse2 checks for end-of-stream, let's chop
// the input at the position of the error and try again (!).
let mut collected = Vec::new();
let upto = start_pos(e.span());
while !c.eof() && start_pos(c.span()) != upto {
let (tt, next) = c.token_tree().unwrap();
collected.push(tt);
c = next;
}
match T::parse.parse2(collected.into_iter().collect()) {
Ok(t) => Some((t, c)),
Err(_) => None,
}
}
}
}
fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
if let Some((p, next)) = c.punct() {
match p.as_char() {
'<' => parse_seq('>', next).and_then(|(mut q,c)| if q.is_empty() {
Err(Error::new(c.span(), "Missing Record label"))
} else {
Ok((Stx::Rec(Box::new(q.remove(0)), q), c))
}),
'$' => {
let (maybe_id, next) = adjacent_ident(end_pos(p.span()), next);
let (maybe_type, next) = if let Some((':', next)) = punct_char(next) {
match parse_generic::<Type>(next) {
Some((t, next)) => (Some(t), next),
None => (None, next)
}
} else {
(None, next)
};
if let Some((inner, _, next)) = next.group(Delimiter::Brace) {
parse_exactly_one(inner).map(
|q| (Stx::Binder(maybe_id, maybe_type, Some(Box::new(q))), next))
} else {
Ok((Stx::Binder(maybe_id, maybe_type, None), next))
}
}
'#' => {
if let Some((inner, _, next)) = next.group(Delimiter::Brace) {
parse_group(inner, parse1, next).map(|(q,c)| (Stx::Set(q),c))
} else if let Some((inner, _, next)) = next.group(Delimiter::Parenthesis) {
Ok((Stx::Subst(inner.token_stream()), next))
} else if let Some((tt, next)) = next.token_tree() {
Ok((Stx::Subst(vec![tt].into_iter().collect()), next))
} else {
Err(Error::new(c.span(), "Expected expression to substitute"))
}
}
_ => Err(Error::new(c.span(), "Unexpected punctuation")),
}
} else if let Some((i, next)) = c.ident() {
if i.to_string() == "_" {
Ok((Stx::Discard, next))
} else {
parse_id(c).and_then(|(q,c)| Ok((Stx::Atom(IOValue::symbol(&q)), c)))
}
} else if let Some((literal, next)) = c.literal() {
let t: ExprLit = syn::parse_str(&literal.to_string())?;
let v = match t.lit {
Lit::Str(s) => IOValue::new(s.value()),
Lit::ByteStr(bs) => IOValue::new(&bs.value()[..]),
Lit::Byte(b) => IOValue::new(b.value()),
Lit::Char(_) => return Err(Error::new(c.span(), "Literal characters not supported")),
Lit::Int(i) => if i.suffix().starts_with("u") || !i.base10_digits().starts_with("-") {
IOValue::new(i.base10_parse::<u128>()?)
} else {
IOValue::new(i.base10_parse::<i128>()?)
}
Lit::Float(f) => if f.suffix() == "f32" {
IOValue::new(&Double(f.base10_parse::<f32>()? as f64))
} else {
IOValue::new(&Double(f.base10_parse::<f64>()?))
}
Lit::Bool(_) => return Err(Error::new(c.span(), "Literal booleans not supported")),
Lit::Verbatim(_) => return Err(Error::new(c.span(), "Verbatim literals not supported")),
};
Ok((Stx::Atom(v), next))
} else if let Some((inner, _, after)) = c.group(Delimiter::Brace) {
parse_group(inner, parse_kv, after).map(|(q,c)| (Stx::Dict(q),c))
} else if let Some((inner, _, after)) = c.group(Delimiter::Bracket) {
parse_group(inner, parse1, after).map(|(q,c)| (Stx::Seq(q),c))
} else {
Err(Error::new(c.span(), "Unexpected input"))
}
}

View File

@ -1,103 +0,0 @@
use proc_macro2::Span;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::convert::TryFrom;
use syn::LitByteStr;
use syndicate::value::IOValue;
use syndicate::value::NestedValue;
use syndicate::value::Value;
use crate::stx::Stx;
pub fn emit_record(label: TokenStream2, fs: &[TokenStream2]) -> TokenStream2 {
let arity = fs.len();
quote!({
let mut ___r = syndicate::value::Value::record(#label, #arity);
#(___r.fields_vec_mut().push(#fs);)*
___r.finish().wrap()
})
}
pub fn emit_seq(vs: &[TokenStream2]) -> TokenStream2 {
quote!(syndicate::value::Value::from(vec![#(#vs),*]).wrap())
}
pub fn emit_set(vs: &[TokenStream2]) -> TokenStream2 {
quote!({
let mut ___s = syndicate::value::Set::new();
#(___s.insert(#vs);)*
syndicate::value::Value::from(___s).wrap()
})
}
pub fn emit_dict<'a, I: Iterator<Item = (TokenStream2, TokenStream2)>>(d: I) -> TokenStream2 {
let members: Vec<_> = d.map(|(k, v)| quote!(___d.insert(#k, #v))).collect();
quote!({
let mut ___d = syndicate::value::Map::new();
#(#members;)*
syndicate::value::Value::from(___d).wrap()
})
}
pub fn value_to_value_expr(v: &IOValue) -> TokenStream2 {
#[allow(non_snake_case)]
let V_: TokenStream2 = quote!(syndicate::value);
match v.value() {
Value::Boolean(b) =>
quote!(#V_::Value::from(#b).wrap()),
Value::Double(d) => {
let d = d.0;
quote!(#V_::Value::from(#d).wrap())
}
Value::SignedInteger(i) => {
let i = i128::try_from(i).expect("Literal integer out-of-range");
quote!(#V_::Value::from(#i).wrap())
}
Value::String(s) =>
quote!(#V_::Value::from(#s).wrap()),
Value::ByteString(bs) => {
let bs = LitByteStr::new(bs, Span::call_site());
quote!(#V_::Value::ByteString(#bs.to_vec()).wrap())
}
Value::Symbol(s) =>
quote!(#V_::Value::symbol(#s).wrap()),
Value::Record(r) =>
emit_record(value_to_value_expr(r.label()),
&r.fields().iter().map(value_to_value_expr).collect::<Vec<_>>()),
Value::Sequence(vs) =>
emit_seq(&vs.iter().map(value_to_value_expr).collect::<Vec<_>>()),
Value::Set(vs) =>
emit_set(&vs.iter().map(value_to_value_expr).collect::<Vec<_>>()),
Value::Dictionary(d) =>
emit_dict(d.into_iter().map(|(k, v)| (value_to_value_expr(k), value_to_value_expr(v)))),
Value::Embedded(_) =>
panic!("Embedded values in compile-time Preserves templates not (yet?) supported"),
}
}
pub fn to_value_expr(stx: &Stx) -> Result<TokenStream2, &'static str> {
match stx {
Stx::Atom(v) => Ok(value_to_value_expr(&v)),
Stx::Binder(_, _, _) => Err("Cannot use binder in literal value"),
Stx::Discard => Err("Cannot use discard in literal value"),
Stx::Subst(e) => Ok(e.clone().into()),
Stx::Rec(l, fs) =>
Ok(emit_record(to_value_expr(&*l)?,
&fs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)),
Stx::Seq(vs) =>
Ok(emit_seq(&vs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)),
Stx::Set(vs) =>
Ok(emit_set(&vs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)),
Stx::Dict(kvs) =>
Ok(emit_dict(kvs.into_iter()
.map(|(k, v)| Ok((to_value_expr(k)?, to_value_expr(v)?)))
.collect::<Result<Vec<_>,&'static str>>()?
.into_iter())),
}
}

View File

@ -1,15 +0,0 @@
{
"folders": [
{
"path": "."
},
{
"path": "../syndicate-protocols"
}
],
"settings": {
"files.exclude": {
"target": true
}
}
}

View File

@ -1,19 +0,0 @@
[package]
name = "syndicate-schema-plugin"
version = "0.9.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
description = "Support for using Preserves Schema with Syndicate macros."
homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0"
[lib]
[dependencies]
preserves-schema = "5.995"
syndicate = { path = "../syndicate", version = "0.40.0"}
[package.metadata.workspaces]
independent = true

View File

@ -1,3 +0,0 @@
mod pattern_plugin;
pub use pattern_plugin::PatternPlugin;

View File

@ -1,164 +0,0 @@
use preserves_schema::*;
use preserves_schema::compiler::*;
use preserves_schema::compiler::context::ModuleContext;
use preserves_schema::compiler::types::definition_type;
use preserves_schema::compiler::types::Purpose;
use preserves_schema::gen::schema::*;
use preserves_schema::syntax::block::escape_string;
use preserves_schema::syntax::block::constructors::*;
use std::iter::FromIterator;
use syndicate::pattern::lift_literal;
use syndicate::schemas::dataspace_patterns as P;
use syndicate::value::IOValue;
use syndicate::value::Map;
use syndicate::value::NestedValue;
#[derive(Debug)]
pub struct PatternPlugin;
type WalkState<'a, 'm, 'b> =
preserves_schema::compiler::cycles::WalkState<&'a ModuleContext<'m, 'b>>;
impl Plugin for PatternPlugin {
fn generate_definition(
&self,
ctxt: &mut ModuleContext,
definition_name: &str,
definition: &Definition,
) {
if ctxt.mode == context::ModuleContextMode::TargetGeneric {
let mut s = WalkState::new(ctxt, ctxt.module_path.clone());
if let Some(p) = definition.wc(&mut s) {
let ty = definition_type(&ctxt.module_path,
Purpose::Codegen,
definition_name,
definition);
let v = syndicate::language().unparse(&p);
let v = preserves_schema::support::preserves::value::TextWriter::encode(
&mut preserves_schema::support::preserves::value::NoEmbeddedDomainCodec,
&v).unwrap();
ctxt.define_type(item(seq![
"impl",
ty.generic_decl(ctxt),
" ",
names::render_constructor(definition_name),
ty.generic_arg(ctxt),
" ", codeblock![
seq!["#[allow(unused)] pub fn wildcard_dataspace_pattern() ",
"-> syndicate::schemas::dataspace_patterns::Pattern ",
codeblock![
"use syndicate::schemas::dataspace_patterns::*;",
"use preserves_schema::Codec;",
seq!["let _v = syndicate::value::text::from_str(",
escape_string(&v),
", syndicate::value::ViaCodec::new(syndicate::value::NoEmbeddedDomainCodec)).unwrap();"],
"syndicate::language().parse(&_v).unwrap()"]]]]));
}
}
}
}
fn discard() -> P::Pattern {
P::Pattern::Discard
}
trait WildcardPattern {
fn wc(&self, s: &mut WalkState) -> Option<P::Pattern>;
}
impl WildcardPattern for Definition {
fn wc(&self, s: &mut WalkState) -> Option<P::Pattern> {
match self {
Definition::Or { .. } => None,
Definition::And { .. } => None,
Definition::Pattern(p) => p.wc(s),
}
}
}
impl WildcardPattern for Pattern {
fn wc(&self, s: &mut WalkState) -> Option<P::Pattern> {
match self {
Pattern::CompoundPattern(p) => p.wc(s),
Pattern::SimplePattern(p) => p.wc(s),
}
}
}
fn from_io(v: &IOValue) -> Option<P::_Any> {
Some(v.value().copy_via(&mut |_| Err(())).ok()?.wrap())
}
impl WildcardPattern for CompoundPattern {
fn wc(&self, s: &mut WalkState) -> Option<P::Pattern> {
match self {
CompoundPattern::Tuple { patterns } |
CompoundPattern::TuplePrefix { fixed: patterns, .. }=>
Some(P::Pattern::Group {
type_: Box::new(P::GroupType::Arr),
entries: patterns.iter().enumerate()
.map(|(i, p)| Some((P::_Any::new(i), unname(p).wc(s)?)))
.collect::<Option<Map<P::_Any, P::Pattern>>>()?,
}),
CompoundPattern::Dict { entries } =>
Some(P::Pattern::Group {
type_: Box::new(P::GroupType::Dict),
entries: Map::from_iter(
entries.0.iter()
.map(|(k, p)| Some((from_io(k)?, unname_simple(p).wc(s)?)))
.filter(|e| discard() != e.as_ref().unwrap().1)
.collect::<Option<Vec<(P::_Any, P::Pattern)>>>()?
.into_iter()),
}),
CompoundPattern::Rec { label, fields } => match (unname(label), unname(fields)) {
(Pattern::SimplePattern(label), Pattern::CompoundPattern(fields)) =>
match (*label, *fields) {
(SimplePattern::Lit { value }, CompoundPattern::Tuple { patterns }) =>
Some(P::Pattern::Group{
type_: Box::new(P::GroupType::Rec { label: from_io(&value)? }),
entries: patterns.iter().enumerate()
.map(|(i, p)| Some((P::_Any::new(i), unname(p).wc(s)?)))
.collect::<Option<Map<P::_Any, P::Pattern>>>()?,
}),
_ => None,
},
_ => None,
},
}
}
}
impl WildcardPattern for SimplePattern {
fn wc(&self, s: &mut WalkState) -> Option<P::Pattern> {
match self {
SimplePattern::Any |
SimplePattern::Atom { .. } |
SimplePattern::Embedded { .. } |
SimplePattern::Seqof { .. } |
SimplePattern::Setof { .. } |
SimplePattern::Dictof { .. } => Some(discard()),
SimplePattern::Lit { value } => Some(lift_literal(&from_io(value)?)),
SimplePattern::Ref(r) => s.cycle_check(
r,
|ctxt, r| ctxt.bundle.lookup_definition(r).map(|v| v.0),
|s, d| d.and_then(|d| d.wc(s)).or_else(|| Some(discard())),
|| Some(discard())),
}
}
}
fn unname(np: &NamedPattern) -> Pattern {
match np {
NamedPattern::Anonymous(p) => (**p).clone(),
NamedPattern::Named(b) => Pattern::SimplePattern(Box::new(b.pattern.clone())),
}
}
fn unname_simple(np: &NamedSimplePattern) -> &SimplePattern {
match np {
NamedSimplePattern::Anonymous(p) => p,
NamedSimplePattern::Named(b) => &b.pattern,
}
}

View File

@ -1,47 +0,0 @@
[package]
name = "syndicate-server"
version = "0.45.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
description = "Dataspace server."
homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0"
[features]
jemalloc = ["dep:tikv-jemallocator"]
[build-dependencies]
preserves-schema = "5.995"
syndicate = { path = "../syndicate", version = "0.40.0"}
syndicate-schema-plugin = { path = "../syndicate-schema-plugin", version = "0.9.0"}
[dependencies]
preserves-schema = "5.995"
syndicate = { path = "../syndicate", version = "0.40.0"}
syndicate-macros = { path = "../syndicate-macros", version = "0.32.0"}
chrono = "0.4"
futures = "0.3"
lazy_static = "1.4"
noise-protocol = "0.1"
noise-rust-crypto = "0.5"
notify = "4.0"
structopt = "0.3"
tikv-jemallocator = { version = "0.5.0", optional = true }
tokio = { version = "1.10", features = ["io-std", "time", "process"] }
tokio-util = "0.6"
tokio-stream = "0.1"
tracing = "0.1"
tracing-subscriber = "0.2"
tracing-futures = "0.2"
hyper = { version = "0.14.27", features = ["server", "http1", "stream"] }
hyper-tungstenite = "0.11.1"
parking_lot = "0.12.1"
[package.metadata.workspaces]
independent = true

View File

@ -1,19 +0,0 @@
all: binary-debug
# cargo install cargo-watch
watch:
cargo watch -c -x check -x 'test -- --nocapture'
run-watch:
RUST_BACKTRACE=1 cargo watch -c -x 'build --all-targets' -x 'run'
inotifytest:
inotifytest sh -c 'reset; cargo build && RUST_BACKTRACE=1 cargo test -- --nocapture'
binary: binary-release
binary-release:
cargo build --release --all-targets --features jemalloc
binary-debug:
cargo build --all-targets

View File

@ -1,32 +0,0 @@
use preserves_schema::compiler::*;
fn main() -> std::io::Result<()> {
let buildroot = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
let mut gen_dir = buildroot.clone();
gen_dir.push("src/schemas");
let mut c = CompilerConfig::new("crate::schemas".to_owned());
c.plugins.push(Box::new(syndicate_schema_plugin::PatternPlugin));
c.add_external_module(ExternalModule::new(vec!["EntityRef".to_owned()], "syndicate::actor"));
c.add_external_module(
ExternalModule::new(vec!["TransportAddress".to_owned()],
"syndicate::schemas::transport_address")
.set_fallback_language_types(
|v| vec![format!("syndicate::schemas::Language<{}>", v)].into_iter().collect()));
c.add_external_module(
ExternalModule::new(vec!["gatekeeper".to_owned()], "syndicate::schemas::gatekeeper")
.set_fallback_language_types(
|v| vec![format!("syndicate::schemas::Language<{}>", v)].into_iter().collect())
);
c.add_external_module(
ExternalModule::new(vec!["noise".to_owned()], "syndicate::schemas::noise")
.set_fallback_language_types(
|v| vec![format!("syndicate::schemas::Language<{}>", v)].into_iter().collect())
);
let inputs = expand_inputs(&vec!["protocols/schema-bundle.bin".to_owned()])?;
c.load_schemas_and_bundles(&inputs, &vec![])?;
c.load_xref_bin("syndicate", syndicate::schemas::_bundle())?;
compile(&c, &mut CodeCollector::files(gen_dir))
}

View File

@ -1,55 +0,0 @@
use std::sync::Arc;
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::language;
use syndicate::relay;
use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy;
use syndicate::value::NestedValue;
use tokio::net::TcpStream;
use core::time::Duration;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
let config = Config::from_args();
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split();
Actor::top(None, |t| {
relay::connect_stream(t, i, o, false, sturdyref, (), |_state, t, ds| {
let consumer = syndicate::entity(0)
.on_message(|message_count, _t, m: AnyValue| {
if m.value().is_boolean() {
tracing::info!("{:?} messages in the last second", message_count);
*message_count = 0;
} else {
*message_count += 1;
}
Ok(())
})
.create_cap(t);
ds.assert(t, language(), &Observe {
pattern: syndicate_macros::pattern!{<Says $ $>},
observer: Arc::clone(&consumer),
});
t.every(Duration::from_secs(1), move |t| {
consumer.message(t, &(), &AnyValue::new(true));
Ok(())
})?;
Ok(None)
})
}).await??;
Ok(())
}

View File

@ -1,96 +0,0 @@
//! I am a low-level hack intended to consume bytes as quickly as
//! possible, so that the consumer isn't the bottleneck in
//! single-producer/single-consumer broker throughput measurement.
use preserves_schema::Codec;
use structopt::StructOpt;
use syndicate::schemas::Language;
use syndicate::schemas::protocol as P;
use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy;
use syndicate::value::BinarySource;
use syndicate::value::BytesBinarySource;
use syndicate::value::IOValue;
use syndicate::value::PackedWriter;
use syndicate::value::Reader;
use std::io::Read;
use std::io::Write;
use std::net::TcpStream;
use std::time::Duration;
use std::time::Instant;
mod dirty;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_args();
let mut stream = TcpStream::connect("127.0.0.1:9001")?;
dirty::dirty_resolve(&mut stream, &config.dataspace)?;
let iolang = Language::<IOValue>::default();
{
let turn = P::Turn::<IOValue>(vec![
P::TurnEvent {
oid: P::Oid(1.into()),
event: P::Event::Assert(Box::new(P::Assert {
assertion: P::Assertion(iolang.unparse(&Observe {
pattern: syndicate_macros::pattern!{<Says $ $>},
observer: iolang.unparse(&sturdy::WireRef::Mine {
oid: Box::new(sturdy::Oid(2.into())),
}),
})),
handle: P::Handle(2.into()),
})),
}
]);
stream.write_all(&PackedWriter::encode_iovalue(&iolang.unparse(&turn))?)?;
}
let mut buf = [0; 131072];
let turn_size = {
let n = stream.read(&mut buf)?;
if n == 0 {
return Ok(());
}
let mut src = BytesBinarySource::new(&buf);
src.packed_iovalues().demand_next(false)?;
src.index
};
let mut start = Instant::now();
let interval = Duration::from_secs(1);
let mut deadline = start + interval;
let mut total_bytes = 0;
loop {
let n = stream.read(&mut buf)?;
if n == 0 {
break;
}
total_bytes += n;
let now = Instant::now();
if now >= deadline {
let delta = now - start;
let message_count = total_bytes as f64 / turn_size as f64;
println!("{} messages in the last second ({} Hz)",
message_count,
message_count / delta.as_secs_f64());
start = now;
total_bytes = 0;
deadline = deadline + interval;
}
}
Ok(())
}

View File

@ -1,67 +0,0 @@
//! I am a low-level hack intended to shovel bytes out the gate as
//! quickly as possible, so that the producer isn't the bottleneck in
//! single-producer/single-consumer broker throughput measurement.
use preserves_schema::Codec;
use structopt::StructOpt;
use syndicate::schemas::Language;
use syndicate::schemas::protocol as P;
use syndicate::value::IOValue;
use syndicate::value::PackedWriter;
use syndicate::value::Value;
use std::io::Write;
use std::net::TcpStream;
mod dirty;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "a", default_value = "1")]
action_count: u32,
#[structopt(short = "b", default_value = "0")]
bytes_padding: usize,
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
#[inline]
fn says(who: IOValue, what: IOValue) -> IOValue {
let mut r = Value::simple_record("Says", 2);
r.fields_vec_mut().push(who);
r.fields_vec_mut().push(what);
r.finish().wrap()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_args();
let mut stream = TcpStream::connect("127.0.0.1:9001")?;
dirty::dirty_resolve(&mut stream, &config.dataspace)?;
let padding: IOValue = Value::ByteString(vec![0; config.bytes_padding]).wrap();
let mut events = Vec::new();
for _ in 0 .. config.action_count {
events.push(P::TurnEvent::<IOValue> {
oid: P::Oid(1.into()),
event: P::Event::Message(Box::new(P::Message {
body: P::Assertion(says(Value::from("producer").wrap(), padding.clone())),
})),
});
}
let turn = P::Turn(events);
let mut buf: Vec<u8> = vec![];
let iolang = Language::<IOValue>::default();
while buf.len() < 16384 {
buf.extend(&PackedWriter::encode_iovalue(&iolang.unparse(&turn))?);
}
loop {
stream.write_all(&buf)?;
}
}

View File

@ -1,47 +0,0 @@
use preserves_schema::Codec;
use syndicate::schemas::Language;
use syndicate::schemas::gatekeeper;
use syndicate::schemas::protocol as P;
use syndicate::sturdy;
use syndicate::value::IOValue;
use syndicate::value::NestedValue;
use syndicate::value::PackedWriter;
use std::io::Read;
use std::io::Write;
use std::net::TcpStream;
pub fn dirty_resolve(stream: &mut TcpStream, dataspace: &str) -> Result<(), Box<dyn std::error::Error>> {
let iolang = Language::<IOValue>::default();
let sturdyref = sturdy::SturdyRef::from_hex(dataspace)?;
let sturdyref = iolang.parse::<gatekeeper::Step<IOValue>>(
&syndicate::language().unparse(&sturdyref)
.copy_via(&mut |_| Err("no!"))?)?;
let resolve_turn = P::Turn(vec![
P::TurnEvent {
oid: P::Oid(0.into()),
event: P::Event::Assert(Box::new(P::Assert {
assertion: P::Assertion(iolang.unparse(&gatekeeper::Resolve::<IOValue> {
step: sturdyref,
observer: iolang.unparse(&sturdy::WireRef::Mine {
oid: Box::new(sturdy::Oid(0.into())),
}),
})),
handle: P::Handle(1.into()),
})),
}
]);
stream.write_all(&PackedWriter::encode_iovalue(&iolang.unparse(&resolve_turn))?)?;
{
let mut buf = [0; 1024];
stream.read(&mut buf)?;
// We just assume we got a positive response here!!
// We further assume that the resolved dataspace was assigned peer-oid 1
}
Ok(())
}

View File

@ -1,205 +0,0 @@
use std::sync::Arc;
use std::sync::Mutex;
use std::time::SystemTime;
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::language;
use syndicate::relay;
use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy;
use syndicate::value::NestedValue;
use syndicate::value::Value;
use tokio::net::TcpStream;
use core::time::Duration;
#[derive(Clone, Debug, StructOpt)]
pub struct PingConfig {
#[structopt(short = "t", default_value = "1")]
turn_count: u32,
#[structopt(short = "a", default_value = "1")]
action_count: u32,
#[structopt(short = "l", default_value = "0")]
report_latency_every: usize,
#[structopt(short = "b", default_value = "0")]
bytes_padding: usize,
}
#[derive(Clone, Debug, StructOpt)]
pub enum PingPongMode {
Ping(PingConfig),
Pong,
}
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(subcommand)]
mode: PingPongMode,
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
fn now() -> u64 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("time after epoch").as_nanos() as u64
}
fn simple_record2(label: &str, v1: AnyValue, v2: AnyValue) -> AnyValue {
let mut r = Value::simple_record(label, 2);
r.fields_vec_mut().push(v1);
r.fields_vec_mut().push(v2);
r.finish().wrap()
}
fn report_latencies(rtt_ns_samples: &Vec<u64>) {
let n = rtt_ns_samples.len();
let rtt_0 = rtt_ns_samples[0];
let rtt_50 = rtt_ns_samples[n * 1 / 2];
let rtt_90 = rtt_ns_samples[n * 90 / 100];
let rtt_95 = rtt_ns_samples[n * 95 / 100];
let rtt_99 = rtt_ns_samples[n * 99 / 100];
let rtt_99_9 = rtt_ns_samples[n * 999 / 1000];
let rtt_99_99 = rtt_ns_samples[n * 9999 / 10000];
let rtt_max = rtt_ns_samples[n - 1];
println!("rtt: 0% {:05.5}ms, 50% {:05.5}ms, 90% {:05.5}ms, 95% {:05.5}ms, 99% {:05.5}ms, 99.9% {:05.5}ms, 99.99% {:05.5}ms, max {:05.5}ms",
rtt_0 as f64 / 1000000.0,
rtt_50 as f64 / 1000000.0,
rtt_90 as f64 / 1000000.0,
rtt_95 as f64 / 1000000.0,
rtt_99 as f64 / 1000000.0,
rtt_99_9 as f64 / 1000000.0,
rtt_99_99 as f64 / 1000000.0,
rtt_max as f64 / 1000000.0);
println!("msg: 0% {:05.5}ms, 50% {:05.5}ms, 90% {:05.5}ms, 95% {:05.5}ms, 99% {:05.5}ms, 99.9% {:05.5}ms, 99.99% {:05.5}ms, max {:05.5}ms",
rtt_0 as f64 / 2000000.0,
rtt_50 as f64 / 2000000.0,
rtt_90 as f64 / 2000000.0,
rtt_95 as f64 / 2000000.0,
rtt_99 as f64 / 2000000.0,
rtt_99_9 as f64 / 2000000.0,
rtt_99_99 as f64 / 2000000.0,
rtt_max as f64 / 2000000.0);
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
let config = Config::from_args();
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split();
Actor::top(None, |t| {
relay::connect_stream(t, i, o, false, sturdyref, (), move |_state, t, ds| {
let (send_label, recv_label, report_latency_every, should_echo, bytes_padding) =
match config.mode {
PingPongMode::Ping(ref c) =>
("Ping", "Pong", c.report_latency_every, false, c.bytes_padding),
PingPongMode::Pong =>
("Pong", "Ping", 0, true, 0),
};
let consumer = {
let ds = Arc::clone(&ds);
let mut turn_counter: u64 = 0;
let mut event_counter: u64 = 0;
let mut rtt_ns_samples: Vec<u64> = vec![0; report_latency_every];
let mut rtt_batch_count: usize = 0;
let current_reply = Arc::new(Mutex::new(None));
Cap::new(&t.create(
syndicate::entity(())
.on_message(move |(), t, m: AnyValue| {
match m.value().as_boolean() {
Some(_) => {
tracing::info!("{:?} turns, {:?} events in the last second",
turn_counter,
event_counter);
turn_counter = 0;
event_counter = 0;
}
None => {
event_counter += 1;
let bindings = m.value().to_sequence()?;
let timestamp = &bindings[0];
let padding = &bindings[1];
if should_echo || (report_latency_every == 0) {
ds.message(t, &(), &simple_record2(&send_label,
timestamp.clone(),
padding.clone()));
} else {
let mut g = current_reply.lock().expect("unpoisoned");
if let None = *g {
turn_counter += 1;
t.pre_commit(enclose!((current_reply) move |_| {
*current_reply.lock().expect("unpoisoned") = None;
Ok(())
}));
let rtt_ns = now() - timestamp.value().to_u64()?;
rtt_ns_samples[rtt_batch_count] = rtt_ns;
rtt_batch_count += 1;
if rtt_batch_count == report_latency_every {
rtt_ns_samples.sort();
report_latencies(&rtt_ns_samples);
rtt_batch_count = 0;
}
*g = Some(simple_record2(&send_label,
Value::from(now()).wrap(),
padding.clone()));
}
ds.message(t, &(), g.as_ref().expect("some reply"));
}
}
}
Ok(())
})))
};
ds.assert(t, language(), &Observe {
pattern: {
let recv_label = AnyValue::symbol(recv_label);
syndicate_macros::pattern!{<#(recv_label) $ $>}
},
observer: Arc::clone(&consumer),
});
t.every(Duration::from_secs(1), move |t| {
consumer.message(t, &(), &AnyValue::new(true));
Ok(())
})?;
if let PingPongMode::Ping(c) = &config.mode {
let facet = t.facet_ref();
let turn_count = c.turn_count;
let action_count = c.action_count;
let account = Arc::clone(t.account());
t.linked_task(Some(AnyValue::symbol("boot-ping")), async move {
let padding = AnyValue::bytestring(vec![0; bytes_padding]);
for _ in 0..turn_count {
let current_rec = simple_record2(send_label,
Value::from(now()).wrap(),
padding.clone());
facet.activate(&account, None, |t| {
for _ in 0..action_count {
ds.message(t, &(), &current_rec);
}
Ok(())
});
}
Ok(LinkedTaskTermination::KeepFacet)
});
}
Ok(None)
})
}).await??;
Ok(())
}

View File

@ -1,52 +0,0 @@
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::preserves::rec;
use syndicate::relay;
use syndicate::sturdy;
use syndicate::value::NestedValue;
use tokio::net::TcpStream;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "a", default_value = "1")]
action_count: u32,
#[structopt(short = "b", default_value = "0")]
bytes_padding: usize,
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
let config = Config::from_args();
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split();
Actor::top(None, |t| {
relay::connect_stream(t, i, o, false, sturdyref, (), move |_state, t, ds| {
let facet = t.facet_ref();
let padding = AnyValue::new(&vec![0u8; config.bytes_padding][..]);
let action_count = config.action_count;
let account = Account::new(None, None);
t.linked_task(Some(AnyValue::symbol("sender")), async move {
loop {
account.ensure_clear_funds().await;
facet.activate(&account, None, |t| {
for _ in 0..action_count {
ds.message(t, &(), &rec![AnyValue::symbol("Says"),
AnyValue::new("producer"),
padding.clone()]);
}
Ok(())
});
}
});
Ok(None)
})
}).await??;
Ok(())
}

View File

@ -1,76 +0,0 @@
use std::sync::Arc;
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::language;
use syndicate::relay;
use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy;
use syndicate::value::NestedValue;
use tokio::net::TcpStream;
use core::time::Duration;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
let config = Config::from_args();
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split();
Actor::top(None, |t| {
relay::connect_stream(t, i, o, false, sturdyref, (), |_state, t, ds| {
let consumer = {
#[derive(Default)]
struct State {
event_counter: u64,
arrival_counter: u64,
departure_counter: u64,
occupancy: u64,
}
syndicate::entity(State::default()).on_asserted(move |s, _, _| {
s.event_counter += 1;
s.arrival_counter += 1;
s.occupancy += 1;
Ok(Some(Box::new(|s, _| {
s.event_counter += 1;
s.departure_counter += 1;
s.occupancy -= 1;
Ok(())
})))
}).on_message(move |s, _, _| {
tracing::info!(
"{:?} events, {:?} arrivals, {:?} departures, {:?} present in the last second",
s.event_counter,
s.arrival_counter,
s.departure_counter,
s.occupancy);
s.event_counter = 0;
s.arrival_counter = 0;
s.departure_counter = 0;
Ok(())
}).create_cap(t)
};
ds.assert(t, language(), &Observe {
pattern: syndicate_macros::pattern!{<Present $>},
observer: Arc::clone(&consumer),
});
t.every(Duration::from_secs(1), move |t| {
consumer.message(t, &(), &AnyValue::new(true));
Ok(())
})?;
Ok(None)
})
}).await??;
Ok(())
}

View File

@ -1,48 +0,0 @@
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::preserves::rec;
use syndicate::relay;
use syndicate::sturdy;
use syndicate::value::NestedValue;
use tokio::net::TcpStream;
#[derive(Clone, Debug, StructOpt)]
pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")]
dataspace: String,
}
#[tokio::main]
async fn main() -> ActorResult {
syndicate::convenient_logging()?;
let config = Config::from_args();
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split();
Actor::top(None, |t| {
relay::connect_stream(t, i, o, false, sturdyref, (), move |_state, t, ds| {
let facet = t.facet_ref();
let account = Account::new(None, None);
t.linked_task(Some(AnyValue::symbol("sender")), async move {
let presence = rec![AnyValue::symbol("Present"), AnyValue::new(std::process::id())];
loop {
let mut handle = None;
facet.activate(&account, None, |t| {
handle = ds.assert(t, &(), &presence);
Ok(())
});
account.ensure_clear_funds().await;
facet.activate(&account, None, |t| {
if let Some(h) = handle {
t.retract(h);
}
Ok(())
});
}
});
Ok(None)
})
}).await??;
Ok(())
}

View File

@ -1,9 +0,0 @@
// tracing::info!(r" {} __{}__{}__ {}", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// tracing::info!(r" {} /{}_/ \_{}\ {}", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// tracing::info!(r" {} / \__/ \ {} __ __", BRIGHT_GREEN, NORMAL);
// tracing::info!(r" {}/{}\__/ \__/{}\{} _______ ______ ____/ /__________ / /____", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// tracing::info!(r" {}\{}/ \__/ \{}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// tracing::info!(r" {} \__/ \__/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", BRIGHT_GREEN, NORMAL);
// tracing::info!(r" {} \_{}\__/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
// tracing::info!(r" /____/");

View File

@ -1,8 +0,0 @@
all: schema-bundle.bin
clean:
rm -f schema-bundle.bin
schema-bundle.bin: schemas/*.prs
preserves-schemac schemas > $@.tmp
mv $@.tmp $@

View File

@ -1,17 +0,0 @@
´³bundle·µ³control„´³schema·³version°³ definitions·³
ExitServer´³rec´³lit³exit„´³tupleµ´³named³code´³atom³ SignedInteger„„„„„„³ embeddedType€„„µ³ documentation„´³schema·³version°³ definitions·³Url´³orµµ±present´³dict·³url´³named³url´³atom³String„„„„„µ±invalid´³dict·³url´³named³url³any„„„„µ±absent´³dict·„„„„„³IOList´³orµµ±bytes´³atom³
ByteString„„µ±string´³atom³String„„µ±nested´³seqof´³refµ„³IOList„„„„„³Metadata´³rec´³lit³metadata„´³tupleµ´³named³object³any„´³named³info´³dictof´³atom³Symbol„³any„„„„„³ Description´³orµµ±present´³dict·³ description´³named³ description´³refµ„³IOList„„„„„µ±invalid´³dict·³ description´³named³ description³any„„„„µ±absent´³dict·„„„„„„³ embeddedType€„„µ³externalServices„´³schema·³version°³ definitions·³Process´³orµµ±simple´³refµ„³ CommandLine„„µ±full´³refµ„³ FullProcess„„„„³Service´³refµ„³ DaemonService„³ClearEnv´³orµµ±present´³dict·³clearEnv´³named³clearEnv´³atom³Boolean„„„„„µ±invalid´³dict·³clearEnv´³named³clearEnv³any„„„„µ±absent´³dict·„„„„„³EnvValue´³orµµ±set´³atom³String„„µ±remove´³lit€„„µ±invalid³any„„„³Protocol´³orµµ±none´³lit³none„„µ±binarySyndicate´³lit³application/syndicate„„µ± textSyndicate´³lit³text/syndicate„„„„³
ProcessDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³
ProcessEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ FullProcess´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³
ProcessEnv„„´³named³dir´³refµ„³
ProcessDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³ ReadyOnStart´³orµµ±present´³dict·³ readyOnStart´³named³ readyOnStart´³atom³Boolean„„„„„µ±invalid´³dict·³ readyOnStart´³named³ readyOnStart³any„„„„µ±absent´³dict·„„„„„³ RestartField´³orµµ±present´³dict·³restart´³named³restart´³refµ„³ RestartPolicy„„„„„µ±invalid´³dict·³restart´³named³restart³any„„„„µ±absent´³dict·„„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id³any„´³named³config´³refµ„³DaemonProcessSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id³any„„„„³ ProtocolField´³orµµ±present´³dict·³protocol´³named³protocol´³refµ„³Protocol„„„„„µ±invalid´³dict·³protocol´³named³protocol³any„„„„µ±absent´³dict·„„„„„³ RestartPolicy´³orµµ±always´³lit³always„„µ±onError´³lit³on-error„„µ±all´³lit³all„„µ±never´³lit³never„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„³DaemonProcessSpec´³orµµ±simple´³refµ„³ CommandLine„„µ±oneShot´³rec´³lit³one-shot„´³tupleµ´³named³setup´³refµ„³ CommandLine„„„„„„µ±full´³refµ„³FullDaemonProcess„„„„³FullDaemonProcess´³andµ´³named³process´³refµ„³ FullProcess„„´³named³ readyOnStart´³refµ„³ ReadyOnStart„„´³named³restart´³refµ„³ RestartField„„´³named³protocol´³refµ„³ ProtocolField„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³internalServices„´³schema·³version°³ definitions·³ ConfigEnv´³dictof´³atom³Symbol„³any„³
Gatekeeper´³rec´³lit³
gatekeeper„´³tupleµ´³named³ bindspace´³embedded´³refµ³
gatekeeper„³Bind„„„„„„³
HttpRouter´³rec´³lit³ http-router„´³tupleµ´³named³httpd´³embedded³any„„„„„³ TcpWithHttp´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„´³named³
gatekeeper´³embedded´³refµ³
gatekeeper„³Resolve„„„´³named³httpd´³embedded´³refµ³http„³ HttpContext„„„„„„³ DebtReporter´³rec´³lit³ debt-reporter„´³tupleµ´³named³intervalSeconds´³atom³Double„„„„„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„´³named³env´³refµ„³ ConfigEnv„„„„„³TcpWithoutHttp´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„´³named³
gatekeeper´³embedded´³refµ³
gatekeeper„³Resolve„„„„„„³TcpRelayListener´³orµµ±TcpWithoutHttp´³refµ„³TcpWithoutHttp„„µ± TcpWithHttp´³refµ„³ TcpWithHttp„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„´³named³
gatekeeper´³embedded´³refµ³
gatekeeper„³Resolve„„„„„„³HttpStaticFileServer´³rec´³lit³http-static-files„´³tupleµ´³named³dir´³atom³String„„´³named³pathPrefixElements´³atom³ SignedInteger„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„

View File

@ -1,12 +0,0 @@
version 1 .
# Messages and assertions relating to the `$control` entity enabled in syndicate-server when
# the `--control` flag is supplied.
#
# For example, placing the following into `control-config.pr` and starting the server with
# `syndicate-server --control -c control-config.pr` will result in the server exiting with
# exit code 2:
#
# $control ! <exit 2>
ExitServer = <exit @code int> .

View File

@ -1,11 +0,0 @@
version 1 .
# Assertion. Describes `object`.
Metadata = <metadata @object any @info { symbol: any ...:... }> .
# Projections of the `info` in a `Metadata` record.
Description = @present { description: IOList } / @invalid { description: any } / @absent {} .
Url = @present { url: string } / @invalid { url: any } / @absent {} .
# Data type. From preserves' `conventions.md`.
IOList = @bytes bytes / @string string / @nested [IOList ...] .

View File

@ -1,55 +0,0 @@
version 1 .
embeddedType EntityRef.Cap .
Service = DaemonService .
DaemonService = <daemon @id any> .
DaemonProcess = <daemon @id any @config DaemonProcessSpec>.
DaemonProcessSpec = @simple CommandLine / @oneShot <one-shot @setup CommandLine> / @full FullDaemonProcess .
FullDaemonProcess = @process FullProcess & @readyOnStart ReadyOnStart & @restart RestartField & @protocol ProtocolField .
ReadyOnStart = @present { readyOnStart: bool } / @invalid { readyOnStart: any } / @absent {} .
RestartField = @present { restart: RestartPolicy } / @invalid { restart: any } / @absent {} .
ProtocolField = @present { protocol: Protocol } / @invalid { protocol: any } / @absent {} .
Process = @simple CommandLine / @full FullProcess .
FullProcess =
& { argv: CommandLine }
& @env ProcessEnv
& @dir ProcessDir
& @clearEnv ClearEnv
.
ProcessEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} .
ProcessDir = @present { dir: string } / @invalid { dir: any } / @absent {} .
ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} .
CommandLine = @shell string / @full FullCommandLine .
FullCommandLine = [@program string, @args string ...] .
EnvVariable = @string string / @symbol symbol / @invalid any .
EnvValue = @set string / @remove #f / @invalid any .
RestartPolicy =
/ # Whether the process terminates normally or abnormally, restart it
# without affecting any peer processes within the service.
=always
/ # If the process terminates normally, leave everything alone; if it
# terminates abnormally, restart it without affecting peers.
@onError =on-error
/ # If the process terminates normally, leave everything alone; if it
# terminates abnormally, restart the whole daemon (all processes
# within the daemon).
=all
/ # Treat both normal and abnormal termination as normal termination; that is, never restart,
# and enter state "complete" even if the process fails.
=never
.
Protocol =
/ # stdin is /dev/null, output and error are logged
=none
/ # stdin and stdout are *binary* Syndicate-protocol channels
@binarySyndicate =application/syndicate
/ # stdin and stdout are *text* Syndicate-protocol channels
@textSyndicate =text/syndicate
.

View File

@ -1,18 +0,0 @@
version 1 .
embeddedType EntityRef.Cap .
Gatekeeper = <gatekeeper @bindspace #:gatekeeper.Bind> .
DebtReporter = <debt-reporter @intervalSeconds double>.
TcpRelayListener = TcpWithoutHttp / TcpWithHttp .
TcpWithoutHttp = <relay-listener @addr TransportAddress.Tcp @gatekeeper #:gatekeeper.Resolve> .
TcpWithHttp = <relay-listener @addr TransportAddress.Tcp @gatekeeper #:gatekeeper.Resolve @httpd #:http.HttpContext> .
UnixRelayListener = <relay-listener @addr TransportAddress.Unix @gatekeeper #:gatekeeper.Resolve> .
ConfigWatcher = <config-watcher @path string @env ConfigEnv>.
ConfigEnv = { symbol: any ...:... }.
HttpRouter = <http-router @httpd #:any> .
HttpStaticFileServer = <http-static-files @dir string @pathPrefixElements int> .

View File

@ -1,27 +0,0 @@
use std::sync::Arc;
use syndicate::actor::*;
pub fn adjust(t: &mut Activation, f: &Arc<Field<isize>>, delta: isize) {
let f = f.clone();
tracing::trace!(?f, v0 = ?t.get(&f), "adjust");
*t.get_mut(&f) += delta;
tracing::trace!(?f, v1 = ?t.get(&f), "adjust");
t.on_stop(move |t| {
tracing::trace!(?f, v0 = ?t.get(&f), "cleanup");
*t.get_mut(&f) -= delta;
tracing::trace!(?f, v1 = ?t.get(&f), "cleanup");
Ok(())
});
}
pub fn sync_and_adjust<M: 'static + Send>(t: &mut Activation, r: &Arc<Ref<M>>, f: &Arc<Field<isize>>, delta: isize) {
let f = f.clone();
let sync_handler = t.create(move |t: &mut Activation| {
tracing::trace!(?f, v0 = ?t.get(&f), "sync");
*t.get_mut(&f) += delta;
tracing::trace!(?f, v1 = ?t.get(&f), "sync");
Ok(())
});
t.sync(r, sync_handler)
}

View File

@ -1,76 +0,0 @@
use preserves_schema::Codec;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::preserves::rec;
use syndicate::schemas::service;
use syndicate::value::NestedValue;
use crate::counter;
use crate::language::language;
use syndicate_macros::during;
pub fn boot(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("dependencies_listener")), move |t| {
Ok(during!(t, ds, language(), <require-service $spec>, |t: &mut Activation| {
tracing::debug!(?spec, "tracking dependencies");
t.spawn_link(Some(rec![AnyValue::symbol("dependencies"), language().unparse(&spec)]),
enclose!((ds) |t| run(t, ds, spec)));
Ok(())
}))
});
}
fn run(t: &mut Activation, ds: Arc<Cap>, service_name: AnyValue) -> ActorResult {
let obstacle_count = t.named_field("obstacle_count", 1isize);
t.dataflow(enclose!((service_name, obstacle_count) move |t| {
tracing::trace!(?service_name, obstacle_count = ?t.get(&obstacle_count));
Ok(())
}))?;
t.dataflow({
let mut handle = None;
enclose!((ds, obstacle_count, service_name) move |t| {
let obstacle_count = *t.get(&obstacle_count);
if obstacle_count == 0 {
ds.update(t, &mut handle, language(), Some(&service::RunService {
service_name: service_name.clone(),
}));
} else {
ds.update::<_, service::RunService>(t, &mut handle, language(), None);
}
Ok(())
})
})?;
let depender = service_name.clone();
enclose!((ds, obstacle_count) during!(
t, ds, language(), <depends-on #(&depender) $dependee>,
enclose!((service_name, ds, obstacle_count) move |t: &mut Activation| {
if let Ok(dependee) = language().parse::<service::ServiceState>(&dependee) {
tracing::trace!(?service_name, ?dependee, "new dependency");
ds.assert(t, language(), &service::RequireService {
service_name: dependee.service_name,
});
} else {
tracing::warn!(?service_name, ?dependee, "cannot deduce dependee service name");
}
counter::adjust(t, &obstacle_count, 1);
let d = &dependee.clone();
during!(t, ds, language(), #d, enclose!(
(service_name, obstacle_count, dependee) move |t: &mut Activation| {
tracing::trace!(?service_name, ?dependee, "dependency satisfied");
counter::adjust(t, &obstacle_count, -1);
Ok(())
}));
Ok(())
})));
counter::sync_and_adjust(t, &ds.underlying, &obstacle_count, -1);
Ok(())
}

View File

@ -1,500 +0,0 @@
use noise_protocol::CipherState;
use noise_protocol::U8Array;
use noise_protocol::patterns::HandshakePattern;
use noise_rust_crypto::Blake2s;
use noise_rust_crypto::ChaCha20Poly1305;
use noise_rust_crypto::X25519;
use preserves_schema::Codec;
use syndicate::relay::Mutex;
use syndicate::relay::TunnelRelay;
use syndicate::trace::TurnCause;
use syndicate::value::NoEmbeddedDomainCodec;
use syndicate::value::packed::PackedWriter;
use std::convert::TryInto;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::value::NestedValue;
use syndicate::schemas::dataspace;
use syndicate::schemas::gatekeeper;
use syndicate::schemas::noise;
use syndicate::schemas::sturdy;
use crate::language::language;
use syndicate_macros::during;
use syndicate_macros::pattern;
fn sturdy_step_type() -> String {
language().unparse(&sturdy::SturdyStepType).value().to_symbol().unwrap().clone()
}
fn noise_step_type() -> String {
language().unparse(&noise::NoiseStepType).value().to_symbol().unwrap().clone()
}
pub fn handle_binds(t: &mut Activation, ds: &Arc<Cap>) -> ActorResult {
during!(t, ds, language(), <bind <ref $desc> $target $observer>, |t: &mut Activation| {
t.spawn_link(None, move |t| {
target.value().to_embedded()?;
let observer = language().parse::<gatekeeper::BindObserver>(&observer)?;
let desc = language().parse::<sturdy::SturdyDescriptionDetail>(&desc)?;
let sr = sturdy::SturdyRef::mint(desc.oid, &desc.key);
if let gatekeeper::BindObserver::Present(o) = observer {
o.assert(t, language(), &gatekeeper::Bound::Bound {
path_step: Box::new(gatekeeper::PathStep {
step_type: sturdy_step_type(),
detail: language().unparse(&sr.parameters),
}),
});
}
Ok(())
});
Ok(())
});
during!(t, ds, language(), <bind <noise $desc> $target $observer>, |t: &mut Activation| {
t.spawn_link(None, move |t| {
target.value().to_embedded()?;
let observer = language().parse::<gatekeeper::BindObserver>(&observer)?;
let spec = language().parse::<noise::NoiseDescriptionDetail<AnyValue>>(&desc)?.0;
match validate_noise_spec(spec) {
Ok(spec) => if let gatekeeper::BindObserver::Present(o) = observer {
o.assert(t, language(), &gatekeeper::Bound::Bound {
path_step: Box::new(gatekeeper::PathStep {
step_type: noise_step_type(),
detail: language().unparse(&noise::NoisePathStepDetail(noise::NoiseSpec {
key: spec.public_key,
service: noise::ServiceSelector(spec.service),
protocol: if spec.protocol == default_noise_protocol() {
noise::NoiseProtocol::Absent
} else {
noise::NoiseProtocol::Present {
protocol: spec.protocol,
}
},
pre_shared_keys: if spec.psks.is_empty() {
noise::NoisePreSharedKeys::Absent
} else {
noise::NoisePreSharedKeys::Present {
pre_shared_keys: spec.psks,
}
},
})),
}),
});
},
Err(e) => {
if let gatekeeper::BindObserver::Present(o) = observer {
o.assert(t, language(), &gatekeeper::Bound::Rejected(
Box::new(gatekeeper::Rejected {
detail: AnyValue::new(format!("{}", &e)),
})));
}
tracing::error!("Invalid noise bind description: {}", e);
}
}
Ok(())
});
Ok(())
});
Ok(())
}
pub fn facet_handle_resolve(
ds: &mut Arc<Cap>,
t: &mut Activation,
a: gatekeeper::Resolve,
) -> ActorResult {
let mut detail: &'static str = "unsupported";
if a.step.step_type == sturdy_step_type() {
detail = "invalid";
if let Ok(s) = language().parse::<sturdy::SturdyStepDetail>(&a.step.detail) {
t.facet(|t| {
let f = handle_direct_resolution(ds, t, a.clone())?;
await_bind_sturdyref(ds, t, sturdy::SturdyRef { parameters: s.0 }, a.observer, f)
})?;
return Ok(());
}
}
if a.step.step_type == noise_step_type() {
detail = "invalid";
if let Ok(s) = language().parse::<noise::NoiseStepDetail<AnyValue>>(&a.step.detail) {
t.facet(|t| {
let f = handle_direct_resolution(ds, t, a.clone())?;
await_bind_noise(ds, t, s.0.0, a.observer, f)
})?;
return Ok(());
}
}
a.observer.assert(t, language(), &gatekeeper::Rejected {
detail: AnyValue::symbol(detail),
});
Ok(())
}
fn handle_direct_resolution(
ds: &mut Arc<Cap>,
t: &mut Activation,
a: gatekeeper::Resolve,
) -> Result<FacetId, ActorError> {
let outer_facet = t.facet_id();
t.facet(move |t| {
let handler = syndicate::entity(a.observer)
.on_asserted(move |observer, t, a: AnyValue| {
t.stop_facet_and_continue(outer_facet, Some(
enclose!((observer, a) move |t: &mut Activation| {
observer.assert(t, language(), &a);
Ok(())
})))?;
Ok(None)
})
.create_cap(t);
ds.assert(t, language(), &gatekeeper::Resolve {
step: a.step.clone(),
observer: handler,
});
Ok(())
})
}
fn await_bind_sturdyref(
ds: &mut Arc<Cap>,
t: &mut Activation,
sturdyref: sturdy::SturdyRef,
observer: Arc<Cap>,
direct_resolution_facet: FacetId,
) -> ActorResult {
let queried_oid = sturdyref.parameters.oid.clone();
let handler = syndicate::entity(observer)
.on_asserted(move |observer, t, a: AnyValue| {
t.stop_facet(direct_resolution_facet);
let bindings = a.value().to_sequence()?;
let key = bindings[0].value().to_bytestring()?;
let unattenuated_target = bindings[1].value().to_embedded()?;
match sturdyref.validate_and_attenuate(key, unattenuated_target) {
Err(e) => {
tracing::warn!(sturdyref = ?language().unparse(&sturdyref),
"sturdyref failed validation: {}", e);
observer.assert(t, language(), &gatekeeper::Resolved::Rejected(
Box::new(gatekeeper::Rejected {
detail: AnyValue::symbol("sturdyref-failed-validation"),
})));
},
Ok(target) => {
tracing::trace!(sturdyref = ?language().unparse(&sturdyref),
?target,
"sturdyref resolved");
observer.assert(t, language(), &gatekeeper::Resolved::Accepted {
responder_session: target,
});
}
}
Ok(None)
})
.create_cap(t);
ds.assert(t, language(), &dataspace::Observe {
// TODO: codegen plugin to generate pattern constructors
pattern: pattern!{<bind <ref { oid: #(&queried_oid), key: $ }> $ _>},
observer: handler,
});
Ok(())
}
struct ValidatedNoiseSpec {
service: AnyValue,
protocol: String,
pattern: HandshakePattern,
psks: Vec<Vec<u8>>,
secret_key: Option<Vec<u8>>,
public_key: Vec<u8>,
}
fn default_noise_protocol() -> String {
language().unparse(&noise::DefaultProtocol).value().to_string().unwrap().clone()
}
fn validate_noise_spec(
spec: noise::NoiseServiceSpec<AnyValue>,
) -> Result<ValidatedNoiseSpec, ActorError> {
let protocol = match spec.base.protocol {
noise::NoiseProtocol::Present { protocol } => protocol,
noise::NoiseProtocol::Invalid { protocol } =>
Err(format!("Invalid noise protocol {:?}", protocol))?,
noise::NoiseProtocol::Absent => default_noise_protocol(),
};
const PREFIX: &'static str = "Noise_";
const SUFFIX: &'static str = "_25519_ChaChaPoly_BLAKE2s";
if !protocol.starts_with(PREFIX) || !protocol.ends_with(SUFFIX) {
Err(format!("Unsupported protocol {:?}", protocol))?;
}
let pattern_name = &protocol[PREFIX.len()..(protocol.len()-SUFFIX.len())];
let pattern = lookup_pattern(pattern_name).ok_or_else::<ActorError, _>(
|| format!("Unsupported handshake pattern {:?}", pattern_name).into())?;
let psks = match spec.base.pre_shared_keys {
noise::NoisePreSharedKeys::Present { pre_shared_keys } => pre_shared_keys,
noise::NoisePreSharedKeys::Invalid { pre_shared_keys } =>
Err(format!("Invalid pre-shared-keys {:?}", pre_shared_keys))?,
noise::NoisePreSharedKeys::Absent => vec![],
};
let secret_key = match spec.secret_key {
noise::SecretKeyField::Present { secret_key } => Some(secret_key),
noise::SecretKeyField::Invalid { secret_key } =>
Err(format!("Invalid secret key {:?}", secret_key))?,
noise::SecretKeyField::Absent => None,
};
Ok(ValidatedNoiseSpec {
service: spec.base.service.0,
protocol,
pattern,
psks,
secret_key,
public_key: spec.base.key,
})
}
fn await_bind_noise(
ds: &mut Arc<Cap>,
t: &mut Activation,
service_selector: AnyValue,
observer: Arc<Cap>,
direct_resolution_facet: FacetId,
) -> ActorResult {
let handler = syndicate::entity(())
.on_asserted_facet(move |_state, t, a: AnyValue| {
t.stop_facet(direct_resolution_facet);
let observer = Arc::clone(&observer);
t.spawn_link(None, move |t| {
let bindings = a.value().to_sequence()?;
let spec = validate_noise_spec(language().parse(&bindings[0])?)?;
let service = bindings[1].value().to_embedded()?;
run_noise_responder(t, spec, observer, Arc::clone(service))
});
Ok(())
})
.create_cap(t);
ds.assert(t, language(), &dataspace::Observe {
// TODO: codegen plugin to generate pattern constructors
pattern: pattern!{
<bind <noise $spec:NoiseServiceSpec{ { service: #(&service_selector) } }> $service _>
},
observer: handler,
});
Ok(())
}
type HandshakeState = noise_protocol::HandshakeState<X25519, ChaCha20Poly1305, Blake2s>;
enum ResponderState {
Invalid, // used during state transitions
Introduction {
service: Arc<Cap>,
hs: HandshakeState,
},
Handshake {
initiator_session: Arc<Cap>,
service: Arc<Cap>,
hs: HandshakeState,
},
Transport {
relay_input: Arc<Mutex<Option<TunnelRelay>>>,
c_recv: CipherState<ChaCha20Poly1305>,
},
}
impl Entity<noise::SessionItem> for ResponderState {
fn assert(&mut self, _t: &mut Activation, item: noise::SessionItem, _handle: Handle) -> ActorResult {
let initiator_session = match item {
noise::SessionItem::Initiator(i_box) => i_box.initiator_session,
noise::SessionItem::Packet(_) => Err("Unexpected Packet assertion")?,
};
match std::mem::replace(self, ResponderState::Invalid) {
ResponderState::Introduction { service, hs } => {
*self = ResponderState::Handshake { initiator_session, service, hs };
Ok(())
}
_ =>
Err("Received second Initiator")?,
}
}
fn message(&mut self, t: &mut Activation, item: noise::SessionItem) -> ActorResult {
let p = match item {
noise::SessionItem::Initiator(_) => Err("Unexpected Initiator message")?,
noise::SessionItem::Packet(p_box) => *p_box,
};
match self {
ResponderState::Invalid | ResponderState::Introduction { .. } =>
Err("Received Packet in invalid ResponderState")?,
ResponderState::Handshake { initiator_session, service, hs } => match p {
noise::Packet::Complete(bs) => {
if bs.len() < hs.get_next_message_overhead() {
Err("Invalid handshake message for pattern")?;
}
if bs.len() > hs.get_next_message_overhead() {
Err("Cannot accept payload during handshake")?;
}
hs.read_message(&bs, &mut [])?;
let mut reply = vec![0u8; hs.get_next_message_overhead()];
hs.write_message(&[], &mut reply[..])?;
initiator_session.message(t, language(), &noise::Packet::Complete(reply.into()));
if hs.completed() {
let (c_recv, mut c_send) = hs.get_ciphers();
let (_, relay_input, mut relay_output) =
TunnelRelay::_run(t, Some(Arc::clone(service)), None, false);
let trace_collector = t.trace_collector();
let initiator_session = Arc::clone(initiator_session);
let relay_output_name = Some(AnyValue::symbol("relay_output"));
let transport_facet = t.facet_ref();
t.linked_task(relay_output_name.clone(), async move {
let account = Account::new(relay_output_name, trace_collector);
let cause = TurnCause::external("relay_output");
loop {
match relay_output.recv().await {
None => return Ok(LinkedTaskTermination::KeepFacet),
Some(loaned_item) => {
const MAXSIZE: usize = 65535 - 16; /* Noise tag length is 16 */
let p = if loaned_item.item.len() > MAXSIZE {
noise::Packet::Fragmented(
loaned_item.item
.chunks(MAXSIZE)
.map(|c| c_send.encrypt_vec(c))
.collect())
} else {
noise::Packet::Complete(c_send.encrypt_vec(&loaned_item.item))
};
if !transport_facet.activate(&account, Some(cause.clone()), |t| {
initiator_session.message(t, language(), &p);
Ok(())
}) {
break;
}
}
}
}
Ok(LinkedTaskTermination::Normal)
});
*self = ResponderState::Transport { relay_input, c_recv };
}
}
_ => Err("Fragmented handshake is not allowed")?,
},
ResponderState::Transport { relay_input, c_recv } => {
let bs = match p {
noise::Packet::Complete(bs) =>
c_recv.decrypt_vec(&bs[..]).map_err(|_| "Cannot decrypt packet")?,
noise::Packet::Fragmented(pieces) => {
let mut result = Vec::with_capacity(1024);
for piece in pieces {
result.extend(c_recv.decrypt_vec(&piece[..])
.map_err(|_| "Cannot decrypt packet fragment")?);
}
result
}
};
let mut g = relay_input.lock();
let tr = g.as_mut().expect("initialized");
tr.handle_inbound_datagram(t, &bs[..])?;
}
}
Ok(())
}
}
fn lookup_pattern(name: &str) -> Option<HandshakePattern> {
use noise_protocol::patterns::*;
Some(match name {
"N" => noise_n(),
"K" => noise_k(),
"X" => noise_x(),
"NN" => noise_nn(),
"NK" => noise_nk(),
"NX" => noise_nx(),
"XN" => noise_xn(),
"XK" => noise_xk(),
"XX" => noise_xx(),
"KN" => noise_kn(),
"KK" => noise_kk(),
"KX" => noise_kx(),
"IN" => noise_in(),
"IK" => noise_ik(),
"IX" => noise_ix(),
"Npsk0" => noise_n_psk0(),
"Kpsk0" => noise_k_psk0(),
"Xpsk1" => noise_x_psk1(),
"NNpsk0" => noise_nn_psk0(),
"NNpsk2" => noise_nn_psk2(),
"NKpsk0" => noise_nk_psk0(),
"NKpsk2" => noise_nk_psk2(),
"NXpsk2" => noise_nx_psk2(),
"XNpsk3" => noise_xn_psk3(),
"XKpsk3" => noise_xk_psk3(),
"XXpsk3" => noise_xx_psk3(),
"KNpsk0" => noise_kn_psk0(),
"KNpsk2" => noise_kn_psk2(),
"KKpsk0" => noise_kk_psk0(),
"KKpsk2" => noise_kk_psk2(),
"KXpsk2" => noise_kx_psk2(),
"INpsk1" => noise_in_psk1(),
"INpsk2" => noise_in_psk2(),
"IKpsk1" => noise_ik_psk1(),
"IKpsk2" => noise_ik_psk2(),
"IXpsk2" => noise_ix_psk2(),
"NNpsk0+psk2" => noise_nn_psk0_psk2(),
"NXpsk0+psk1+psk2" => noise_nx_psk0_psk1_psk2(),
"XNpsk1+psk3" => noise_xn_psk1_psk3(),
"XKpsk0+psk3" => noise_xk_psk0_psk3(),
"KNpsk1+psk2" => noise_kn_psk1_psk2(),
"KKpsk0+psk2" => noise_kk_psk0_psk2(),
"INpsk1+psk2" => noise_in_psk1_psk2(),
"IKpsk0+psk2" => noise_ik_psk0_psk2(),
"IXpsk0+psk2" => noise_ix_psk0_psk2(),
"XXpsk0+psk1" => noise_xx_psk0_psk1(),
"XXpsk0+psk2" => noise_xx_psk0_psk2(),
"XXpsk0+psk3" => noise_xx_psk0_psk3(),
"XXpsk0+psk1+psk2+psk3" => noise_xx_psk0_psk1_psk2_psk3(),
_ => return None,
})
}
fn run_noise_responder(
t: &mut Activation,
spec: ValidatedNoiseSpec,
observer: Arc<Cap>,
service: Arc<Cap>,
) -> ActorResult {
let hs = {
let mut builder = noise_protocol::HandshakeStateBuilder::new();
builder.set_pattern(spec.pattern);
builder.set_is_initiator(false);
let prologue = PackedWriter::encode(&mut NoEmbeddedDomainCodec, &spec.service)?;
builder.set_prologue(&prologue);
match spec.secret_key {
None => (),
Some(sk) => {
let sk: [u8; 32] = sk.try_into().map_err(|_| "Bad secret key length")?;
builder.set_s(U8Array::from_slice(&sk));
},
}
let mut hs = builder.build_handshake_state();
for psk in spec.psks.into_iter() {
hs.push_psk(&psk);
}
hs
};
let responder_session =
Cap::guard(crate::Language::arc(), t.create(ResponderState::Introduction{ service, hs }));
observer.assert(t, language(), &gatekeeper::Resolved::Accepted { responder_session });
Ok(())
}

View File

@ -1,195 +0,0 @@
use std::convert::TryInto;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use hyper::{Request, Response, Body, StatusCode};
use hyper::body;
use hyper::header::HeaderName;
use hyper::header::HeaderValue;
use syndicate::actor::*;
use syndicate::error::Error;
use syndicate::trace;
use syndicate::value::Map;
use syndicate::value::NestedValue;
use syndicate::schemas::http;
use tokio::sync::oneshot;
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
use tokio_stream::wrappers::UnboundedReceiverStream;
use crate::language;
static NEXT_SEQ: AtomicU64 = AtomicU64::new(0);
pub fn empty_response(code: StatusCode) -> Response<Body> {
let mut r = Response::new(Body::empty());
*r.status_mut() = code;
r
}
type ChunkItem = Result<body::Bytes, Box<dyn std::error::Error + Send + Sync>>;
struct ResponseCollector {
tx_res: Option<(oneshot::Sender<Response<Body>>, Response<Body>)>,
body_tx: Option<UnboundedSender<ChunkItem>>,
}
impl ResponseCollector {
fn new(tx: oneshot::Sender<Response<Body>>) -> Self {
let (body_tx, body_rx) = unbounded_channel();
let body_stream: Box<dyn futures::Stream<Item = ChunkItem> + Send> =
Box::new(UnboundedReceiverStream::new(body_rx));
let mut res = Response::new(body_stream.into());
*res.status_mut() = StatusCode::OK;
ResponseCollector {
tx_res: Some((tx, res)),
body_tx: Some(body_tx),
}
}
fn with_res<F: FnOnce(&mut Response<Body>) -> ActorResult>(&mut self, f: F) -> ActorResult {
if let Some((_, res)) = &mut self.tx_res {
f(res)?;
}
Ok(())
}
fn deliver_res(&mut self) {
if let Some((tx, res)) = std::mem::replace(&mut self.tx_res, None) {
let _ = tx.send(res);
}
}
fn add_chunk(&mut self, value: http::Chunk) -> ActorResult {
self.deliver_res();
if let Some(body_tx) = self.body_tx.as_mut() {
body_tx.send(Ok(match value {
http::Chunk::Bytes(bs) => bs.into(),
http::Chunk::String(s) => s.as_bytes().to_vec().into(),
}))?;
}
Ok(())
}
fn finish(&mut self, t: &mut Activation) -> ActorResult {
self.deliver_res();
self.body_tx = None;
t.stop();
Ok(())
}
}
impl Entity<http::HttpResponse> for ResponseCollector {
fn message(&mut self, t: &mut Activation, message: http::HttpResponse) -> ActorResult {
match message {
http::HttpResponse::Status { code, .. } => self.with_res(|r| {
*r.status_mut() = StatusCode::from_u16(
(&code).try_into().map_err(|_| "bad status code")?)?;
Ok(())
}),
http::HttpResponse::Header { name, value } => self.with_res(|r| {
r.headers_mut().insert(HeaderName::from_bytes(name.as_bytes())?,
HeaderValue::from_str(value.as_str())?);
Ok(())
}),
http::HttpResponse::Chunk { chunk } => {
self.add_chunk(*chunk)
}
http::HttpResponse::Done { chunk } => {
self.add_chunk(*chunk)?;
self.finish(t)
}
}
}
}
pub async fn serve(
trace_collector: Option<trace::TraceCollector>,
facet: FacetRef,
httpd: Arc<Cap>,
mut req: Request<Body>,
port: u16,
) -> Result<Response<Body>, Error> {
let host = match req.headers().get("host").and_then(|v| v.to_str().ok()) {
None => http::RequestHost::Absent,
Some(h) => http::RequestHost::Present(match h.rsplit_once(':') {
None => h.to_string(),
Some((h, _port)) => h.to_string(),
})
};
let uri = req.uri();
let mut path: Vec<String> = uri.path().split('/').map(|s| s.to_string()).collect();
path.remove(0);
let mut query: Map<String, Vec<http::QueryValue>> = Map::new();
for piece in uri.query().unwrap_or("").split('&').into_iter() {
match piece.split_once('=') {
Some((k, v)) => {
let key = k.to_string();
let value = v.to_string();
match query.get_mut(&key) {
None => { query.insert(key, vec![http::QueryValue::String(value)]); },
Some(vs) => { vs.push(http::QueryValue::String(value)); },
}
}
None => {
if piece.len() > 0 {
let key = piece.to_string();
if !query.contains_key(&key) {
query.insert(key, vec![]);
}
}
}
}
}
let mut headers: Map<String, String> = Map::new();
for h in req.headers().into_iter() {
match h.1.to_str() {
Ok(v) => { headers.insert(h.0.as_str().to_string().to_lowercase(), v.to_string()); },
Err(_) => return Ok(empty_response(StatusCode::BAD_REQUEST)),
}
}
let body = match body::to_bytes(req.body_mut()).await {
Ok(b) => http::RequestBody::Present(b.to_vec()),
Err(_) => return Ok(empty_response(StatusCode::BAD_REQUEST)),
};
let account = Account::new(Some(AnyValue::symbol("http")), trace_collector);
let (tx, rx) = oneshot::channel();
facet.activate(&account, Some(trace::TurnCause::external("http")), |t| {
t.facet(move |t| {
let sreq = http::HttpRequest {
sequence_number: NEXT_SEQ.fetch_add(1, Ordering::Relaxed).into(),
host,
port: port.into(),
method: req.method().to_string().to_lowercase(),
path,
headers: http::Headers(headers),
query,
body,
};
tracing::debug!(?sreq);
let srep = Cap::guard(&language().syndicate, t.create(ResponseCollector::new(tx)));
httpd.assert(t, language(), &http::HttpContext { req: sreq, res: srep });
Ok(())
})?;
Ok(())
});
let response_result = rx.await;
match response_result {
Ok(response) => Ok(response),
Err(_ /* sender dropped */) => Ok(empty_response(StatusCode::INTERNAL_SERVER_ERROR)),
}
}

View File

@ -1,6 +0,0 @@
use syndicate::actor;
preserves_schema::define_language!(language(): Language<actor::AnyValue> {
syndicate: syndicate::schemas::Language,
server: crate::schemas::Language,
});

View File

@ -1,62 +0,0 @@
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::schemas::service::*;
use syndicate::preserves_schema::support::Unparse;
use crate::language::Language;
use crate::language::language;
use syndicate_macros::on_message;
pub fn updater<'a, N: Clone + Unparse<&'a Language<AnyValue>, AnyValue>>(
ds: Arc<Cap>,
name: N,
) -> impl FnMut(&mut Activation, State) -> ActorResult {
let mut handle = None;
move |t, state| {
ds.update(t, &mut handle, language(), Some(&lifecycle(&name, state)));
Ok(())
}
}
pub fn lifecycle<'a, N: Unparse<&'a Language<AnyValue>, AnyValue>>(
service_name: &N,
state: State,
) -> ServiceState {
ServiceState {
service_name: service_name.unparse(language()),
state,
}
}
pub fn started<'a, N: Unparse<&'a Language<AnyValue>, AnyValue>>(service_name: &N) -> ServiceState {
lifecycle(service_name, State::Started)
}
pub fn ready<'a, N: Unparse<&'a Language<AnyValue>, AnyValue>>(service_name: &N) -> ServiceState {
lifecycle(service_name, State::Ready)
}
pub fn on_service_restart<'a,
N: Unparse<&'a Language<AnyValue>, AnyValue>,
F: 'static + Send + FnMut(&mut Activation) -> ActorResult>(
t: &mut Activation,
ds: &Arc<Cap>,
service_name: &N,
mut f: F,
) {
on_message!(t, ds, language(), <restart-service #(&service_name.unparse(language()))>, f);
}
pub fn terminate_on_service_restart<'a, N: Unparse<&'a Language<AnyValue>, AnyValue>>(
t: &mut Activation,
ds: &Arc<Cap>,
service_name: &N,
) {
on_service_restart(t, ds, service_name, |t| {
tracing::info!("Terminating to restart");
t.stop_root();
Ok(())
});
}

View File

@ -1,243 +0,0 @@
use preserves_schema::Codec;
use std::convert::TryInto;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use structopt::StructOpt;
use syndicate::actor::*;
use syndicate::dataspace::*;
use syndicate::enclose;
use syndicate::relay;
use syndicate::schemas::service;
use syndicate::schemas::transport_address;
use syndicate::trace;
use syndicate::value::Map;
use syndicate::value::NestedValue;
mod counter;
mod dependencies;
mod gatekeeper;
mod http;
mod language;
mod lifecycle;
mod protocol;
mod script;
mod services;
#[cfg(feature = "jemalloc")]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
mod schemas {
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
}
use language::Language;
use language::language;
use schemas::internal_services;
#[derive(Clone, StructOpt)]
struct ServerConfig {
#[structopt(short = "p", long = "port")]
ports: Vec<u16>,
#[structopt(short = "s", long = "socket")]
sockets: Vec<PathBuf>,
#[structopt(long)]
inferior: bool,
#[structopt(long)]
debt_reporter: bool,
#[structopt(short = "c", long)]
config: Vec<PathBuf>,
#[structopt(long)]
no_banner: bool,
#[structopt(short = "t", long)]
trace_file: Option<PathBuf>,
/// Enable `$control` entity.
#[structopt(long)]
control: bool,
}
#[tokio::main]
async fn main() -> ActorResult {
let config = Arc::new(ServerConfig::from_args());
syndicate::convenient_logging()?;
if !config.no_banner && !config.inferior {
const BRIGHT_GREEN: &str = "\x1b[92m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const NORMAL: &str = "\x1b[0m";
const BRIGHT_YELLOW: &str = "\x1b[93m";
eprintln!(r"{} ______ {}", GREEN, NORMAL);
eprintln!(r"{} / {}\_{}\{} ", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} / {},{}__/{} \ {} ____ __", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} /{}\__/ \{},{} \{} _______ ______ ____/ /_/________ / /____", GREEN, BRIGHT_GREEN, RED, GREEN, NORMAL);
eprintln!(r"{} \{}/ \__/ {}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} \ {}'{} \__{}/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} \____{}/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r" /____/");
eprintln!(r"");
eprintln!(r" {}version {} [syndicate {}]{}", BRIGHT_YELLOW, env!("CARGO_PKG_VERSION"), syndicate::syndicate_package_version(), NORMAL);
eprintln!(r"");
eprintln!(r" documentation & reference material: https://syndicate-lang.org/");
eprintln!(r" source code & bugs: https://git.syndicate-lang.org/syndicate-lang/syndicate-rs");
eprintln!(r"");
}
tracing::trace!("startup");
let trace_collector = config.trace_file.clone().map(
|p| Ok::<trace::TraceCollector, io::Error>(trace::TraceCollector::ascii(
io::BufWriter::new(std::fs::File::create(p)?))))
.transpose()?;
Actor::top(trace_collector, move |t| {
let server_config_ds = Cap::new(&t.create(Dataspace::new(Some(AnyValue::symbol("config")))));
let log_ds = Cap::new(&t.create(Dataspace::new(Some(AnyValue::symbol("log")))));
if config.inferior {
tracing::info!("inferior server instance");
t.spawn(Some(AnyValue::symbol("parent")), enclose!((server_config_ds) move |t| {
protocol::run_io_relay(t,
relay::Input::Bytes(Box::pin(tokio::io::stdin())),
relay::Output::Bytes(Box::pin(tokio::io::stdout())),
server_config_ds)
}));
}
let gatekeeper = Cap::guard(Language::arc(), t.create(
syndicate::entity(Arc::clone(&server_config_ds))
.on_asserted_facet(gatekeeper::facet_handle_resolve)));
gatekeeper::handle_binds(t, &server_config_ds)?;
let mut env = Map::new();
env.insert("config".to_owned(), AnyValue::domain(Arc::clone(&server_config_ds)));
env.insert("log".to_owned(), AnyValue::domain(Arc::clone(&log_ds)));
env.insert("gatekeeper".to_owned(), AnyValue::domain(Arc::clone(&gatekeeper)));
if config.control {
env.insert("control".to_owned(), AnyValue::domain(Cap::guard(Language::arc(), t.create(
syndicate::entity(())
.on_message(|_, _t, m: crate::schemas::control::ExitServer| {
tracing::info!("$control received exit request with code {}", m.code);
std::process::exit((&m.code).try_into().unwrap_or_else(|_| {
tracing::warn!(
"exit code {} out-of-range of 32-bit signed integer, using 1 instead",
m.code);
1
}))
})))));
}
dependencies::boot(t, Arc::clone(&server_config_ds));
services::config_watcher::on_demand(t, Arc::clone(&server_config_ds));
services::daemon::on_demand(t, Arc::clone(&server_config_ds), Arc::clone(&log_ds));
services::debt_reporter::on_demand(t, Arc::clone(&server_config_ds));
services::gatekeeper::on_demand(t, Arc::clone(&server_config_ds));
services::http_router::on_demand(t, Arc::clone(&server_config_ds));
services::tcp_relay_listener::on_demand(t, Arc::clone(&server_config_ds));
services::unix_relay_listener::on_demand(t, Arc::clone(&server_config_ds));
if config.debt_reporter {
server_config_ds.assert(t, language(), &service::RunService {
service_name: language().unparse(&internal_services::DebtReporter {
interval_seconds: (1.0).into(),
}),
});
}
for port in config.ports.clone() {
server_config_ds.assert(t, language(), &service::RunService {
service_name: language().unparse(&internal_services::TcpWithoutHttp {
addr: transport_address::Tcp {
host: "0.0.0.0".to_owned(),
port: (port as i32).into(),
},
gatekeeper: gatekeeper.clone(),
}),
});
}
for path in config.sockets.clone() {
server_config_ds.assert(t, language(), &service::RunService {
service_name: language().unparse(&internal_services::UnixRelayListener {
addr: transport_address::Unix {
path: path.to_str().expect("representable UnixListener path").to_owned(),
},
gatekeeper: gatekeeper.clone(),
}),
});
}
for path in config.config.clone() {
server_config_ds.assert(t, language(), &service::RunService {
service_name: language().unparse(&internal_services::ConfigWatcher {
path: path.to_str().expect("representable ConfigWatcher path").to_owned(),
env: internal_services::ConfigEnv(env.clone()),
}),
});
}
t.spawn(Some(AnyValue::symbol("logger")), enclose!((log_ds) move |t| {
let n_unknown: AnyValue = AnyValue::symbol("-");
let n_pid: AnyValue = AnyValue::symbol("pid");
let n_line: AnyValue = AnyValue::symbol("line");
let n_service: AnyValue = AnyValue::symbol("service");
let n_stream: AnyValue = AnyValue::symbol("stream");
let e = syndicate::during::entity(())
.on_message(move |(), _t, captures: AnyValue| {
if let Some(captures) = captures.value_owned().into_sequence() {
let mut captures = captures.into_iter();
let timestamp = captures.next()
.and_then(|t| t.value_owned().into_string())
.unwrap_or_else(|| "-".to_owned());
if let Some(mut d) = captures.next()
.and_then(|d| d.value_owned().into_dictionary())
{
let pid = d.remove(&n_pid).unwrap_or_else(|| n_unknown.clone());
let service = d.remove(&n_service).unwrap_or_else(|| n_unknown.clone());
let line = d.remove(&n_line).unwrap_or_else(|| n_unknown.clone());
let stream = d.remove(&n_stream).unwrap_or_else(|| n_unknown.clone());
let message = format!("{} {:?}[{:?}] {:?}: {:?}",
timestamp,
service,
pid,
stream,
line);
if d.is_empty() {
tracing::info!(target: "", "{}", message);
} else {
tracing::info!(target: "", "{} {:?}", message, d);
}
}
}
Ok(())
})
.create_cap(t);
log_ds.assert(t, language(), &syndicate::schemas::dataspace::Observe {
pattern: syndicate_macros::pattern!(<log $ $>),
observer: e,
});
Ok(())
}));
Ok(())
}).await??;
wait_for_all_actors_to_stop(std::time::Duration::from_secs(2)).await;
Ok(())
}

View File

@ -1,155 +0,0 @@
use futures::SinkExt;
use futures::StreamExt;
use hyper::header::HeaderValue;
use hyper::service::service_fn;
use std::future::ready;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::error::Error;
use syndicate::error::error;
use syndicate::relay;
use syndicate::trace;
use syndicate::value::NestedValue;
use tokio::net::TcpStream;
use hyper_tungstenite::tungstenite::Message;
struct ExitListener;
impl Entity<()> for ExitListener {
fn exit_hook(&mut self, _t: &mut Activation, exit_status: &Arc<ExitStatus>) {
tracing::info!(?exit_status, "disconnect");
}
}
pub fn run_io_relay(
t: &mut Activation,
i: relay::Input,
o: relay::Output,
initial_ref: Arc<Cap>,
) -> ActorResult {
let exit_listener = t.create(ExitListener);
t.add_exit_hook(&exit_listener);
relay::TunnelRelay::run(t, i, o, Some(initial_ref), None, false);
Ok(())
}
pub fn run_connection(
trace_collector: Option<trace::TraceCollector>,
facet: FacetRef,
i: relay::Input,
o: relay::Output,
initial_ref: Arc<Cap>,
) {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("start-session"));
let account = Account::new(Some(AnyValue::symbol("start-session")), trace_collector);
facet.activate(&account, cause, |t| run_io_relay(t, i, o, initial_ref));
}
pub async fn detect_protocol(
trace_collector: Option<trace::TraceCollector>,
facet: FacetRef,
stream: TcpStream,
gateway: Arc<Cap>,
httpd: Option<Arc<Cap>>,
addr: std::net::SocketAddr,
server_port: u16,
) -> ActorResult {
let mut buf = [0; 1]; // peek at the first byte to see what kind of connection to expect
match stream.peek(&mut buf).await? {
1 => match buf[0] {
v if v == b'[' /* Turn */ || v == b'<' /* Error and Extension */ || v >= 128 => {
tracing::info!(protocol = %(if v >= 128 { "application/syndicate" } else { "text/syndicate" }), peer = ?addr);
let (i, o) = stream.into_split();
let i = relay::Input::Bytes(Box::pin(i));
let o = relay::Output::Bytes(Box::pin(o /* BufWriter::new(o) */));
run_connection(trace_collector, facet, i, o, gateway);
Ok(())
}
_ => {
let upgraded = Arc::new(AtomicBool::new(false));
let keepalive = facet.actor.keep_alive();
let mut http = hyper::server::conn::Http::new();
http.http1_keep_alive(true);
http.http1_only(true);
let service = service_fn(|mut req| enclose!(
(upgraded, keepalive, trace_collector, facet, gateway, httpd) async move {
if hyper_tungstenite::is_upgrade_request(&req) {
tracing::info!(protocol = %"websocket",
method=%req.method(),
uri=?req.uri(),
host=?req.headers().get("host").unwrap_or(&HeaderValue::from_static("")));
let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)
.map_err(|e| message_error(e))?;
upgraded.store(true, Ordering::SeqCst);
tokio::spawn(enclose!(() async move {
let (o, i) = websocket.await?.split();
let i = i.filter_map(|r| ready(extract_binary_packets(r).transpose()));
let o = o.sink_map_err(message_error).with(|bs| ready(Ok(Message::Binary(bs))));
let i = relay::Input::Packets(Box::pin(i));
let o = relay::Output::Packets(Box::pin(o));
run_connection(trace_collector, facet, i, o, gateway);
drop(keepalive);
Ok(()) as ActorResult
}));
Ok(response)
} else {
match httpd {
None => Ok(crate::http::empty_response(
hyper::StatusCode::SERVICE_UNAVAILABLE)),
Some(httpd) => {
tracing::info!(protocol = %"http",
method=%req.method(),
uri=?req.uri(),
host=?req.headers().get("host").unwrap_or(&HeaderValue::from_static("")));
crate::http::serve(trace_collector, facet, httpd, req, server_port).await
}
}
}
}));
http.serve_connection(stream, service).with_upgrades().await?;
if upgraded.load(Ordering::SeqCst) {
tracing::debug!("serve_connection completed after upgrade to websocket");
} else {
tracing::debug!("serve_connection completed after regular HTTP session");
facet.activate(&Account::new(None, None), None, |t| Ok(t.stop()));
}
Ok(())
},
}
0 => Err(error("closed before starting", AnyValue::new(false)))?,
_ => unreachable!()
}
}
fn message_error<E: std::fmt::Display>(e: E) -> Error {
error(&e.to_string(), AnyValue::new(false))
}
fn extract_binary_packets(
r: Result<Message, hyper_tungstenite::tungstenite::Error>,
) -> Result<Option<Vec<u8>>, Error> {
match r {
Ok(m) => match m {
Message::Text(_) =>
Err("Text websocket frames are not accepted")?,
Message::Binary(bs) =>
Ok(Some(bs)),
Message::Ping(_) =>
Ok(None), // pings are handled by tungstenite before we see them
Message::Pong(_) =>
Ok(None), // unsolicited pongs are to be ignored
Message::Close(_) =>
Ok(None), // we're about to see the end of the stream, so ignore this
Message::Frame(_) =>
Err("Raw frames are not accepted")?,
},
Err(e) => Err(message_error(e)),
}
}

View File

@ -1,941 +0,0 @@
use preserves_schema::Codec;
use std::io;
use std::borrow::Cow;
use std::path::PathBuf;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::dataspace::Dataspace;
use syndicate::during;
use syndicate::enclose;
use syndicate::pattern::{lift_literal, drop_literal, pattern_seq_from_dictionary};
use syndicate::schemas::dataspace;
use syndicate::schemas::dataspace_patterns as P;
use syndicate::schemas::sturdy;
use syndicate::value::Map;
use syndicate::value::NestedValue;
use syndicate::value::NoEmbeddedDomainCodec;
use syndicate::value::Record;
use syndicate::value::Set;
use syndicate::value::TextWriter;
use syndicate::value::Value;
use crate::language::language;
#[derive(Debug)]
struct PatternInstantiator<'env> {
env: &'env Env,
binding_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct Env {
pub path: PathBuf,
bindings: Map<String, AnyValue>,
}
#[derive(Debug)]
pub struct Parser<'t> {
tokens: &'t [AnyValue],
errors: Vec<String>,
}
#[derive(Debug)]
pub enum Parsed<T> {
Value(T),
Skip,
Eof,
}
#[derive(Debug, Clone)]
pub enum Instruction {
Assert {
target: String,
template: AnyValue,
},
Message {
target: String,
template: AnyValue,
},
During {
target: String,
pattern_template: AnyValue,
body: Box<Instruction>,
},
OnMessage {
target: String,
pattern_template: AnyValue,
body: Box<Instruction>,
},
OnStop {
body: Box<Instruction>,
},
Sequence {
instructions: Vec<Instruction>,
},
Let {
pattern_template: AnyValue,
expr: Expr,
},
Cond {
value_var: String,
pattern_template: AnyValue,
on_match: Box<Instruction>,
on_nomatch: Box<Instruction>,
},
}
#[derive(Debug, Clone)]
pub enum Expr {
Template {
template: AnyValue,
},
Dataspace,
Timestamp,
Facet,
Stringify {
expr: Box<Expr>,
},
}
#[derive(Debug, Clone)]
enum RewriteTemplate {
Accept {
pattern_template: AnyValue,
},
Rewrite {
pattern_template: AnyValue,
template_template: AnyValue,
},
}
#[derive(Debug, Clone)]
enum CaveatTemplate {
Alts {
alternatives: Vec<RewriteTemplate>,
},
Reject {
pattern_template: AnyValue,
},
}
#[derive(Debug)]
enum Symbolic {
Reference(String),
Binder(String),
Discard,
Literal(String),
Bare(String),
}
struct FacetHandle;
impl<T> Default for Parsed<T> {
fn default() -> Self {
Parsed::Skip
}
}
impl FacetHandle {
fn new() -> Self {
FacetHandle
}
}
impl Entity<AnyValue> for FacetHandle {
fn message(&mut self, t: &mut Activation, body: AnyValue) -> ActorResult {
if let Some("stop") = body.value().as_symbol().map(|s| s.as_str()) {
t.stop();
return Ok(())
}
tracing::warn!(?body, "Unrecognised message sent to FacetHandle");
return Ok(())
}
}
fn analyze(s: &str) -> Symbolic {
if s == "_" {
Symbolic::Discard
} else if s.starts_with("?") {
Symbolic::Binder(s[1..].to_owned())
} else if s.starts_with("$") {
Symbolic::Reference(s[1..].to_owned())
} else if s.starts_with("=") {
Symbolic::Literal(s[1..].to_owned())
} else {
Symbolic::Bare(s.to_owned())
}
}
fn bad_instruction(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
}
fn discard() -> P::Pattern {
P::Pattern::Discard
}
fn dlit(value: AnyValue) -> P::Pattern {
lift_literal(&value)
}
fn tlit(value: AnyValue) -> sturdy::Template {
sturdy::Template::Lit(Box::new(sturdy::Lit { value }))
}
fn parse_rewrite(raw_base_name: &AnyValue, e: &AnyValue) -> io::Result<RewriteTemplate> {
if let Some(fields) = e.value().as_simple_record("accept", Some(1)) {
return Ok(RewriteTemplate::Accept {
pattern_template: fields[0].clone(),
});
}
if let Some(fields) = e.value().as_simple_record("rewrite", Some(2)) {
return Ok(RewriteTemplate::Rewrite {
pattern_template: fields[0].clone(),
template_template: fields[1].clone(),
});
}
Err(bad_instruction(&format!("Bad rewrite in attenuation of {:?}: {:?}", raw_base_name, e)))
}
fn parse_caveat(raw_base_name: &AnyValue, e: &AnyValue) -> io::Result<CaveatTemplate> {
if let Some(fields) = e.value().as_simple_record("or", Some(1)) {
let raw_rewrites = match fields[0].value().as_sequence() {
None => Err(bad_instruction(&format!(
"Alternatives in <or> in attenuation of {:?} must have sequence of rewrites; got {:?}",
raw_base_name,
fields[0])))?,
Some(vs) => vs,
};
let alternatives =
raw_rewrites.iter().map(|r| parse_rewrite(raw_base_name, r)).collect::<Result<Vec<_>, _>>()?;
return Ok(CaveatTemplate::Alts{ alternatives });
}
if let Some(fields) = e.value().as_simple_record("reject", Some(1)) {
return Ok(CaveatTemplate::Reject{ pattern_template: fields[0].clone() });
}
if let Ok(r) = parse_rewrite(raw_base_name, e) {
return Ok(CaveatTemplate::Alts { alternatives: vec![r] });
}
Err(bad_instruction(&format!("Bad caveat in attenuation of {:?}: {:?}", raw_base_name, e)))
}
fn parse_attenuation(r: &Record<AnyValue>) -> io::Result<Option<(String, Vec<CaveatTemplate>)>> {
if r.label() != &AnyValue::symbol("*") {
return Ok(None);
}
if r.fields().len() != 2 {
Err(bad_instruction(&format!(
"Attenuation requires a reference and a sequence of caveats; got {:?}",
r)))?;
}
let raw_base_name = &r.fields()[0];
let base_name = match raw_base_name.value().as_symbol().map(|s| analyze(&s)) {
Some(Symbolic::Reference(s)) => s,
_ => Err(bad_instruction(&format!(
"Attenuation must have variable reference as first argument; got {:?}",
raw_base_name)))?,
};
let raw_caveats = match r.fields()[1].value().as_sequence() {
None => Err(bad_instruction(&format!(
"Attenuation of {:?} must have sequence of caveats; got {:?}",
raw_base_name,
r.fields()[1])))?,
Some(vs) => vs,
};
let caveats = raw_caveats.iter().map(|c| parse_caveat(raw_base_name, c)).collect::<Result<Vec<_>, _>>()?;
Ok(Some((base_name, caveats)))
}
impl<'env> PatternInstantiator<'env> {
fn instantiate_pattern(&mut self, template: &AnyValue) -> io::Result<P::Pattern> {
Ok(match template.value() {
Value::Boolean(_) |
Value::Double(_) |
Value::SignedInteger(_) |
Value::String(_) |
Value::ByteString(_) |
Value::Embedded(_) =>
dlit(template.clone()),
Value::Symbol(s) => match analyze(s) {
Symbolic::Discard => discard(),
Symbolic::Binder(s) => {
self.binding_names.push(s);
P::Pattern::Bind { pattern: Box::new(discard()) }
}
Symbolic::Reference(s) =>
dlit(self.env.lookup(&s, "pattern-template variable")?.clone()),
Symbolic::Literal(s) | Symbolic::Bare(s) =>
dlit(Value::Symbol(s).wrap()),
},
Value::Record(r) => match parse_attenuation(r)? {
Some((base_name, caveats)) =>
dlit(self.env.eval_attenuation(base_name, caveats)?),
None => match self.maybe_binder_with_pattern(r)? {
Some(pat) => pat,
None => {
let label = self.instantiate_pattern(r.label())?;
let entries = r.fields().iter().enumerate()
.map(|(i, p)| Ok((AnyValue::new(i), self.instantiate_pattern(p)?)))
.collect::<io::Result<Map<AnyValue, P::Pattern>>>()?;
P::Pattern::Group {
type_: Box::new(P::GroupType::Rec {
label: drop_literal(&label)
.ok_or(bad_instruction("Record pattern must have literal label"))?,
}),
entries,
}
}
}
},
Value::Sequence(v) =>
P::Pattern::Group {
type_: Box::new(P::GroupType::Arr),
entries: v.iter().enumerate()
.map(|(i, p)| Ok((AnyValue::new(i), self.instantiate_pattern(p)?)))
.collect::<io::Result<Map<AnyValue, P::Pattern>>>()?,
},
Value::Set(_) =>
Err(bad_instruction(&format!("Sets not permitted in patterns: {:?}", template)))?,
Value::Dictionary(v) =>
P::Pattern::Group {
type_: Box::new(P::GroupType::Dict),
entries: v.iter()
.map(|(a, b)| Ok((a.clone(), self.instantiate_pattern(b)?)))
.collect::<io::Result<Map<AnyValue, P::Pattern>>>()?,
},
})
}
fn maybe_binder_with_pattern(&mut self, r: &Record<AnyValue>) -> io::Result<Option<P::Pattern>> {
match r.label().value().as_symbol().map(|s| analyze(&s)) {
Some(Symbolic::Binder(formal)) if r.fields().len() == 1 => {
let pattern = self.instantiate_pattern(&r.fields()[0])?;
self.binding_names.push(formal);
Ok(Some(P::Pattern::Bind { pattern: Box::new(pattern) }))
},
_ => Ok(None),
}
}
}
impl Env {
pub fn new(path: PathBuf, bindings: Map<String, AnyValue>) -> Self {
Env {
path: path.clone(),
bindings,
}
}
pub fn clone_with_path(&self, path: PathBuf) -> Self {
Env {
path,
bindings: self.bindings.clone(),
}
}
fn lookup_target(&self, s: &str) -> io::Result<Arc<Cap>> {
Ok(self.lookup(s, "target variable")?.value().to_embedded()?.clone())
}
fn lookup(&self, s: &str, what: &'static str) -> io::Result<AnyValue> {
if s == "." {
Ok(AnyValue::new(self.bindings.iter().map(|(k, v)| (AnyValue::symbol(k), v.clone()))
.collect::<Map<AnyValue, AnyValue>>()))
} else {
Ok(self.bindings.get(s).ok_or_else(
|| bad_instruction(&format!("Undefined {}: {:?}", what, s)))?.clone())
}
}
fn instantiate_pattern(
&self,
pattern_template: &AnyValue,
) -> io::Result<(Vec<String>, P::Pattern)> {
let mut inst = PatternInstantiator {
env: self,
binding_names: Vec::new(),
};
let pattern = inst.instantiate_pattern(pattern_template)?;
Ok((inst.binding_names, pattern))
}
fn instantiate_value(&self, template: &AnyValue) -> io::Result<AnyValue> {
Ok(match template.value() {
Value::Boolean(_) |
Value::Double(_) |
Value::SignedInteger(_) |
Value::String(_) |
Value::ByteString(_) |
Value::Embedded(_) =>
template.clone(),
Value::Symbol(s) => match analyze(s) {
Symbolic::Binder(_) | Symbolic::Discard =>
Err(bad_instruction(&format!(
"Invalid use of wildcard in template: {:?}", template)))?,
Symbolic::Reference(s) =>
self.lookup(&s, "template variable")?,
Symbolic::Literal(s) | Symbolic::Bare(s) =>
Value::Symbol(s).wrap(),
},
Value::Record(r) => match parse_attenuation(r)? {
Some((base_name, caveats)) =>
self.eval_attenuation(base_name, caveats)?,
None =>
Value::Record(Record(r.fields_vec().iter().map(|a| self.instantiate_value(a))
.collect::<Result<Vec<_>, _>>()?)).wrap(),
},
Value::Sequence(v) =>
Value::Sequence(v.iter().map(|a| self.instantiate_value(a))
.collect::<Result<Vec<_>, _>>()?).wrap(),
Value::Set(v) =>
Value::Set(v.iter().map(|a| self.instantiate_value(a))
.collect::<Result<Set<_>, _>>()?).wrap(),
Value::Dictionary(v) =>
Value::Dictionary(v.iter().map(|(a,b)| Ok((self.instantiate_value(a)?,
self.instantiate_value(b)?)))
.collect::<io::Result<Map<_, _>>>()?).wrap(),
})
}
pub fn safe_eval(&mut self, t: &mut Activation, i: &Instruction) -> bool {
match self.eval(t, i) {
Ok(()) => true,
Err(error) => {
tracing::error!(path = ?self.path, ?error);
t.stop();
false
}
}
}
pub fn extend(&mut self, binding_names: &Vec<String>, captures: Vec<AnyValue>) {
for (k, v) in binding_names.iter().zip(captures) {
self.bindings.insert(k.clone(), v);
}
}
fn eval_attenuation(
&self,
base_name: String,
caveats: Vec<CaveatTemplate>,
) -> io::Result<AnyValue> {
let base_value = self.lookup(&base_name, "attenuation-base variable")?;
match base_value.value().as_embedded() {
None => Err(bad_instruction(&format!(
"Value to be attenuated is {:?} but must be capability",
base_value))),
Some(base_cap) => {
match base_cap.attenuate(&caveats.iter().map(|c| self.instantiate_caveat(c)).collect::<Result<Vec<_>, _>>()?) {
Ok(derived_cap) => Ok(AnyValue::domain(derived_cap)),
Err(caveat_error) =>
Err(bad_instruction(&format!("Attenuation of {:?} failed: {:?}",
base_value,
caveat_error))),
}
}
}
}
fn bind_and_run(
&self,
t: &mut Activation,
binding_names: &Vec<String>,
captures: AnyValue,
body: &Instruction,
) -> ActorResult {
if let Some(captures) = captures.value_owned().into_sequence() {
let mut env = self.clone();
env.extend(binding_names, captures);
env.safe_eval(t, body);
}
Ok(())
}
pub fn eval(&mut self, t: &mut Activation, i: &Instruction) -> io::Result<()> {
match i {
Instruction::Assert { target, template } => {
self.lookup_target(target)?.assert(t, &(), &self.instantiate_value(template)?);
}
Instruction::Message { target, template } => {
self.lookup_target(target)?.message(t, &(), &self.instantiate_value(template)?);
}
Instruction::During { target, pattern_template, body } => {
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
let observer = during::entity(self.clone())
.on_asserted_facet(enclose!((binding_names, body) move |env, t, cs: AnyValue| {
env.bind_and_run(t, &binding_names, cs, &*body) }))
.create_cap(t);
self.lookup_target(target)?.assert(t, language(), &dataspace::Observe {
pattern,
observer,
});
}
Instruction::OnMessage { target, pattern_template, body } => {
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
let observer = during::entity(self.clone())
.on_message(enclose!((binding_names, body) move |env, t, cs: AnyValue| {
t.facet(|t| env.bind_and_run(t, &binding_names, cs, &*body))?;
Ok(())
}))
.create_cap(t);
self.lookup_target(target)?.assert(t, language(), &dataspace::Observe {
pattern,
observer,
});
}
Instruction::OnStop { body } => {
let mut env = self.clone();
t.on_stop(enclose!((body) move |t| Ok(env.eval(t, &*body)?)));
}
Instruction::Sequence { instructions } => {
for i in instructions {
self.eval(t, i)?;
}
}
Instruction::Let { pattern_template, expr } => {
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
let value = self.eval_expr(t, expr)?;
match pattern.match_value(&value) {
None => Err(bad_instruction(
&format!("Could not match pattern {:?} with value {:?}",
pattern_template,
value)))?,
Some(captures) => {
self.extend(&binding_names, captures);
}
}
}
Instruction::Cond { value_var, pattern_template, on_match, on_nomatch } => {
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
let value = self.lookup(value_var, "value in conditional expression")?;
match pattern.match_value(&value) {
None => self.eval(t, on_nomatch)?,
Some(captures) => {
self.extend(&binding_names, captures);
self.eval(t, on_match)?
}
}
}
}
Ok(())
}
pub fn eval_expr(&self, t: &mut Activation, e: &Expr) -> io::Result<AnyValue> {
match e {
Expr::Template { template } => self.instantiate_value(template),
Expr::Dataspace => Ok(AnyValue::domain(Cap::new(&t.create(Dataspace::new(None))))),
Expr::Timestamp => Ok(AnyValue::new(chrono::Utc::now().to_rfc3339())),
Expr::Facet => Ok(AnyValue::domain(Cap::new(&t.create(FacetHandle::new())))),
Expr::Stringify { expr } => {
let v = self.eval_expr(t, expr)?;
let s = TextWriter::encode(&mut NoEmbeddedDomainCodec, &v)?;
Ok(AnyValue::new(s))
}
}
}
fn instantiate_rewrite(
&self,
rw: &RewriteTemplate,
) -> io::Result<sturdy::Rewrite> {
match rw {
RewriteTemplate::Accept { pattern_template } => {
let (_binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
Ok(sturdy::Rewrite {
pattern: embed_pattern(&P::Pattern::Bind { pattern: Box::new(pattern) }),
template: sturdy::Template::TRef(Box::new(sturdy::TRef { binding: 0.into() })),
})
}
RewriteTemplate::Rewrite { pattern_template, template_template } => {
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
Ok(sturdy::Rewrite {
pattern: embed_pattern(&pattern),
template: self.instantiate_template(&binding_names, template_template)?,
})
}
}
}
fn instantiate_caveat(
&self,
c: &CaveatTemplate,
) -> io::Result<sturdy::Caveat> {
match c {
CaveatTemplate::Alts { alternatives } => {
let mut rewrites =
alternatives.iter().map(|r| self.instantiate_rewrite(r)).collect::<Result<Vec<_>, _>>()?;
if rewrites.len() == 1 {
Ok(sturdy::Caveat::Rewrite(Box::new(rewrites.pop().unwrap())))
} else {
Ok(sturdy::Caveat::Alts(Box::new(sturdy::Alts {
alternatives: rewrites,
})))
}
}
CaveatTemplate::Reject { pattern_template } => {
Ok(sturdy::Caveat::Reject(Box::new(
sturdy::Reject {
pattern: embed_pattern(&self.instantiate_pattern(pattern_template)?.1),
})))
}
}
}
fn instantiate_template(
&self,
binding_names: &Vec<String>,
template: &AnyValue,
) -> io::Result<sturdy::Template> {
let find_bound = |s: &str| {
binding_names.iter().enumerate().find(|(_i, n)| *n == s).map(|(i, _n)| i)
};
Ok(match template.value() {
Value::Boolean(_) |
Value::Double(_) |
Value::SignedInteger(_) |
Value::String(_) |
Value::ByteString(_) |
Value::Embedded(_) =>
tlit(template.clone()),
Value::Symbol(s) => match analyze(s) {
Symbolic::Binder(_) | Symbolic::Discard =>
Err(bad_instruction(&format!(
"Invalid use of wildcard in template: {:?}", template)))?,
Symbolic::Reference(s) =>
match find_bound(&s) {
Some(i) =>
sturdy::Template::TRef(Box::new(sturdy::TRef { binding: i.into() })),
None =>
tlit(self.lookup(&s, "attenuation-template variable")?),
},
Symbolic::Literal(s) | Symbolic::Bare(s) =>
tlit(Value::Symbol(s).wrap()),
},
Value::Record(r) => match parse_attenuation(r)? {
Some((base_name, caveats)) =>
match find_bound(&base_name) {
Some(i) =>
sturdy::Template::TAttenuate(Box::new(sturdy::TAttenuate {
template: sturdy::Template::TRef(Box::new(sturdy::TRef {
binding: i.into(),
})),
attenuation: caveats.iter()
.map(|c| self.instantiate_caveat(c))
.collect::<Result<Vec<_>, _>>()?,
})),
None =>
tlit(self.eval_attenuation(base_name, caveats)?),
},
None => {
// TODO: properly consolidate constant templates into literals.
match self.instantiate_template(binding_names, r.label())? {
sturdy::Template::Lit(b) =>
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Rec {
label: b.value,
fields: r.fields().iter()
.map(|t| self.instantiate_template(binding_names, t))
.collect::<io::Result<Vec<sturdy::Template>>>()?,
})),
_ => Err(bad_instruction("Record template must have literal label"))?,
}
}
},
Value::Sequence(v) =>
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Arr {
items: v.iter()
.map(|p| self.instantiate_template(binding_names, p))
.collect::<io::Result<Vec<sturdy::Template>>>()?,
})),
Value::Set(_) =>
Err(bad_instruction(&format!("Sets not permitted in templates: {:?}", template)))?,
Value::Dictionary(v) =>
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Dict {
entries: v.iter()
.map(|(a, b)| Ok((a.clone(), self.instantiate_template(binding_names, b)?)))
.collect::<io::Result<Map<_, sturdy::Template>>>()?,
})),
})
}
}
fn embed_pattern(p: &P::Pattern) -> sturdy::Pattern {
match p {
P::Pattern::Discard => sturdy::Pattern::PDiscard(Box::new(sturdy::PDiscard)),
P::Pattern::Bind { pattern } => sturdy::Pattern::PBind(Box::new(sturdy::PBind {
pattern: embed_pattern(&**pattern),
})),
P::Pattern::Lit { value } => sturdy::Pattern::Lit(Box::new(sturdy::Lit {
value: language().unparse(&**value),
})),
P::Pattern::Group { type_, entries } => sturdy::Pattern::PCompound(Box::new(match &**type_ {
P::GroupType::Rec { label } =>
sturdy::PCompound::Rec {
label: label.clone(),
fields: pattern_seq_from_dictionary(entries).expect("correct field entries")
.into_iter().map(embed_pattern).collect(),
},
P::GroupType::Arr =>
sturdy::PCompound::Arr {
items: pattern_seq_from_dictionary(entries).expect("correct element entries")
.into_iter().map(embed_pattern).collect(),
},
P::GroupType::Dict =>
sturdy::PCompound::Dict {
entries: entries.iter().map(|(k, v)| (k.clone(), embed_pattern(v))).collect(),
},
})),
}
}
impl<'t> Parser<'t> {
pub fn new(tokens: &'t [AnyValue]) -> Self {
Parser {
tokens,
errors: Vec::new(),
}
}
fn peek(&mut self) -> &'t Value<AnyValue> {
self.tokens[0].value()
}
fn shift(&mut self) -> AnyValue {
let v = self.tokens[0].clone();
self.drop();
v
}
fn drop(&mut self) {
self.tokens = &self.tokens[1..];
}
fn len(&self) -> usize {
self.tokens.len()
}
fn ateof(&self) -> bool {
self.len() == 0
}
fn error<'a, T: Default, E: Into<Cow<'a, str>>>(&mut self, message: E) -> T {
self.errors.push(message.into().into_owned());
T::default()
}
pub fn parse(&mut self, target: &str, outer_target: &str) -> Parsed<Instruction> {
if self.ateof() {
return Parsed::Eof;
}
if self.peek().is_record() || self.peek().is_dictionary() {
return Parsed::Value(Instruction::Assert {
target: target.to_owned(),
template: self.shift(),
});
}
if let Some(tokens) = self.peek().as_sequence() {
self.drop();
let mut inner_parser = Parser::new(tokens);
let instructions = inner_parser.parse_all(target, outer_target);
self.errors.extend(inner_parser.errors);
return Parsed::Value(Instruction::Sequence { instructions });
}
if let Some(s) = self.peek().as_symbol() {
match analyze(s) {
Symbolic::Binder(s) => {
self.drop();
let ctor = match s.as_ref() {
"" => |target, pattern_template, body| { // "?"
Instruction::During { target, pattern_template, body } },
"?" => |target, pattern_template, body| { // "??"
Instruction::OnMessage { target, pattern_template, body } },
"-" => match self.parse(target, outer_target) { // "?-"
Parsed::Value(i) => return Parsed::Value(Instruction::OnStop {
body: Box::new(i),
}),
other => return other,
},
_ => return self.error(format!(
"Invalid use of pattern binder in target: ?{}", s)),
};
if self.ateof() {
return self.error("Missing pattern and instruction in react");
}
let pattern_template = self.shift();
return match self.parse(target, outer_target) {
Parsed::Eof =>
self.error(format!(
"Missing instruction in react with pattern {:?}",
pattern_template)),
Parsed::Skip =>
Parsed::Skip,
Parsed::Value(body) =>
Parsed::Value(ctor(target.to_owned(),
pattern_template,
Box::new(body))),
};
}
Symbolic::Discard => {
self.drop();
let m = format!("Invalid use of discard in target: {:?}", self.peek());
return self.error(m);
},
Symbolic::Reference(s) => {
self.drop();
if self.ateof() {
let m = format!("Missing instruction after retarget: {:?}", self.peek());
return self.error(m);
}
return self.parse(&s, target);
}
Symbolic::Bare(s) => {
if s == "let" {
self.drop();
if self.len() >= 2 && self.tokens[1].value().as_symbol().map(String::as_str) == Some("=")
{
let pattern_template = self.shift();
self.drop();
return match self.parse_expr() {
Some(expr) =>
Parsed::Value(Instruction::Let { pattern_template, expr }),
None => Parsed::Skip,
};
} else {
return self.error("Invalid let statement");
}
} else if s == "!" {
self.drop();
if self.ateof() {
return self.error("Missing payload after '!'");
}
return Parsed::Value(Instruction::Message {
target: target.to_owned(),
template: self.shift(),
});
} else if s == "+=" {
self.drop();
if self.ateof() {
return self.error("Missing payload after '+='");
}
return Parsed::Value(Instruction::Assert {
target: target.to_owned(),
template: self.shift(),
});
} else {
/* fall through */
}
}
Symbolic::Literal(s) => {
if s == "~" { // "=~"
self.drop();
if self.ateof() {
return self.error("Missing pattern, true-instruction and false-continuation in match");
}
let match_template = self.shift();
return match self.parse(outer_target, outer_target) {
Parsed::Eof =>
self.error(format!(
"Missing true-instruction in conditional with pattern {:?}",
match_template)),
Parsed::Skip =>
Parsed::Skip,
Parsed::Value(true_instruction) => {
let false_instructions = self.parse_all(outer_target, outer_target);
Parsed::Value(Instruction::Cond {
value_var: target.to_owned(),
pattern_template: match_template,
on_match: Box::new(true_instruction),
on_nomatch: Box::new(Instruction::Sequence {
instructions: false_instructions,
}),
})
}
};
} else {
/* fall through */
}
}
}
}
{
let m = format!("Invalid token: {:?}", self.shift());
return self.error(m);
}
}
pub fn parse_all(&mut self, target: &str, outer_target: &str) -> Vec<Instruction> {
let mut instructions = Vec::new();
loop {
match self.parse(target, outer_target) {
Parsed::Value(i) => instructions.push(i),
Parsed::Skip => (),
Parsed::Eof => break,
}
}
instructions
}
pub fn parse_top(&mut self, target: &str) -> Result<Option<Instruction>, Vec<String>> {
let instructions = self.parse_all(target, target);
if self.errors.is_empty() {
match instructions.len() {
0 => Ok(None),
_ => Ok(Some(Instruction::Sequence { instructions })),
}
} else {
Err(std::mem::take(&mut self.errors))
}
}
pub fn parse_expr(&mut self) -> Option<Expr> {
if self.ateof() {
return None;
}
if self.peek() == &Value::symbol("dataspace") {
self.drop();
return Some(Expr::Dataspace);
}
if self.peek() == &Value::symbol("timestamp") {
self.drop();
return Some(Expr::Timestamp);
}
if self.peek() == &Value::symbol("facet") {
self.drop();
return Some(Expr::Facet);
}
if self.peek() == &Value::symbol("stringify") {
self.drop();
return Some(Expr::Stringify { expr: Box::new(self.parse_expr()?) });
}
return Some(Expr::Template{ template: self.shift() });
}
}

View File

@ -1,274 +0,0 @@
use notify::DebouncedEvent;
use notify::Watcher;
use notify::RecursiveMode;
use notify::watcher;
use syndicate::preserves::rec;
use std::fs;
use std::future;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;
use syndicate::actor::*;
use syndicate::error::Error;
use syndicate::enclose;
use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use syndicate::trace;
use syndicate::value::BinarySource;
use syndicate::value::BytesBinarySource;
use syndicate::value::Map;
use syndicate::value::NestedValue;
use syndicate::value::NoEmbeddedDomainCodec;
use syndicate::value::Reader;
use syndicate::value::ViaCodec;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services;
use crate::script;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, config_ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("config_watcher")), move |t| {
Ok(during!(t, config_ds, language(), <run-service $spec: internal_services::ConfigWatcher::<AnyValue>>, |t| {
Supervisor::start(
t,
Some(rec![AnyValue::symbol("config"), AnyValue::new(spec.path.clone())]),
SupervisorConfiguration::default(),
enclose!((config_ds, spec) lifecycle::updater(config_ds, spec)),
enclose!((config_ds) move |t| enclose!((config_ds, spec) run(t, config_ds, spec))))
}))
});
}
fn convert_notify_error(e: notify::Error) -> Error {
syndicate::error::error(&format!("Notify error: {:?}", e), AnyValue::new(false))
}
fn process_existing_file(
t: &mut Activation,
mut env: script::Env,
) -> io::Result<Option<FacetId>> {
let mut contents = fs::read(&env.path)?;
contents.append(&mut Vec::from("\n[]".as_bytes())); // improved ergonomics of trailing comments
let tokens: Vec<AnyValue> = BytesBinarySource::new(&contents)
.text::<AnyValue, _>(ViaCodec::new(NoEmbeddedDomainCodec))
.configured(true)
.collect::<Result<Vec<_>, _>>()?;
match script::Parser::new(&tokens).parse_top("config") {
Ok(Some(i)) => Ok(Some(t.facet(|t| {
tracing::debug!("Instructions for file {:?}: {:#?}", &env.path, &i);
env.safe_eval(t, &i);
Ok(())
}).expect("Successful facet startup"))),
Ok(None) => Ok(None),
Err(errors) => {
for e in errors {
tracing::error!(path = ?env.path, message = %e);
}
Ok(None)
}
}
}
fn process_path(
t: &mut Activation,
env: script::Env,
) -> io::Result<Option<FacetId>> {
match fs::metadata(&env.path) {
Ok(md) => if md.is_file() {
process_existing_file(t, env)
} else {
Ok(None)
}
Err(e) => if e.kind() != io::ErrorKind::NotFound {
Err(e)?
} else {
Ok(None)
}
}
}
fn is_hidden(path: &PathBuf) -> bool {
match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.starts_with("."),
None => true, // ?
}
}
fn should_process(path: &PathBuf) -> bool {
path.file_name().and_then(|n| n.to_str()).map(|n| n.ends_with(".pr")).unwrap_or(false)
}
fn scan_file(
t: &mut Activation,
path_state: &mut Map<PathBuf, FacetId>,
env: script::Env,
) -> bool {
let path = env.path.clone();
if is_hidden(&path) || !should_process(&path) {
return true;
}
tracing::trace!("scan_file: scanning {:?}", &path);
match process_path(t, env) {
Ok(maybe_facet_id) => {
if let Some(facet_id) = maybe_facet_id {
tracing::info!("scan_file: processed {:?}", &path);
path_state.insert(path, facet_id);
}
true
},
Err(e) => {
tracing::error!("scan_file: {:?}: {:?}", &path, e);
false
}
}
}
fn initial_scan(
t: &mut Activation,
path_state: &mut Map<PathBuf, FacetId>,
config_ds: &Arc<Cap>,
env: script::Env,
) {
if is_hidden(&env.path) {
return;
}
match fs::metadata(&env.path) {
Ok(md) => if md.is_file() {
scan_file(t, path_state, env);
} else {
match fs::read_dir(&env.path) {
Ok(unsorted_entries) => {
let mut entries: Vec<fs::DirEntry> = Vec::new();
for er in unsorted_entries {
match er {
Ok(e) =>
entries.push(e),
Err(e) =>
tracing::warn!(
"initial_scan: transient during scan of {:?}: {:?}", &env.path, e),
}
}
entries.sort_by_key(|e| e.file_name());
for e in entries {
initial_scan(t, path_state, config_ds, env.clone_with_path(e.path()));
}
}
Err(e) => tracing::warn!("initial_scan: enumerating {:?}: {:?}", &env.path, e),
}
},
Err(e) => tracing::warn!("initial_scan: `stat`ing {:?}: {:?}", &env.path, e),
}
}
fn run(
t: &mut Activation,
config_ds: Arc<Cap>,
spec: internal_services::ConfigWatcher,
) -> ActorResult {
lifecycle::terminate_on_service_restart(t, &config_ds, &spec);
let path = fs::canonicalize(spec.path.clone())?;
let env = script::Env::new(path, spec.env.0.clone());
tracing::info!(?env);
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_millis(100)).map_err(convert_notify_error)?;
watcher.watch(&env.path, RecursiveMode::Recursive).map_err(convert_notify_error)?;
let facet = t.facet_ref();
let trace_collector = t.trace_collector();
let span = tracing::Span::current();
thread::spawn(move || {
let _entry = span.enter();
let mut path_state: Map<PathBuf, FacetId> = Map::new();
{
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("initial_scan"));
let account = Account::new(Some(AnyValue::symbol("initial_scan")), trace_collector.clone());
if !facet.activate(
&account, cause, |t| {
initial_scan(t, &mut path_state, &config_ds, env.clone());
config_ds.assert(t, language(), &lifecycle::ready(&spec));
Ok(())
})
{
return;
}
}
tracing::trace!("initial_scan complete");
let mut rescan = |paths: Vec<PathBuf>| {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("rescan"));
let account = Account::new(Some(AnyValue::symbol("rescan")), trace_collector.clone());
facet.activate(&account, cause, |t| {
let mut to_stop = Vec::new();
for path in paths.into_iter() {
let maybe_facet_id = path_state.remove(&path);
let new_content_ok =
scan_file(t, &mut path_state, env.clone_with_path(path.clone()));
if let Some(old_facet_id) = maybe_facet_id {
if new_content_ok {
to_stop.push(old_facet_id);
} else {
path_state.insert(path, old_facet_id);
}
}
}
for facet_id in to_stop.into_iter() {
t.stop_facet(facet_id);
}
Ok(())
})
};
while let Ok(event) = rx.recv() {
tracing::trace!("notification: {:?}", &event);
let keep_running = match event {
DebouncedEvent::NoticeWrite(_p) |
DebouncedEvent::NoticeRemove(_p) =>
true,
DebouncedEvent::Create(p) |
DebouncedEvent::Write(p) |
DebouncedEvent::Chmod(p) |
DebouncedEvent::Remove(p) =>
rescan(vec![p]),
DebouncedEvent::Rename(p, q) =>
rescan(vec![p, q]),
_ => {
tracing::info!("{:?}", event);
true
}
};
if !keep_running { break; }
}
{
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("termination"));
let account = Account::new(Some(AnyValue::symbol("termination")), trace_collector);
facet.activate(&account, cause, |t| {
tracing::trace!("linked thread terminating associated facet");
Ok(t.stop())
});
}
tracing::trace!("linked thread done");
});
t.linked_task(Some(AnyValue::symbol("cancel-wait")), async move {
future::pending::<()>().await;
drop(watcher);
Ok(LinkedTaskTermination::KeepFacet)
});
Ok(())
}

View File

@ -1,475 +0,0 @@
use preserves_schema::Codec;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::preserves::rec;
use syndicate::schemas::service;
use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use syndicate::trace;
use syndicate::value::NestedValue;
use tokio::io::AsyncRead;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio::process;
use crate::counter;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::external_services::*;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, config_ds: Arc<Cap>, root_ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("daemon_listener")), move |t| {
Ok(during!(t, config_ds, language(), <run-service $spec: DaemonService::<AnyValue>>,
enclose!((config_ds, root_ds) move |t: &mut Activation| {
supervise_daemon(t, config_ds, root_ds, spec)
})))
});
}
fn supervise_daemon(
t: &mut Activation,
config_ds: Arc<Cap>,
root_ds: Arc<Cap>,
spec: DaemonService,
) -> ActorResult {
t.facet(|t| {
lifecycle::on_service_restart(t, &config_ds, &spec, enclose!(
(config_ds, root_ds, spec) move |t| {
tracing::info!(id = ?spec.id, "Terminating to restart");
t.stop_facet_and_continue(t.facet_id(), Some(
enclose!((config_ds, root_ds, spec) move |t: &mut Activation| {
supervise_daemon(t, config_ds, root_ds, spec)
})))
}));
Supervisor::start(
t,
Some(language().unparse(&spec)),
SupervisorConfiguration::on_error_only(),
enclose!((config_ds, spec) lifecycle::updater(config_ds, spec)),
enclose!((config_ds, root_ds) move |t|
enclose!((config_ds, root_ds, spec) run(t, config_ds, root_ds, spec))))
})?;
Ok(())
}
impl Process {
fn elaborate(self) -> FullProcess {
match self {
Process::Simple(command_line) => FullProcess {
argv: *command_line,
env: ProcessEnv::Absent,
dir: ProcessDir::Absent,
clear_env: ClearEnv::Absent,
},
Process::Full(spec) => *spec,
}
}
}
impl FullProcess {
fn build_command(&self) -> Option<process::Command> {
let argv = self.argv.clone().elaborate();
let mut cmd = process::Command::new(argv.program);
cmd.args(argv.args);
match &self.dir {
ProcessDir::Present { dir } => { cmd.current_dir(dir); () },
ProcessDir::Absent => (),
ProcessDir::Invalid { dir } => {
tracing::error!(?dir, "Invalid working directory");
return None;
}
}
match &self.clear_env {
ClearEnv::Present { clear_env: true } => { cmd.env_clear(); () },
ClearEnv::Present { clear_env: false } => (),
ClearEnv::Absent => (),
ClearEnv::Invalid { clear_env } => {
tracing::error!(?clear_env, "Invalid clearEnv setting");
return None;
}
}
match &self.env {
ProcessEnv::Present { env } => {
for (k, v) in env {
if let Some(env_variable) = match k {
EnvVariable::String(k) => Some(k),
EnvVariable::Symbol(k) => Some(k),
EnvVariable::Invalid(env_variable) => {
tracing::error!(?env_variable,
"Invalid environment variable name");
return None;
}
} {
match v {
EnvValue::Set(value) => { cmd.env(env_variable, value); () }
EnvValue::Remove => { cmd.env_remove(env_variable); () }
EnvValue::Invalid(value) => {
tracing::error!(?env_variable, ?value,
"Invalid environment variable value");
return None;
}
}
}
}
}
ProcessEnv::Absent => (),
ProcessEnv::Invalid { env } => {
tracing::error!(?env, "Invalid daemon environment");
return None;
}
}
cmd.kill_on_drop(true);
Some(cmd)
}
}
impl DaemonProcessSpec {
fn elaborate(self) -> FullDaemonProcess {
match self {
DaemonProcessSpec::Simple(command_line) => FullDaemonProcess {
process: Process::Simple(command_line).elaborate(),
ready_on_start: ReadyOnStart::Absent,
restart: RestartField::Absent,
protocol: ProtocolField::Absent,
},
DaemonProcessSpec::OneShot { setup } => FullDaemonProcess {
process: Process::Simple(setup).elaborate(),
ready_on_start: ReadyOnStart::Present { ready_on_start: false },
restart: RestartField::Present { restart: Box::new(RestartPolicy::OnError) },
protocol: ProtocolField::Absent,
},
DaemonProcessSpec::Full(spec) => *spec,
}
}
}
impl CommandLine {
fn elaborate(self) -> FullCommandLine {
match self {
CommandLine::Shell(s) => FullCommandLine {
program: "sh".to_owned(),
args: vec!["-c".to_owned(), s],
},
CommandLine::Full(command_line) => *command_line,
}
}
}
struct DaemonInstance {
config_ds: Arc<Cap>,
log_ds: Arc<Cap>,
service: AnyValue,
cmd: process::Command,
announce_presumed_readiness: bool,
unready_configs: Arc<Field<isize>>,
completed_processes: Arc<Field<isize>>,
restart_policy: RestartPolicy,
protocol: Protocol,
}
impl DaemonInstance {
fn handle_exit(self, t: &mut Activation, error_message: Option<String>) -> ActorResult {
let delay =
std::time::Duration::from_millis(if let None = error_message { 200 } else { 1000 });
t.stop_facet_and_continue(t.facet_id(), Some(move |t: &mut Activation| {
#[derive(Debug)]
enum NextStep {
SleepAndRestart,
SignalSuccessfulCompletion,
}
use NextStep::*;
let next_step = match self.restart_policy {
RestartPolicy::Always => SleepAndRestart,
RestartPolicy::OnError =>
match &error_message {
None => SignalSuccessfulCompletion,
Some(_) => SleepAndRestart,
},
RestartPolicy::All =>
match &error_message {
None => SignalSuccessfulCompletion,
Some(s) => {
tracing::error!(cmd = ?self.cmd, next_step = %"RestartDaemon", message = %s);
Err(s.as_str())?
}
},
RestartPolicy::Never => SignalSuccessfulCompletion,
};
match error_message {
Some(m) => tracing::error!(cmd = ?self.cmd, ?next_step, message = %m),
None => tracing::info!(cmd = ?self.cmd, ?next_step),
}
match next_step {
SleepAndRestart => t.after(delay, |t| self.start(t)),
SignalSuccessfulCompletion => {
t.facet(|t| {
let _ = t.prevent_inert_check();
counter::adjust(t, &self.completed_processes, 1);
Ok(())
})?;
()
}
}
Ok(())
}))
}
fn log<R: 'static + Send + AsyncRead + Unpin>(
&self,
t: &mut Activation,
pid: Option<u32>,
r: R,
kind: &str
) -> ActorResult {
t.facet(|t| {
let facet = t.facet_ref();
let log_ds = self.log_ds.clone();
let service = self.service.clone();
let kind = AnyValue::symbol(kind);
let pid = match pid {
Some(n) => AnyValue::new(n),
None => AnyValue::symbol("unknown"),
};
let trace_collector = t.trace_collector();
t.linked_task(None, async move {
let mut r = BufReader::new(r);
let cause = trace_collector.as_ref().map(
|_| trace::TurnCause::external(kind.value().as_symbol().unwrap()));
let account = Account::new(None, trace_collector);
loop {
let mut buf = Vec::new();
match r.read_until(b'\n', &mut buf).await {
Ok(0) | Err(_) => break,
Ok(_) => (),
}
let buf = match std::str::from_utf8(&buf) {
Ok(s) => AnyValue::new(s),
Err(_) => AnyValue::bytestring(buf),
};
let now = AnyValue::new(chrono::Utc::now().to_rfc3339());
if !facet.activate(
&account, cause.clone(), enclose!((pid, service, kind) |t| {
log_ds.message(t, &(), &syndicate_macros::template!(
"<log =now {
pid: =pid,
service: =service,
stream: =kind,
line: =buf,
}>"));
Ok(())
}))
{
break;
}
}
Ok(LinkedTaskTermination::Normal)
});
Ok(())
})?;
Ok(())
}
fn start(mut self, t: &mut Activation) -> ActorResult {
t.facet(|t| {
tracing::trace!(cmd = ?self.cmd, "starting");
let mut child = match self.cmd.spawn() {
Ok(child) => child,
Err(e) => {
tracing::debug!(spawn_err = ?e);
return self.handle_exit(t, Some(format!("{}", e)));
}
};
let pid = child.id();
tracing::debug!(?pid, cmd = ?self.cmd, "started");
let facet = t.facet_ref();
if let Some(r) = child.stderr.take() {
self.log(t, pid, r, "stderr")?;
}
match self.protocol {
Protocol::TextSyndicate => self.relay_facet(t, &mut child, true)?,
Protocol::BinarySyndicate => self.relay_facet(t, &mut child, false)?,
Protocol::None => {
if let Some(r) = child.stdout.take() {
self.log(t, pid, r, "stdout")?;
}
}
}
if self.announce_presumed_readiness {
counter::adjust(t, &self.unready_configs, -1);
}
let trace_collector = t.trace_collector();
t.linked_task(
Some(rec![AnyValue::symbol("wait"), self.service.clone()]),
enclose!((facet) async move {
tracing::trace!("waiting for process exit");
let status = child.wait().await?;
tracing::debug!(?status);
let cause = trace_collector.as_ref().map(
|_| trace::TurnCause::external("instance-terminated"));
let account = Account::new(Some(AnyValue::symbol("instance-terminated")), trace_collector);
facet.activate(&account, cause, |t| {
let m = if status.success() { None } else { Some(format!("{}", status)) };
self.handle_exit(t, m)
});
Ok(LinkedTaskTermination::Normal)
}));
Ok(())
})?;
Ok(())
}
fn relay_facet(&self, t: &mut Activation, child: &mut process::Child, output_text: bool) -> ActorResult {
use syndicate::relay;
use syndicate::schemas::sturdy;
let to_child = child.stdin.take().expect("pipe to child");
let from_child = child.stdout.take().expect("pipe from child");
let i = relay::Input::Bytes(Box::pin(from_child));
let o = relay::Output::Bytes(Box::pin(to_child));
t.facet(|t| {
let cap = relay::TunnelRelay::run(t, i, o, None, Some(sturdy::Oid(0.into())), output_text)
.ok_or("initial capability reference unavailable")?;
tracing::info!(?cap);
self.config_ds.assert(t, language(), &service::ServiceObject {
service_name: self.service.clone(),
object: AnyValue::domain(cap),
});
Ok(())
})?;
Ok(())
}
}
fn run(
t: &mut Activation,
config_ds: Arc<Cap>,
root_ds: Arc<Cap>,
service: DaemonService,
) -> ActorResult {
let spec = language().unparse(&service);
let total_configs = t.named_field("total_configs", 0isize);
let unready_configs = t.named_field("unready_configs", 1isize);
let completed_processes = t.named_field("completed_processes", 0isize);
t.dataflow({
let mut handle = None;
let ready = lifecycle::ready(&spec);
enclose!((config_ds, unready_configs) move |t| {
let busy_count = *t.get(&unready_configs);
tracing::debug!(?busy_count);
config_ds.update(t, &mut handle, language(), if busy_count == 0 { Some(&ready) } else { None });
Ok(())
})
})?;
t.dataflow(enclose!((completed_processes, total_configs) move |t| {
let total = *t.get(&total_configs);
let completed = *t.get(&completed_processes);
tracing::debug!(total_configs = ?total, completed_processes = ?completed);
if total > 0 && total == completed {
t.stop();
}
Ok(())
}))?;
let trace_collector = t.trace_collector();
enclose!((config_ds, unready_configs, completed_processes)
during!(t, config_ds.clone(), language(), <daemon #(&service.id) $config>, {
enclose!((spec, config_ds, root_ds, unready_configs, completed_processes, trace_collector)
|t: &mut Activation| {
tracing::debug!(?config, "new config");
counter::adjust(t, &unready_configs, 1);
counter::adjust(t, &total_configs, 1);
match language().parse::<DaemonProcessSpec>(&config) {
Ok(config) => {
tracing::info!(?config);
let config = config.elaborate();
let facet = t.facet_ref();
t.linked_task(Some(AnyValue::symbol("subprocess")), async move {
let mut cmd = config.process.build_command().ok_or("Cannot start daemon process")?;
let announce_presumed_readiness = match config.ready_on_start {
ReadyOnStart::Present { ready_on_start } => ready_on_start,
ReadyOnStart::Absent => true,
ReadyOnStart::Invalid { ready_on_start } => {
tracing::error!(?ready_on_start, "Invalid readyOnStart value");
Err("Invalid readyOnStart value")?
}
};
let restart_policy = match config.restart {
RestartField::Present { restart } => *restart,
RestartField::Absent => RestartPolicy::Always,
RestartField::Invalid { restart } => {
tracing::error!(?restart, "Invalid restart value");
Err("Invalid restart value")?
}
};
let protocol = match config.protocol {
ProtocolField::Present { protocol } => *protocol,
ProtocolField::Absent => Protocol::None,
ProtocolField::Invalid { protocol } => {
tracing::error!(?protocol, "Invalid protocol value");
Err("Invalid protocol value")?
}
};
cmd.stdin(match &protocol {
Protocol::None =>
std::process::Stdio::null(),
Protocol::TextSyndicate | Protocol::BinarySyndicate =>
std::process::Stdio::piped(),
});
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
let daemon_instance = DaemonInstance {
config_ds,
log_ds: root_ds,
service: spec,
cmd,
announce_presumed_readiness,
unready_configs,
completed_processes,
restart_policy,
protocol,
};
let cause = trace_collector.as_ref().map(
|_| trace::TurnCause::external("instance-startup"));
let account = Account::new(Some(AnyValue::symbol("instance-startup")), trace_collector);
facet.activate(&account, cause, |t| {
daemon_instance.start(t)
});
Ok(LinkedTaskTermination::KeepFacet)
});
Ok(())
}
Err(_) => {
tracing::error!(?config, "Invalid Process specification");
return Ok(());
}
}
})
}));
tracing::debug!("syncing to ds");
counter::sync_and_adjust(t, &config_ds.underlying, &unready_configs, -1);
Ok(())
}

View File

@ -1,65 +0,0 @@
use preserves_schema::Codec;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::preserves::rec;
use syndicate::preserves::value::NestedValue;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services::DebtReporter;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("debt_reporter_listener")), move |t| {
Ok(during!(t, ds, language(), <run-service $spec: DebtReporter>, |t: &mut Activation| {
t.spawn_link(Some(rec![AnyValue::symbol("debt_reporter"), language().unparse(&spec)]),
enclose!((ds) |t| run(t, ds, spec)));
Ok(())
}))
});
}
fn run(t: &mut Activation, ds: Arc<Cap>, spec: DebtReporter) -> ActorResult {
ds.assert(t, language(), &lifecycle::started(&spec));
ds.assert(t, language(), &lifecycle::ready(&spec));
t.every(core::time::Duration::from_millis((spec.interval_seconds.0 * 1000.0) as u64), |_t| {
for (account_id, (name, debt)) in syndicate::actor::ACCOUNTS.read().iter() {
tracing::info!(account_id, ?name, debt = ?debt.load(Ordering::Relaxed));
}
// let snapshot = syndicate::actor::ACTORS.read().clone();
// for (id, (name, ac_ref)) in snapshot.iter() {
// if *id == _t.state.actor_id {
// tracing::debug!("skipping report on the reporting actor, to avoid deadlock");
// continue;
// }
// tracing::trace!(?id, "about to lock");
// tracing::info_span!("actor", id, ?name).in_scope(|| match &*ac_ref.state.lock() {
// ActorState::Terminated { exit_status } =>
// tracing::info!(?exit_status, "terminated"),
// ActorState::Running(state) => {
// tracing::info!(field_count = ?state.fields.len(),
// outbound_assertion_count = ?state.outbound_assertions.len(),
// facet_count = ?state.facet_nodes.len());
// tracing::info_span!("facets").in_scope(|| {
// for (facet_id, f) in state.facet_nodes.iter() {
// tracing::info!(
// ?facet_id,
// parent_id = ?f.parent_facet_id,
// outbound_handle_count = ?f.outbound_handles.len(),
// linked_task_count = ?f.linked_tasks.len(),
// inert_check_preventers = ?f.inert_check_preventers.load(Ordering::Relaxed));
// }
// });
// }
// });
// }
Ok(())
})
}

View File

@ -1,39 +0,0 @@
use preserves_schema::Codec;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::preserves::rec;
use syndicate::preserves::value::NestedValue;
use crate::gatekeeper;
use crate::language::Language;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services::Gatekeeper;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("gatekeeper_listener")), move |t| {
Ok(during!(t, ds, language(), <run-service $spec: Gatekeeper::<AnyValue>>, |t: &mut Activation| {
t.spawn_link(Some(rec![AnyValue::symbol("gatekeeper"), language().unparse(&spec)]),
enclose!((ds) |t| run(t, ds, spec)));
Ok(())
}))
});
}
fn run(t: &mut Activation, ds: Arc<Cap>, spec: Gatekeeper<AnyValue>) -> ActorResult {
let resolver = t.create(syndicate::entity(Arc::clone(&spec.bindspace))
.on_asserted_facet(gatekeeper::facet_handle_resolve));
ds.assert(t, language(), &syndicate::schemas::service::ServiceObject {
service_name: language().unparse(&spec),
object: AnyValue::domain(Cap::guard(Language::arc(), resolver)),
});
gatekeeper::handle_binds(t, &spec.bindspace)?;
ds.assert(t, language(), &lifecycle::started(&spec));
ds.assert(t, language(), &lifecycle::ready(&spec));
Ok(())
}

View File

@ -1,348 +0,0 @@
use preserves_schema::Codec;
use std::convert::TryFrom;
use std::io::Read;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::error::Error;
use syndicate::preserves::rec;
use syndicate::preserves::value::Map;
use syndicate::preserves::value::NestedValue;
use syndicate::schemas::http;
use syndicate::value::signed_integer::SignedInteger;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services::HttpRouter;
use crate::schemas::internal_services::HttpStaticFileServer;
use syndicate_macros::during;
lazy_static::lazy_static! {
pub static ref MIME_TABLE: Map<String, String> = load_mime_table("/etc/mime.types").unwrap_or_default();
}
pub fn load_mime_table(path: &str) -> Result<Map<String, String>, std::io::Error> {
let mut table = Map::new();
let file = std::fs::read_to_string(path)?;
for line in file.split('\n') {
if line.starts_with('#') {
continue;
}
let pieces = line.split(&[' ', '\t'][..]).collect::<Vec<&str>>();
for i in 1..pieces.len() {
table.insert(pieces[i].to_string(), pieces[0].to_string());
}
}
Ok(table)
}
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("http_router_listener")), move |t| {
enclose!((ds) during!(t, ds, language(), <run-service $spec: HttpRouter::<AnyValue>>, |t: &mut Activation| {
t.spawn_link(Some(rec![AnyValue::symbol("http_router"), language().unparse(&spec)]),
enclose!((ds) |t| run(t, ds, spec)));
Ok(())
}));
enclose!((ds) during!(t, ds, language(), <run-service $spec: HttpStaticFileServer>, |t: &mut Activation| {
t.spawn_link(Some(rec![AnyValue::symbol("http_static_file_server"), language().unparse(&spec)]),
enclose!((ds) |t| run_static_file_server(t, ds, spec)));
Ok(())
}));
Ok(())
});
}
#[derive(Debug, Clone)]
struct ActiveHandler {
cap: Arc<Cap>,
terminated: Arc<Field<bool>>,
}
type MethodTable = Map<http::MethodPattern, Vec<ActiveHandler>>;
type HostTable = Map<http::HostPattern, Map<http::PathPattern, MethodTable>>;
type RoutingTable = Map<SignedInteger, HostTable>;
fn request_host(value: &http::RequestHost) -> Option<String> {
match value {
http::RequestHost::Present(h) => Some(h.to_owned()),
http::RequestHost::Absent => None,
}
}
fn run(t: &mut Activation, ds: Arc<Cap>, spec: HttpRouter) -> ActorResult {
ds.assert(t, language(), &lifecycle::started(&spec));
ds.assert(t, language(), &lifecycle::ready(&spec));
let httpd = spec.httpd;
let routes: Arc<Field<RoutingTable>> = t.named_field("routes", Map::new());
enclose!((httpd, routes) during!(t, httpd, language(), <http-bind _ $port _ _ _>, |t: &mut Activation| {
let port1 = port.clone();
enclose!((httpd, routes) during!(t, httpd, language(), <http-listener #(&port1)>, enclose!((routes, port) |t: &mut Activation| {
let port2 = port.clone();
during!(t, httpd, language(), <http-bind $host #(&port2) $method $path $handler>, |t: &mut Activation| {
tracing::debug!("+HTTP binding {:?} {:?} {:?} {:?} {:?}", host, port, method, path, handler);
let port = port.value().to_signedinteger()?;
let host = language().parse::<http::HostPattern>(&host)?;
let path = language().parse::<http::PathPattern>(&path)?;
let method = language().parse::<http::MethodPattern>(&method)?;
let handler_cap = handler.value().to_embedded()?.clone();
let handler_terminated = t.named_field("handler-terminated", false);
t.get_mut(&routes)
.entry(port.clone()).or_default()
.entry(host.clone()).or_default()
.entry(path.clone()).or_default()
.entry(method.clone()).or_default()
.push(ActiveHandler {
cap: handler_cap.clone(),
terminated: handler_terminated,
});
t.on_stop(enclose!((routes, method, path, host, port) move |t| {
tracing::debug!("-HTTP binding {:?} {:?} {:?} {:?} {:?}", host, port, method, path, handler);
let port_map = t.get_mut(&routes);
let host_map = port_map.entry(port.clone()).or_default();
let path_map = host_map.entry(host.clone()).or_default();
let method_map = path_map.entry(path.clone()).or_default();
let handler_vec = method_map.entry(method.clone()).or_default();
let handler = {
let i = handler_vec.iter().position(|a| a.cap == handler_cap)
.expect("Expected an index of an active handler to remove");
handler_vec.swap_remove(i)
};
if handler_vec.is_empty() {
method_map.remove(&method);
}
if method_map.is_empty() {
path_map.remove(&path);
}
if path_map.is_empty() {
host_map.remove(&host);
}
if host_map.is_empty() {
port_map.remove(&port);
}
*t.get_mut(&handler.terminated) = true;
Ok(())
}));
Ok(())
});
Ok(())
})));
Ok(())
}));
during!(t, httpd, language(), <request $req $res>, |t: &mut Activation| {
let req = match language().parse::<http::HttpRequest>(&req) { Ok(v) => v, Err(_) => return Ok(()) };
let res = match res.value().to_embedded() { Ok(v) => v, Err(_) => return Ok(()) };
tracing::trace!("Looking up handler for {:#?} in {:#?}", &req, &t.get(&routes));
let host_map = match t.get(&routes).get(&req.port) {
Some(host_map) => host_map,
None => return send_empty(t, res, 404, "Not found"),
};
let methods = match request_host(&req.host).and_then(|h| try_hostname(host_map, http::HostPattern::Host(h), &req.path).transpose()).transpose()? {
Some(methods) => methods,
None => match try_hostname(host_map, http::HostPattern::Any, &req.path)? {
Some(methods) => methods,
None => return send_empty(t, res, 404, "Not found"),
}
};
let handlers = match methods.get(&http::MethodPattern::Specific(req.method.clone())) {
Some(handlers) => handlers,
None => match methods.get(&http::MethodPattern::Any) {
Some(handlers) => handlers,
None => {
let allowed = methods.keys().map(|k| match k {
http::MethodPattern::Specific(m) => m.to_uppercase(),
http::MethodPattern::Any => unreachable!(),
}).collect::<Vec<String>>().join(", ");
res.message(t, language(), &http::HttpResponse::Status {
code: 405.into(), message: "Method Not Allowed".into() });
res.message(t, language(), &http::HttpResponse::Header {
name: "allow".into(), value: allowed });
return send_done(t, res);
}
}
};
if handlers.len() > 1 {
tracing::warn!(?req, "Too many handlers available");
}
let ActiveHandler { cap, terminated } = handlers.first().expect("Nonempty handler set").clone();
tracing::trace!("Handler for {:?} is {:?}", &req, &cap);
t.dataflow(enclose!((terminated, req, res) move |t| {
if *t.get(&terminated) {
tracing::trace!("Handler for {:?} terminated", &req);
send_empty(t, &res, 500, "Internal Server Error")?;
}
Ok(())
}))?;
cap.assert(t, language(), &http::HttpContext { req, res: res.clone() });
Ok(())
});
Ok(())
}
fn send_done(t: &mut Activation, res: &Arc<Cap>) -> ActorResult {
res.message(t, language(), &http::HttpResponse::Done {
chunk: Box::new(http::Chunk::Bytes(vec![])) });
Ok(())
}
fn send_empty(t: &mut Activation, res: &Arc<Cap>, code: u16, message: &str) -> ActorResult {
res.message(t, language(), &http::HttpResponse::Status {
code: code.into(), message: message.into() });
send_done(t, res)
}
fn path_pattern_matches(path_pat: &http::PathPattern, path: &Vec<String>) -> bool {
let mut path_iter = path.iter();
for pat_elem in path_pat.0.iter() {
match pat_elem {
http::PathPatternElement::Label(v) => match path_iter.next() {
Some(path_elem) => {
if v != path_elem {
return false;
}
}
None => return false,
},
http::PathPatternElement::Wildcard => match path_iter.next() {
Some(_) => (),
None => return false,
},
http::PathPatternElement::Rest => return true,
}
}
match path_iter.next() {
Some(_more) => false,
None => true,
}
}
fn try_hostname<'table>(
host_map: &'table HostTable,
host_pat: http::HostPattern,
path: &Vec<String>,
) -> Result<Option<&'table MethodTable>, Error> {
match host_map.get(&host_pat) {
None => Ok(None),
Some(path_table) => {
for (path_pat, method_table) in path_table.iter() {
tracing::trace!("Checking path {:?} against pattern {:?}", &path, &path_pat);
if path_pattern_matches(path_pat, path) {
return Ok(Some(method_table));
}
}
Ok(None)
}
}
}
fn render_dir(path: std::path::PathBuf) -> Result<(Vec<u8>, Option<&'static str>), Error> {
let mut body = String::new();
for entry in std::fs::read_dir(&path)? {
if let Ok(entry) = entry {
let is_dir = entry.metadata().map(|m| m.is_dir()).unwrap_or(false);
let name = entry.file_name().to_string_lossy()
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('\'', "&apos;")
.replace('"', "&quot;") + (if is_dir { "/" } else { "" });
body.push_str(&format!("<a href=\"{}\">{}</a><br>\n", name, name));
}
}
Ok((body.into_bytes(), Some("text/html")))
}
impl HttpStaticFileServer {
fn respond(&mut self, t: &mut Activation, req: &http::HttpRequest, res: &Arc<Cap>) -> ActorResult {
let path_prefix_elements = usize::try_from(&self.path_prefix_elements)
.map_err(|_| "Bad pathPrefixElements")?;
let mut is_index = false;
let mut path = req.path[path_prefix_elements..].iter().cloned().collect::<Vec<String>>();
if let Some(e) = path.last_mut() {
if e.len() == 0 {
*e = "index.html".into();
is_index = true;
}
}
let mut realpath = std::path::PathBuf::from(&self.dir);
for element in path.into_iter() {
if element.contains('/') || element.starts_with('.') { Err("Invalid path element")?; }
realpath.push(element);
}
let (body, mime_type) = match std::fs::File::open(&realpath) {
Err(_) => {
if is_index {
realpath.pop();
}
if std::fs::metadata(&realpath).is_ok_and(|m| m.is_dir()) {
render_dir(realpath)?
} else {
return send_empty(t, res, 404, "Not found")
}
},
Ok(mut fh) => {
if fh.metadata().is_ok_and(|m| m.is_dir()) {
drop(fh);
res.message(t, language(), &http::HttpResponse::Status {
code: 301.into(), message: "Moved permanently".into() });
res.message(t, language(), &http::HttpResponse::Header {
name: "location".into(), value: format!("/{}/", req.path.join("/")) });
return send_done(t, res);
} else {
let mut buf = Vec::new();
fh.read_to_end(&mut buf)?;
if let Some(extension) = realpath.extension().and_then(|e| e.to_str()) {
(buf, MIME_TABLE.get(extension).map(|m| m.as_str()))
} else {
(buf, None)
}
}
}
};
res.message(t, language(), &http::HttpResponse::Status {
code: 200.into(), message: "OK".into() });
if let Some(mime_type) = mime_type {
res.message(t, language(), &http::HttpResponse::Header {
name: "content-type".into(), value: mime_type.to_owned() });
}
res.message(t, language(), &http::HttpResponse::Done {
chunk: Box::new(http::Chunk::Bytes(body)) });
Ok(())
}
}
impl Entity<http::HttpContext<AnyValue>> for HttpStaticFileServer {
fn assert(&mut self, t: &mut Activation, assertion: http::HttpContext<AnyValue>, _handle: Handle) -> ActorResult {
let http::HttpContext { req, res } = assertion;
if let Err(e) = self.respond(t, &req, &res) {
tracing::error!(?req, error=?e);
send_empty(t, &res, 500, "Internal server error")?;
}
Ok(())
}
}
fn run_static_file_server(t: &mut Activation, ds: Arc<Cap>, spec: HttpStaticFileServer) -> ActorResult {
let object = Cap::guard(&language().syndicate, t.create(spec.clone()));
ds.assert(t, language(), &syndicate::schemas::service::ServiceObject {
service_name: language().unparse(&spec),
object: AnyValue::domain(object),
});
Ok(())
}

View File

@ -1,7 +0,0 @@
pub mod config_watcher;
pub mod daemon;
pub mod debt_reporter;
pub mod gatekeeper;
pub mod http_router;
pub mod tcp_relay_listener;
pub mod unix_relay_listener;

View File

@ -1,120 +0,0 @@
use preserves_schema::Codec;
use std::convert::TryFrom;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::preserves::rec;
use syndicate::preserves::value::NestedValue;
use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use syndicate::trace;
use tokio::net::TcpListener;
use crate::language::language;
use crate::lifecycle;
use crate::protocol::detect_protocol;
use crate::schemas::internal_services::TcpWithoutHttp;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("tcp_relay_listener")), move |t| {
enclose!((ds) during!(t, ds, language(), <run-service $spec: TcpWithoutHttp::<AnyValue>>, |t| {
run_supervisor(t, ds.clone(), spec)
}));
Ok(())
});
}
fn run_supervisor(t: &mut Activation, ds: Arc<Cap>, spec: TcpWithoutHttp) -> ActorResult {
Supervisor::start(
t,
Some(rec![AnyValue::symbol("relay"), language().unparse(&spec)]),
SupervisorConfiguration::default(),
enclose!((ds, spec) lifecycle::updater(ds, spec)),
enclose!((ds) move |t| enclose!((ds, spec) run(t, ds, spec))))
}
fn run(t: &mut Activation, ds: Arc<Cap>, spec: TcpWithoutHttp) -> ActorResult {
lifecycle::terminate_on_service_restart(t, &ds, &spec);
let httpd = t.named_field("httpd", None::<Arc<Cap>>);
{
let ad = spec.addr.clone();
let ad2 = ad.clone();
let gk = spec.gatekeeper.clone();
enclose!((ds, httpd) during!(t, ds, language(),
<run-service <relay-listener #(&language().unparse(&ad)) #(&AnyValue::domain(gk)) $h>>, |t: &mut Activation| {
if let Some(h) = h.value().as_embedded().cloned() {
h.assert(t, language(), &syndicate::schemas::http::HttpListener { port: ad2.port.clone() });
*t.get_mut(&httpd) = Some(h.clone());
t.on_stop(enclose!((httpd) move |t| {
let f = t.get_mut(&httpd);
if *f == Some(h.clone()) { *f = None; }
Ok(())
}));
}
Ok(())
}));
}
let TcpWithoutHttp { addr, gatekeeper } = spec.clone();
let host = addr.host.clone();
let port = u16::try_from(&addr.port).map_err(|_| "Invalid TCP port number")?;
let facet = t.facet_ref();
let trace_collector = t.trace_collector();
t.linked_task(Some(AnyValue::symbol("listener")), async move {
let listen_addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(listen_addr).await?;
{
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("readiness"));
let account = Account::new(Some(AnyValue::symbol("readiness")), trace_collector.clone());
if !facet.activate(
&account, cause, |t| {
tracing::info!("listening");
ds.assert(t, language(), &lifecycle::ready(&spec));
Ok(())
})
{
return Ok(LinkedTaskTermination::Normal);
}
}
loop {
let (stream, addr) = listener.accept().await?;
let gatekeeper = gatekeeper.clone();
let name = Some(rec![AnyValue::symbol("tcp"), AnyValue::new(format!("{}", &addr))]);
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("connect"));
let account = Account::new(name.clone(), trace_collector.clone());
if !facet.activate(
&account, cause, enclose!((trace_collector, httpd) move |t| {
let httpd = t.get(&httpd).clone();
t.spawn(name, move |t| {
Ok(t.linked_task(None, {
let facet = t.facet_ref();
async move {
detect_protocol(trace_collector,
facet,
stream,
gatekeeper,
httpd,
addr,
port).await?;
Ok(LinkedTaskTermination::KeepFacet)
}
}))
});
Ok(())
}))
{
return Ok(LinkedTaskTermination::Normal);
}
}
});
Ok(())
}

View File

@ -1,120 +0,0 @@
use preserves_schema::Codec;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::error::Error;
use syndicate::preserves::rec;
use syndicate::preserves::value::NestedValue;
use syndicate::relay;
use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use syndicate::trace;
use tokio::net::UnixListener;
use tokio::net::UnixStream;
use crate::language::language;
use crate::lifecycle;
use crate::protocol::run_connection;
use crate::schemas::internal_services::UnixRelayListener;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("unix_relay_listener")), move |t| {
Ok(during!(t, ds, language(), <run-service $spec: UnixRelayListener::<AnyValue>>, |t| {
Supervisor::start(
t,
Some(rec![AnyValue::symbol("relay"), language().unparse(&spec)]),
SupervisorConfiguration::default(),
enclose!((ds, spec) lifecycle::updater(ds, spec)),
enclose!((ds) move |t| enclose!((ds, spec) run(t, ds, spec))))
}))
});
}
fn run(t: &mut Activation, ds: Arc<Cap>, spec: UnixRelayListener) -> ActorResult {
lifecycle::terminate_on_service_restart(t, &ds, &spec);
let path_str = spec.addr.path.clone();
let facet = t.facet_ref();
let trace_collector = t.trace_collector();
t.linked_task(Some(AnyValue::symbol("listener")), async move {
let listener = bind_unix_listener(&PathBuf::from(path_str)).await?;
{
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("readiness"));
let account = Account::new(Some(AnyValue::symbol("readiness")), trace_collector.clone());
if !facet.activate(
&account, cause, |t| {
tracing::info!("listening");
ds.assert(t, language(), &lifecycle::ready(&spec));
Ok(())
})
{
return Ok(LinkedTaskTermination::Normal);
}
}
loop {
let (stream, _addr) = listener.accept().await?;
let peer = stream.peer_cred()?;
let gatekeeper = spec.gatekeeper.clone();
let name = Some(rec![AnyValue::symbol("unix"),
AnyValue::new(peer.pid().unwrap_or(-1)),
AnyValue::new(peer.uid())]);
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("connect"));
let account = Account::new(name.clone(), trace_collector.clone());
if !facet.activate(
&account, cause, enclose!((trace_collector) move |t| {
t.spawn(name, |t| {
Ok(t.linked_task(None, {
let facet = t.facet_ref();
async move {
tracing::info!(protocol = %"unix");
let (i, o) = stream.into_split();
run_connection(trace_collector,
facet,
relay::Input::Bytes(Box::pin(i)),
relay::Output::Bytes(Box::pin(o)),
gatekeeper);
Ok(LinkedTaskTermination::KeepFacet)
}
}))
});
Ok(())
}))
{
return Ok(LinkedTaskTermination::Normal);
}
}
});
Ok(())
}
async fn bind_unix_listener(path: &PathBuf) -> Result<UnixListener, Error> {
match UnixListener::bind(path) {
Ok(s) => Ok(s),
Err(e) if e.kind() == io::ErrorKind::AddrInUse => {
// Potentially-stale socket file sitting around. Try
// connecting to it to see if it is alive, and remove it
// if not.
match UnixStream::connect(path).await {
Ok(_probe) => Err(e)?, // Someone's already there! Give up.
Err(f) if f.kind() == io::ErrorKind::ConnectionRefused => {
// Try to steal the socket.
tracing::debug!("Cleaning stale socket");
std::fs::remove_file(path)?;
Ok(UnixListener::bind(path)?)
}
Err(error) => {
tracing::error!(?error, "Problem while probing potentially-stale socket");
return Err(e)? // signal the *original* error, not the probe error
}
}
},
Err(e) => Err(e)?,
}
}

View File

@ -1,23 +0,0 @@
[package]
name = "syndicate-tools"
version = "0.18.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
description = "Syndicate command-line utilities."
homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0"
[dependencies]
preserves = "4.995"
syndicate = { path = "../syndicate", version = "0.40.0"}
clap = { version = "^4.0", features = ["derive"] }
clap_complete = "^4.0"
noise-protocol = "0.1"
noise-rust-crypto = "0.5"
[package.metadata.workspaces]
independent = true

View File

@ -1,168 +0,0 @@
use std::io;
use std::str::FromStr;
use clap::ArgGroup;
use clap::CommandFactory;
use clap::Parser;
use clap::Subcommand;
use clap::arg;
use clap_complete::{generate, Shell};
use noise_protocol::DH;
use noise_protocol::Hash;
use noise_rust_crypto::Blake2s;
use noise_rust_crypto::X25519;
use preserves::hex::HexParser;
use preserves::value::BytesBinarySource;
use preserves::value::NestedValue;
use preserves::value::NoEmbeddedDomainCodec;
use preserves::value::Reader;
use preserves::value::TextReader;
use preserves::value::ViaCodec;
use preserves::value::TextWriter;
use syndicate::language;
use syndicate::preserves_schema::Codec;
use syndicate::preserves_schema::ParseError;
use syndicate::schemas::noise;
use syndicate::sturdy::Caveat;
use syndicate::sturdy::SturdyRef;
use syndicate::sturdy::_Any;
#[derive(Clone, Debug)]
struct Preserves<N: NestedValue>(N);
#[derive(Subcommand, Debug)]
enum Action {
#[command(group(ArgGroup::new("key").required(true)))]
/// Generate a fresh SturdyRef from an OID value and a key
Mint {
#[arg(long, value_name="VALUE")]
/// Preserves value to use as SturdyRef OID
oid: Preserves<_Any>,
#[arg(long, group="key")]
/// Key phrase
phrase: Option<String>,
#[arg(long, group="key")]
/// Key bytes, encoded as hex
hex: Option<String>,
#[arg(long)]
/// Caveats to add
caveat: Vec<Preserves<_Any>>,
},
#[command(group(ArgGroup::new("key").required(true)))]
/// Generate a fresh NoiseServiceSpec from a service selector and a key
Noise {
#[arg(long, value_name="VALUE")]
/// Preserves value to use as the service selector
service: Preserves<_Any>,
#[arg(long, value_name="PROTOCOL")]
/// Noise handshake protocol name
protocol: Option<String>,
#[arg(long, group="key")]
/// Key phrase
phrase: Option<String>,
#[arg(long, group="key")]
/// Key bytes, encoded as hex
hex: Option<String>,
#[arg(long, group="key")]
/// Generate a random key
random: bool,
},
/// Emit shell completion code
Completions {
/// Shell dialect to generate
shell: Shell,
}
}
#[derive(Parser, Debug)]
#[command(version)]
struct Cli {
#[command(subcommand)]
action: Action,
}
impl<N: NestedValue> FromStr for Preserves<N> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Preserves(TextReader::new(&mut BytesBinarySource::new(s.as_bytes()),
ViaCodec::new(NoEmbeddedDomainCodec)).demand_next(false)?))
}
}
fn main() -> io::Result<()> {
let args = <Cli as Parser>::parse();
match args.action {
Action::Completions { shell } => {
let mut cmd = <Cli as CommandFactory>::command();
let name = cmd.get_name().to_string();
generate(shell, &mut cmd, name, &mut io::stdout());
}
Action::Noise { service, protocol, phrase, hex, random } => {
let key =
if random {
X25519::genkey()
} else if let Some(hex) = hex {
let mut hash = Blake2s::default();
hash.input(hex.as_bytes());
hash.result()
} else if let Some(phrase) = phrase {
let mut hash = Blake2s::default();
hash.input(phrase.as_bytes());
hash.result()
} else {
unreachable!()
};
let n = noise::NoiseServiceSpec {
base: noise::NoiseSpec {
key: X25519::pubkey(&key).to_vec(),
service: noise::ServiceSelector(service.0),
pre_shared_keys: noise::NoisePreSharedKeys::Absent,
protocol: if let Some(p) = protocol {
noise::NoiseProtocol::Present { protocol: p }
} else {
noise::NoiseProtocol::Absent
},
},
secret_key: noise::SecretKeyField::Present {
secret_key: key.to_vec(),
},
};
println!("{}", TextWriter::encode(&mut NoEmbeddedDomainCodec,
&language().unparse(&n))?);
}
Action::Mint { oid, phrase, hex, caveat: caveats } => {
let key =
if let Some(hex) = hex {
HexParser::Liberal.decode(&hex).expect("hex encoded sturdyref")
} else if let Some(phrase) = phrase {
phrase.as_bytes().to_owned()
} else {
unreachable!()
};
let attenuation = caveats.into_iter().map(|c| {
let r = language().parse(&c.0);
if let Ok(Caveat::Unknown(_)) = &r {
eprintln!("Warning: Unknown caveat format: {:?}", &c.0);
}
r
}).collect::<Result<Vec<Caveat>, _>>()?;
let m = SturdyRef::mint(oid.0, &key).attenuate(&attenuation)?;
println!("{}", TextWriter::encode(&mut NoEmbeddedDomainCodec,
&language().unparse(&m))?);
}
}
Ok(())
}

View File

@ -1,53 +0,0 @@
[package]
name = "syndicate"
version = "0.40.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018"
description = "Syndicated Actor model for Rust, including network communication and Dataspaces."
homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0"
[features]
vendored-openssl = ["openssl/vendored"]
[build-dependencies]
preserves-schema = "5.995"
[dependencies]
preserves = "4.995"
preserves-schema = "5.995"
tokio = { version = "1.10", features = ["io-std", "io-util", "macros", "rt", "rt-multi-thread", "time"] }
tokio-util = "0.6"
bytes = "1.0"
futures = "0.3"
blake2 = "0.10"
getrandom = "0.2"
hmac = "0.12"
lazy_static = "1.4"
parking_lot = "0.11"
tracing = "0.1"
tracing-subscriber = "0.2"
tracing-futures = "0.2"
# Only used for vendored-openssl, which in turn is being used for cross-builds
openssl = { version = "0.10", optional = true }
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "bench_dataspace"
harness = false
[[bench]]
name = "ring"
harness = false
[package.metadata.workspaces]
independent = true

View File

@ -1,11 +0,0 @@
all: binary-debug
# cargo install cargo-watch
watch:
cargo watch -c -x check -x 'test -- --nocapture'
inotifytest:
inotifytest sh -c 'reset; cargo build && RUST_BACKTRACE=1 cargo test -- --nocapture'
binary-debug:
cargo build --all-targets

View File

@ -1,8 +0,0 @@
This crate implements the
[Syndicated Actor model](https://syndicate-lang.org/about/) for Rust,
including
- intra-process communication (the [actor] module),
- point-to-point links between actor spaces (the [relay] module),
- and Dataspace objects (the [dataspace] module) for replicating
state and messages among interested parties.

View File

@ -1,164 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Instant;
use syndicate::language;
use syndicate::actor::*;
use syndicate::during::entity;
use syndicate::dataspace::Dataspace;
use syndicate::schemas::dataspace::Observe;
use syndicate::schemas::dataspace_patterns as p;
use syndicate::value::Map;
use syndicate::value::NestedValue;
use syndicate::value::Value;
use tokio::runtime::Runtime;
use tracing::Level;
#[inline]
fn says(who: AnyValue, what: AnyValue) -> AnyValue {
let mut r = Value::simple_record("Says", 2);
r.fields_vec_mut().push(who);
r.fields_vec_mut().push(what);
r.finish().wrap()
}
struct ShutdownEntity;
impl Entity<AnyValue> for ShutdownEntity {
fn message(&mut self, t: &mut Activation, _m: AnyValue) -> ActorResult {
Ok(t.stop())
}
}
pub fn bench_pub(c: &mut Criterion) {
let filter = tracing_subscriber::filter::EnvFilter::from_default_env()
.add_directive(tracing_subscriber::filter::LevelFilter::INFO.into());
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_ansi(true)
.with_max_level(Level::TRACE)
.with_env_filter(filter)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Could not set tracing global subscriber");
let rt = Runtime::new().unwrap();
c.bench_function("publication alone", |b| {
b.iter_custom(|iters| {
let start = Instant::now();
rt.block_on(async move {
Actor::top(None, move |t| {
let _ = t.prevent_inert_check();
// The reason this works is that all the messages to `ds` will be delivered
// before the message to `shutdown`, because `ds` and `shutdown` are in the
// same Actor.
let ds = t.create(Dataspace::new(None));
let shutdown = t.create(ShutdownEntity);
for _ in 0..iters {
t.message(&ds, says(AnyValue::new("bench_pub"),
Value::ByteString(vec![]).wrap()));
}
t.message(&shutdown, AnyValue::new(true));
Ok(())
}).await.unwrap().unwrap();
});
start.elapsed()
})
});
c.bench_function("publication and subscription", |b| {
b.iter_custom(|iters| {
let start = Instant::now();
rt.block_on(async move {
let turn_count = Arc::new(AtomicU64::new(0));
Actor::top(None, {
let iters = iters.clone();
let turn_count = Arc::clone(&turn_count);
move |t| {
let ds = Cap::new(&t.create(Dataspace::new(None)));
let shutdown = entity(())
.on_asserted(|_, _, _| Ok(Some(Box::new(|_, t| Ok(t.stop())))))
.create_cap(t);
ds.assert(t, language(), &Observe {
pattern: p::Pattern::Bind {
pattern: Box::new(p::Pattern::Lit {
value: Box::new(p::AnyAtom::Symbol("consumer".to_owned())),
}),
},
observer: shutdown,
});
t.spawn(Some(AnyValue::symbol("consumer")), move |t| {
struct Receiver(Arc<AtomicU64>);
impl Entity<AnyValue> for Receiver {
fn message(&mut self, _t: &mut Activation, _m: AnyValue) -> ActorResult {
self.0.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}
let shutdown = Cap::new(&t.create(ShutdownEntity));
let receiver = Cap::new(&t.create(Receiver(Arc::clone(&turn_count))));
ds.assert(t, &(), &AnyValue::symbol("consumer"));
ds.assert(t, language(), &Observe {
pattern: p::Pattern::Group {
type_: Box::new(p::GroupType::Rec {
label: AnyValue::symbol("Says"),
}),
entries: Map::from([
(p::_Any::new(0), p::Pattern::Lit {
value: Box::new(p::AnyAtom::String("bench_pub".to_owned())),
}),
(p::_Any::new(1), p::Pattern::Bind {
pattern: Box::new(p::Pattern::Discard),
}),
]),
},
observer: receiver,
});
ds.assert(t, language(), &Observe {
pattern: p::Pattern::Bind {
pattern: Box::new(p::Pattern::Lit {
value: Box::new(p::AnyAtom::Bool(true)),
}),
},
observer: shutdown,
});
t.after(core::time::Duration::from_secs(0), move |t| {
for _i in 0..iters {
ds.message(t, &(), &says(AnyValue::new("bench_pub"),
Value::ByteString(vec![]).wrap()));
}
ds.message(t, &(), &AnyValue::new(true));
Ok(())
});
Ok(())
});
Ok(())
}
}).await.unwrap().unwrap();
let actual_turns = turn_count.load(Ordering::SeqCst);
if actual_turns != iters {
panic!("Expected {}, got {} messages", iters, actual_turns);
}
});
start.elapsed()
})
});
}
criterion_group!(publish, bench_pub);
criterion_main!(publish);

View File

@ -1,145 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use syndicate::actor::*;
use syndicate::preserves::rec;
use syndicate::value::NestedValue;
use tokio::runtime::Runtime;
static ACTORS_CREATED: AtomicU64 = AtomicU64::new(0);
static MESSAGES_SENT: AtomicU64 = AtomicU64::new(0);
pub fn bench_ring(c: &mut Criterion) {
syndicate::convenient_logging().unwrap();
let rt = Runtime::new().unwrap();
c.bench_function("Armstrong's Ring", |b| {
// "Write a ring benchmark. Create N processes in a ring. Send a message round the ring
// M times so that a total of N * M messages get sent. Time how long this takes for
// different values of N and M."
// -- Joe Armstrong, "Programming Erlang: Software for a Concurrent World"
//
// Here we fix N = 1000, and let `iters` take on the role of M.
//
b.iter_custom(|iters| {
const ACTOR_COUNT: u32 = 1000;
ACTORS_CREATED.store(0, Ordering::SeqCst);
MESSAGES_SENT.store(0, Ordering::SeqCst);
let (tx, rx) = std::sync::mpsc::sync_channel(1);
rt.block_on(async move {
struct Forwarder {
next: Arc<Ref<()>>,
}
struct Counter {
start: Instant,
tx: std::sync::mpsc::SyncSender<Duration>,
remaining_to_send: u64,
iters: u64,
next: Arc<Ref<()>>,
}
struct Spawner {
self_ref: Arc<Ref<Arc<Ref<()>>>>, // !
tx: std::sync::mpsc::SyncSender<Duration>,
iters: u64,
i: u32,
c: Arc<Ref<()>>,
}
impl Entity<()> for Forwarder {
fn message(&mut self, t: &mut Activation, _message: ()) -> ActorResult {
MESSAGES_SENT.fetch_add(1, Ordering::Relaxed);
t.message(&self.next, ());
Ok(())
}
}
impl Counter {
fn step(&mut self, t: &mut Activation) -> ActorResult {
if self.remaining_to_send > 0 {
self.remaining_to_send -= 1;
MESSAGES_SENT.fetch_add(1, Ordering::Relaxed);
t.message(&self.next, ());
} else {
tracing::info!(iters = self.iters,
actors_created = ACTORS_CREATED.load(Ordering::SeqCst),
messages_sent = MESSAGES_SENT.load(Ordering::SeqCst));
t.stop();
self.tx.send(self.start.elapsed() / ACTOR_COUNT).unwrap()
}
Ok(())
}
}
impl Entity<()> for Counter {
fn message(&mut self, t: &mut Activation, _message: ()) -> ActorResult {
self.step(t)
}
}
impl Spawner {
fn step(&mut self, t: &mut Activation, next: Arc<Ref<()>>) -> ActorResult {
if self.i < ACTOR_COUNT {
let i = self.i;
self.i += 1;
let spawner_ref = Arc::clone(&self.self_ref);
ACTORS_CREATED.fetch_add(1, Ordering::Relaxed);
t.spawn(
Some(rec![AnyValue::symbol("forwarder"), AnyValue::new(i)]),
move |t| {
let _ = t.prevent_inert_check();
let f = t.create(Forwarder {
next,
});
t.message(&spawner_ref, f);
Ok(())
});
} else {
let mut c_state = Counter {
start: Instant::now(),
tx: self.tx.clone(),
remaining_to_send: self.iters,
iters: self.iters,
next,
};
c_state.step(t)?;
self.c.become_entity(c_state);
}
Ok(())
}
}
impl Entity<Arc<Ref<()>>> for Spawner {
fn message(&mut self, t: &mut Activation, f: Arc<Ref<()>>) -> ActorResult {
self.step(t, f)
}
}
ACTORS_CREATED.fetch_add(1, Ordering::Relaxed);
Actor::top(None, move |t| {
let _ = t.prevent_inert_check();
let mut s = Spawner {
self_ref: t.create_inert(),
tx,
iters,
i: 1,
c: t.create_inert(),
};
s.step(t, Arc::clone(&s.c))?;
Arc::clone(&s.self_ref).become_entity(s);
Ok(())
}).await.unwrap().unwrap();
});
rx.recv().unwrap()
})
});
}
criterion_group!(ring, bench_ring);
criterion_main!(ring);

View File

@ -1,36 +0,0 @@
use preserves_schema::compiler::*;
mod syndicate_plugins {
use preserves_schema::compiler::*;
use preserves_schema::gen::schema::*;
// use preserves_schema::syntax::block::constructors::*;
#[derive(Debug)]
pub(super) struct PatternPlugin;
impl Plugin for PatternPlugin {
fn generate_definition(
&self,
_m: &mut context::ModuleContext,
_definition_name: &str,
_definition: &Definition,
) {
// TODO: Emit code for building instances of sturdy.Pattern and sturdy.Template
}
}
}
fn main() -> std::io::Result<()> {
let buildroot = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
let mut gen_dir = buildroot.clone();
gen_dir.push("src/schemas");
let mut c = CompilerConfig::new("crate::schemas".to_owned());
c.plugins.push(Box::new(syndicate_plugins::PatternPlugin));
c.add_external_module(ExternalModule::new(vec!["EntityRef".to_owned()], "crate::actor"));
let inputs = expand_inputs(&vec!["protocols/schema-bundle.bin".to_owned()])?;
c.load_schemas_and_bundles(&inputs, &vec![])?;
compile(&c, &mut CodeCollector::files(gen_dir))
}

View File

@ -1,18 +0,0 @@
The [actor][crate::actor] module is the core of the Syndicated Actor model implementation.
Central features:
- struct [`Activation`], the API for programming a Syndicated Actor
object
- trait [`Entity`], the core protocol that must be implemented by
every object
- struct [`Facet`], a node in the tree of nested conversations that
an Actor is participating in
- type [`AnyValue`], the type of messages and assertions that can be
exchanged among distributed objects, including via
[dataspace][crate::dataspace]
- struct [`Ref<M>`], a reference to a local or remote object
- struct [`Cap`], a specialization of `Ref<M>` for
messages/assertions of type `AnyValue`
- struct [`Guard`], an adapter for converting an underlying
[`Ref<M>`] to a [`Cap`]

View File

@ -1,76 +0,0 @@
# Flow control
- struct [`Account`]
- struct [`LoanedItem`]
In order to handle high-speed scenarios where actors can become
overloaded by incoming events, this crate takes a
([possibly novel](https://syndicate-lang.org/journal/2021/09/02/internal-flow-control))
approach to *internal flow control* that is a variant of "credit-based
flow control" (as widely used in telephony systems).
The idea is to associate each individually-identifiable activity in an
actor system with an [*account*][Account] that records how much
outstanding work has to be done, system-wide, to fully complete
processing of that activity.
Each Actor scheduling new activities in response to some external
source (e.g while reading from a network socket in a
[linked task][Activation::linked_task]) calls
[`Account::ensure_clear_funds`] on its associated [`Account`]. This
will suspend the actor until enough of the account's "debt" has been
"cleared". (In the case of reading from a socket, this causes the TCP
socket's read buffers to fill up and the TCP window to close, which
throttles upstream senders.)
Every time any actor sends an event to any other actor, a
[`LoanedItem`] is constructed which "borrows" enough credit from some
nominated [`Account`] to cover the event. Crucially, when an actor is
*responding* to an event by *sending* more events, the account chosen
is *the one that the triggering event was charged to*. This lets the
server automatically account for fan-out of events.[^corollary]
Finally, once a `LoanedItem` is completely processed (i.e. when it is
[dropped][LoanedItem::drop]), its cost is "repaid" to its associated
account.
## Does it work?
Anecdotally, this approach appears to work well. Experimenting using
`syndicate-server` with producers sending as quickly as they can,
producers are throttled by the server, and the server seems stable
even though its consumers are not able to keep up with the unthrottled
send rate of each producer.
## Example
Imagine an actor *A* receiving publications from a TCP/IP socket. If
it ever "owes" more than, say, 5 units of cost on its account, it
stops reading from its socket until its debt decreases. Each message
it forwards on to another actor costs it 1 unit. Say a given incoming
message *M* is routed to a dataspace actor *D* (thereby charging *A*'s
account 1 unit), where it results in nine outbound events *M* to peer
actors *O1*···*O9*.
Then, when *D* receives *M*, 1 unit is repaid to *A*'s account. When
*D* sends *M* on to each of *O1*···*O9*, 1 unit is charged to *A*'s
account, resulting in a total of 9 units charged. At this point in
time, *A*'s account has had net +11+9 = 9 units withdrawn from it as
a result of *M*'s processing.
Imagine now that all of *O1*···*O9* are busy with other work. Then,
next time around *A*'s main loop, *A* notices that its outstanding
debt is higher than its configured threshold, and stops reading from
its socket. As each of *O1*···*O9* eventually gets around to
processing its copy of *M*, it repays the associated 1 unit to *A*'s
account.[^may-result-in-further-costs] Eventually, *A*'s account drops
below the threshold, *A* is woken up, and it resumes reading from its
socket.
[^corollary]: A corollary to this is that, for each event internal to
the system, you can potentially identify the "ultimate cause" of
the event: namely, the actor owning the associated account.
[^may-result-in-further-costs]: Of course, if *O1*, say, sends *more*
events internally as a result of receiving *M*, more units will
be charged to *A*'s account!

View File

@ -1,3 +0,0 @@
# Linked Tasks
- [Activation::linked_task]

View File

@ -1,38 +0,0 @@
# What is an Actor?
A [Syndicated Actor][Actor] ([Garnock-Jones 2017](#GarnockJones2017))
is a collection of stateful [Entities][Entity], organised in a tree of
[Facets][Facet], with each facet representing a
[(sub)conversation](https://syndicate-lang.org/about/#conversational-concurrency-1)
that the Actor is engaged in. Each entity belongs to exactly one
facet; each facet has exactly one parent and zero or more children;
each actor has exactly one associated root facet. When a facet is its
actor's root facet, its parent is the actor itself; otherwise, its
parent is always another facet.
In the taxonomy of De Koster *et al.* ([2016](#DeKoster2016)), the
Syndicated Actor model is a *Communicating Event-Loop* actor model,
similar to that offered by the E programming language
([Wikipedia](https://en.wikipedia.org/wiki/E_(programming_language));
[erights.org](http://erights.org/)).
- [Actor], [ActorRef], [Facet], [FacetRef], [ActorState], [Mailbox],
[Activation]
**References.**
- De Koster, Joeri, Tom Van Cutsem, and Wolfgang De Meuter. <a
name="DeKoster2016"
href="http://soft.vub.ac.be/Publications/2016/vub-soft-tr-16-11.pdf">“43
Years of Actors: A Taxonomy of Actor Models and Their Key
Properties.”</a> In Proc. AGERE, 3140. Amsterdam, The
Netherlands, 2016. [DOI](https://doi.org/10.1145/3001886.3001890).
[PDF](http://soft.vub.ac.be/Publications/2016/vub-soft-tr-16-11.pdf).
- Garnock-Jones, Tony. <a name="GarnockJones2017"
href="https://syndicate-lang.org/tonyg-dissertation/html/">“Conversational
Concurrency.”</a> PhD, Northeastern University, 2017.
[Permalink](http://hdl.handle.net/2047/D20261862).
[PDF@Northeastern](https://repository.library.northeastern.edu/files/neu:cj82qs441/fulltext.pdf).
[PDF@syndicate-lang.org](https://syndicate-lang.org/papers/conversational-concurrency-201712310922.pdf).
[HTML](https://syndicate-lang.org/tonyg-dissertation/html/).

View File

@ -1,8 +0,0 @@
all: schema-bundle.bin
clean:
rm -f schema-bundle.bin
schema-bundle.bin: schemas/*.prs
preserves-schemac schemas > $@.tmp
mv $@.tmp $@

View File

@ -1,44 +0,0 @@
´³bundle·µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³
tcp-remote„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpPeerInfo´³rec´³lit³tcp-peer„´³tupleµ´³named³handle´³embedded³any„„´³named³local´³refµ„³TcpLocal„„´³named³remote´³refµ„³ TcpRemote„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³http„´³schema·³version°³ definitions·³Chunk´³orµµ±string´³atom³String„„µ±bytes´³atom³
ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³
QueryValue´³orµµ±string´³atom³String„„µ±file´³rec´³lit³file„´³tupleµ´³named³filename´³atom³String„„´³named³headers´³refµ„³Headers„„´³named³body´³atom³
ByteString„„„„„„„„³ HostPattern´³orµµ±host´³atom³String„„µ±any´³lit€„„„„³ HttpBinding´³rec´³lit³ http-bind„´³tupleµ´³named³host´³refµ„³ HostPattern„„´³named³port´³atom³ SignedInteger„„´³named³method´³refµ„³ MethodPattern„„´³named³path´³refµ„³ PathPattern„„´³named³handler´³embedded´³refµ„³ HttpRequest„„„„„„³ HttpContext´³rec´³lit³request„´³tupleµ´³named³req´³refµ„³ HttpRequest„„´³named³res´³embedded´³refµ„³ HttpResponse„„„„„„³ HttpRequest´³rec´³lit³ http-request„´³tupleµ´³named³sequenceNumber´³atom³ SignedInteger„„´³named³host´³refµ„³ RequestHost„„´³named³port´³atom³ SignedInteger„„´³named³method´³atom³Symbol„„´³named³path´³seqof´³atom³String„„„´³named³headers´³refµ„³Headers„„´³named³query´³dictof´³atom³Symbol„´³seqof´³refµ„³
QueryValue„„„„´³named³body´³refµ„³ RequestBody„„„„„³ HttpService´³rec´³lit³ http-service„´³tupleµ´³named³host´³refµ„³ HostPattern„„´³named³port´³atom³ SignedInteger„„´³named³method´³refµ„³ MethodPattern„„´³named³path´³refµ„³ PathPattern„„„„„³ PathPattern´³seqof´³refµ„³PathPatternElement„„³ RequestBody´³orµµ±present´³atom³
ByteString„„µ±absent´³lit€„„„„³ RequestHost´³orµµ±present´³atom³String„„µ±absent´³lit€„„„„³ HttpListener´³rec´³lit³ http-listener„´³tupleµ´³named³port´³atom³ SignedInteger„„„„„³ HttpResponse´³orµµ±status´³rec´³lit³status„´³tupleµ´³named³code´³atom³ SignedInteger„„´³named³message´³atom³String„„„„„„µ±header´³rec´³lit³header„´³tupleµ´³named³name´³atom³Symbol„„´³named³value´³atom³String„„„„„„µ±chunk´³rec´³lit³chunk„´³tupleµ´³named³chunk´³refµ„³Chunk„„„„„„µ±done´³rec´³lit³done„´³tupleµ´³named³chunk´³refµ„³Chunk„„„„„„„„³ MethodPattern´³orµµ±any´³lit€„„µ±specific´³atom³Symbol„„„„³PathPatternElement´³orµµ±label´³atom³String„„µ±wildcard´³lit³_„„µ±rest´³lit³...„„„„„³ embeddedType€„„µ³noise„´³schema·³version°³ definitions·³Packet´³orµµ±complete´³atom³
ByteString„„µ±
fragmented´³seqof´³atom³
ByteString„„„„„³ Initiator´³rec´³lit³ initiator„´³tupleµ´³named³initiatorSession´³embedded´³refµ„³Packet„„„„„„³ NoiseSpec´³andµ´³dict·³key´³named³key´³atom³
ByteString„„³service´³named³service´³refµ„³ServiceSelector„„„„´³named³protocol´³refµ„³ NoiseProtocol„„´³named³ preSharedKeys´³refµ„³NoisePreSharedKeys„„„„³ SessionItem´³orµµ± Initiator´³refµ„³ Initiator„„µ±Packet´³refµ„³Packet„„„„³ NoiseProtocol´³orµµ±present´³dict·³protocol´³named³protocol´³atom³String„„„„„µ±invalid´³dict·³protocol´³named³protocol³any„„„„µ±absent´³dict·„„„„„³ NoiseStepType´³lit³noise„³SecretKeyField´³orµµ±present´³dict·³ secretKey´³named³ secretKey´³atom³
ByteString„„„„„µ±invalid´³dict·³ secretKey´³named³ secretKey³any„„„„µ±absent´³dict·„„„„„³DefaultProtocol´³lit±!Noise_NK_25519_ChaChaPoly_BLAKE2s„³NoiseStepDetail´³refµ„³ServiceSelector„³ServiceSelector³any³NoiseServiceSpec´³andµ´³named³base´³refµ„³ NoiseSpec„„´³named³ secretKey´³refµ„³SecretKeyField„„„„³NoisePreSharedKeys´³orµµ±present´³dict·³ preSharedKeys´³named³ preSharedKeys´³seqof´³atom³
ByteString„„„„„„µ±invalid´³dict·³ preSharedKeys´³named³ preSharedKeys³any„„„„µ±absent´³dict·„„„„„³NoisePathStepDetail´³refµ„³ NoiseSpec„³NoiseDescriptionDetail´³refµ„³NoiseServiceSpec„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³timer„´³schema·³version°³ definitions·³SetTimer´³rec´³lit³ set-timer„´³tupleµ´³named³label³any„´³named³seconds´³atom³Double„„´³named³kind´³refµ„³ TimerKind„„„„„³ LaterThan´³rec´³lit³
later-than„´³tupleµ´³named³seconds´³atom³Double„„„„„³ TimerKind´³orµµ±relative´³lit³relative„„µ±absolute´³lit³absolute„„µ±clear´³lit³clear„„„„³ TimerExpired´³rec´³lit³ timer-expired„´³tupleµ´³named³label³any„´³named³seconds´³atom³Double„„„„„„³ embeddedType€„„µ³trace„´³schema·³version°³ definitions·³Oid³any³Name´³orµµ± anonymous´³rec´³lit³ anonymous„´³tupleµ„„„„µ±named´³rec´³lit³named„´³tupleµ´³named³name³any„„„„„„„³Target´³rec´³lit³entity„´³tupleµ´³named³actor´³refµ„³ActorId„„´³named³facet´³refµ„³FacetId„„´³named³oid´³refµ„³Oid„„„„„³TaskId³any³TurnId³any³ActorId³any³FacetId³any³ TurnCause´³orµµ±turn´³rec´³lit³ caused-by„´³tupleµ´³named³id´³refµ„³TurnId„„„„„„µ±cleanup´³rec´³lit³cleanup„´³tupleµ„„„„µ±linkedTaskRelease´³rec´³lit³linked-task-release„´³tupleµ´³named³id´³refµ„³TaskId„„´³named³reason´³refµ„³LinkedTaskReleaseReason„„„„„„µ±periodicActivation´³rec´³lit³periodic-activation„´³tupleµ´³named³period´³atom³Double„„„„„„µ±delay´³rec´³lit³delay„´³tupleµ´³named³ causingTurn´³refµ„³TurnId„„´³named³amount´³atom³Double„„„„„„µ±external´³rec´³lit³external„´³tupleµ´³named³ description³any„„„„„„„³ TurnEvent´³orµµ±assert´³rec´³lit³assert„´³tupleµ´³named³ assertion´³refµ„³AssertionDescription„„´³named³handle´³refµ³protocol„³Handle„„„„„„µ±retract´³rec´³lit³retract„´³tupleµ´³named³handle´³refµ³protocol„³Handle„„„„„„µ±message´³rec´³lit³message„´³tupleµ´³named³body´³refµ„³AssertionDescription„„„„„„µ±sync´³rec´³lit³sync„´³tupleµ´³named³peer´³refµ„³Target„„„„„„µ± breakLink´³rec´³lit³
break-link„´³tupleµ´³named³source´³refµ„³ActorId„„´³named³handle´³refµ³protocol„³Handle„„„„„„„„³
ExitStatus´³orµµ±ok´³lit³ok„„µ±Error´³refµ³protocol„³Error„„„„³
TraceEntry´³rec´³lit³trace„´³tupleµ´³named³ timestamp´³atom³Double„„´³named³actor´³refµ„³ActorId„„´³named³item´³refµ„³ActorActivation„„„„„³ActorActivation´³orµµ±start´³rec´³lit³start„´³tupleµ´³named³ actorName´³refµ„³Name„„„„„„µ±turn´³refµ„³TurnDescription„„µ±stop´³rec´³lit³stop„´³tupleµ´³named³status´³refµ„³
ExitStatus„„„„„„„„³FacetStopReason´³orµµ±explicitAction´³lit³explicit-action„„µ±inert´³lit³inert„„µ±parentStopping´³lit³parent-stopping„„µ± actorStopping´³lit³actor-stopping„„„„³TurnDescription´³rec´³lit³turn„´³tupleµ´³named³id´³refµ„³TurnId„„´³named³cause´³refµ„³ TurnCause„„´³named³actions´³seqof´³refµ„³ActionDescription„„„„„„³ActionDescription´³orµµ±dequeue´³rec´³lit³dequeue„´³tupleµ´³named³event´³refµ„³TargetedTurnEvent„„„„„„µ±enqueue´³rec´³lit³enqueue„´³tupleµ´³named³event´³refµ„³TargetedTurnEvent„„„„„„µ±dequeueInternal´³rec´³lit³dequeue-internal„´³tupleµ´³named³event´³refµ„³TargetedTurnEvent„„„„„„µ±enqueueInternal´³rec´³lit³enqueue-internal„´³tupleµ´³named³event´³refµ„³TargetedTurnEvent„„„„„„µ±spawn´³rec´³lit³spawn„´³tupleµ´³named³link´³atom³Boolean„„´³named³id´³refµ„³ActorId„„„„„„µ±link´³rec´³lit³link„´³tupleµ´³named³ parentActor´³refµ„³ActorId„„´³named³ childToParent´³refµ³protocol„³Handle„„´³named³
childActor´³refµ„³ActorId„„´³named³ parentToChild´³refµ³protocol„³Handle„„„„„„µ±
facetStart´³rec´³lit³ facet-start„´³tupleµ´³named³path´³seqof´³refµ„³FacetId„„„„„„„µ± facetStop´³rec´³lit³
facet-stop„´³tupleµ´³named³path´³seqof´³refµ„³FacetId„„„´³named³reason´³refµ„³FacetStopReason„„„„„„µ±linkedTaskStart´³rec´³lit³linked-task-start„´³tupleµ´³named³taskName´³refµ„³Name„„´³named³id´³refµ„³TaskId„„„„„„„„³TargetedTurnEvent´³rec´³lit³event„´³tupleµ´³named³target´³refµ„³Target„„´³named³detail´³refµ„³ TurnEvent„„„„„³AssertionDescription´³orµµ±value´³rec´³lit³value„´³tupleµ´³named³value³any„„„„„µ±opaque´³rec´³lit³opaque„´³tupleµ´³named³ description³any„„„„„„„³LinkedTaskReleaseReason´³orµµ± cancelled´³lit³ cancelled„„µ±normal´³lit³normal„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³stdenv„´³schema·³version°³ definitions·³ StandardRoute´³orµµ±standard´³ tuplePrefixµ´³named³
transports´³seqof´³refµ„³StandardTransport„„„´³named³key´³atom³
ByteString„„´³named³service³any„´³named³sig´³atom³
ByteString„„´³named³oid³any„„´³named³caveats´³seqof´³refµ³sturdy„³Caveat„„„„„µ±general´³refµ³
gatekeeper„³Route„„„„³StandardTransport´³orµµ±wsUrl´³atom³String„„µ±other³any„„„„³ embeddedType€„„µ³stream„´³schema·³version°³ definitions·³Mode´³orµµ±bytes´³lit³bytes„„µ±lines´³refµ„³LineMode„„µ±packet´³rec´³lit³packet„´³tupleµ´³named³size´³atom³ SignedInteger„„„„„„µ±object´³rec´³lit³object„´³tupleµ´³named³ description³any„„„„„„„³Sink´³orµµ±source´³rec´³lit³source„´³tupleµ´³named³
controller´³embedded´³refµ„³Source„„„„„„„µ± StreamError´³refµ„³ StreamError„„µ±data´³rec´³lit³data„´³tupleµ´³named³payload³any„´³named³mode´³refµ„³Mode„„„„„„µ±eof´³rec´³lit³eof„´³tupleµ„„„„„„³Source´³orµµ±sink´³rec´³lit³sink„´³tupleµ´³named³
controller´³embedded´³refµ„³Sink„„„„„„„µ± StreamError´³refµ„³ StreamError„„µ±credit´³rec´³lit³credit„´³tupleµ´³named³amount´³refµ„³ CreditAmount„„´³named³mode´³refµ„³Mode„„„„„„„„³LineMode´³orµµ±lf´³lit³lf„„µ±crlf´³lit³crlf„„„„³ StreamError´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„„„„³ CreditAmount´³orµµ±count´³atom³ SignedInteger„„µ± unbounded´³lit³ unbounded„„„„³StreamConnection´³rec´³lit³stream-connection„´³tupleµ´³named³source´³embedded´³refµ„³Source„„„´³named³sink´³embedded´³refµ„³Sink„„„´³named³spec³any„„„„³StreamListenerError´³rec´³lit³stream-listener-error„´³tupleµ´³named³spec³any„´³named³message´³atom³String„„„„„³StreamListenerReady´³rec´³lit³stream-listener-ready„´³tupleµ´³named³spec³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³sturdy„´³schema·³version°³ definitions·³Lit´³rec´³lit³lit„´³tupleµ´³named³value³any„„„„³Oid´³atom³ SignedInteger„³Alts´³rec´³lit³or„´³tupleµ´³named³ alternatives´³seqof´³refµ„³Rewrite„„„„„„³PAnd´³rec´³lit³and„´³tupleµ´³named³patterns´³seqof´³refµ„³Pattern„„„„„„³PNot´³rec´³lit³not„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³TRef´³rec´³lit³ref„´³tupleµ´³named³binding´³atom³ SignedInteger„„„„„³PAtom´³orµµ±Boolean´³lit³Boolean„„µ±Double´³lit³Double„„µ± SignedInteger´³lit³ SignedInteger„„µ±String´³lit³String„„µ±
ByteString´³lit³
ByteString„„µ±Symbol´³lit³Symbol„„„„³PBind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Caveat´³orµµ±Rewrite´³refµ„³Rewrite„„µ±Alts´³refµ„³Alts„„µ±Reject´³refµ„³Reject„„µ±unknown³any„„„³Reject´³rec´³lit³reject„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Pattern´³orµµ±PDiscard´³refµ„³PDiscard„„µ±PAtom´³refµ„³PAtom„„µ± PEmbedded´³refµ„³ PEmbedded„„µ±PBind´³refµ„³PBind„„µ±PAnd´³refµ„³PAnd„„µ±PNot´³refµ„³PNot„„µ±Lit´³refµ„³Lit„„µ± PCompound´³refµ„³ PCompound„„„„³Rewrite´³rec´³lit³rewrite„´³tupleµ´³named³pattern´³refµ„³Pattern„„´³named³template´³refµ„³Template„„„„„³WireRef´³orµµ±mine´³tupleµ´³lit°„´³named³oid´³refµ„³Oid„„„„„µ±yours´³ tuplePrefixµ´³lit°„´³named³oid´³refµ„³Oid„„„´³named³ attenuation´³seqof´³refµ„³Caveat„„„„„„„³PDiscard´³rec´³lit³_„´³tupleµ„„„³Template´³orµµ±
TAttenuate´³refµ„³
TAttenuate„„µ±TRef´³refµ„³TRef„„µ±Lit´³refµ„³Lit„„µ± TCompound´³refµ„³ TCompound„„„„³ PCompound´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„´³named³fields´³seqof´³refµ„³Pattern„„„„„„„µ±arr´³rec´³lit³arr„´³tupleµ´³named³items´³seqof´³refµ„³Pattern„„„„„„„µ±dict´³rec´³lit³dict„´³tupleµ´³named³entries´³dictof³any´³refµ„³Pattern„„„„„„„„„³ PEmbedded´³lit³Embedded„³ SturdyRef´³rec´³lit³ref„´³tupleµ´³named³
parameters´³refµ„³
Parameters„„„„„³ TCompound´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„´³named³fields´³seqof´³refµ„³Template„„„„„„„µ±arr´³rec´³lit³arr„´³tupleµ´³named³items´³seqof´³refµ„³Template„„„„„„„µ±dict´³rec´³lit³dict„´³tupleµ´³named³entries´³dictof³any´³refµ„³Template„„„„„„„„„³
Parameters´³andµ´³dict·³oid´³named³oid³any„³sig´³named³sig´³atom³
ByteString„„„„´³named³caveats´³refµ„³ CaveatsField„„„„³
TAttenuate´³rec´³lit³ attenuate„´³tupleµ´³named³template´³refµ„³Template„„´³named³ attenuation´³seqof´³refµ„³Caveat„„„„„„³ CaveatsField´³orµµ±present´³dict·³caveats´³named³caveats´³seqof´³refµ„³Caveat„„„„„„µ±invalid´³dict·³caveats´³named³caveats³any„„„„µ±absent´³dict·„„„„„³SturdyStepType´³lit³ref„³SturdyStepDetail´³refµ„³
Parameters„³SturdyPathStepDetail´³refµ„³
Parameters„³SturdyDescriptionDetail´³dict·³key´³named³key´³atom³
ByteString„„³oid´³named³oid³any„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³worker„´³schema·³version°³ definitions·³Instance´³rec´³lit³Instance„´³tupleµ´³named³name´³atom³String„„´³named³argument³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³service„´³schema·³version°³ definitions·³State´³orµµ±started´³lit³started„„µ±ready´³lit³ready„„µ±failed´³lit³failed„„µ±complete´³lit³complete„„µ± userDefined³any„„„³
RunService´³rec´³lit³ run-service„´³tupleµ´³named³ serviceName³any„„„„³ ServiceState´³rec´³lit³ service-state„´³tupleµ´³named³ serviceName³any„´³named³state´³refµ„³State„„„„„³ ServiceObject´³rec´³lit³service-object„´³tupleµ´³named³ serviceName³any„´³named³object³any„„„„³RequireService´³rec´³lit³require-service„´³tupleµ´³named³ serviceName³any„„„„³RestartService´³rec´³lit³restart-service„´³tupleµ´³named³ serviceName³any„„„„³ServiceDependency´³rec´³lit³
depends-on„´³tupleµ´³named³depender³any„´³named³dependee´³refµ„³ ServiceState„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³protocol„´³schema·³version°³ definitions·³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³S„´³tupleµ´³named³peer´³embedded´³lit<69>„„„„„„³Turn´³seqof´³refµ„³ TurnEvent„„³Error´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„´³named³detail³any„„„„³Event´³orµµ±Assert´³refµ„³Assert„„µ±Retract´³refµ„³Retract„„µ±Message´³refµ„³Message„„µ±Sync´³refµ„³Sync„„„„³Assert´³rec´³lit³A„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„µ± Extension´³refµ„³ Extension„„„„³Message´³rec´³lit³M„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³R„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ Extension´³rec´³named³label³any„´³named³fields´³seqof³any„„„³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType€„„µ³ dataspace„´³schema·³version°³ definitions·³Observe´³rec´³lit³Observe„´³tupleµ´³named³pattern´³refµ³dataspacePatterns„³Pattern„„´³named³observer´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³
gatekeeper„´³schema·³version°³ definitions·³Bind´³rec´³lit³bind„´³tupleµ´³named³ description´³refµ„³ Description„„´³named³target´³embedded³any„„´³named³observer´³refµ„³ BindObserver„„„„„³Step´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Bound´³orµµ±bound´³rec´³lit³bound„´³tupleµ´³named³pathStep´³refµ„³PathStep„„„„„„µ±Rejected´³refµ„³Rejected„„„„³Route´³rec´³lit³route„´³ tuplePrefixµ´³named³
transports´³seqof³any„„„´³named³ pathSteps´³seqof´³refµ„³PathStep„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³step´³refµ„³Step„„´³named³observer´³embedded´³refµ„³Resolved„„„„„„³PathStep´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Rejected´³rec´³lit³rejected„´³tupleµ´³named³detail³any„„„„³Resolved´³orµµ±accepted´³rec´³lit³accepted„´³tupleµ´³named³responderSession´³embedded³any„„„„„„µ±Rejected´³refµ„³Rejected„„„„³ Description´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³ ResolvePath´³rec´³lit³ resolve-path„´³tupleµ´³named³route´³refµ„³Route„„´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„³ BindObserver´³orµµ±present´³embedded´³refµ„³Bound„„„µ±absent´³lit€„„„„³ForceDisconnect´³rec´³lit³force-disconnect„´³tupleµ„„„³ResolvedPathStep´³rec´³lit³ path-step„´³tupleµ´³named³origin´³embedded´³refµ„³Resolve„„„´³named³pathStep´³refµ„³PathStep„„´³named³resolved´³refµ„³Resolved„„„„„³TransportControl´³refµ„³ForceDisconnect„³TransportConnection´³rec´³lit³connect-transport„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³transportAddress„´³schema·³version°³ definitions·³Tcp´³rec´³lit³tcp„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³Unix´³rec´³lit³unix„´³tupleµ´³named³path´³atom³String„„„„„³Stdio´³rec´³lit³stdio„´³tupleµ„„„³ WebSocket´³rec´³lit³ws„´³tupleµ´³named³url´³atom³String„„„„„„³ embeddedType€„„µ³dataspacePatterns„´³schema·³version°³ definitions·³AnyAtom´³orµµ±bool´³atom³Boolean„„µ±double´³atom³Double„„µ±int´³atom³ SignedInteger„„µ±string´³atom³String„„µ±bytes´³atom³
ByteString„„µ±symbol´³atom³Symbol„„µ±embedded´³embedded³any„„„„³Pattern´³orµµ±discard´³rec´³lit³_„´³tupleµ„„„„µ±bind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value´³refµ„³AnyAtom„„„„„„µ±group´³rec´³lit³group„´³tupleµ´³named³type´³refµ„³ GroupType„„´³named³entries´³dictof³any´³refµ„³Pattern„„„„„„„„„³ GroupType´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„„„„„µ±arr´³rec´³lit³arr„´³tupleµ„„„„µ±dict´³rec´³lit³dict„´³tupleµ„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„

View File

@ -1,4 +0,0 @@
version 1 .
embeddedType EntityRef.Cap .
Observe = <Observe @pattern dataspacePatterns.Pattern @observer #:any>.

Some files were not shown because too many files have changed in this diff Show More