Compare commits

..

1 Commits

110 changed files with 3279 additions and 10321 deletions

1
.gitignore vendored
View File

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

1736
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,31 +4,14 @@ cargo-features = ["strip"]
members = [ members = [
"syndicate", "syndicate",
"syndicate-macros", "syndicate-macros",
"syndicate-schema-plugin",
"syndicate-server", "syndicate-server",
"syndicate-tools",
] ]
# [patch.crates-io] # [patch.crates-io]
# # # preserves = { path = "/home/tonyg/src/preserves/implementations/rust/preserves" }
# # Use a bind mount for localdev: # preserves-schema = { path = "/home/tonyg/src/preserves/implementations/rust/preserves-schema" }
# #
# # 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" }
[profile.release] [profile.release]
strip = true strip = true
# debug = true # debug = true
# lto = true # lto = true
[profile.bench]
debug = 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" }

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

@ -11,28 +11,33 @@ test:
test-all: test-all:
cargo test --all-targets cargo test --all-targets
ws-bump: # Try
cargo workspaces version \ #
--no-global-tag \ # make release-minor
--individual-tag-prefix '%n-v' \ #
--allow-branch 'main' \ # to check things, and
$(BUMP_ARGS) #
# make release-minor RELEASE_DRY_RUN=
#
# to do things for real.
ws-publish: RELEASE_DRY_RUN=--dry-run
cargo workspaces publish \ release-%:
--from-git PUBLISH_GRACE_SLEEP=15 cargo release \
$(RELEASE_DRY_RUN) \
-vv --no-dev-version --exclude-unchanged \
$*
PROTOCOLS_BRANCH=main
pull-protocols: pull-protocols:
git subtree pull -P syndicate/protocols \ git subtree pull -P syndicate/protocols \
-m 'Merge latest changes from the syndicate-protocols repository' \ -m 'Merge latest changes from the syndicate-protocols repository' \
git@git.syndicate-lang.org:syndicate-lang/syndicate-protocols \ git@git.syndicate-lang.org:syndicate-lang/syndicate-protocols \
$(PROTOCOLS_BRANCH) main
static: static-x86_64 static: static-x86_64
static-%: static-%:
CARGO_TARGET_DIR=target/target.$* cross build --target $*-unknown-linux-musl --features vendored-openssl,jemalloc cross build --target $*-unknown-linux-musl --features vendored-openssl
########################################################################### ###########################################################################
@ -51,30 +56,18 @@ static-%:
# etc, ready on my system despite being otherwise able to rely on # etc, ready on my system despite being otherwise able to rely on
# cross. I think. It's a bit confusing. # cross. I think. It's a bit confusing.
x86_64-binary: x86_64-binary-release arm-binary: arm-binary-release
x86_64-binary-release: arm-binary-release:
CARGO_TARGET_DIR=target/target.x86_64 cross build --target x86_64-unknown-linux-musl --release --all-targets --features vendored-openssl,jemalloc cross build --target=armv7-unknown-linux-musleabihf --release --all-targets --features vendored-openssl
x86_64-binary-debug: arm-binary-debug:
CARGO_TARGET_DIR=target/target.x86_64 cross build --target x86_64-unknown-linux-musl --all-targets --features vendored-openssl cross build --target=armv7-unknown-linux-musleabihf --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: aarch64-binary-release
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 cross build --target=aarch64-unknown-linux-musl --release --all-targets --features vendored-openssl
aarch64-binary-debug: aarch64-binary-debug:
CARGO_TARGET_DIR=target/target.aarch64 cross build --target=aarch64-unknown-linux-musl --all-targets --features vendored-openssl cross build --target=aarch64-unknown-linux-musl --all-targets --features vendored-openssl

View File

@ -23,30 +23,16 @@ A Rust implementation of:
## Quickstart ## 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 git clone https://git.syndicate-lang.org/syndicate-lang/syndicate-rs
cd syndicate-rs cd syndicate-rs
cargo build --release cargo build --release
./target/release/syndicate-server -p 8001 ./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.
## Running the examples ## 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 -p 8001
Then, choose one of the examples below. Then, choose one of the examples below.
@ -84,7 +70,7 @@ about who kicks off the pingpong session.
You may find better performance by restricting the server to fewer You may find better performance by restricting the server to fewer
cores than you have available. For example, for me, running 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 -p 8001
roughly *doubles* throughput for a single producer/consumer pair, roughly *quadruples* throughput for a single producer/consumer pair,
on my 48-core AMD CPU. on my 48-core AMD CPU.

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 +1,2 @@
#!/bin/sh #!/bin/sh
while true; do ../target/release/examples/dirty-consumer "$@"; sleep 2; done while true; do ../target/release/examples/consumer "$@"; sleep 2; done

View File

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

View File

@ -1,7 +1,2 @@
#!/bin/sh #!/bin/sh
TASKSET='taskset -c 0,1' make -C ../syndicate-server binary && exec taskset -c 0,1 ../target/release/syndicate-server -p 8001 "$@"
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"

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,6 +1,6 @@
[package] [package]
name = "syndicate-macros" name = "syndicate-macros"
version = "0.32.0" version = "0.5.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"] authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018" edition = "2018"
@ -13,15 +13,8 @@ license = "Apache-2.0"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
syndicate = { path = "../syndicate", version = "0.40.0"} syndicate = { path = "../syndicate", version = "^0.10.0"}
proc-macro2 = { version = "^1.0", features = ["span-locations"] } proc-macro2 = { version = "^1.0", features = ["span-locations"] }
quote = "^1.0" quote = "^1.0"
syn = { version = "^1.0", features = ["extra-traits"] } # for impl Debug for syn::Expr syn = "^1.0"
[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

@ -18,16 +18,14 @@ use syn::Ident;
use syn::Lit; use syn::Lit;
use syn::LitByteStr; use syn::LitByteStr;
mod dur;
mod pat;
mod stx; mod stx;
mod pat;
mod val; mod val;
use pat::lit; use pat::lit;
enum SymbolVariant<'a> { enum SymbolVariant<'a> {
Normal(&'a str), Normal(&'a str),
#[allow(dead_code)] // otherwise we get 'warning: field `0` is never read'
Binder(&'a str), Binder(&'a str),
Substitution(&'a str), Substitution(&'a str),
Discard, Discard,
@ -36,7 +34,7 @@ enum SymbolVariant<'a> {
fn compile_sequence_members(vs: &[IOValue]) -> Vec<TokenStream> { fn compile_sequence_members(vs: &[IOValue]) -> Vec<TokenStream> {
vs.iter().enumerate().map(|(i, f)| { vs.iter().enumerate().map(|(i, f)| {
let p = compile_pattern(f); let p = compile_pattern(f);
quote!((syndicate::value::Value::from(#i).wrap(), #p)) quote!((#i .into(), #p))
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
} }
@ -80,6 +78,10 @@ impl ValueCompiler {
match v.value() { match v.value() {
Value::Boolean(b) => Value::Boolean(b) =>
quote!(#V_::Value::from(#b).wrap()), quote!(#V_::Value::from(#b).wrap()),
Value::Float(f) => {
let f = f.0;
quote!(#V_::Value::from(#f).wrap())
}
Value::Double(d) => { Value::Double(d) => {
let d = d.0; let d = d.0;
quote!(#V_::Value::from(#d).wrap()) quote!(#V_::Value::from(#d).wrap())
@ -151,26 +153,31 @@ fn compile_pattern(v: &IOValue) -> TokenStream {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let V_: TokenStream = quote!(syndicate::value); let V_: TokenStream = quote!(syndicate::value);
#[allow(non_snake_case)] #[allow(non_snake_case)]
let MapFrom_: TokenStream = quote!(<#V_::Map<_, _>>::from); let MapFromIterator_: TokenStream = quote!(<#V_::Map<_, _> as std::iter::FromIterator<_>>::from_iter);
match v.value() { match v.value() {
Value::Symbol(s) => match analyze_symbol(&s, true) { Value::Symbol(s) => match analyze_symbol(&s, true) {
SymbolVariant::Binder(_) => SymbolVariant::Binder(_) =>
quote!(#P_::Pattern::Bind{ pattern: Box::new(#P_::Pattern::Discard) }), quote!(#P_::Pattern::DBind(Box::new(#P_::DBind {
pattern: #P_::Pattern::DDiscard(Box::new(#P_::DDiscard))
}))),
SymbolVariant::Discard => SymbolVariant::Discard =>
quote!(#P_::Pattern::Discard), quote!(#P_::Pattern::DDiscard(Box::new(#P_::DDiscard))),
SymbolVariant::Substitution(s) => SymbolVariant::Substitution(s) =>
lit(Ident::new(s, Span::call_site())), lit(Ident::new(s, Span::call_site())),
SymbolVariant::Normal(_) => SymbolVariant::Normal(_) =>
lit(ValueCompiler::for_patterns().compile(v)), lit(ValueCompiler::for_patterns().compile(v)),
} }
Value::Record(r) => { Value::Record(r) => {
let arity = r.arity() as u128;
match r.label().value().as_symbol() { match r.label().value().as_symbol() {
None => panic!("Record labels in patterns must be symbols"), None => panic!("Record labels in patterns must be symbols"),
Some(label) => Some(label) =>
if label.starts_with("$") && r.arity() == 1 { if label.starts_with("$") && arity == 1 {
let nested = compile_pattern(&r.fields()[0]); let nested = compile_pattern(&r.fields()[0]);
quote!(#P_::Pattern::Bind{ pattern: Box::new(#nested) }) quote!(#P_::Pattern::DBind(Box::new(#P_::DBind {
pattern: #nested
})))
} else { } else {
let label_stx = if label.starts_with("=") { let label_stx = if label.starts_with("=") {
let id = Ident::new(&label[1..], Span::call_site()); let id = Ident::new(&label[1..], Span::call_site());
@ -179,19 +186,25 @@ fn compile_pattern(v: &IOValue) -> TokenStream {
quote!(#V_::Value::symbol(#label).wrap()) quote!(#V_::Value::symbol(#label).wrap())
}; };
let members = compile_sequence_members(r.fields()); let members = compile_sequence_members(r.fields());
quote!(#P_::Pattern::Group { quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Rec {
type_: Box::new(#P_::GroupType::Rec { label: #label_stx }), ctor: Box::new(#P_::CRec {
entries: #MapFrom_([#(#members),*]), label: #label_stx,
}) arity: #arity .into(),
}),
members: #MapFromIterator_(vec![#(#members),*])
})))
} }
} }
} }
Value::Sequence(vs) => { Value::Sequence(vs) => {
let arity = vs.len() as u128;
let members = compile_sequence_members(vs); let members = compile_sequence_members(vs);
quote!(#P_::Pattern::Group { quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Arr {
type_: Box::new(#P_::GroupType::Arr), ctor: Box::new(#P_::CArr {
entries: #MapFrom_([#(#members),*]), arity: #arity .into(),
}) }),
members: #MapFromIterator_(vec![#(#members),*])
})))
} }
Value::Set(_) => Value::Set(_) =>
panic!("Cannot match sets in patterns"), panic!("Cannot match sets in patterns"),
@ -201,10 +214,10 @@ fn compile_pattern(v: &IOValue) -> TokenStream {
let v = compile_pattern(v); let v = compile_pattern(v);
quote!((#k, #v)) quote!((#k, #v))
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
quote!(#P_::Pattern::Group { quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Dict {
type_: Box::new(#P_::GroupType::Dict), ctor: Box::new(#P_::CDict),
entries: #MapFrom_([#(#members),*]), members: #MapFromIterator_(vec![#(#members),*])
}) })))
} }
_ => lit(ValueCompiler::for_patterns().compile(v)), _ => lit(ValueCompiler::for_patterns().compile(v)),
} }
@ -248,15 +261,3 @@ pub fn template(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn pattern(src: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn pattern(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
pat::pattern(src) 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

@ -7,81 +7,82 @@ use quote::quote;
use syn::parse_macro_input; use syn::parse_macro_input;
use crate::stx::Stx; use crate::stx::Stx;
use crate::val::emit_set;
use crate::val::to_value_expr; use crate::val::to_value_expr;
use crate::val::value_to_value_expr; use crate::val::value_to_value_expr;
pub fn lit<T: ToTokens>(e: T) -> TokenStream2 { pub fn lit<T: ToTokens>(e: T) -> TokenStream2 {
quote!(syndicate::pattern::lift_literal(#e)) quote!(
syndicate::schemas::dataspace_patterns::Pattern::DLit(Box::new(
syndicate::schemas::dataspace_patterns::DLit { value: #e })))
} }
fn compile_sequence_members(stxs: &Vec<Stx>) -> Result<Vec<TokenStream2>, &'static str> { fn compile_sequence_members(stxs: Vec<Stx>) -> Result<Vec<TokenStream2>, &'static str> {
stxs.iter().enumerate().map(|(i, stx)| { stxs.into_iter().enumerate().map(|(i, stx)| {
let p = to_pattern_expr(stx)?; let p = to_pattern_expr(stx)?;
Ok(quote!((syndicate::value::Value::from(#i).wrap(), #p))) Ok(quote!((#i .into(), #p)))
}).collect() }).collect()
} }
pub fn to_pattern_expr(stx: &Stx) -> Result<TokenStream2, &'static str> { fn to_pattern_expr(stx: Stx) -> Result<TokenStream2, &'static str> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let P_: TokenStream2 = quote!(syndicate::schemas::dataspace_patterns); let P_: TokenStream2 = quote!(syndicate::schemas::dataspace_patterns);
#[allow(non_snake_case)] #[allow(non_snake_case)]
let V_: TokenStream2 = quote!(syndicate::value); let V_: TokenStream2 = quote!(syndicate::value);
#[allow(non_snake_case)] #[allow(non_snake_case)]
let MapFrom_: TokenStream2 = quote!(<#V_::Map<_, _>>::from); let MapFromIterator_: TokenStream2 = quote!(<#V_::Map<_, _> as std::iter::FromIterator<_>>::from_iter);
match stx { match stx {
Stx::Atom(v) => Stx::Atom(v) =>
Ok(lit(value_to_value_expr(&v))), Ok(lit(value_to_value_expr(&v))),
Stx::Binder(_, maybe_ty, maybe_pat) => { Stx::Binder(_, p) => {
let inner_pat_expr = match maybe_pat { let pe = to_pattern_expr(*p)?;
Some(p) => to_pattern_expr(&*p)?, Ok(quote!(#P_::Pattern::DBind(Box::new(#P_::DBind { pattern: #pe }))))
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) => Stx::Subst(e) =>
Ok(lit(e)), Ok(lit(e)),
Stx::Discard => Stx::Discard =>
Ok(quote!(#P_::Pattern::Discard)), Ok(quote!(#P_::Pattern::DDiscard(Box::new(#P_::DDiscard)))),
Stx::Ctor1(_, _) => todo!(),
Stx::CtorN(_, _) => todo!(),
Stx::Rec(l, fs) => { Stx::Rec(l, fs) => {
let label = to_value_expr(&*l)?; let arity = fs.len() as u128;
let label = to_value_expr(*l)?;
let members = compile_sequence_members(fs)?; let members = compile_sequence_members(fs)?;
Ok(quote!(#P_::Pattern::Group { Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Rec {
type_: Box::new(#P_::GroupType::Rec { label: #label }), ctor: Box::new(#P_::CRec { label: #label, arity: #arity .into() }),
entries: #MapFrom_([#(#members),*]), members: #MapFromIterator_(vec![#(#members),*])
})) }))))
}, },
Stx::Seq(stxs) => { Stx::Seq(stxs) => {
let arity = stxs.len() as u128;
let members = compile_sequence_members(stxs)?; let members = compile_sequence_members(stxs)?;
Ok(quote!(#P_::Pattern::Group { Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Arr {
type_: Box::new(#P_::GroupType::Arr), ctor: Box::new(#P_::CArr { arity: #arity .into() }),
entries: #MapFrom_([#(#members),*]), members: #MapFromIterator_(vec![#(#members),*])
})) }))))
} }
Stx::Set(_stxs) => Stx::Set(stxs) =>
Err("Set literals not supported in patterns"), Ok(lit(emit_set(&stxs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?))),
Stx::Dict(d) => { Stx::Dict(d) => {
let members = d.iter().map(|(k, v)| { let members = d.into_iter().map(|(k, v)| {
let k = to_value_expr(k)?; let k = to_value_expr(k)?;
let v = to_pattern_expr(v)?; let v = to_pattern_expr(v)?;
Ok(quote!((#k, #v))) Ok(quote!((#k, #v)))
}).collect::<Result<Vec<_>, &'static str>>()?; }).collect::<Result<Vec<_>, &'static str>>()?;
Ok(quote!(#P_::Pattern::Group { Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Dict {
type_: Box::new(#P_::GroupType::Dict), ctor: Box::new(#P_::CDict),
entries: #MapFrom_([#(#members),*]) members: #MapFromIterator_(vec![#(#members),*])
})) }))))
} }
} }
} }
pub fn pattern(src: TokenStream) -> TokenStream { pub fn pattern(src: TokenStream) -> TokenStream {
let src2 = src.clone(); let src2 = src.clone();
let e = to_pattern_expr(&parse_macro_input!(src2 as Stx)) let e = to_pattern_expr(parse_macro_input!(src2 as Stx)).expect("Cannot compile pattern").into();
.expect("Cannot compile pattern").into();
// println!("\n{:#} -->\n{:#}\n", &src, &e); // println!("\n{:#} -->\n{:#}\n", &src, &e);
e e
} }

View File

@ -1,20 +1,16 @@
use proc_macro2::Delimiter; use proc_macro2::Delimiter;
use proc_macro2::LineColumn; use proc_macro2::LineColumn;
use proc_macro2::Span; use proc_macro2::TokenTree;
use proc_macro2::TokenStream;
use syn::ExprLit; use syn::ExprLit;
use syn::Ident;
use syn::Lit; use syn::Lit;
use syn::Result; use syn::Result;
use syn::Type;
use syn::buffer::Cursor; use syn::buffer::Cursor;
use syn::parse::Error; use syn::parse::Error;
use syn::parse::Parse; use syn::parse::Parse;
use syn::parse::Parser;
use syn::parse::ParseStream; use syn::parse::ParseStream;
use syn::parse_str;
use syndicate::value::Float;
use syndicate::value::Double; use syndicate::value::Double;
use syndicate::value::IOValue; use syndicate::value::IOValue;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
@ -22,9 +18,11 @@ use syndicate::value::NestedValue;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stx { pub enum Stx {
Atom(IOValue), Atom(IOValue),
Binder(Option<Ident>, Option<Type>, Option<Box<Stx>>), Binder(Option<String>, Box<Stx>),
Discard, Discard,
Subst(TokenStream), Subst(TokenTree),
Ctor1(String, Box<Stx>),
CtorN(String, Vec<(Stx, Stx)>),
Rec(Box<Stx>, Vec<Stx>), Rec(Box<Stx>, Vec<Stx>),
Seq(Vec<Stx>), Seq(Vec<Stx>),
Set(Vec<Stx>), Set(Vec<Stx>),
@ -37,74 +35,24 @@ impl Parse for Stx {
} }
} }
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)> { fn parse_id(mut c: Cursor) -> Result<(String, Cursor)> {
let mut id = String::new(); let mut id = String::new();
let mut prev_pos = start_pos(c.span()); let mut prev_pos = c.span().start();
loop { loop {
if c.eof() || start_pos(c.span()) != prev_pos { if c.eof() || c.span().start() != prev_pos {
return Ok((id, c)); return Ok((id, c));
} else if let Some((p, next)) = c.punct() { } else if let Some((p, next)) = c.punct() {
match p.as_char() { match p.as_char() {
'<' | '>' | '(' | ')' | '{' | '}' | '[' | ']' | ',' | ':' => return Ok((id, c)), '<' | '>' | '(' | ')' | '{' | '}' | '[' | ']' | ',' | ':' => return Ok((id, c)),
ch => { ch => {
id.push(ch); id.push(ch);
prev_pos = end_pos(c.span()); prev_pos = c.span().end();
c = next; c = next;
} }
} }
} else if let Some((i, next)) = c.ident() { } else if let Some((i, next)) = c.ident() {
id.push_str(&i.to_string()); id.push_str(&i.to_string());
prev_pos = end_pos(i.span()); prev_pos = i.span().end();
c = next; c = next;
} else { } else {
return Ok((id, c)); return Ok((id, c));
@ -135,15 +83,17 @@ fn parse_seq(delim_ch: char, mut c: Cursor) -> Result<(Vec<Stx>, Cursor)> {
fn skip_commas(mut c: Cursor) -> Cursor { fn skip_commas(mut c: Cursor) -> Cursor {
loop { loop {
if let Some((',', next)) = punct_char(c) { if let Some((p, next)) = c.punct() {
c = next; if p.as_char() == ',' {
continue; c = next;
continue;
}
} }
return c; return c;
} }
} }
fn parse_group<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>( fn parse_group_inner<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>(
mut c: Cursor<'c>, mut c: Cursor<'c>,
f: F, f: F,
after: Cursor<'c>, after: Cursor<'c>,
@ -160,22 +110,36 @@ fn parse_group<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>(
} }
} }
fn parse_group<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>(
d: Delimiter,
f: F,
c: Cursor<'c>,
) -> Result<(Vec<R>, Cursor<'c>)> {
let (inner, _, after) = c.group(d).unwrap();
parse_group_inner(inner, f, after)
}
fn parse_kv(c: Cursor) -> Result<((Stx, Stx), Cursor)> { fn parse_kv(c: Cursor) -> Result<((Stx, Stx), Cursor)> {
let (k, c) = parse1(c)?; let (k, c) = parse1(c)?;
if let Some((':', c)) = punct_char(c) { if let Some((p, c)) = c.punct() {
let (v, c) = parse1(c)?; if p.as_char() == ':' {
return Ok(((k, v), c)); let (v, c) = parse1(c)?;
return Ok(((k, v), c));
}
} }
Err(Error::new(c.span(), "Expected ':'")) Err(Error::new(c.span(), "Expected ':'"))
} }
fn adjacent_ident(pos: LineColumn, c: Cursor) -> (Option<Ident>, Cursor) { fn adjacent_id(pos: LineColumn, c: Cursor) -> (Option<String>, Cursor) {
if start_pos(c.span()) != pos { if c.span().start() != pos {
(None, c) (None, c)
} else if let Some((id, next)) = c.ident() {
(Some(id), next)
} else { } else {
(None, c) let (s, next) = parse_id(c).unwrap();
if s.is_empty() {
(None, c)
} else {
(Some(s), next)
}
} }
} }
@ -187,27 +151,6 @@ fn parse_exactly_one<'c>(c: Cursor<'c>) -> Result<Stx> {
}) })
} }
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)> { fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
if let Some((p, next)) = c.punct() { if let Some((p, next)) = c.punct() {
match p.as_char() { match p.as_char() {
@ -216,30 +159,22 @@ fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
} else { } else {
Ok((Stx::Rec(Box::new(q.remove(0)), q), c)) Ok((Stx::Rec(Box::new(q.remove(0)), q), c))
}), }),
'{' => parse_group(Delimiter::Brace, parse_kv, c).map(|(q,c)| (Stx::Dict(q),c)),
'[' => parse_group(Delimiter::Bracket, parse1, c).map(|(q,c)| (Stx::Seq(q),c)),
'$' => { '$' => {
let (maybe_id, next) = adjacent_ident(end_pos(p.span()), next); let (maybe_id, next) = adjacent_id(p.span().end(), next);
let (maybe_type, next) = if let Some((':', next)) = punct_char(next) { if let Some((inner, _, next)) = next.group(Delimiter::Parenthesis) {
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( parse_exactly_one(inner).map(
|q| (Stx::Binder(maybe_id, maybe_type, Some(Box::new(q))), next)) |q| (Stx::Binder(maybe_id, Box::new(q)), next))
} else { } else {
Ok((Stx::Binder(maybe_id, maybe_type, None), next)) Ok((Stx::Binder(maybe_id, Box::new(Stx::Discard)), next))
} }
} }
'#' => { '#' => {
if let Some((inner, _, next)) = next.group(Delimiter::Brace) { if let Some((inner, _, next)) = next.group(Delimiter::Brace) {
parse_group(inner, parse1, next).map(|(q,c)| (Stx::Set(q),c)) parse_group_inner(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() { } else if let Some((tt, next)) = next.token_tree() {
Ok((Stx::Subst(vec![tt].into_iter().collect()), next)) Ok((Stx::Subst(tt), next))
} else { } else {
Err(Error::new(c.span(), "Expected expression to substitute")) Err(Error::new(c.span(), "Expected expression to substitute"))
} }
@ -250,7 +185,17 @@ fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
if i.to_string() == "_" { if i.to_string() == "_" {
Ok((Stx::Discard, next)) Ok((Stx::Discard, next))
} else { } else {
parse_id(c).and_then(|(q,c)| Ok((Stx::Atom(IOValue::symbol(&q)), c))) parse_id(c).and_then(|(q,c)| {
if let Some((inner, _, next)) = c.group(Delimiter::Parenthesis) {
match parse_group_inner(inner, parse_kv, next) {
Ok((kvs, next)) => Ok((Stx::CtorN(q, kvs), next)),
Err(_) => parse_exactly_one(inner).map(
|v| (Stx::Ctor1(q, Box::new(v)), next)),
}
} else {
Ok((Stx::Atom(IOValue::symbol(&q)), c))
}
})
} }
} else if let Some((literal, next)) = c.literal() { } else if let Some((literal, next)) = c.literal() {
let t: ExprLit = syn::parse_str(&literal.to_string())?; let t: ExprLit = syn::parse_str(&literal.to_string())?;
@ -265,7 +210,7 @@ fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
IOValue::new(i.base10_parse::<i128>()?) IOValue::new(i.base10_parse::<i128>()?)
} }
Lit::Float(f) => if f.suffix() == "f32" { Lit::Float(f) => if f.suffix() == "f32" {
IOValue::new(&Double(f.base10_parse::<f32>()? as f64)) IOValue::new(&Float(f.base10_parse::<f32>()?))
} else { } else {
IOValue::new(&Double(f.base10_parse::<f64>()?)) IOValue::new(&Double(f.base10_parse::<f64>()?))
} }
@ -273,10 +218,6 @@ fn parse1(c: Cursor) -> Result<(Stx, Cursor)> {
Lit::Verbatim(_) => return Err(Error::new(c.span(), "Verbatim literals not supported")), Lit::Verbatim(_) => return Err(Error::new(c.span(), "Verbatim literals not supported")),
}; };
Ok((Stx::Atom(v), next)) 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 { } else {
Err(Error::new(c.span(), "Unexpected input")) Err(Error::new(c.span(), "Unexpected input"))
} }

View File

@ -50,6 +50,10 @@ pub fn value_to_value_expr(v: &IOValue) -> TokenStream2 {
match v.value() { match v.value() {
Value::Boolean(b) => Value::Boolean(b) =>
quote!(#V_::Value::from(#b).wrap()), quote!(#V_::Value::from(#b).wrap()),
Value::Float(f) => {
let f = f.0;
quote!(#V_::Value::from(#f).wrap())
}
Value::Double(d) => { Value::Double(d) => {
let d = d.0; let d = d.0;
quote!(#V_::Value::from(#d).wrap()) quote!(#V_::Value::from(#d).wrap())
@ -80,15 +84,18 @@ pub fn value_to_value_expr(v: &IOValue) -> TokenStream2 {
} }
} }
pub fn to_value_expr(stx: &Stx) -> Result<TokenStream2, &'static str> { pub fn to_value_expr(stx: Stx) -> Result<TokenStream2, &'static str> {
match stx { match stx {
Stx::Atom(v) => Ok(value_to_value_expr(&v)), Stx::Atom(v) => Ok(value_to_value_expr(&v)),
Stx::Binder(_, _, _) => Err("Cannot use binder in literal value"), Stx::Binder(_, _) => Err("Cannot use binder in literal value"),
Stx::Discard => Err("Cannot use discard in literal value"), Stx::Discard => Err("Cannot use discard in literal value"),
Stx::Subst(e) => Ok(e.clone().into()), Stx::Subst(e) => Ok(e.into()),
Stx::Ctor1(_, _) => todo!(),
Stx::CtorN(_, _) => todo!(),
Stx::Rec(l, fs) => Stx::Rec(l, fs) =>
Ok(emit_record(to_value_expr(&*l)?, Ok(emit_record(to_value_expr(*l)?,
&fs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)), &fs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)),
Stx::Seq(vs) => Stx::Seq(vs) =>
Ok(emit_seq(&vs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)), Ok(emit_seq(&vs.into_iter().map(to_value_expr).collect::<Result<Vec<_>,_>>()?)),

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,6 +1,6 @@
[package] [package]
name = "syndicate-server" name = "syndicate-server"
version = "0.45.0" version = "0.10.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"] authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018" edition = "2018"
@ -9,39 +9,26 @@ homepage = "https://syndicate-lang.org/"
repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs" repository = "https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"
license = "Apache-2.0" license = "Apache-2.0"
[features]
jemalloc = ["dep:tikv-jemallocator"]
[build-dependencies] [build-dependencies]
preserves-schema = "5.995" preserves-schema = "1.0.0"
syndicate = { path = "../syndicate", version = "0.40.0"}
syndicate-schema-plugin = { path = "../syndicate-schema-plugin", version = "0.9.0"}
[dependencies] [dependencies]
preserves-schema = "5.995" preserves-schema = "1.0.0"
syndicate = { path = "../syndicate", version = "0.40.0"} syndicate = { path = "../syndicate", version = "^0.10.0"}
syndicate-macros = { path = "../syndicate-macros", version = "0.32.0"} syndicate-macros = { path = "../syndicate-macros", version = "^0.5.0"}
chrono = "0.4"
futures = "0.3" futures = "0.3"
lazy_static = "1.4"
noise-protocol = "0.1"
noise-rust-crypto = "0.5"
notify = "4.0" notify = "4.0"
structopt = "0.3" structopt = "0.3"
tikv-jemallocator = { version = "0.5.0", optional = true } tungstenite = "0.13"
tokio-tungstenite = "0.14"
tokio = { version = "1.10", features = ["io-std", "time", "process"] } tokio = { version = "1.10", features = ["io-std", "time"] }
tokio-util = "0.6" tokio-util = "0.6"
tokio-stream = "0.1"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.2" tracing-subscriber = "0.2"
tracing-futures = "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

@ -13,7 +13,7 @@ inotifytest:
binary: binary-release binary: binary-release
binary-release: binary-release:
cargo build --release --all-targets --features jemalloc cargo build --release --all-targets
binary-debug: binary-debug:
cargo build --all-targets cargo build --all-targets

View File

@ -6,27 +6,12 @@ fn main() -> std::io::Result<()> {
let mut gen_dir = buildroot.clone(); let mut gen_dir = buildroot.clone();
gen_dir.push("src/schemas"); gen_dir.push("src/schemas");
let mut c = CompilerConfig::new("crate::schemas".to_owned()); let mut c = CompilerConfig::new(gen_dir, "crate::schemas".to_owned());
c.plugins.push(Box::new(syndicate_schema_plugin::PatternPlugin)); // c.plugins.push(Box::new(syndicate_plugins::PatternPlugin));
c.add_external_module(ExternalModule::new(vec!["EntityRef".to_owned()], "syndicate::actor")); c.module_aliases.insert(vec!["EntityRef".to_owned()], "syndicate::actor".to_owned());
c.add_external_module( c.module_aliases.insert(vec!["TransportAddress".to_owned()], "syndicate::schemas::transport_address".to_owned());
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()])?; let inputs = expand_inputs(&vec!["protocols/schema-bundle.bin".to_owned()])?;
c.load_schemas_and_bundles(&inputs, &vec![])?; c.load_schemas_and_bundles(&inputs)?;
c.load_xref_bin("syndicate", syndicate::schemas::_bundle())?; compile(&c)
compile(&c, &mut CodeCollector::files(gen_dir))
} }

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::language;
use syndicate::relay; use syndicate::relay;
use syndicate::schemas::dataspace::Observe; use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy; use syndicate::sturdy;
@ -12,44 +11,59 @@ use syndicate::value::NestedValue;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use core::time::Duration; use core::time::Duration;
use tokio::time::interval;
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
pub struct Config { pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")] #[structopt(short = "d", default_value = "b4b303726566b10973796e646963617465b584b210a6480df5306611ddd0d3882b546e197784")]
dataspace: String, dataspace: String,
} }
#[tokio::main] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
syndicate::convenient_logging()?; syndicate::convenient_logging()?;
let config = Config::from_args(); Actor::new().boot(syndicate::name!("consumer"), |t| {
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?; let facet = t.facet.clone();
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split(); let boot_account = Arc::clone(t.account());
Actor::top(None, |t| { Ok(t.linked_task(tracing::Span::current(), async move {
relay::connect_stream(t, i, o, false, sturdyref, (), |_state, t, ds| { let config = Config::from_args();
let consumer = syndicate::entity(0) let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
.on_message(|message_count, _t, m: AnyValue| { let (i, o) = TcpStream::connect("127.0.0.1:8001").await?.into_split();
if m.value().is_boolean() { facet.activate(boot_account, |t| {
tracing::info!("{:?} messages in the last second", message_count); relay::connect_stream(t, i, o, sturdyref, (), |_state, t, ds| {
*message_count = 0; let consumer = syndicate::entity(0)
} else { .on_message(|message_count, _t, m: AnyValue| {
*message_count += 1; if m.value().is_boolean() {
} tracing::info!("{:?} messages in the last second", message_count);
Ok(()) *message_count = 0;
}) } else {
.create_cap(t); *message_count += 1;
ds.assert(t, language(), &Observe { }
pattern: syndicate_macros::pattern!{<Says $ $>}, Ok(())
observer: Arc::clone(&consumer), })
}); .create_cap(t);
ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<Says $ $>},
observer: Arc::clone(&consumer),
});
t.every(Duration::from_secs(1), move |t| { t.linked_task(syndicate::name!("tick"), async move {
consumer.message(t, &(), &AnyValue::new(true)); let mut stats_timer = interval(Duration::from_secs(1));
loop {
stats_timer.tick().await;
let consumer = Arc::clone(&consumer);
external_event(&Arc::clone(&consumer.underlying.mailbox),
&Account::new(syndicate::name!("account")),
Box::new(move |t| t.with_entity(
&consumer.underlying,
|t, e| e.message(t, AnyValue::new(true)))))?;
}
});
Ok(None)
});
Ok(()) Ok(())
})?; })
}))
Ok(None)
})
}).await??; }).await??;
Ok(()) 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,12 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::time::SystemTime; use std::time::SystemTime;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose;
use syndicate::language;
use syndicate::relay; use syndicate::relay;
use syndicate::schemas::dataspace::Observe; use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy; use syndicate::sturdy;
@ -16,6 +13,7 @@ use syndicate::value::Value;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use core::time::Duration; use core::time::Duration;
use tokio::time::interval;
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
pub struct PingConfig { pub struct PingConfig {
@ -43,7 +41,7 @@ pub struct Config {
#[structopt(subcommand)] #[structopt(subcommand)]
mode: PingPongMode, mode: PingPongMode,
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")] #[structopt(short = "d", default_value = "b4b303726566b10973796e646963617465b584b210a6480df5306611ddd0d3882b546e197784")]
dataspace: String, dataspace: String,
} }
@ -89,117 +87,137 @@ fn report_latencies(rtt_ns_samples: &Vec<u64>) {
} }
#[tokio::main] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
syndicate::convenient_logging()?; 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) = Actor::new().boot(syndicate::name!("pingpong"), |t| {
match config.mode { let facet = t.facet.clone();
PingPongMode::Ping(ref c) => let boot_account = Arc::clone(t.account());
("Ping", "Pong", c.report_latency_every, false, c.bytes_padding), Ok(t.linked_task(tracing::Span::current(), async move {
PingPongMode::Pong => let config = Config::from_args();
("Pong", "Ping", 0, true, 0), let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
}; let (i, o) = TcpStream::connect("127.0.0.1:8001").await?.into_split();
facet.activate(boot_account, |t| {
relay::connect_stream(t, i, o, sturdyref, (), move |_state, t, ds| {
let consumer = { let (send_label, recv_label, report_latency_every, should_echo, bytes_padding) =
let ds = Arc::clone(&ds); match config.mode {
let mut turn_counter: u64 = 0; PingPongMode::Ping(ref c) =>
let mut event_counter: u64 = 0; ("Ping", "Pong", c.report_latency_every, false, c.bytes_padding),
let mut rtt_ns_samples: Vec<u64> = vec![0; report_latency_every]; PingPongMode::Pong =>
let mut rtt_batch_count: usize = 0; ("Pong", "Ping", 0, true, 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) { let consumer = {
ds.message(t, &(), &simple_record2(&send_label, let ds = Arc::clone(&ds);
timestamp.clone(), let mut turn_counter: u64 = 0;
padding.clone())); let mut event_counter: u64 = 0;
} else { let mut rtt_ns_samples: Vec<u64> = vec![0; report_latency_every];
let mut g = current_reply.lock().expect("unpoisoned"); let mut rtt_batch_count: usize = 0;
if let None = *g { let mut current_reply = None;
turn_counter += 1; let self_ref = t.create_inert();
t.pre_commit(enclose!((current_reply) move |_| { self_ref.become_entity(
*current_reply.lock().expect("unpoisoned") = None; syndicate::entity(Arc::clone(&self_ref))
Ok(()) .on_message(move |self_ref, t, m: AnyValue| {
})); match m.value().as_boolean() {
let rtt_ns = now() - timestamp.value().to_u64()?; Some(true) => {
rtt_ns_samples[rtt_batch_count] = rtt_ns; tracing::info!("{:?} turns, {:?} events in the last second",
rtt_batch_count += 1; turn_counter,
event_counter);
if rtt_batch_count == report_latency_every { turn_counter = 0;
rtt_ns_samples.sort(); event_counter = 0;
report_latencies(&rtt_ns_samples); }
rtt_batch_count = 0; Some(false) => {
} current_reply = None;
}
*g = Some(simple_record2(&send_label, None => {
Value::from(now()).wrap(), event_counter += 1;
padding.clone())); 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 {
if let None = current_reply {
turn_counter += 1;
t.message_for_myself(&self_ref, AnyValue::new(false));
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;
}
current_reply = Some(
simple_record2(&send_label,
Value::from(now()).wrap(),
padding.clone()));
}
ds.message(t, current_reply.as_ref().expect("some reply").clone());
}
} }
ds.message(t, &(), g.as_ref().expect("some reply"));
} }
Ok(())
}));
Cap::new(&self_ref)
};
ds.assert(t, &Observe {
pattern: {
let recv_label = AnyValue::symbol(recv_label);
syndicate_macros::pattern!{<#(recv_label) $ $>}
},
observer: Arc::clone(&consumer),
});
t.linked_task(syndicate::name!("tick"), async move {
let mut stats_timer = interval(Duration::from_secs(1));
loop {
stats_timer.tick().await;
let consumer = Arc::clone(&consumer);
external_event(&Arc::clone(&consumer.underlying.mailbox),
&Account::new(syndicate::name!("account")),
Box::new(move |t| t.with_entity(
&consumer.underlying,
|t, e| e.message(t, AnyValue::new(true)))))?;
}
});
if let PingPongMode::Ping(c) = &config.mode {
let turn_count = c.turn_count;
let action_count = c.action_count;
let account = Arc::clone(t.account());
t.linked_task(syndicate::name!("boot-ping"), async move {
let padding = AnyValue::bytestring(vec![0; bytes_padding]);
for _ in 0..turn_count {
let mut events: PendingEventQueue = vec![];
let current_rec = simple_record2(send_label,
Value::from(now()).wrap(),
padding.clone());
for _ in 0..action_count {
let ds = Arc::clone(&ds);
let current_rec = current_rec.clone();
events.push(Box::new(move |t| t.with_entity(
&ds.underlying,
|t, e| e.message(t, current_rec))));
} }
} external_events(&ds.underlying.mailbox, &account, events)?
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(())
}); });
} }
Ok(LinkedTaskTermination::KeepFacet)
});
}
Ok(None) Ok(None)
}) });
Ok(())
})
}))
}).await??; }).await??;
Ok(()) Ok(())
} }

View File

@ -1,10 +1,11 @@
use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::preserves::rec;
use syndicate::relay; use syndicate::relay;
use syndicate::sturdy; use syndicate::sturdy;
use syndicate::value::NestedValue; use syndicate::value::Value;
use tokio::net::TcpStream; use tokio::net::TcpStream;
@ -16,37 +17,52 @@ pub struct Config {
#[structopt(short = "b", default_value = "0")] #[structopt(short = "b", default_value = "0")]
bytes_padding: usize, bytes_padding: usize,
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")] #[structopt(short = "d", default_value = "b4b303726566b10973796e646963617465b584b210a6480df5306611ddd0d3882b546e197784")]
dataspace: String, dataspace: String,
} }
#[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()
}
#[tokio::main] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
syndicate::convenient_logging()?; syndicate::convenient_logging()?;
let config = Config::from_args(); Actor::new().boot(syndicate::name!("producer"), |t| {
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?; let facet = t.facet.clone();
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split(); let boot_account = Arc::clone(t.account());
Actor::top(None, |t| { Ok(t.linked_task(tracing::Span::current(), async move {
relay::connect_stream(t, i, o, false, sturdyref, (), move |_state, t, ds| { let config = Config::from_args();
let facet = t.facet_ref(); let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let padding = AnyValue::new(&vec![0u8; config.bytes_padding][..]); let (i, o) = TcpStream::connect("127.0.0.1:8001").await?.into_split();
let action_count = config.action_count; facet.activate(boot_account, |t| {
let account = Account::new(None, None); relay::connect_stream(t, i, o, sturdyref, (), move |_state, t, ds| {
t.linked_task(Some(AnyValue::symbol("sender")), async move { let padding: AnyValue = Value::ByteString(vec![0; config.bytes_padding]).wrap();
loop { let action_count = config.action_count;
account.ensure_clear_funds().await; let account = Account::new(syndicate::name!("account"));
facet.activate(&account, None, |t| { t.linked_task(syndicate::name!("sender"), async move {
for _ in 0..action_count { loop {
ds.message(t, &(), &rec![AnyValue::symbol("Says"), account.ensure_clear_funds().await;
AnyValue::new("producer"), let mut events: PendingEventQueue = Vec::new();
padding.clone()]); for _ in 0..action_count {
let ds = Arc::clone(&ds);
let padding = padding.clone();
events.push(Box::new(move |t| t.with_entity(
&ds.underlying,
|t, e| e.message(t, says(Value::from("producer").wrap(), padding)))));
}
external_events(&ds.underlying.mailbox, &account, events)?;
} }
Ok(())
}); });
} Ok(None)
}); });
Ok(None) Ok(())
}) })
}))
}).await??; }).await??;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::language;
use syndicate::relay; use syndicate::relay;
use syndicate::schemas::dataspace::Observe; use syndicate::schemas::dataspace::Observe;
use syndicate::sturdy; use syndicate::sturdy;
@ -12,65 +11,80 @@ use syndicate::value::NestedValue;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use core::time::Duration; use core::time::Duration;
use tokio::time::interval;
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
pub struct Config { pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")] #[structopt(short = "d", default_value = "b4b303726566b10973796e646963617465b584b210a6480df5306611ddd0d3882b546e197784")]
dataspace: String, dataspace: String,
} }
#[tokio::main] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
syndicate::convenient_logging()?; syndicate::convenient_logging()?;
let config = Config::from_args(); Actor::new().boot(syndicate::name!("state-consumer"), |t| {
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?; let facet = t.facet.clone();
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split(); let boot_account = Arc::clone(t.account());
Actor::top(None, |t| { Ok(t.linked_task(tracing::Span::current(), async move {
relay::connect_stream(t, i, o, false, sturdyref, (), |_state, t, ds| { let config = Config::from_args();
let consumer = { let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
#[derive(Default)] let (i, o) = TcpStream::connect("127.0.0.1:8001").await?.into_split();
struct State { facet.activate(boot_account, |t| {
event_counter: u64, relay::connect_stream(t, i, o, sturdyref, (), |_state, t, ds| {
arrival_counter: u64, let consumer = {
departure_counter: u64, #[derive(Default)]
occupancy: u64, struct State {
} event_counter: u64,
syndicate::entity(State::default()).on_asserted(move |s, _, _| { arrival_counter: u64,
s.event_counter += 1; departure_counter: u64,
s.arrival_counter += 1; occupancy: u64,
s.occupancy += 1; }
Ok(Some(Box::new(|s, _| { syndicate::entity(State::default()).on_asserted(move |s, _, _| {
s.event_counter += 1; s.event_counter += 1;
s.departure_counter += 1; s.arrival_counter += 1;
s.occupancy -= 1; s.occupancy += 1;
Ok(()) Ok(Some(Box::new(|s, _| {
}))) s.event_counter += 1;
}).on_message(move |s, _, _| { s.departure_counter += 1;
tracing::info!( s.occupancy -= 1;
"{:?} events, {:?} arrivals, {:?} departures, {:?} present in the last second", Ok(())
s.event_counter, })))
s.arrival_counter, }).on_message(move |s, _, _| {
s.departure_counter, tracing::info!(
s.occupancy); "{:?} events, {:?} arrivals, {:?} departures, {:?} present in the last second",
s.event_counter = 0; s.event_counter,
s.arrival_counter = 0; s.arrival_counter,
s.departure_counter = 0; s.departure_counter,
Ok(()) s.occupancy);
}).create_cap(t) s.event_counter = 0;
}; s.arrival_counter = 0;
s.departure_counter = 0;
Ok(())
}).create_cap(t)
};
ds.assert(t, language(), &Observe { ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<Present $>}, pattern: syndicate_macros::pattern!{<Present $>},
observer: Arc::clone(&consumer), observer: Arc::clone(&consumer),
}); });
t.every(Duration::from_secs(1), move |t| { t.linked_task(syndicate::name!("tick"), async move {
consumer.message(t, &(), &AnyValue::new(true)); let mut stats_timer = interval(Duration::from_secs(1));
loop {
stats_timer.tick().await;
let consumer = Arc::clone(&consumer);
external_event(&Arc::clone(&consumer.underlying.mailbox),
&Account::new(syndicate::name!("account")),
Box::new(move |t| t.with_entity(
&consumer.underlying,
|t, e| e.message(t, AnyValue::new(true)))))?;
}
});
Ok(None)
});
Ok(()) Ok(())
})?; })
}))
Ok(None)
})
}).await??; }).await??;
Ok(()) Ok(())
} }

View File

@ -1,48 +1,63 @@
use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::preserves::rec;
use syndicate::relay; use syndicate::relay;
use syndicate::sturdy; use syndicate::sturdy;
use syndicate::value::NestedValue; use syndicate::value::Value;
use tokio::net::TcpStream; use tokio::net::TcpStream;
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
pub struct Config { pub struct Config {
#[structopt(short = "d", default_value = "b4b303726566b7b3036f6964b10973796e646963617465b303736967b21069ca300c1dbfa08fba692102dd82311a8484")] #[structopt(short = "d", default_value = "b4b303726566b10973796e646963617465b584b210a6480df5306611ddd0d3882b546e197784")]
dataspace: String, dataspace: String,
} }
#[tokio::main] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
syndicate::convenient_logging()?; syndicate::convenient_logging()?;
let config = Config::from_args(); Actor::new().boot(syndicate::name!("state-producer"), |t| {
let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?; let facet = t.facet.clone();
let (i, o) = TcpStream::connect("127.0.0.1:9001").await?.into_split(); let boot_account = Arc::clone(t.account());
Actor::top(None, |t| { Ok(t.linked_task(tracing::Span::current(), async move {
relay::connect_stream(t, i, o, false, sturdyref, (), move |_state, t, ds| { let config = Config::from_args();
let facet = t.facet_ref(); let sturdyref = sturdy::SturdyRef::from_hex(&config.dataspace)?;
let account = Account::new(None, None); let (i, o) = TcpStream::connect("127.0.0.1:8001").await?.into_split();
t.linked_task(Some(AnyValue::symbol("sender")), async move { facet.activate(boot_account, |t| {
let presence = rec![AnyValue::symbol("Present"), AnyValue::new(std::process::id())]; relay::connect_stream(t, i, o, sturdyref, (), move |_state, t, ds| {
loop { let account = Account::new(syndicate::name!("account"));
let mut handle = None; t.linked_task(syndicate::name!("sender"), async move {
facet.activate(&account, None, |t| { let presence: AnyValue = Value::simple_record1(
handle = ds.assert(t, &(), &presence); "Present",
Ok(()) Value::from(std::process::id()).wrap()).wrap();
}); let handle = syndicate::actor::next_handle();
account.ensure_clear_funds().await; let assert_e = || {
facet.activate(&account, None, |t| { let ds = Arc::clone(&ds);
if let Some(h) = handle { let presence = presence.clone();
t.retract(h); let handle = handle.clone();
external_event(&Arc::clone(&ds.underlying.mailbox), &account, Box::new(
move |t| t.with_entity(&ds.underlying, |t, e| e.assert(t, presence, handle))))
};
let retract_e = || {
let ds = Arc::clone(&ds);
let handle = handle.clone();
external_event(&Arc::clone(&ds.underlying.mailbox), &account, Box::new(
move |t| t.with_entity(&ds.underlying, |t, e| e.retract(t, handle))))
};
assert_e()?;
loop {
account.ensure_clear_funds().await;
retract_e()?;
assert_e()?;
} }
Ok(())
}); });
} Ok(None)
}); });
Ok(None) Ok(())
}) })
}))
}).await??; }).await??;
Ok(()) Ok(())
} }

View File

@ -4,5 +4,5 @@ clean:
rm -f schema-bundle.bin rm -f schema-bundle.bin
schema-bundle.bin: schemas/*.prs schema-bundle.bin: schemas/*.prs
preserves-schemac schemas > $@.tmp preserves-schemac schemas/*.prs > $@.tmp
mv $@.tmp $@ mv $@.tmp $@

View File

@ -1,17 +1 @@
´³bundle·µ³control„´³schema·³version°³ definitions·³ ´³bundle·µ³internalServices„´³schema·³version³ definitions·³ DebtReporter´³lit³ debt-reporter„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„„„„³TcpRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„„„„„³ embeddedType€„„„„
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 +1,7 @@
version 1 . version 1 .
embeddedType EntityRef.Cap .
Gatekeeper = <gatekeeper @bindspace #:gatekeeper.Bind> . DebtReporter = =debt-reporter .
DebtReporter = <debt-reporter @intervalSeconds double>. TcpRelayListener = <relay-listener @addr TransportAddress.Tcp> .
UnixRelayListener = <relay-listener @addr TransportAddress.Unix> .
TcpRelayListener = TcpWithoutHttp / TcpWithHttp . ConfigWatcher = <config-watcher @path string>.
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 +1,63 @@
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 std::sync::Arc;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose; use syndicate::during::DuringResult;
use syndicate::value::NestedValue;
use syndicate::schemas::dataspace;
use syndicate::schemas::gatekeeper; use syndicate::schemas::gatekeeper;
use syndicate::schemas::noise; use syndicate::sturdy;
use syndicate::schemas::sturdy; use syndicate::value::NestedValue;
use crate::language::language; pub fn bind(
t: &mut Activation,
use syndicate_macros::during; ds: &Arc<Cap>,
use syndicate_macros::pattern; oid: syndicate::schemas::sturdy::_Any,
key: [u8; 16],
fn sturdy_step_type() -> String { target: Arc<Cap>,
language().unparse(&sturdy::SturdyStepType).value().to_symbol().unwrap().clone() ) {
let sr = sturdy::SturdyRef::mint(oid.clone(), &key);
tracing::info!(cap = ?AnyValue::from(&sr), hex = %sr.to_hex());
ds.assert(t, &gatekeeper::Bind { oid, key: key.to_vec(), target });
} }
fn noise_step_type() -> String { pub fn handle_resolve(
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>, ds: &mut Arc<Cap>,
t: &mut Activation, t: &mut Activation,
a: gatekeeper::Resolve, a: gatekeeper::Resolve,
) -> ActorResult { ) -> DuringResult<Arc<Cap>> {
let mut detail: &'static str = "unsupported"; use syndicate::schemas::dataspace;
if a.step.step_type == sturdy_step_type() { let gatekeeper::Resolve { sturdyref, observer } = a;
detail = "invalid"; let queried_oid = sturdyref.oid.clone();
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) let handler = syndicate::entity(observer)
.on_asserted(move |observer, t, a: AnyValue| { .on_asserted(move |observer, t, a: AnyValue| {
t.stop_facet(direct_resolution_facet);
let bindings = a.value().to_sequence()?; let bindings = a.value().to_sequence()?;
let key = bindings[0].value().to_bytestring()?; let key = bindings[0].value().to_bytestring()?;
let unattenuated_target = bindings[1].value().to_embedded()?; let unattenuated_target = bindings[1].value().to_embedded()?;
match sturdyref.validate_and_attenuate(key, unattenuated_target) { match sturdyref.validate_and_attenuate(key, unattenuated_target) {
Err(e) => { Err(e) => {
tracing::warn!(sturdyref = ?language().unparse(&sturdyref), tracing::warn!(sturdyref = ?AnyValue::from(&sturdyref),
"sturdyref failed validation: {}", e); "sturdyref failed validation: {}", e);
observer.assert(t, language(), &gatekeeper::Resolved::Rejected( Ok(None)
Box::new(gatekeeper::Rejected {
detail: AnyValue::symbol("sturdyref-failed-validation"),
})));
}, },
Ok(target) => { Ok(target) => {
tracing::trace!(sturdyref = ?language().unparse(&sturdyref), tracing::trace!(sturdyref = ?AnyValue::from(&sturdyref),
?target, ?target,
"sturdyref resolved"); "sturdyref resolved");
observer.assert(t, language(), &gatekeeper::Resolved::Accepted { if let Some(h) = observer.assert(t, AnyValue::domain(target)) {
responder_session: target, Ok(Some(Box::new(move |_observer, t| Ok(t.retract(h)))))
}); } else {
} Ok(None)
}
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(()) .create_cap(t);
if let Some(oh) = ds.assert(t, &dataspace::Observe {
// TODO: codegen plugin to generate pattern constructors
pattern: syndicate_macros::pattern!{<bind #(queried_oid) $ $>},
observer: handler,
}) {
Ok(Some(Box::new(move |_ds, t| Ok(t.retract(oh)))))
} else {
Ok(None)
} }
} }
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,43 +1,25 @@
use preserves_schema::Codec;
use std::convert::TryInto;
use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::convert::from_io_value;
use syndicate::dataspace::*; use syndicate::dataspace::*;
use syndicate::enclose;
use syndicate::relay; use syndicate::relay;
use syndicate::schemas::service; use syndicate::schemas::service;
use syndicate::schemas::transport_address; use syndicate::schemas::transport_address;
use syndicate::trace;
use syndicate::value::Map;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
mod counter;
mod dependencies;
mod gatekeeper; mod gatekeeper;
mod http;
mod language;
mod lifecycle;
mod protocol; mod protocol;
mod script;
mod services; mod services;
#[cfg(feature = "jemalloc")]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
mod schemas { mod schemas {
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs")); include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
} }
use language::Language;
use language::language;
use schemas::internal_services; use schemas::internal_services;
#[derive(Clone, StructOpt)] #[derive(Clone, StructOpt)]
@ -56,188 +38,107 @@ struct ServerConfig {
#[structopt(short = "c", long)] #[structopt(short = "c", long)]
config: Vec<PathBuf>, 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] #[tokio::main]
async fn main() -> ActorResult { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Arc::new(ServerConfig::from_args());
syndicate::convenient_logging()?; syndicate::convenient_logging()?;
if !config.no_banner && !config.inferior { let config = Arc::new(ServerConfig::from_args());
{
const BRIGHT_GREEN: &str = "\x1b[92m"; const BRIGHT_GREEN: &str = "\x1b[92m";
const RED: &str = "\x1b[31m"; const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m"; const GREEN: &str = "\x1b[32m";
const NORMAL: &str = "\x1b[0m"; const NORMAL: &str = "\x1b[0m";
const BRIGHT_YELLOW: &str = "\x1b[93m"; const BRIGHT_YELLOW: &str = "\x1b[93m";
eprintln!(r"{} ______ {}", GREEN, NORMAL); tracing::info!(r"{} ______ {}", GREEN, NORMAL);
eprintln!(r"{} / {}\_{}\{} ", GREEN, BRIGHT_GREEN, GREEN, NORMAL); tracing::info!(r"{} / {}\_{}\{} ", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} / {},{}__/{} \ {} ____ __", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL); tracing::info!(r"{} / {},{}__/{} \ {} ____ __", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} /{}\__/ \{},{} \{} _______ ______ ____/ /_/________ / /____", GREEN, BRIGHT_GREEN, RED, GREEN, NORMAL); tracing::info!(r"{} /{}\__/ \{},{} \{} _______ ______ ____/ /_/________ / /____", GREEN, BRIGHT_GREEN, RED, GREEN, NORMAL);
eprintln!(r"{} \{}/ \__/ {}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL); tracing::info!(r"{} \{}/ \__/ {}/{} / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} \ {}'{} \__{}/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL); tracing::info!(r"{} \ {}'{} \__{}/ {} _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/", GREEN, RED, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r"{} \____{}/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL); tracing::info!(r"{} \____{}/{}_/ {} /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/", GREEN, BRIGHT_GREEN, GREEN, NORMAL);
eprintln!(r" /____/"); tracing::info!(r" /____/");
eprintln!(r""); tracing::info!(r"");
eprintln!(r" {}version {} [syndicate {}]{}", BRIGHT_YELLOW, env!("CARGO_PKG_VERSION"), syndicate::syndicate_package_version(), NORMAL); tracing::info!(r" {}version {}{}", BRIGHT_YELLOW, env!("CARGO_PKG_VERSION"), NORMAL);
eprintln!(r""); tracing::info!(r"");
eprintln!(r" documentation & reference material: https://syndicate-lang.org/"); tracing::info!(r" documentation & reference material: https://syndicate-lang.org/");
eprintln!(r" source code & bugs: https://git.syndicate-lang.org/syndicate-lang/syndicate-rs"); tracing::info!(r" source code & bugs: https://git.syndicate-lang.org/syndicate-lang/syndicate-rs");
eprintln!(r""); tracing::info!(r"");
} }
tracing::trace!("startup"); tracing::trace!("startup");
let trace_collector = config.trace_file.clone().map( Actor::new().boot(tracing::Span::current(), move |t| {
|p| Ok::<trace::TraceCollector, io::Error>(trace::TraceCollector::ascii( let root_ds = Cap::new(&t.create(Dataspace::new()));
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 { if config.inferior {
tracing::info!("inferior server instance"); tracing::info!("inferior server instance");
t.spawn(Some(AnyValue::symbol("parent")), enclose!((server_config_ds) move |t| { let root_ds = Arc::clone(&root_ds);
protocol::run_io_relay(t, t.spawn(syndicate::name!("parent"), move |t| protocol::run_io_relay(
relay::Input::Bytes(Box::pin(tokio::io::stdin())), t,
relay::Output::Bytes(Box::pin(tokio::io::stdout())), relay::Input::Bytes(Box::pin(tokio::io::stdin())),
server_config_ds) relay::Output::Bytes(Box::pin(tokio::io::stdout())),
})); root_ds));
} }
let gatekeeper = Cap::guard(Language::arc(), t.create( let server_config_ds = Cap::new(&t.create(Dataspace::new()));
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(); gatekeeper::bind(t, &root_ds, AnyValue::new("syndicate"), [0; 16],
env.insert("config".to_owned(), AnyValue::domain(Arc::clone(&server_config_ds))); Arc::clone(&root_ds));
env.insert("log".to_owned(), AnyValue::domain(Arc::clone(&log_ds))); gatekeeper::bind(t, &root_ds, AnyValue::new("server-config"), [0; 16],
env.insert("gatekeeper".to_owned(), AnyValue::domain(Arc::clone(&gatekeeper))); Arc::clone(&server_config_ds));
if config.control { let gateway = Cap::guard(&t.create(
env.insert("control".to_owned(), AnyValue::domain(Cap::guard(Language::arc(), t.create( syndicate::entity(Arc::clone(&root_ds)).on_asserted(gatekeeper::handle_resolve)));
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::debt_reporter::on_demand(t, Arc::clone(&server_config_ds));
services::gatekeeper::on_demand(t, Arc::clone(&server_config_ds)); services::tcp_relay_listener::on_demand(t, Arc::clone(&server_config_ds), Arc::clone(&gateway));
services::http_router::on_demand(t, Arc::clone(&server_config_ds)); services::unix_relay_listener::on_demand(t, Arc::clone(&server_config_ds), Arc::clone(&gateway));
services::tcp_relay_listener::on_demand(t, Arc::clone(&server_config_ds)); services::config_watcher::on_demand(t, Arc::clone(&server_config_ds));
services::unix_relay_listener::on_demand(t, Arc::clone(&server_config_ds));
if config.debt_reporter { if config.debt_reporter {
server_config_ds.assert(t, language(), &service::RunService { server_config_ds.assert(t, &service::RequireService {
service_name: language().unparse(&internal_services::DebtReporter { service_name: from_io_value(&internal_services::DebtReporter)?,
interval_seconds: (1.0).into(),
}),
}); });
} }
for port in config.ports.clone() { for port in config.ports.clone() {
server_config_ds.assert(t, language(), &service::RunService { server_config_ds.assert(t, &service::RequireService {
service_name: language().unparse(&internal_services::TcpWithoutHttp { service_name: from_io_value(
addr: transport_address::Tcp { &internal_services::TcpRelayListener {
host: "0.0.0.0".to_owned(), addr: transport_address::Tcp {
port: (port as i32).into(), host: "0.0.0.0".to_owned(),
}, port: (port as i32).into(),
gatekeeper: gatekeeper.clone(), }
}), })?,
}); });
} }
for path in config.sockets.clone() { for path in config.sockets.clone() {
server_config_ds.assert(t, language(), &service::RunService { server_config_ds.assert(t, &service::RequireService {
service_name: language().unparse(&internal_services::UnixRelayListener { service_name: from_io_value(
addr: transport_address::Unix { &internal_services::UnixRelayListener {
path: path.to_str().expect("representable UnixListener path").to_owned(), addr: transport_address::Unix {
}, path: path.to_str().expect("representable UnixListener path").to_owned(),
gatekeeper: gatekeeper.clone(), }
}), })?,
}); });
} }
for path in config.config.clone() { for path in config.config.clone() {
server_config_ds.assert(t, language(), &service::RunService { server_config_ds.assert(t, &service::RequireService {
service_name: language().unparse(&internal_services::ConfigWatcher { service_name: from_io_value(
path: path.to_str().expect("representable ConfigWatcher path").to_owned(), &internal_services::ConfigWatcher {
env: internal_services::ConfigEnv(env.clone()), path: path.to_str().expect("representable ConfigWatcher path").to_owned(),
}), })?,
}); });
} }
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(()) Ok(())
}).await??; }).await??;
wait_for_all_actors_to_stop(std::time::Duration::from_secs(2)).await;
Ok(()) Ok(())
} }

View File

@ -1,30 +1,26 @@
use futures::SinkExt; use futures::SinkExt;
use futures::StreamExt; use futures::StreamExt;
use hyper::header::HeaderValue;
use hyper::service::service_fn;
use std::future::ready; use std::future::ready;
use std::io;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose;
use syndicate::error::Error; use syndicate::error::Error;
use syndicate::error::error; use syndicate::error::error;
use syndicate::relay; use syndicate::relay;
use syndicate::trace;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use hyper_tungstenite::tungstenite::Message; use tungstenite::Message;
struct ExitListener; struct ExitListener;
impl Entity<()> for ExitListener { impl Entity<()> for ExitListener {
fn exit_hook(&mut self, _t: &mut Activation, exit_status: &Arc<ExitStatus>) { fn exit_hook(&mut self, _t: &mut Activation, exit_status: &Arc<ActorResult>) -> ActorResult {
tracing::info!(?exit_status, "disconnect"); tracing::info!(?exit_status, "disconnect");
Ok(())
} }
} }
@ -35,97 +31,52 @@ pub fn run_io_relay(
initial_ref: Arc<Cap>, initial_ref: Arc<Cap>,
) -> ActorResult { ) -> ActorResult {
let exit_listener = t.create(ExitListener); let exit_listener = t.create(ExitListener);
t.add_exit_hook(&exit_listener); t.state.add_exit_hook(&exit_listener);
relay::TunnelRelay::run(t, i, o, Some(initial_ref), None, false); relay::TunnelRelay::run(t, i, o, Some(initial_ref), None);
Ok(()) Ok(())
} }
pub fn run_connection( pub fn run_connection(
trace_collector: Option<trace::TraceCollector>,
facet: FacetRef, facet: FacetRef,
i: relay::Input, i: relay::Input,
o: relay::Output, o: relay::Output,
initial_ref: Arc<Cap>, initial_ref: Arc<Cap>,
) { ) -> ActorResult {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("start-session")); facet.activate(Account::new(syndicate::name!("start-session")),
let account = Account::new(Some(AnyValue::symbol("start-session")), trace_collector); |t| run_io_relay(t, i, o, initial_ref))
facet.activate(&account, cause, |t| run_io_relay(t, i, o, initial_ref));
} }
pub async fn detect_protocol( pub async fn detect_protocol(
trace_collector: Option<trace::TraceCollector>,
facet: FacetRef, facet: FacetRef,
stream: TcpStream, stream: TcpStream,
gateway: Arc<Cap>, gateway: Arc<Cap>,
httpd: Option<Arc<Cap>>,
addr: std::net::SocketAddr, addr: std::net::SocketAddr,
server_port: u16,
) -> ActorResult { ) -> ActorResult {
let mut buf = [0; 1]; // peek at the first byte to see what kind of connection to expect let (i, o) = {
match stream.peek(&mut buf).await? { let mut buf = [0; 1]; // peek at the first byte to see what kind of connection to expect
1 => match buf[0] { match stream.peek(&mut buf).await? {
v if v == b'[' /* Turn */ || v == b'<' /* Error and Extension */ || v >= 128 => { 1 => match buf[0] {
tracing::info!(protocol = %(if v >= 128 { "application/syndicate" } else { "text/syndicate" }), peer = ?addr); b'G' /* ASCII 'G' for "GET" */ => {
let (i, o) = stream.into_split(); tracing::info!(protocol = %"websocket", peer = ?addr);
let i = relay::Input::Bytes(Box::pin(i)); let s = tokio_tungstenite::accept_async(stream).await
let o = relay::Output::Bytes(Box::pin(o /* BufWriter::new(o) */)); .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
run_connection(trace_collector, facet, i, o, gateway); let (o, i) = s.split();
Ok(()) 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))));
_ => { (relay::Input::Packets(Box::pin(i)), relay::Output::Packets(Box::pin(o)))
let upgraded = Arc::new(AtomicBool::new(false)); },
let keepalive = facet.actor.keep_alive(); _ => {
let mut http = hyper::server::conn::Http::new(); tracing::info!(protocol = %"raw", peer = ?addr);
http.http1_keep_alive(true); let (i, o) = stream.into_split();
http.http1_only(true); (relay::Input::Bytes(Box::pin(i)),
let service = service_fn(|mut req| enclose!( relay::Output::Bytes(Box::pin(o /* BufWriter::new(o) */)))
(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!()
} }
0 => Err(error("closed before starting", AnyValue::new(false)))?, };
_ => unreachable!() run_connection(facet, i, o, gateway)
}
} }
fn message_error<E: std::fmt::Display>(e: E) -> Error { fn message_error<E: std::fmt::Display>(e: E) -> Error {
@ -133,7 +84,7 @@ fn message_error<E: std::fmt::Display>(e: E) -> Error {
} }
fn extract_binary_packets( fn extract_binary_packets(
r: Result<Message, hyper_tungstenite::tungstenite::Error>, r: Result<Message, tungstenite::Error>,
) -> Result<Option<Vec<u8>>, Error> { ) -> Result<Option<Vec<u8>>, Error> {
match r { match r {
Ok(m) => match m { Ok(m) => match m {
@ -147,8 +98,6 @@ fn extract_binary_packets(
Ok(None), // unsolicited pongs are to be ignored Ok(None), // unsolicited pongs are to be ignored
Message::Close(_) => Message::Close(_) =>
Ok(None), // we're about to see the end of the stream, so ignore this 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)), 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

@ -3,8 +3,7 @@ use notify::Watcher;
use notify::RecursiveMode; use notify::RecursiveMode;
use notify::watcher; use notify::watcher;
use syndicate::preserves::rec; use std::convert::TryFrom;
use std::fs; use std::fs;
use std::future; use std::future;
use std::io; use std::io;
@ -15,82 +14,73 @@ use std::thread;
use std::time::Duration; use std::time::Duration;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::error::Error; use syndicate::convert::*;
use syndicate::enclose; use syndicate::during::entity;
use syndicate::supervise::{Supervisor, SupervisorConfiguration}; use syndicate::schemas::dataspace::Observe;
use syndicate::trace;
use syndicate::value::BinarySource; use syndicate::value::BinarySource;
use syndicate::value::BytesBinarySource; use syndicate::value::IOBinarySource;
use syndicate::value::Map; use syndicate::value::Map;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
use syndicate::value::NoEmbeddedDomainCodec; use syndicate::value::NoEmbeddedDomainCodec;
use syndicate::value::Reader; use syndicate::value::Reader;
use syndicate::value::Set;
use syndicate::value::ViaCodec; use syndicate::value::ViaCodec;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services; use crate::schemas::internal_services;
use crate::script;
use syndicate_macros::during; pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(syndicate::name!("on_demand", module = module_path!()), move |t| {
pub fn on_demand(t: &mut Activation, config_ds: Arc<Cap>) { let monitor = entity(())
t.spawn(Some(AnyValue::symbol("config_watcher")), move |t| { .on_asserted_facet({
Ok(during!(t, config_ds, language(), <run-service $spec: internal_services::ConfigWatcher::<AnyValue>>, |t| { let ds = Arc::clone(&ds);
Supervisor::start( move |_, t, captures| {
t, let ds = Arc::clone(&ds);
Some(rec![AnyValue::symbol("config"), AnyValue::new(spec.path.clone())]), t.spawn_link(syndicate::name!(parent: None, "config", spec = ?captures),
SupervisorConfiguration::default(), |t| run(t, ds, captures));
enclose!((config_ds, spec) lifecycle::updater(config_ds, spec)), Ok(())
enclose!((config_ds) move |t| enclose!((config_ds, spec) run(t, config_ds, spec)))) }
})) })
.create_cap(t);
ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<require-service $(<config-watcher _>)>},
observer: monitor,
});
Ok(())
}); });
} }
fn convert_notify_error(e: notify::Error) -> Error { fn convert_notify_error(e: notify::Error) -> syndicate::error::Error {
syndicate::error::error(&format!("Notify error: {:?}", e), AnyValue::new(false)) syndicate::error::error(&format!("Notify error: {:?}", e), AnyValue::new(false))
} }
fn process_existing_file( fn assertions_at_existing_file(t: &mut Activation, ds: &Arc<Cap>, path: &PathBuf) -> io::Result<Set<Handle>> {
t: &mut Activation, let mut handles = Set::new();
mut env: script::Env, let fh = fs::File::open(path)?;
) -> io::Result<Option<FacetId>> { let mut src = IOBinarySource::new(fh);
let mut contents = fs::read(&env.path)?; let mut r = src.text::<_, AnyValue, _>(ViaCodec::new(NoEmbeddedDomainCodec));
contents.append(&mut Vec::from("\n[]".as_bytes())); // improved ergonomics of trailing comments let mut values = Vec::new();
let tokens: Vec<AnyValue> = BytesBinarySource::new(&contents) while let Some(value) = Reader::<_, AnyValue>::next(&mut r, true)? {
.text::<AnyValue, _>(ViaCodec::new(NoEmbeddedDomainCodec)) values.push(value);
.configured(true) }
.collect::<Result<Vec<_>, _>>()?; for value in values.into_iter() {
match script::Parser::new(&tokens).parse_top("config") { if let Some(handle) = ds.assert(t, value.clone()) {
Ok(Some(i)) => Ok(Some(t.facet(|t| { handles.insert(handle);
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)
} }
} }
Ok(handles)
} }
fn process_path( fn assertions_at_path(t: &mut Activation, ds: &Arc<Cap>, path: &PathBuf) -> io::Result<Set<Handle>> {
t: &mut Activation, match fs::metadata(path) {
env: script::Env,
) -> io::Result<Option<FacetId>> {
match fs::metadata(&env.path) {
Ok(md) => if md.is_file() { Ok(md) => if md.is_file() {
process_existing_file(t, env) assertions_at_existing_file(t, ds, path)
} else { } else {
Ok(None) Ok(Set::new())
} }
Err(e) => if e.kind() != io::ErrorKind::NotFound { Err(e) => if e.kind() != io::ErrorKind::NotFound {
Err(e)? Err(e)?
} else { } else {
Ok(None) Ok(Set::new())
} }
} }
} }
@ -102,30 +92,25 @@ fn is_hidden(path: &PathBuf) -> bool {
} }
} }
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( fn scan_file(
t: &mut Activation, t: &mut Activation,
path_state: &mut Map<PathBuf, FacetId>, path_state: &mut Map<PathBuf, Set<Handle>>,
env: script::Env, ds: &Arc<Cap>,
path: &PathBuf,
) -> bool { ) -> bool {
let path = env.path.clone(); if is_hidden(path) {
if is_hidden(&path) || !should_process(&path) {
return true; return true;
} }
tracing::trace!("scan_file: scanning {:?}", &path); tracing::info!("scan_file: {:?}", path);
match process_path(t, env) { match assertions_at_path(t, ds, path) {
Ok(maybe_facet_id) => { Ok(new_handles) => {
if let Some(facet_id) = maybe_facet_id { if !new_handles.is_empty() {
tracing::info!("scan_file: processed {:?}", &path); path_state.insert(path.clone(), new_handles);
path_state.insert(path, facet_id);
} }
true true
}, },
Err(e) => { Err(e) => {
tracing::error!("scan_file: {:?}: {:?}", &path, e); tracing::warn!("scan_file: {:?}: {:?}", path, e);
false false
} }
} }
@ -133,110 +118,87 @@ fn scan_file(
fn initial_scan( fn initial_scan(
t: &mut Activation, t: &mut Activation,
path_state: &mut Map<PathBuf, FacetId>, path_state: &mut Map<PathBuf, Set<Handle>>,
config_ds: &Arc<Cap>, ds: &Arc<Cap>,
env: script::Env, path: &PathBuf,
) { ) {
if is_hidden(&env.path) { if is_hidden(path) {
return; return;
} }
match fs::metadata(&env.path) { match fs::metadata(path) {
Ok(md) => if md.is_file() { Ok(md) => if md.is_file() {
scan_file(t, path_state, env); scan_file(t, path_state, ds, path);
} else { } else {
match fs::read_dir(&env.path) { match fs::read_dir(path) {
Ok(unsorted_entries) => { Ok(entries) => for er in entries {
let mut entries: Vec<fs::DirEntry> = Vec::new(); match er {
for er in unsorted_entries { Ok(e) => initial_scan(t, path_state, ds, &e.path()),
match er { Err(e) => tracing::warn!(
Ok(e) => "initial_scan: transient during scan of {:?}: {:?}", path, 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: enumerating {:?}: {:?}", path, e),
} }
}, },
Err(e) => tracing::warn!("initial_scan: `stat`ing {:?}: {:?}", &env.path, e), Err(e) => tracing::warn!("initial_scan: `stat`ing {:?}: {:?}", path, e),
} }
} }
fn run( fn run(t: &mut Activation, ds: Arc<Cap>, captures: AnyValue) -> ActorResult {
t: &mut Activation, let spec = internal_services::ConfigWatcher::try_from(&from_any_value(
config_ds: Arc<Cap>, &captures.value().to_sequence()?[0])?)?;
spec: internal_services::ConfigWatcher, {
) -> ActorResult { let spec = from_io_value(&spec)?;
lifecycle::terminate_on_service_restart(t, &config_ds, &spec); ds.assert(t, syndicate_macros::template!("<service-running =spec>"));
}
let path = fs::canonicalize(spec.path)?;
let path = fs::canonicalize(spec.path.clone())?; tracing::info!("watching {:?}", &path);
let env = script::Env::new(path, spec.env.0.clone());
tracing::info!(?env);
let (tx, rx) = channel(); let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_millis(100)).map_err(convert_notify_error)?; let mut watcher = watcher(tx, Duration::from_secs(1)).map_err(convert_notify_error)?;
watcher.watch(&env.path, RecursiveMode::Recursive).map_err(convert_notify_error)?; watcher.watch(&path, RecursiveMode::Recursive).map_err(convert_notify_error)?;
let facet = t.facet_ref(); let facet = t.facet.clone();
let trace_collector = t.trace_collector();
let span = tracing::Span::current();
thread::spawn(move || { thread::spawn(move || {
let _entry = span.enter(); let mut path_state: Map<PathBuf, Set<Handle>> = Map::new();
let mut path_state: Map<PathBuf, FacetId> = Map::new();
{ {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("initial_scan")); let root_path = path.clone().into();
let account = Account::new(Some(AnyValue::symbol("initial_scan")), trace_collector.clone()); facet.activate(Account::new(syndicate::name!("initial_scan")), |t| {
if !facet.activate( initial_scan(t, &mut path_state, &ds, &root_path);
&account, cause, |t| { Ok(())
initial_scan(t, &mut path_state, &config_ds, env.clone()); }).unwrap();
config_ds.assert(t, language(), &lifecycle::ready(&spec)); tracing::trace!("initial_scan complete");
Ok(())
})
{
return;
}
} }
tracing::trace!("initial_scan complete");
let mut rescan = |paths: Vec<PathBuf>| { let mut rescan = |paths: Vec<PathBuf>| {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("rescan")); facet.activate(Account::new(syndicate::name!("rescan")), |t| {
let account = Account::new(Some(AnyValue::symbol("rescan")), trace_collector.clone()); let mut to_retract = Set::new();
facet.activate(&account, cause, |t| {
let mut to_stop = Vec::new();
for path in paths.into_iter() { for path in paths.into_iter() {
let maybe_facet_id = path_state.remove(&path); let maybe_handles = path_state.remove(&path);
let new_content_ok = let new_content_ok = scan_file(t, &mut path_state, &ds, &path);
scan_file(t, &mut path_state, env.clone_with_path(path.clone())); if let Some(old_handles) = maybe_handles {
if let Some(old_facet_id) = maybe_facet_id {
if new_content_ok { if new_content_ok {
to_stop.push(old_facet_id); to_retract.extend(old_handles.into_iter());
} else { } else {
path_state.insert(path, old_facet_id); path_state.insert(path, old_handles);
} }
} }
} }
for facet_id in to_stop.into_iter() { for h in to_retract.into_iter() {
t.stop_facet(facet_id); t.retract(h);
} }
Ok(()) Ok(())
}) }).unwrap()
}; };
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
tracing::trace!("notification: {:?}", &event); tracing::trace!("notification: {:?}", &event);
let keep_running = match event { match event {
DebouncedEvent::NoticeWrite(_p) | DebouncedEvent::NoticeWrite(_p) |
DebouncedEvent::NoticeRemove(_p) => DebouncedEvent::NoticeRemove(_p) =>
true, (),
DebouncedEvent::Create(p) | DebouncedEvent::Create(p) |
DebouncedEvent::Write(p) | DebouncedEvent::Write(p) |
DebouncedEvent::Chmod(p) | DebouncedEvent::Chmod(p) |
@ -244,30 +206,24 @@ fn run(
rescan(vec![p]), rescan(vec![p]),
DebouncedEvent::Rename(p, q) => DebouncedEvent::Rename(p, q) =>
rescan(vec![p, q]), rescan(vec![p, q]),
_ => { _ =>
tracing::info!("{:?}", event); tracing::info!("{:?}", event),
true }
}
};
if !keep_running { break; }
} }
{ let _ = facet.activate(Account::new(syndicate::name!("termination")), |t| {
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("termination")); tracing::trace!("linked thread terminating associated facet");
let account = Account::new(Some(AnyValue::symbol("termination")), trace_collector); t.stop();
facet.activate(&account, cause, |t| { Ok(())
tracing::trace!("linked thread terminating associated facet"); });
Ok(t.stop())
});
}
tracing::trace!("linked thread done"); tracing::trace!("linked thread done");
}); });
t.linked_task(Some(AnyValue::symbol("cancel-wait")), async move { t.linked_task(syndicate::name!("cancel-wait"), async move {
future::pending::<()>().await; future::pending::<()>().await;
drop(watcher); drop(watcher);
Ok(LinkedTaskTermination::KeepFacet) Ok(())
}); });
Ok(()) 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 +1,45 @@
use preserves_schema::Codec;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose; use syndicate::convert::*;
use syndicate::preserves::rec; use syndicate::during::entity;
use syndicate::preserves::value::NestedValue; use syndicate::schemas::dataspace::Observe;
use crate::language::language; use crate::schemas::internal_services;
use crate::lifecycle;
use crate::schemas::internal_services::DebtReporter;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) { pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("debt_reporter_listener")), move |t| { t.spawn(syndicate::name!("on_demand", module = module_path!()), move |t| {
Ok(during!(t, ds, language(), <run-service $spec: DebtReporter>, |t: &mut Activation| { let monitor = entity(())
t.spawn_link(Some(rec![AnyValue::symbol("debt_reporter"), language().unparse(&spec)]), .on_asserted_facet({
enclose!((ds) |t| run(t, ds, spec))); let ds = Arc::clone(&ds);
Ok(()) move |_, t, _| {
})) let ds = Arc::clone(&ds);
t.spawn_link(tracing::Span::current(), |t| run(t, ds));
Ok(())
}
})
.create_cap(t);
let spec = from_io_value(&internal_services::DebtReporter)?;
ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<require-service #(spec)>},
observer: monitor,
});
Ok(())
}); });
} }
fn run(t: &mut Activation, ds: Arc<Cap>, spec: DebtReporter) -> ActorResult { fn run(t: &mut Activation, ds: Arc<Cap>) -> ActorResult {
ds.assert(t, language(), &lifecycle::started(&spec)); let spec = from_io_value(&internal_services::DebtReporter)?;
ds.assert(t, language(), &lifecycle::ready(&spec)); ds.assert(t, syndicate_macros::template!("<service-running =spec>"));
t.every(core::time::Duration::from_millis((spec.interval_seconds.0 * 1000.0) as u64), |_t| { t.linked_task(syndicate::name!("tick"), async {
for (account_id, (name, debt)) in syndicate::actor::ACCOUNTS.read().iter() { let mut timer = tokio::time::interval(core::time::Duration::from_secs(1));
tracing::info!(account_id, ?name, debt = ?debt.load(Ordering::Relaxed)); loop {
timer.tick().await;
for (id, (name, debt)) in syndicate::actor::ACCOUNTS.read().unwrap().iter() {
let _enter = name.enter();
tracing::info!(id, debt = ?debt.load(std::sync::atomic::Ordering::Relaxed));
}
} }
});
// let snapshot = syndicate::actor::ACTORS.read().clone(); Ok(())
// 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 +1,4 @@
pub mod config_watcher; pub mod config_watcher;
pub mod daemon;
pub mod debt_reporter; pub mod debt_reporter;
pub mod gatekeeper;
pub mod http_router;
pub mod tcp_relay_listener; pub mod tcp_relay_listener;
pub mod unix_relay_listener; pub mod unix_relay_listener;

View File

@ -1,119 +1,69 @@
use preserves_schema::Codec;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::Arc; use std::sync::Arc;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose; use syndicate::convert::*;
use syndicate::preserves::rec; use syndicate::during::entity;
use syndicate::preserves::value::NestedValue; use syndicate::schemas::dataspace::Observe;
use syndicate::supervise::{Supervisor, SupervisorConfiguration}; use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use syndicate::trace; use syndicate::value::NestedValue;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use crate::language::language;
use crate::lifecycle;
use crate::protocol::detect_protocol; use crate::protocol::detect_protocol;
use crate::schemas::internal_services::TcpWithoutHttp; use crate::schemas::internal_services;
use syndicate_macros::during; pub fn on_demand(t: &mut Activation, ds: Arc<Cap>, gateway: Arc<Cap>) {
t.spawn(syndicate::name!("on_demand", module = module_path!()), move |t| {
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) { let monitor = entity(())
t.spawn(Some(AnyValue::symbol("tcp_relay_listener")), move |t| { .on_asserted_facet({
enclose!((ds) during!(t, ds, language(), <run-service $spec: TcpWithoutHttp::<AnyValue>>, |t| { let ds = Arc::clone(&ds);
run_supervisor(t, ds.clone(), spec) move |_, t, captures: AnyValue| {
})); let ds = Arc::clone(&ds);
let gateway = Arc::clone(&gateway);
Supervisor::start(
t,
syndicate::name!(parent: None, "relay", addr = ?captures),
SupervisorConfiguration::default(),
move |t| run(t, Arc::clone(&ds), Arc::clone(&gateway), captures.clone()));
Ok(())
}
})
.create_cap(t);
ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<require-service $(<relay-listener <tcp _ _>>)>},
observer: monitor,
});
Ok(()) Ok(())
}); });
} }
fn run_supervisor(t: &mut Activation, ds: Arc<Cap>, spec: TcpWithoutHttp) -> ActorResult { fn run(
Supervisor::start( t: &'_ mut Activation,
t, ds: Arc<Cap>,
Some(rec![AnyValue::symbol("relay"), language().unparse(&spec)]), gateway: Arc<Cap>,
SupervisorConfiguration::default(), captures: AnyValue,
enclose!((ds, spec) lifecycle::updater(ds, spec)), ) -> ActorResult {
enclose!((ds) move |t| enclose!((ds, spec) run(t, ds, spec)))) let spec = internal_services::TcpRelayListener::try_from(&from_any_value(
} &captures.value().to_sequence()?[0])?)?;
let host = spec.addr.host.clone();
fn run(t: &mut Activation, ds: Arc<Cap>, spec: TcpWithoutHttp) -> ActorResult { let port = u16::try_from(&spec.addr.port).map_err(|_| "Invalid TCP port number")?;
lifecycle::terminate_on_service_restart(t, &ds, &spec);
let httpd = t.named_field("httpd", None::<Arc<Cap>>);
{ {
let ad = spec.addr.clone(); let spec = from_io_value(&spec)?;
let ad2 = ad.clone(); ds.assert(t, syndicate_macros::template!("<service-running =spec>"));
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 parent_span = tracing::Span::current();
let TcpWithoutHttp { addr, gatekeeper } = spec.clone(); t.linked_task(syndicate::name!("listener"), async move {
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 listen_addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(listen_addr).await?; let listener = TcpListener::bind(listen_addr).await?;
tracing::info!("listening");
{
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 { loop {
let (stream, addr) = listener.accept().await?; let (stream, addr) = listener.accept().await?;
let gatekeeper = gatekeeper.clone(); let gateway = Arc::clone(&gateway);
let name = Some(rec![AnyValue::symbol("tcp"), AnyValue::new(format!("{}", &addr))]); Actor::new().boot(syndicate::name!(parent: parent_span.clone(), "conn"),
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("connect")); move |t| Ok(t.linked_task(
let account = Account::new(name.clone(), trace_collector.clone()); tracing::Span::current(),
if !facet.activate( detect_protocol(t.facet.clone(), stream, gateway, addr))));
&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(()) Ok(())

View File

@ -1,94 +1,82 @@
use preserves_schema::Codec; use std::convert::TryFrom;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose; use syndicate::convert::*;
use syndicate::during::entity;
use syndicate::error::Error; use syndicate::error::Error;
use syndicate::preserves::rec;
use syndicate::preserves::value::NestedValue;
use syndicate::relay; use syndicate::relay;
use syndicate::supervise::{Supervisor, SupervisorConfiguration}; use syndicate::schemas::dataspace::Observe;
use syndicate::trace; use syndicate::value::NestedValue;
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tokio::net::UnixStream; use tokio::net::UnixStream;
use crate::language::language;
use crate::lifecycle;
use crate::protocol::run_connection; use crate::protocol::run_connection;
use crate::schemas::internal_services::UnixRelayListener; use crate::schemas::internal_services;
use syndicate_macros::during; pub fn on_demand(t: &mut Activation, ds: Arc<Cap>, gateway: Arc<Cap>) {
t.spawn(syndicate::name!("on_demand", module = module_path!()), move |t| {
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) { let monitor = entity(())
t.spawn(Some(AnyValue::symbol("unix_relay_listener")), move |t| { .on_asserted_facet({
Ok(during!(t, ds, language(), <run-service $spec: UnixRelayListener::<AnyValue>>, |t| { let ds = Arc::clone(&ds);
Supervisor::start( move |_, t, captures| {
t, let ds = Arc::clone(&ds);
Some(rec![AnyValue::symbol("relay"), language().unparse(&spec)]), let gateway = Arc::clone(&gateway);
SupervisorConfiguration::default(), t.spawn_link(syndicate::name!(parent: None, "relay", addr = ?captures),
enclose!((ds, spec) lifecycle::updater(ds, spec)), |t| run(t, ds, gateway, captures));
enclose!((ds) move |t| enclose!((ds, spec) run(t, ds, spec)))) Ok(())
})) }
})
.create_cap(t);
ds.assert(t, &Observe {
pattern: syndicate_macros::pattern!{<require-service $(<relay-listener <unix _>>)>},
observer: monitor,
});
Ok(())
}); });
} }
fn run(t: &mut Activation, ds: Arc<Cap>, spec: UnixRelayListener) -> ActorResult { fn run(
lifecycle::terminate_on_service_restart(t, &ds, &spec); t: &'_ mut Activation,
ds: Arc<Cap>,
gateway: Arc<Cap>,
captures: AnyValue,
) -> ActorResult {
let spec = internal_services::UnixRelayListener::try_from(&from_any_value(
&captures.value().to_sequence()?[0])?)?;
let path_str = spec.addr.path.clone(); let path_str = spec.addr.path.clone();
let facet = t.facet_ref(); {
let trace_collector = t.trace_collector(); let spec = from_io_value(&spec)?;
t.linked_task(Some(AnyValue::symbol("listener")), async move { ds.assert(t, syndicate_macros::template!("<service-running =spec>"));
}
let parent_span = tracing::Span::current();
t.linked_task(syndicate::name!("listener"), async move {
let listener = bind_unix_listener(&PathBuf::from(path_str)).await?; let listener = bind_unix_listener(&PathBuf::from(path_str)).await?;
tracing::info!("listening");
{
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 { loop {
let (stream, _addr) = listener.accept().await?; let (stream, _addr) = listener.accept().await?;
let peer = stream.peer_cred()?; let peer = stream.peer_cred()?;
let gatekeeper = spec.gatekeeper.clone(); let gateway = Arc::clone(&gateway);
let name = Some(rec![AnyValue::symbol("unix"), Actor::new().boot(
AnyValue::new(peer.pid().unwrap_or(-1)), syndicate::name!(parent: parent_span.clone(), "conn",
AnyValue::new(peer.uid())]); pid = ?peer.pid().unwrap_or(-1),
let cause = trace_collector.as_ref().map(|_| trace::TurnCause::external("connect")); uid = peer.uid()),
let account = Account::new(name.clone(), trace_collector.clone()); |t| Ok(t.linked_task(
if !facet.activate( tracing::Span::current(),
&account, cause, enclose!((trace_collector) move |t| { {
t.spawn(name, |t| { let facet = t.facet.clone();
Ok(t.linked_task(None, { async move {
let facet = t.facet_ref(); tracing::info!(protocol = %"unix");
async move { let (i, o) = stream.into_split();
tracing::info!(protocol = %"unix"); run_connection(facet,
let (i, o) = stream.into_split(); relay::Input::Bytes(Box::pin(i)),
run_connection(trace_collector, relay::Output::Bytes(Box::pin(o)),
facet, gateway)
relay::Input::Bytes(Box::pin(i)), }
relay::Output::Bytes(Box::pin(o)), })));
gatekeeper);
Ok(LinkedTaskTermination::KeepFacet)
}
}))
});
Ok(())
}))
{
return Ok(LinkedTaskTermination::Normal);
}
} }
}); });
Ok(()) Ok(())
@ -105,7 +93,7 @@ async fn bind_unix_listener(path: &PathBuf) -> Result<UnixListener, Error> {
Ok(_probe) => Err(e)?, // Someone's already there! Give up. Ok(_probe) => Err(e)?, // Someone's already there! Give up.
Err(f) if f.kind() == io::ErrorKind::ConnectionRefused => { Err(f) if f.kind() == io::ErrorKind::ConnectionRefused => {
// Try to steal the socket. // Try to steal the socket.
tracing::debug!("Cleaning stale socket"); tracing::info!("Cleaning stale socket");
std::fs::remove_file(path)?; std::fs::remove_file(path)?;
Ok(UnixListener::bind(path)?) Ok(UnixListener::bind(path)?)
} }

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,6 +1,6 @@
[package] [package]
name = "syndicate" name = "syndicate"
version = "0.40.0" version = "0.10.0"
authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"] authors = ["Tony Garnock-Jones <tonyg@leastfixedpoint.com>"]
edition = "2018" edition = "2018"
@ -13,23 +13,21 @@ license = "Apache-2.0"
vendored-openssl = ["openssl/vendored"] vendored-openssl = ["openssl/vendored"]
[build-dependencies] [build-dependencies]
preserves-schema = "5.995" preserves-schema = "1.0.0"
[dependencies] [dependencies]
preserves = "4.995" preserves = "1.0.0"
preserves-schema = "5.995" preserves-schema = "1.0.0"
tokio = { version = "1.10", features = ["io-std", "io-util", "macros", "rt", "rt-multi-thread", "time"] } tokio = { version = "1.10", features = ["io-util", "macros", "rt", "rt-multi-thread", "time"] }
tokio-util = "0.6" tokio-util = "0.6"
bytes = "1.0" bytes = "1.0"
futures = "0.3" futures = "0.3"
blake2 = "0.10"
getrandom = "0.2" getrandom = "0.2"
hmac = "0.12" hmac = "0.11"
lazy_static = "1.4" sha2 = "0.9"
parking_lot = "0.11"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.2" tracing-subscriber = "0.2"
@ -44,10 +42,3 @@ criterion = "0.3"
[[bench]] [[bench]]
name = "bench_dataspace" name = "bench_dataspace"
harness = false harness = false
[[bench]]
name = "ring"
harness = false
[package.metadata.workspaces]
independent = true

View File

@ -1,11 +1,11 @@
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use std::iter::FromIterator;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::time::Instant; use std::time::Instant;
use syndicate::language;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::during::entity; use syndicate::during::entity;
use syndicate::dataspace::Dataspace; use syndicate::dataspace::Dataspace;
@ -31,7 +31,8 @@ struct ShutdownEntity;
impl Entity<AnyValue> for ShutdownEntity { impl Entity<AnyValue> for ShutdownEntity {
fn message(&mut self, t: &mut Activation, _m: AnyValue) -> ActorResult { fn message(&mut self, t: &mut Activation, _m: AnyValue) -> ActorResult {
Ok(t.stop()) t.stop();
Ok(())
} }
} }
@ -52,18 +53,25 @@ pub fn bench_pub(c: &mut Criterion) {
b.iter_custom(|iters| { b.iter_custom(|iters| {
let start = Instant::now(); let start = Instant::now();
rt.block_on(async move { rt.block_on(async move {
Actor::top(None, move |t| { Actor::new().boot(syndicate::name!("dataspace"), move |t| {
let _ = t.prevent_inert_check(); let ds = t.create(Dataspace::new());
// 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); let shutdown = t.create(ShutdownEntity);
for _ in 0..iters { let account = Account::new(syndicate::name!("sender-account"));
t.message(&ds, says(AnyValue::new("bench_pub"), t.linked_task(syndicate::name!("sender"), async move {
Value::ByteString(vec![]).wrap())); for _ in 0..iters {
} let ds = Arc::clone(&ds);
t.message(&shutdown, AnyValue::new(true)); external_event(&Arc::clone(&ds.mailbox), &account, Box::new(
move |t| t.with_entity(
&ds,
|t, e| e.message(t, says(AnyValue::new("bench_pub"),
Value::ByteString(vec![]).wrap())))))?
}
external_event(&Arc::clone(&shutdown.mailbox), &account, Box::new(
move |t| t.with_entity(
&shutdown,
|t, e| e.message(t, AnyValue::new(true)))))?;
Ok(())
});
Ok(()) Ok(())
}).await.unwrap().unwrap(); }).await.unwrap().unwrap();
}); });
@ -77,27 +85,32 @@ pub fn bench_pub(c: &mut Criterion) {
rt.block_on(async move { rt.block_on(async move {
let turn_count = Arc::new(AtomicU64::new(0)); let turn_count = Arc::new(AtomicU64::new(0));
Actor::top(None, { Actor::new().boot(syndicate::name!("dataspace"), {
let iters = iters.clone(); let iters = iters.clone();
let turn_count = Arc::clone(&turn_count); let turn_count = Arc::clone(&turn_count);
move |t| { move |t| {
let ds = Cap::new(&t.create(Dataspace::new(None))); let ds = Cap::new(&t.create(Dataspace::new()));
let shutdown = entity(()) let shutdown = entity(())
.on_asserted(|_, _, _| Ok(Some(Box::new(|_, t| Ok(t.stop()))))) .on_asserted(|_, _, _| {
Ok(Some(Box::new(|_, t| {
t.stop();
Ok(())
})))
})
.create_cap(t); .create_cap(t);
ds.assert(t, language(), &Observe { ds.assert(t, &Observe {
pattern: p::Pattern::Bind { pattern: p::Pattern::DBind(Box::new(p::DBind {
pattern: Box::new(p::Pattern::Lit { pattern: p::Pattern::DLit(Box::new(p::DLit {
value: Box::new(p::AnyAtom::Symbol("consumer".to_owned())), value: AnyValue::symbol("consumer"),
}), })),
}, })),
observer: shutdown, observer: shutdown,
}); });
t.spawn(Some(AnyValue::symbol("consumer")), move |t| { Actor::new().boot(syndicate::name!("consumer"), move |t| {
struct Receiver(Arc<AtomicU64>); struct Receiver(Arc<AtomicU64>);
impl Entity<AnyValue> for Receiver { impl Entity<AnyValue> for Receiver {
fn message(&mut self, _t: &mut Activation, _m: AnyValue) -> ActorResult { fn message(&mut self, _t: &mut Activation, _m: AnyValue) -> ActorResult {
@ -109,41 +122,52 @@ pub fn bench_pub(c: &mut Criterion) {
let shutdown = Cap::new(&t.create(ShutdownEntity)); let shutdown = Cap::new(&t.create(ShutdownEntity));
let receiver = Cap::new(&t.create(Receiver(Arc::clone(&turn_count)))); let receiver = Cap::new(&t.create(Receiver(Arc::clone(&turn_count))));
ds.assert(t, &(), &AnyValue::symbol("consumer")); ds.assert(t, Value::<AnyValue, _>::symbol("consumer").wrap());
ds.assert(t, language(), &Observe { ds.assert(t, &Observe {
pattern: p::Pattern::Group { pattern: p::Pattern::DCompound(Box::new(p::DCompound::Rec {
type_: Box::new(p::GroupType::Rec { ctor: Box::new(p::CRec {
label: AnyValue::symbol("Says"), label: AnyValue::symbol("Says"),
arity: 2.into(),
}), }),
entries: Map::from([ members: Map::from_iter(vec![
(p::_Any::new(0), p::Pattern::Lit { (0.into(), p::Pattern::DLit(Box::new(p::DLit {
value: Box::new(p::AnyAtom::String("bench_pub".to_owned())), value: AnyValue::new("bench_pub"),
}), }))),
(p::_Any::new(1), p::Pattern::Bind { (1.into(), p::Pattern::DBind(Box::new(p::DBind {
pattern: Box::new(p::Pattern::Discard), pattern: p::Pattern::DDiscard(Box::new(p::DDiscard)),
}), }))),
]), ].into_iter()),
}, })),
observer: receiver, observer: receiver,
}); });
ds.assert(t, language(), &Observe { ds.assert(t, &Observe {
pattern: p::Pattern::Bind { pattern: p::Pattern::DBind(Box::new(p::DBind {
pattern: Box::new(p::Pattern::Lit { pattern: p::Pattern::DLit(Box::new(p::DLit {
value: Box::new(p::AnyAtom::Bool(true)), value: AnyValue::new(true),
}), })),
}, })),
observer: shutdown, observer: shutdown,
}); });
t.after(core::time::Duration::from_secs(0), move |t| { let account = Arc::clone(t.account());
t.linked_task(syndicate::name!("sender"), async move {
for _i in 0..iters { for _i in 0..iters {
ds.message(t, &(), &says(AnyValue::new("bench_pub"), let ds = Arc::clone(&ds);
Value::ByteString(vec![]).wrap())); external_event(&Arc::clone(&ds.underlying.mailbox), &account, Box::new(
move |t| t.with_entity(
&ds.underlying,
|t, e| e.message(t, says(AnyValue::new("bench_pub"),
Value::ByteString(vec![]).wrap())))))?
}
{
let ds = Arc::clone(&ds);
external_event(&Arc::clone(&ds.underlying.mailbox), &account, Box::new(
move |t| t.with_entity(
&ds.underlying,
|t, e| e.message(t, AnyValue::new(true)))))?;
} }
ds.message(t, &(), &AnyValue::new(true));
Ok(()) Ok(())
}); });
Ok(()) Ok(())
}); });
Ok(()) Ok(())

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

@ -9,12 +9,16 @@ mod syndicate_plugins {
pub(super) struct PatternPlugin; pub(super) struct PatternPlugin;
impl Plugin for PatternPlugin { impl Plugin for PatternPlugin {
fn generate_definition( fn generate(
&self, &self,
_m: &mut context::ModuleContext, m: &mut context::ModuleContext,
_definition_name: &str, _definition_name: &str,
_definition: &Definition, _definition: &Definition,
) { ) {
if m.mode.is_some() {
return;
}
// TODO: Emit code for building instances of sturdy.Pattern and sturdy.Template // TODO: Emit code for building instances of sturdy.Pattern and sturdy.Template
} }
} }
@ -26,11 +30,11 @@ fn main() -> std::io::Result<()> {
let mut gen_dir = buildroot.clone(); let mut gen_dir = buildroot.clone();
gen_dir.push("src/schemas"); gen_dir.push("src/schemas");
let mut c = CompilerConfig::new("crate::schemas".to_owned()); let mut c = CompilerConfig::new(gen_dir, "crate::schemas".to_owned());
c.plugins.push(Box::new(syndicate_plugins::PatternPlugin)); c.plugins.push(Box::new(syndicate_plugins::PatternPlugin));
c.add_external_module(ExternalModule::new(vec!["EntityRef".to_owned()], "crate::actor")); c.module_aliases.insert(vec!["EntityRef".to_owned()], "crate::actor".to_owned());
let inputs = expand_inputs(&vec!["protocols/schema-bundle.bin".to_owned()])?; let inputs = expand_inputs(&vec!["protocols/schema-bundle.bin".to_owned()])?;
c.load_schemas_and_bundles(&inputs, &vec![])?; c.load_schemas_and_bundles(&inputs)?;
compile(&c, &mut CodeCollector::files(gen_dir)) compile(&c)
} }

View File

@ -4,5 +4,5 @@ clean:
rm -f schema-bundle.bin rm -f schema-bundle.bin
schema-bundle.bin: schemas/*.prs schema-bundle.bin: schemas/*.prs
preserves-schemac schemas > $@.tmp preserves-schemac schemas/*.prs > $@.tmp
mv $@.tmp $@ mv $@.tmp $@

View File

@ -1,44 +1,14 @@
´³bundle·µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³ ´³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³ 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„„„µ³timer„´³schema·³version³ definitions·³SetTimer´³rec´³lit³ set-timer„´³tupleµ´³named³label³any„´³named³msecs´³atom³Double„„´³named³kind´³refµ„³ TimerKind„„„„„³ LaterThan´³rec´³lit³
ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³ later-than„´³tupleµ´³named³msecs´³atom³Double„„„„„³ TimerKind´³orµµ±relative´³lit³relative„„µ±absolute´³lit³absolute„„µ±clear´³lit³clear„„„„³ TimerExpired´³rec´³lit³ timer-expired„´³tupleµ´³named³label³any„´³named³msecs´³atom³Double„„„„„„³ 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³
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µµ±absent´³lit€„„µ±present´³atom³
ByteString„„„„³ RequestHost´³orµµ±absent´³lit€„„µ±present´³atom³String„„„„³ 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µ„³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„„µ± 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„„„„„„³CArr´³rec´³lit³arr„´³tupleµ´³named³arity´³atom³ SignedInteger„„„„„³CRec´³rec´³lit³rec„´³tupleµ´³named³label³any„´³named³arity´³atom³ SignedInteger„„„„„³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„„„„„³CDict´³rec´³lit³dict„´³tupleµ„„„³PAtom´³orµµ±Boolean´³lit³Boolean„„µ±Float´³lit³Float„„µ±Double´³lit³Double„„µ± SignedInteger´³lit³ SignedInteger„„µ±String´³lit³String„„µ±
ByteString´³lit³ 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µµ± ByteString„„µ±Symbol´³lit³Symbol„„„„³PBind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Caveat´³orµµ±Rewrite´³refµ„³Rewrite„„µ±Alts´³refµ„³Alts„„„„³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<69>´³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´³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³ TAttenuate„„µ±TRef´³refµ„³TRef„„µ±Lit´³refµ„³Lit„„µ± TCompound´³refµ„³ TCompound„„„„³ PCompound´³rec´³lit³compound„´³tupleµ´³named³ctor´³refµ„³ConstructorSpec„„´³named³members´³refµ„³PCompoundMembers„„„„„³ PEmbedded´³lit³Embedded„³ SturdyRef´³rec´³lit³ref„´³tupleµ´³named³oid³any„´³named³ caveatChain´³seqof´³refµ„³ Attenuation„„„´³named³sig´³atom³
parameters´³refµ„³ ByteString„„„„„³ TCompound´³rec´³lit³compound„´³tupleµ´³named³ctor´³refµ„³ConstructorSpec„„´³named³members´³refµ„³TCompoundMembers„„„„„³
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„„„„„„„„„³ TAttenuate´³rec´³lit³ attenuate„´³tupleµ´³named³template´³refµ„³Template„„´³named³ attenuation´³refµ„³ Attenuation„„„„„³ Attenuation´³seqof´³refµ„³Caveat„„³ConstructorSpec´³orµµ±CRec´³refµ„³CRec„„µ±CArr´³refµ„³CArr„„µ±CDict´³refµ„³CDict„„„„³PCompoundMembers´³dictof³any´³refµ„³Pattern„„³TCompoundMembers´³dictof³any´³refµ„³Template„„„³ 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·³RequireService´³rec´³lit³require-service„´³tupleµ´³named³ serviceName³any„„„„³ServiceRunning´³rec´³lit³service-running„´³tupleµ´³named³ serviceName³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³ dataspace„´³schema·³version³ definitions·³Observe´³rec´³lit³Observe„´³tupleµ´³named³pattern´³refµ³dataspacePatterns„³Pattern„„´³named³observer´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³
Parameters´³andµ´³dict·³oid´³named³oid³any„³sig´³named³sig´³atom³ gatekeeper„´³schema·³version³ definitions·³Bind´³rec´³lit³bind„´³tupleµ´³named³oid³any„´³named³key´³atom³
ByteString„„„„´³named³caveats´³refµ„³ CaveatsField„„„„³ ByteString„„´³named³target´³embedded³any„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³ sturdyref´³refµ³sturdy„³ SturdyRef„„´³named³observer´³embedded´³embedded³any„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³ racketEvent„´³schema·³version³ definitions·³ RacketEvent´³rec´³lit³ racket-event„´³tupleµ´³named³source´³embedded³any„„´³named³event´³embedded³any„„„„„„³ embeddedType€„„µ³genericProtocol„´³schema·³version³ definitions·³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³sync„´³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³assert„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„„„³Message´³rec´³lit³message„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³retract„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType€„„µ³externalProtocol„´³schema·³version³ definitions·³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³sync„´³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³assert„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„„„³Message´³rec´³lit³message„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³retract„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType´³refµ³sturdy„³WireRef„„„µ³internalProtocol„´³schema·³version³ definitions·³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³sync„´³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³assert„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„„„³Message´³rec´³lit³message„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³retract„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ 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·³CArr´³rec´³lit³arr„´³tupleµ´³named³arity´³atom³ SignedInteger„„„„„³CRec´³rec´³lit³rec„´³tupleµ´³named³label³any„´³named³arity´³atom³ SignedInteger„„„„„³DLit´³rec´³lit³lit„´³tupleµ´³named³value³any„„„„³CDict´³rec´³lit³dict„´³tupleµ„„„³DBind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Pattern´³orµµ±DDiscard´³refµ„³DDiscard„„µ±DBind´³refµ„³DBind„„µ±DLit´³refµ„³DLit„„µ± DCompound´³refµ„³ DCompound„„„„³DDiscard´³rec´³lit³_„´³tupleµ„„„³ DCompound´³orµµ±rec´³rec´³lit³compound„´³tupleµ´³named³ctor´³refµ„³CRec„„´³named³members´³dictof´³atom³ SignedInteger„´³refµ„³Pattern„„„„„„„µ±arr´³rec´³lit³compound„´³tupleµ´³named³ctor´³refµ„³CArr„„´³named³members´³dictof´³atom³ SignedInteger„´³refµ„³Pattern„„„„„„„µ±dict´³rec´³lit³compound„´³tupleµ´³named³ctor´³refµ„³CDict„„´³named³members´³dictof³any´³refµ„³Pattern„„„„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³secureChatProtocol„´³schema·³version³ definitions·³Join´³rec´³lit³
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µ„³ joinedUser„´³tupleµ´³named³uid´³refµ„³UserId„„´³named³handle´³embedded´³refµ„³Session„„„„„„³Says´³rec´³lit³says„´³tupleµ´³named³who´³refµ„³UserId„„´³named³what´³atom³String„„„„„³UserId´³atom³ SignedInteger„³Session´³orµµ± observeUsers´³rec´³lit³Observe„´³tupleµ´³lit³user„´³named³observer´³embedded´³refµ„³UserInfo„„„„„„„µ± observeSpeech´³rec´³lit³Observe„´³tupleµ´³lit³says„´³named³observer´³embedded´³refµ„³Says„„„„„„„µ± NickClaim´³refµ„³ NickClaim„„µ±Says´³refµ„³Says„„„„³UserInfo´³rec´³lit³user„´³tupleµ´³named³uid´³refµ„³UserId„„´³named³name´³atom³String„„„„„³ NickClaim´³rec´³lit³ claimNick„´³tupleµ´³named³uid´³refµ„³UserId„„´³named³name´³atom³String„„´³named³k´³embedded´³refµ„³NickClaimResponse„„„„„„³ NickConflict´³rec´³lit³ nickConflict„´³tupleµ„„„³NickClaimResponse´³orµµ±true´³lit<69>„„µ± NickConflict´³refµ„³ NickConflict„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³simpleChatProtocol„´³schema·³version³ definitions·³Says´³rec´³lit³Says„´³tupleµ´³named³who´³atom³String„„´³named³what´³atom³String„„„„„³Present´³rec´³lit³Present„´³tupleµ´³named³username´³atom³String„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„
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·³Nop´³lit€„³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„„µ±Nop´³refµ„³Nop„„„„³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 +1,4 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
Observe = <Observe @pattern dataspacePatterns.Pattern @observer #:any>. Observe = <Observe @pattern dataspacePatterns.Pattern @observer #!any>.

View File

@ -1,30 +1,16 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
# Dataspace patterns: *almost* a sublanguage of attenuation patterns. ; Dataspace patterns: a sublanguage of attenuation patterns.
# Pattern = DDiscard / DBind / DLit / DCompound .
# One key difference is that Dataspace patterns are extensible, in that
# they ignore fields not mentioned in group patterns.
Pattern = DDiscard = <_>.
/ @discard <_> DBind = <bind @pattern Pattern>.
/ <bind @pattern Pattern> DLit = <lit @value any>.
/ <lit @value AnyAtom> DCompound = @rec <compound @ctor CRec @members { int: Pattern ...:... }>
/ <group @type GroupType @entries { any: Pattern ...:... }> / @arr <compound @ctor CArr @members { int: Pattern ...:... }>
. / @dict <compound @ctor CDict @members { any: Pattern ...:... }> .
GroupType = CRec = <rec @label any @arity int>.
/ <rec @label any> CArr = <arr @arity int>.
/ <arr> CDict = <dict>.
/ <dict>
.
AnyAtom =
/ @bool bool
/ @double double
/ @int int
/ @string string
/ @bytes bytes
/ @symbol symbol
/ @embedded #:any
.

View File

@ -0,0 +1,3 @@
version 1 .
embeddedType sturdy.WireRef .
include "genericProtocol.prs".

View File

@ -1,87 +1,5 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
# --------------------------------------------------------------------------- Resolve = <resolve @sturdyref sturdy.SturdyRef @observer #!#!any>.
# Protocol at *gatekeeper* entities Bind = <bind @oid any @key bytes @target #!any>.
# Assertion. Gatekeeper will attempt to resolve `step`, responding with a `Resolved` to
# `observer`.
Resolve = <resolve @step Step @observer #:Resolved> .
Resolved = <accepted @responderSession #:any> / Rejected .
Step = <<rec> @stepType symbol [@detail any]> .
# ---------------------------------------------------------------------------
# Protocol at dataspaces *associated* with gatekeeper entities
# ## Handling `Resolve` requests
#
# When the gatekeeper entity receives a `Resolve` assertion (call it R1), it
#
# 1. asserts a `Resolve` (call it R2) into its associated dataspace that
# is the same as R1 except it has a different `observer`; and
#
# 2. observes a `Bind` with `description` matching the `step` of R1/R2
# according to `stepType` (e.g. treatment of SturdyStepType is not the
# same as treatment of NoiseStepType).
#
# Normally, an appropriate `Bind` is expected to exist. If the gatekeeper
# sees the `Bind` first, it takes the `target` from it and does whatever
# `stepType` mandates before replying to R1's observer.
#
# However, if a `Resolved` is asserted to R2's observer before a `Bind`
# appears, that resolution is relayed on to R1's observer directly, be it
# positive or negative, and the gatekeeper stops waiting for a `Bind`.
#
# This way, entities can keep an eye out for `Resolve` requests that will
# never complete, and answer `Rejected` to them even when no matching
# `Bind` exists. Entities could also use `Resolve` requests to synthesize a
# `Bind` in a "just-in-time" fashion.
#
# ## General treatment of `Bind` assertions
#
# When the gatekeeper sees a `Bind`, independently of any potential
# `Resolve` requests, it computes an appropriate PathStep from
# `description` pointing at `target`, and responds with a `Bound` to
# `observer` (if supplied).
#
Bind = <bind @description Description @target #:any @observer BindObserver> .
Description = <<rec> @stepType symbol [@detail any]> .
BindObserver = @present #:Bound / @absent #f .
Bound = <bound @pathStep PathStep> / Rejected .
# ---------------------------------------------------------------------------
# Protocol at client-side dataspaces, for resolution utilities
# Assertion. In response to observation of this with appropriate captures/wildcards in `addr`
# and `resolved`, respondent will follow `route.pathSteps` starting from one of the
# `route.transports`, asserting `ResolvePath` with the final `Resolved` as well as the selected
# transport `addr` and a `control` for it.
ResolvePath = <resolve-path @route Route @addr any @control #:TransportControl @resolved Resolved> .
TransportConnection = <connect-transport @addr any @control #:TransportControl @resolved Resolved> .
ResolvedPathStep = <path-step @origin #:Resolve @pathStep PathStep @resolved Resolved> .
PathStep = <<rec> @stepType symbol [@detail any]> .
# A `Route` describes a network path that can be followed to reach some target entity.
#
# It starts with a set of zero or more possible non-Syndicate `transports`. These could be
# `transportAddress.Tcp` values or similar. They are just suggestions; it's quite possible the
# endpoint is reachable by some means not listed. The network outside Syndicate is, after all,
# pretty diverse! In particular, *zero* `transports` may be provided, in which case some
# out-of-band means has to be used to make that first connection.
#
# The `transports` give instructions for contacting the first entity in the `Route` path. Often
# this will be a `gatekeeper`, or a `noise` protocol endpoint, or both. Occasionally, it may
# even be the desired target entity. Subsequent `pathSteps` describe how to proceed from the
# initial entity to the target.
#
# (`transports` should by rights be a set, not a sequence, but that opens up a Can Of Worms
# regarding dataspace patterns including literal sets that I can't deal with right now.)
Route = <route @transports [any ...] @pathSteps PathStep ...> .
TransportControl = ForceDisconnect .
ForceDisconnect = <force-disconnect> .
# ---------------------------------------------------------------------------
Rejected = <rejected @detail any> .

View File

@ -0,0 +1,17 @@
version 1 .
Packet = Turn / Error .
Error = <error @message string @detail any>.
Assertion = any .
Handle = int .
Event = Assert / Retract / Message / Sync .
Oid = int .
Turn = [TurnEvent ...].
TurnEvent = [@oid Oid @event Event].
Assert = <assert @assertion Assertion @handle Handle>.
Retract = <retract @handle Handle>.
Message = <message @body Assertion>.
Sync = <sync @peer #!#t>.

View File

@ -1,62 +0,0 @@
version 1 .
# Assertion in driver DS
# Causes creation of server and route
HttpBinding = <http-bind @host HostPattern @port int @method MethodPattern @path PathPattern @handler #:HttpRequest> .
# Assertion in driver DS
# Describes active server and route
HttpService = <http-service @host HostPattern @port int @method MethodPattern @path PathPattern> .
# Assertion in driver DS
# Describes active listener
HttpListener = <http-listener @port int> .
HostPattern = @host string / @any #f .
PathPattern = [PathPatternElement ...] .
PathPatternElement = @label string / @wildcard =_ / @rest =... .
MethodPattern = @any #f / @specific @"Lowercase" symbol .
# Assertion in driver DS
HttpRequest = <http-request
@sequenceNumber int
@host RequestHost
@port int
@method @"Lowercase" symbol
@path [string ...]
@headers Headers
@query {symbol: [QueryValue ...] ...:...}
@body RequestBody> .
Headers = {@"Lowercase" symbol: string ...:...} .
QueryValue = @string string / <file @filename string @headers Headers @body bytes> .
RequestBody = @absent #f / @present bytes .
RequestHost = @absent #f / @present string .
# Assertion to handler entity
HttpContext = <request @req HttpRequest @res #:HttpResponse> .
# HttpResponse protocol. Delivered to the `res` ref in `HttpContext`.
#
# (status | header)* . chunk* . done
#
# Done triggers completion of the response and retraction of the frame by the peer. If the
# HttpBinding responsible for the request is withdrawn mid-way through a response (i.e. when
# chunked transfer is used and at least one chunk has been sent) the request is abruptly
# closed; if it is withdrawn at any other moment in the lifetime of the request, a 500 Internal
# Server Error is send to the client.
#
@<TODO "trailers?">
HttpResponse =
# Messages.
/ <status @code int @message string>
/ <header @name symbol @value string>
/ <chunk @chunk Chunk>
/ <done @chunk Chunk>
.
Chunk = @string string / @bytes bytes .
# e.g. text/plain, text/html, application/json
MimeType = symbol .

View File

@ -0,0 +1,3 @@
version 1 .
embeddedType EntityRef.Cap .
include "genericProtocol.prs".

View File

@ -1,83 +0,0 @@
version 1 .
embeddedType EntityRef.Cap .
# https://noiseprotocol.org/
# ---------------------------------------------------------------------------
# Binding and connection
NoiseStepType = =noise .
# In a gatekeeper.Step, use ServiceSelector as detail.
NoiseStepDetail = ServiceSelector .
# In a gatekeeper.PathStep, use a NoiseSpec as detail.
NoisePathStepDetail = NoiseSpec .
# In a gatekeeper.Description, use a NoiseServiceSpec as detail.
NoiseDescriptionDetail = NoiseServiceSpec .
# ---------------------------------------------------------------------------
# Specification of target and bind addresses
ServiceSelector = any .
NoiseSpec = {
# The `serviceSelector` to use in a `NoiseStep` for `gatekeeper.Resolve`.
service: ServiceSelector,
# The responder's static public key. If not required (uncommon!), supply the empty ByteString.
key: bytes,
}
& @protocol NoiseProtocol
& @preSharedKeys NoisePreSharedKeys
.
NoiseServiceSpec = @base NoiseSpec & @secretKey SecretKeyField .
SecretKeyField = @present { secretKey: bytes } / @invalid { secretKey: any } / @absent {} .
# If absent, a default of DefaultProtocol is used. Most services will speak the default.
NoiseProtocol = @present { protocol: string } / @invalid { protocol: any } / @absent {} .
DefaultProtocol = "Noise_NK_25519_ChaChaPoly_BLAKE2s" .
# If present, Noise pre-shared-keys (PSKs) are drawn from the sequence as required; if the
# sequence is exhausted or not supplied, an all-zeros key is used each time a PSK is needed.
NoisePreSharedKeys = @present { preSharedKeys: [bytes ...] } / @invalid { preSharedKeys: any } / @absent {} .
# ---------------------------------------------------------------------------
# Handshaking and running a session
# 1. initiator asserts <resolve <noise ServiceSelector> #:A> at Gatekeeper
# 2. gatekeeper asserts <accepted #:B> at #:A
# 3. initiator asserts <initiator #:C> at #:B and then sends `Packet`s to #:B
# 4. responder sends `Packet`s to #:C
#
# Sessions begin with introduction of initiator (#:C) and responder (#:B) to each other, and
# then proceed by sending `Packet`s (from #:C) to #:B and (from #:B) to #:C according to
# the Noise protocol definition. Each `Packet` represents a complete logical unit of
# communication; for example, a complete Turn when layering the Syndicate protocol over Noise.
# Note well the restriction on Noise messages: no individual complete packet or packet fragment
# may exceed 65535 bytes (N.B. not 65536!). When `fragmented`, each portion of a `Packet` is a
# complete Noise "transport message"; when `complete`, the whole thing is likewise a complete
# "transport message".
#
# Retraction of the `Initiator` ends the session from the initiator-side; retraction of the
# `<accepted ...>` assertion ends the session from the responder-side.
SessionItem = Initiator / Packet .
# Assertion
Initiator = <initiator @initiatorSession #:Packet> .
# Message
Packet = @complete bytes / @fragmented [bytes ...] .
# When layering Syndicate protocol over noise,
#
# - the canonical encoding of the serviceSelector is the prologue
# - protocol.Packets MUST be encoded using the machine-oriented Preserves syntax
# - zero or more Turns are permitted per noise.Packet
# - each Turn must fit inside a single noise.Packet (fragment if needed)
# - payloads inside a noise.Packet may be padded at the end with byte 0x80 (128), which
# encodes `#f` in the machine-oriented Preserves syntax.
#
# In summary, each noise.Packet, once (reassembled and) decrypted, will be a sequence of zero
# or more machine-encoded protocol.Packets, followed by zero or more 0x80 bytes.
.

View File

@ -1,20 +0,0 @@
version 1 .
Packet = Turn / Error / Extension / Nop .
Extension = <<rec> @label any @fields [any ...]> .
Nop = #f .
Error = <error @message string @detail any>.
Assertion = any .
Handle = int .
Event = Assert / Retract / Message / Sync .
Oid = int .
Turn = [TurnEvent ...].
TurnEvent = [@oid Oid @event Event].
Assert = <A @assertion Assertion @handle Handle>.
Retract = <R @handle Handle>.
Message = <M @body Assertion>.
Sync = <S @peer #:#t>.

View File

@ -0,0 +1,3 @@
version 1 .
RacketEvent = <racket-event @source #!any @event #!any>.

View File

@ -0,0 +1,21 @@
version 1 .
embeddedType EntityRef.Cap .
UserId = int .
Join = <joinedUser @uid UserId @handle #!Session>.
Session = @observeUsers <Observe =user @observer #!UserInfo>
/ @observeSpeech <Observe =says @observer #!Says>
/ NickClaim
/ Says
.
NickClaim = <claimNick @uid UserId @name string @k #!NickClaimResponse>.
NickClaimResponse = #t / NickConflict .
UserInfo = <user @uid UserId @name string>.
Says = <says @who UserId @what string>.
NickConflict = <nickConflict>.

View File

@ -1,51 +1,5 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
# Asserts that a service should begin (and stay) running after waiting
# for its dependencies and considering reverse-dependencies, blocks,
# and so on.
RequireService = <require-service @serviceName any>. RequireService = <require-service @serviceName any>.
ServiceRunning = <service-running @serviceName any>.
# Asserts that a service should begin (and stay) running RIGHT NOW,
# without considering its dependencies.
RunService = <run-service @serviceName any>.
# Asserts one or more current states of service `serviceName`. The
# overall state of the service is the union of asserted `state`s.
#
# Only a few combinations make sense:
# - `started`
# - `started` + `ready`
# - `failed`
# - `complete`
#
ServiceState = <service-state @serviceName any @state State>.
# A running service publishes zero or more of these. The details of
# the object vary by service.
#
ServiceObject = <service-object @serviceName any @object any>.
# Possible service states.
State =
/ # The service has begun its startup routine, and may or may not be
# ready to take requests from other parties.
=started
/ # The service is ready to take requests from other parties.
# (This state is special in that it is asserted *in addition* to `started`.)
=ready
/ # The service has failed.
=failed
/ # The service has completed execution.
=complete
/ # Extension or user-defined state
@userDefined any
.
# Asserts that, when `depender` is `require-service`d, it should not be started until
# `dependee` has been asserted, and also that `dependee`'s `serviceName` should be
# `require-service`d.
ServiceDependency = <depends-on @depender any @dependee ServiceState>.
# Message. Triggers a service restart.
RestartService = <restart-service @serviceName any>.

View File

@ -0,0 +1,5 @@
version 1 .
embeddedType EntityRef.Cap .
Present = <Present @username string>.
Says = <Says @who string @what string>.

View File

@ -1,31 +0,0 @@
version 1 .
# A "standard" route is
#
# - a collection of websocket urls, for transport.
# - a noise tunnel, for server authentication, confidentiality and integrity.
# - a macaroon, for authorization.
#
# Making these choices allows a compact representation. Encoding a binary-syntax representation
# of a standard route using base64 produces a somewhat-convenient blob of text representing
# access to a network object that users can cut and paste.
#
# A `stdenv.StandardRoute.standard` can be rewritten to a `gatekeeper.Route` like this (with
# `$caveats`, if any, added as appropriate):
#
# <route $transports <noise { service: $service key: $key }> <ref { sig: $sig oid: $oid }>>
#
StandardRoute =
/ @standard [@transports [StandardTransport ...]
@key bytes
@service any
@sig bytes
@oid any
@caveats sturdy.Caveat ...]
/ @general gatekeeper.Route
.
StandardTransport =
/ @wsUrl string
/ @other any
.

View File

@ -1,38 +1,38 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
# Assertion: ; Assertion:
StreamConnection = <stream-connection @source #:Source @sink #:Sink @spec any>. StreamConnection = <stream-connection @source #!Source @sink #!Sink @spec any>.
# Assertions: ; Assertions:
StreamListenerReady = <stream-listener-ready @spec any>. StreamListenerReady = <stream-listener-ready @spec any>.
StreamListenerError = <stream-listener-error @spec any @message string>. StreamListenerError = <stream-listener-error @spec any @message string>.
# Assertion: ; Assertion:
StreamError = <error @message string>. StreamError = <error @message string>.
Source = Source =
# Assertions: ; Assertions:
/ <sink @controller #:Sink> / <sink @controller #!Sink>
/ StreamError / StreamError
# Messages: ; Messages:
/ <credit @amount CreditAmount @mode Mode> / <credit @amount CreditAmount @mode Mode>
. .
Sink = Sink =
# Assertions: ; Assertions:
/ <source @controller #:Source> / <source @controller #!Source>
/ StreamError / StreamError
# Messages: ; Messages:
/ <data @payload any @mode Mode> / <data @payload any @mode Mode>
/ <eof> / <eof>
. .
# Value: ; Value:
CreditAmount = @count int / @unbounded =unbounded . CreditAmount = @count int / @unbounded =unbounded .
# Value: ; Value:
Mode = =bytes / @lines LineMode / <packet @size int> / <object @description any>. Mode = =bytes / @lines LineMode / <packet @size int> / <object @description any>.
LineMode = =lf / =crlf . LineMode = =lf / =crlf .

View File

@ -1,70 +1,44 @@
version 1 . version 1 .
embeddedType EntityRef.Cap . embeddedType EntityRef.Cap .
# --------------------------------------------------------------------------- ; Each Attenuation is a stage. The sequence of Attenuations is run RIGHT-TO-LEFT.
# Binding and connection ; That is, the newest Attenuations are at the right.
SturdyRef = <ref @oid any @caveatChain [Attenuation ...] @sig bytes>.
SturdyStepType = =ref . ; An individual Attenuation is run RIGHT-TO-LEFT.
; That is, the newest Caveats are at the right.
Attenuation = [Caveat ...].
# In a gatekeeper.Step or gatekeeper.PathStep, use Parameters as detail. ; embodies 1st-party caveats over assertion structure, but nothing else
SturdyStepDetail = Parameters . ; can add 3rd-party caveats and richer predicates later
SturdyPathStepDetail = Parameters . Caveat = Rewrite / Alts .
Rewrite = <rewrite @pattern Pattern @template Template>.
# In a gatekeeper.Description, use the following detail.
SturdyDescriptionDetail = {
oid: any,
key: bytes,
} .
# ---------------------------------------------------------------------------
# Macaroons
# The sequence of Caveats is run RIGHT-TO-LEFT.
# That is, the newest Caveats are at the right.
#
# Let f(k,d) = HMAC-BLAKE2s-256(k,d)[0..16),
# e = canonical machine-oriented serialization of some preserves value, and
# k = the original secret key for the ref.
#
# The `sig` is then f(f(f(f(k, e(oid)), ...), e(Caveat)), ...).
#
SturdyRef = <ref @parameters Parameters> .
Parameters = {
oid: any,
sig: bytes,
} & @caveats CaveatsField .
CaveatsField = @present { caveats: [Caveat ...] } / @invalid { caveats: any } / @absent {} .
# embodies 1st-party caveats over assertion structure, but nothing else
# can add 3rd-party caveats and richer predicates later
Caveat = Rewrite / Alts / Reject / @unknown any .
Rewrite = <rewrite @pattern Pattern @template Template> .
Reject = <reject @pattern Pattern> .
Alts = <or @alternatives [Rewrite ...]>. Alts = <or @alternatives [Rewrite ...]>.
Oid = int . Oid = int .
WireRef = @mine [0 @oid Oid] / @yours [1 @oid Oid @attenuation Caveat ...]. WireRef = @mine [0 @oid Oid] / @yours [1 @oid Oid @attenuation Caveat ...].
# --------------------------------------------------------------------------- ;---------------------------------------------------------------------------
ConstructorSpec = CRec / CArr / CDict .
CRec = <rec @label any @arity int>.
CArr = <arr @arity int>.
CDict = <dict>.
Lit = <lit @value any>. Lit = <lit @value any>.
Pattern = PDiscard / PAtom / PEmbedded / PBind / PAnd / PNot / Lit / PCompound . Pattern = PDiscard / PAtom / PEmbedded / PBind / PAnd / PNot / Lit / PCompound .
PDiscard = <_>. PDiscard = <_>.
PAtom = =Boolean / =Double / =SignedInteger / =String / =ByteString / =Symbol . PAtom = =Boolean / =Float / =Double / =SignedInteger / =String / =ByteString / =Symbol .
PEmbedded = =Embedded . PEmbedded = =Embedded .
PBind = <bind @pattern Pattern>. PBind = <bind @pattern Pattern>.
PAnd = <and @patterns [Pattern ...]>. PAnd = <and @patterns [Pattern ...]>.
PNot = <not @pattern Pattern>. PNot = <not @pattern Pattern>.
PCompound = PCompound = <compound @ctor ConstructorSpec @members PCompoundMembers>.
/ @rec <rec @label any @fields [Pattern ...]> PCompoundMembers = { any: Pattern ...:... }.
/ @arr <arr @items [Pattern ...]>
/ @dict <dict @entries { any: Pattern ...:... }> .
Template = TAttenuate / TRef / Lit / TCompound . Template = TAttenuate / TRef / Lit / TCompound .
TAttenuate = <attenuate @template Template @attenuation [Caveat ...]>. TAttenuate = <attenuate @template Template @attenuation Attenuation>.
TRef = <ref @binding int>. TRef = <ref @binding int>.
TCompound = TCompound = <compound @ctor ConstructorSpec @members TCompoundMembers>.
/ @rec <rec @label any @fields [Template ...]> TCompoundMembers = { any: Template ...:... }.
/ @arr <arr @items [Template ...]>
/ @dict <dict @entries { any: Template ...:... }> .

View File

@ -4,4 +4,4 @@ embeddedType EntityRef.Cap .
TcpRemote = <tcp-remote @host string @port int>. TcpRemote = <tcp-remote @host string @port int>.
TcpLocal = <tcp-local @host string @port int>. TcpLocal = <tcp-local @host string @port int>.
TcpPeerInfo = <tcp-peer @handle #:any @local TcpLocal @remote TcpRemote>. TcpPeerInfo = <tcp-peer @handle #!any @local TcpLocal @remote TcpRemote>.

View File

@ -1,7 +1,7 @@
version 1 . version 1 .
SetTimer = <set-timer @label any @seconds double @kind TimerKind>. SetTimer = <set-timer @label any @msecs double @kind TimerKind>.
TimerExpired = <timer-expired @label any @seconds double>. TimerExpired = <timer-expired @label any @msecs double>.
TimerKind = =relative / =absolute / =clear . TimerKind = =relative / =absolute / =clear .
LaterThan = <later-than @seconds double>. LaterThan = <later-than @msecs double>.

View File

@ -1,96 +0,0 @@
version 1 .
embeddedType EntityRef.Cap .
TraceEntry = <trace
@timestamp @"seconds since Unix epoch" double
@actor ActorId
@item ActorActivation> .
ActorActivation =
/ <start @actorName Name>
/ @turn TurnDescription
/ <stop @status ExitStatus>
.
Name =
/ <anonymous>
/ <named @name any>
.
ActorId = any .
FacetId = any .
Oid = any .
TaskId = any .
TurnId = any .
ExitStatus = =ok / protocol.Error .
# Trace information associated with a turn.
TurnDescription = <turn @id TurnId @cause TurnCause @actions [ActionDescription ...]> .
# The cause of a turn.
TurnCause =
/ @turn <caused-by @id TurnId>
/ <cleanup>
/ @linkedTaskRelease <linked-task-release @id TaskId @reason LinkedTaskReleaseReason>
/ @periodicActivation <periodic-activation @"`period` is in seconds" @period double>
/ <delay @causingTurn TurnId @"`amount` is in seconds" @amount double>
/ <external @description any>
.
LinkedTaskReleaseReason = =cancelled / =normal .
# An actual event carried within a turn.
TurnEvent =
/ <assert @assertion AssertionDescription @handle protocol.Handle>
/ <retract @handle protocol.Handle>
/ <message @body AssertionDescription>
/ <sync @peer Target>
/ # A souped-up, disguised, special-purpose `retract` event.
@breakLink <break-link @source ActorId @handle protocol.Handle>
.
TargetedTurnEvent = <event @target Target @detail TurnEvent> .
# An action taken during a turn.
ActionDescription =
/ # The active party is processing a new `event` for `target` from the received Turn.
<dequeue @event TargetedTurnEvent>
/ # The active party has queued a new `event` to be processed later by `target`.
<enqueue @event TargetedTurnEvent>
/ # The active party is processing an internally-queued event for one of its own entities.
@dequeueInternal <dequeue-internal @event TargetedTurnEvent>
/ # The active party has scheduled an internally-queued event for one of its own entities.
@enqueueInternal <enqueue-internal @event TargetedTurnEvent>
/ <spawn @link bool @id ActorId>
/ <link
@parentActor ActorId
@childToParent protocol.Handle
@childActor ActorId
@parentToChild protocol.Handle>
/ @facetStart <facet-start @path [FacetId ...]>
/ @facetStop <facet-stop @path [FacetId ...] @reason FacetStopReason>
/ @linkedTaskStart <linked-task-start @taskName Name @id TaskId>
.
# An assertion or the body of a message: either a Preserves value, or
# some opaque system-internal value, represented according to the
# system concerned.
AssertionDescription =
/ <value @value any>
/ <opaque @description any>
.
FacetStopReason =
/ @explicitAction =explicit-action
/ =inert
/ @parentStopping =parent-stopping
/ @actorStopping =actor-stopping
.
Target = <entity @actor ActorId @facet FacetId @oid Oid> .
# For the future: consider including information about `protocol`-level `Turn`s etc sent to
# peers over e.g. Websockets or TCP/IP, allowing cross-correlation of traces from different
# processes and implementations with each other to form a large overall picture.
.

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::btree_map::{Iter, Keys, Entry}; 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. /// Element counts in [`BTreeBag`]s are 32-bit signed integers.
pub type Count = i32; pub type Count = i32;

32
syndicate/src/convert.rs Normal file
View File

@ -0,0 +1,32 @@
//! Useful utilities for working with [`AnyValue`]s.
use preserves::value::Embeddable;
use preserves::value::IOValue;
use preserves::value::NestedValue;
use std::convert::TryInto;
use crate::actor::*;
/// Converts an `AnyValue` to any [`crate::value::NestedValue`],
/// signalling an error if any embedded values are found in `v`.
pub fn from_any_value<N: NestedValue<D>, D: Embeddable>(v: &AnyValue) -> Result<N, &'static str> {
v.copy_via(&mut |_| Err("Embedded values cannot be converted")?)
}
/// Converts any [`crate::value::NestedValue`] to an `AnyValue`,
/// signalling an error if any embedded values are found in `v`.
pub fn to_any_value<N: NestedValue<D>, D: Embeddable>(v: &N) -> Result<AnyValue, &'static str> {
v.copy_via(&mut |_| Err("Embedded values cannot be converted")?)
}
/// Special case of [`to_any_value`] for [`IOValue`].
pub fn from_io_value<V: TryInto<IOValue>>(v: V) -> Result<AnyValue, &'static str> {
to_any_value(&v.try_into().map_err(|_| "Could not convert to IOValue")?)
}
/// Identity function for helping `rustc` decide which
/// [`crate::value::NestedValue`] to use (namely, [`AnyValue`]).
pub fn any_value(v: AnyValue) -> AnyValue {
v
}

View File

@ -1,64 +0,0 @@
use preserves::value::Map;
use preserves::value::Set;
use std::fmt::Debug;
#[derive(Debug)]
pub struct Graph<ObjectId: Debug + Clone + Eq + Ord, SubjectId: Debug + Clone + Eq + Ord>
{
forward_edges: Map<ObjectId, Set<SubjectId>>,
reverse_edges: Map<SubjectId, Set<ObjectId>>,
damaged_nodes: Set<ObjectId>,
}
impl<ObjectId: Debug + Clone + Eq + Ord, SubjectId: Debug + Clone + Eq + Ord>
Graph<ObjectId, SubjectId>
{
pub fn new() -> Self {
Graph {
forward_edges: Map::new(),
reverse_edges: Map::new(),
damaged_nodes: Set::new(),
}
}
pub fn is_clean(&self) -> bool {
self.damaged_nodes.is_empty()
}
pub fn record_observation(&mut self, observer: SubjectId, observed: ObjectId) {
self.forward_edges.entry(observed.clone()).or_default().insert(observer.clone());
self.reverse_edges.entry(observer).or_default().insert(observed);
}
pub fn record_damage(&mut self, observed: ObjectId) {
self.damaged_nodes.insert(observed);
}
fn forget_subject(&mut self, observer: &SubjectId) {
if let Some(observeds) = self.reverse_edges.remove(observer) {
for observed in observeds.into_iter() {
if let Some(observers) = self.forward_edges.get_mut(&observed) {
observers.remove(observer);
}
}
}
}
// To repair: repeat:
// - take_damaged_nodes; if none, return successfully - otherwise:
// - for each, take_observers_of.
// - for each, invoke the observer's repair function.
pub fn take_damaged_nodes(&mut self) -> Set<ObjectId> {
std::mem::take(&mut self.damaged_nodes)
}
pub fn take_observers_of(&mut self, observed: &ObjectId) -> Set<SubjectId> {
let observers = self.forward_edges.remove(&observed).unwrap_or_default();
for observer in observers.iter() {
self.forget_subject(observer);
}
observers
}
}

View File

@ -8,33 +8,65 @@
//! on the web](https://syndicate-lang.org/tonyg-dissertation/). //! on the web](https://syndicate-lang.org/tonyg-dissertation/).
//! [PDF](https://syndicate-lang.org/papers/conversational-concurrency-201712310922.pdf). //! [PDF](https://syndicate-lang.org/papers/conversational-concurrency-201712310922.pdf).
use super::language;
use super::skeleton; use super::skeleton;
use super::actor::*; use super::actor::*;
use super::schemas::dataspace::*; use super::schemas::dataspace::*;
use super::schemas::dataspace::_Any;
use preserves::value::Map; use preserves::value::Map;
use preserves_schema::Codec;
use std::convert::TryFrom;
// #[derive(Debug)]
// pub struct Churn {
// pub assertions_added: usize,
// pub assertions_removed: usize,
// pub endpoints_added: usize,
// pub endpoints_removed: usize,
// pub observers_added: usize,
// pub observers_removed: usize,
// pub messages_injected: usize,
// pub messages_delivered: usize,
// }
// impl Churn {
// pub fn new() -> Self {
// Self {
// assertions_added: 0,
// assertions_removed: 0,
// endpoints_added: 0,
// endpoints_removed: 0,
// observers_added: 0,
// observers_removed: 0,
// messages_injected: 0,
// messages_delivered: 0,
// }
// }
//
// pub fn reset(&mut self) {
// *self = Churn::new()
// }
// }
/// A Dataspace object (entity). /// A Dataspace object (entity).
#[derive(Debug)] #[derive(Debug)]
pub struct Dataspace { pub struct Dataspace {
pub name: Name,
/// Index over assertions placed in the dataspace; used to /// Index over assertions placed in the dataspace; used to
/// efficiently route assertion changes and messages to observers. /// efficiently route assertion changes and messages to observers.
pub index: skeleton::Index, pub index: skeleton::Index,
/// Local memory of assertions indexed by `Handle`, used to remove /// Local memory of assertions indexed by `Handle`, used to remove
/// assertions from the `index` when they are retracted. /// assertions from the `index` when they are retracted.
pub handle_map: Map<Handle, _Any>, pub handle_map: Map<Handle, (_Any, Option<Observe>)>,
// pub churn: Churn,
} }
impl Dataspace { impl Dataspace {
/// Construct a new, empty dataspace. /// Construct a new, empty dataspace.
pub fn new(name: Name) -> Self { pub fn new() -> Self {
Self { Self {
name,
index: skeleton::Index::new(), index: skeleton::Index::new(),
handle_map: Map::new(), handle_map: Map::new(),
// churn: Churn::new(),
} }
} }
@ -60,39 +92,46 @@ impl Dataspace {
impl Entity<_Any> for Dataspace { impl Entity<_Any> for Dataspace {
fn assert(&mut self, t: &mut Activation, a: _Any, h: Handle) -> ActorResult { fn assert(&mut self, t: &mut Activation, a: _Any, h: Handle) -> ActorResult {
let is_new = self.index.insert(t, &a); tracing::trace!(assertion = ?a, handle = ?h, "assert");
tracing::trace!(dataspace = ?self.name, assertion = ?a, handle = ?h, ?is_new, "assert");
if is_new { // let old_assertions = self.index.assertion_count();
if let Ok(o) = language().parse::<Observe>(&a) { self.index.insert(t, &a);
self.index.add_observer(t, &o.pattern, &o.observer); // self.churn.assertions_added += self.index.assertion_count() - old_assertions;
} // self.churn.endpoints_added += 1;
if let Ok(o) = Observe::try_from(&a) {
self.index.add_observer(t, &o.pattern, &o.observer);
// self.churn.observers_added += 1;
self.handle_map.insert(h, (a, Some(o)));
} else {
self.handle_map.insert(h, (a, None));
} }
self.handle_map.insert(h, a);
Ok(()) Ok(())
} }
fn retract(&mut self, t: &mut Activation, h: Handle) -> ActorResult { fn retract(&mut self, t: &mut Activation, h: Handle) -> ActorResult {
match self.handle_map.remove(&h) { tracing::trace!(handle = ?h, "retract");
None => tracing::warn!(dataspace = ?self.name, handle = ?h, "retract of unknown handle"),
Some(a) => {
let is_last = self.index.remove(t, &a);
tracing::trace!(dataspace = ?self.name, assertion = ?a, handle = ?h, ?is_last, "retract");
if is_last { if let Some((a, maybe_o)) = self.handle_map.remove(&h) {
if let Ok(o) = language().parse::<Observe>(&a) { if let Some(o) = maybe_o {
self.index.remove_observer(t, o.pattern, &o.observer); self.index.remove_observer(t, o.pattern, &o.observer);
} // self.churn.observers_removed += 1;
}
} }
// let old_assertions = self.index.assertion_count();
self.index.remove(t, &a);
// self.churn.assertions_removed += old_assertions - self.index.assertion_count();
// self.churn.endpoints_removed += 1;
} }
Ok(()) Ok(())
} }
fn message(&mut self, t: &mut Activation, m: _Any) -> ActorResult { fn message(&mut self, t: &mut Activation, m: _Any) -> ActorResult {
tracing::trace!(dataspace = ?self.name, body = ?m, "message"); tracing::trace!(body = ?m, "message");
// self.index.send(t, &m, &mut self.churn.messages_delivered);
self.index.send(t, &m); self.index.send(t, &m);
// self.churn.messages_injected += 1;
Ok(()) Ok(())
} }
} }

View File

@ -17,7 +17,7 @@ where
Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>, Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>,
Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult, Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult,
Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult, Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult,
Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ExitStatus>), Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult,
{ {
state: E, state: E,
assertion_handler: Option<Fa>, assertion_handler: Option<Fa>,
@ -54,7 +54,7 @@ pub fn entity<M: 'static + Send, E>(
fn (&mut E, &mut Activation, M) -> DuringResult<E>, fn (&mut E, &mut Activation, M) -> DuringResult<E>,
fn (&mut E, &mut Activation, M) -> ActorResult, fn (&mut E, &mut Activation, M) -> ActorResult,
fn (&mut E, &mut Activation) -> ActorResult, fn (&mut E, &mut Activation) -> ActorResult,
fn (&mut E, &mut Activation, &Arc<ExitStatus>)> fn (&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult>
where where
E: 'static + Send, E: 'static + Send,
{ {
@ -68,7 +68,7 @@ where
Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>, Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>,
Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult, Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult,
Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult, Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult,
Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ExitStatus>), Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult,
{ {
pub fn new( pub fn new(
state: E, state: E,
@ -118,7 +118,10 @@ where
let _ = t.prevent_inert_check(); let _ = t.prevent_inert_check();
assertion_handler(state, t, a) assertion_handler(state, t, a)
})?; })?;
Ok(Some(Box::new(move |_state, t| Ok(t.stop_facet(facet_id))))) Ok(Some(Box::new(move |_state, t| {
t.stop_facet(facet_id, None);
Ok(())
})))
})) }))
} }
@ -154,7 +157,7 @@ where
pub fn on_exit<Fx1>(self, exit_handler: Fx1) -> DuringEntity<M, E, Fa, Fm, Fs, Fx1> pub fn on_exit<Fx1>(self, exit_handler: Fx1) -> DuringEntity<M, E, Fa, Fm, Fs, Fx1>
where where
Fx1: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ExitStatus>), Fx1: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult,
{ {
DuringEntity { DuringEntity {
state: self.state, state: self.state,
@ -172,10 +175,10 @@ where
let should_register_exit_hook = self.exit_handler.is_some(); let should_register_exit_hook = self.exit_handler.is_some();
let r = t.create(self); let r = t.create(self);
if should_register_stop_action { if should_register_stop_action {
t.on_stop_notify(&r); t.on_stop(&r);
} }
if should_register_exit_hook { if should_register_exit_hook {
t.add_exit_hook(&r); t.state.add_exit_hook(&r);
} }
r r
} }
@ -187,7 +190,7 @@ where
Fa: 'static + Send + FnMut(&mut E, &mut Activation, AnyValue) -> DuringResult<E>, Fa: 'static + Send + FnMut(&mut E, &mut Activation, AnyValue) -> DuringResult<E>,
Fm: 'static + Send + FnMut(&mut E, &mut Activation, AnyValue) -> ActorResult, Fm: 'static + Send + FnMut(&mut E, &mut Activation, AnyValue) -> ActorResult,
Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult, Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult,
Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ExitStatus>), Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult,
{ {
pub fn create_cap(self, t: &mut Activation) -> Arc<Cap> pub fn create_cap(self, t: &mut Activation) -> Arc<Cap>
{ {
@ -202,7 +205,7 @@ where
Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>, Fa: 'static + Send + FnMut(&mut E, &mut Activation, M) -> DuringResult<E>,
Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult, Fm: 'static + Send + FnMut(&mut E, &mut Activation, M) -> ActorResult,
Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult, Fs: 'static + Send + FnMut(&mut E, &mut Activation) -> ActorResult,
Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ExitStatus>), Fx: 'static + Send + FnMut(&mut E, &mut Activation, &Arc<ActorResult>) -> ActorResult,
{ {
fn assert(&mut self, t: &mut Activation, a: M, h: Handle) -> ActorResult { fn assert(&mut self, t: &mut Activation, a: M, h: Handle) -> ActorResult {
match &mut self.assertion_handler { match &mut self.assertion_handler {
@ -232,9 +235,10 @@ where
} }
} }
fn exit_hook(&mut self, t: &mut Activation, exit_status: &Arc<ExitStatus>) { fn exit_hook(&mut self, t: &mut Activation, exit_status: &Arc<ActorResult>) -> ActorResult {
if let Some(handler) = &mut self.exit_handler { match &mut self.exit_handler {
handler(&mut self.state, t, exit_status); Some(handler) => handler(&mut self.state, t, exit_status),
None => Ok(()),
} }
} }
} }

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