Compare commits
202 Commits
Author | SHA1 | Date |
---|---|---|
|
3fb8028bab | |
|
3a4919b7f8 | |
|
3936abe3fb | |
|
599b4ed469 | |
|
b9e0bf0520 | |
|
6e555c9fd5 | |
|
8ebde104ca | |
|
05fd3ca22e | |
|
7df9ce5248 | |
|
c0239cf322 | |
|
9cc4175f24 | |
|
70f42dd931 | |
|
ef1ebe6412 | |
|
d9e1be2e98 | |
|
6de5e96aa1 | |
|
ca18ca08df | |
|
40ca168eac | |
|
5a73e8d4c3 | |
|
710ff91a64 | |
|
6e5f626ac1 | |
|
92027e94a9 | |
|
420868eae7 | |
|
3035b43941 | |
|
08e49fd14e | |
|
a4c0bf3e6f | |
|
e5b65ad0ed | |
|
42cb62c094 | |
|
561aa01fea | |
|
0429e59ad1 | |
|
f2b8b433cc | |
|
6f6993ce4c | |
|
0364c38068 | |
|
219188d34f | |
|
d8af496d4e | |
|
f73d59b526 | |
|
e0b6838166 | |
|
71c57f4ab7 | |
|
c59e044695 | |
|
bf0d47f1b7 | |
|
7797a3cd09 | |
|
1b72f71a32 | |
|
956a84cc17 | |
|
d457867cc5 | |
|
9864ce0ec8 | |
|
5dd68e87c1 | |
|
79c3788436 | |
|
3b4d0ef418 | |
|
97876335ba | |
|
d7b330e6dd | |
|
b32dc0a947 | |
|
6d18f7549c | |
|
796b1ac344 | |
|
bdb2d86622 | |
|
c0f83a2463 | |
|
fe9ceaf65c | |
|
8bcfed2d4a | |
|
ae2a9b59e6 | |
|
72566ac223 | |
|
23c622f914 | |
|
bd71008e13 | |
|
f00d75b74b | |
|
17f9833708 | |
|
b957490d78 | |
|
2b2d033efb | |
|
d8a139b23a | |
|
b18dbf014c | |
|
96997e86ac | |
|
46fd2dec3b | |
|
c630d35ea9 | |
|
ae2698557c | |
|
587fba6887 | |
|
ab85a1f078 | |
|
4e2db5b17b | |
|
1ae2583414 | |
|
f3c9662607 | |
|
82624d3007 | |
|
8b690b9103 | |
|
5a52f243e5 | |
|
6224baa2b6 | |
|
8619342e5e | |
|
5bcb268ff8 | |
|
6aba0ebe41 | |
|
9cd2e6776c | |
|
a086c1d721 | |
|
bc41182533 | |
|
2ad99b56b8 | |
|
3fdf92daeb | |
|
2be479b1e9 | |
|
4684353018 | |
|
f6b88ee3fb | |
|
ee8a23aa2e | |
|
6d4833d67e | |
|
5cd0335a79 | |
|
b52da09081 | |
|
9ca618268e | |
|
41dbeb1aae | |
|
9f1f76d0ca | |
|
e0deaf3054 | |
|
837570844d | |
|
f4078aabaa | |
|
557a36756f | |
|
07a5f688be | |
|
fff84d4c2a | |
|
5983cd01f1 | |
|
e8881f5980 | |
|
40b4681a6e | |
|
0f5e033174 | |
|
aae53b5525 | |
|
4c03646567 | |
|
a7b5c69000 | |
|
3187c4642b | |
|
ca92d99c52 | |
|
19c96bdef2 | |
|
ab34b62cf1 | |
|
5a65256cf3 | |
|
287a2903a7 | |
|
257c604e2b | |
|
d99d589dd1 | |
|
b4276065a0 | |
|
a06d532006 | |
|
592cffe019 | |
|
8007382db5 | |
|
08bbc4661a | |
|
0e8aca8892 | |
|
78eef4c388 | |
|
90e71a606e | |
|
184363a9a9 | |
|
30d1c067e7 | |
|
c0afd99e46 | |
|
2b5a0cdf02 | |
|
6dcac58377 | |
|
0f4f6a1716 | |
|
4662cbefd6 | |
|
8af47f1a1f | |
|
04d46585fd | |
|
a813780e8d | |
|
e04b898c7f | |
|
fdc8714fe8 | |
|
162ceb4270 | |
|
37da60cf0b | |
|
27b6b57661 | |
|
2e3376a783 | |
|
8270ca975b | |
|
fd36cff912 | |
|
46b5889ea4 | |
|
529754f202 | |
|
0fdffb3965 | |
|
f9f00e9d7e | |
|
d33bb6e48e | |
|
1b44f5bd9c | |
|
d6a303e1e8 | |
|
54ad58df59 | |
|
5f60c22e49 | |
|
49075e7e84 | |
|
b3e24d819c | |
|
b2df99cbc0 | |
|
e8da608e86 | |
|
cb32bc15af | |
|
b2fa476f23 | |
|
00ae6b3afb | |
|
62a1779dc1 | |
|
e88ba0afe0 | |
|
7b6b5adb0b | |
|
b42230b96a | |
|
239b1b15cc | |
|
9078267e76 | |
|
955177b7db | |
|
b4b4995d84 | |
|
422904010b | |
|
6cfd97c91a | |
|
770fb79882 | |
|
0ff8c2c872 | |
|
c2de82a2b7 | |
|
9445a71b53 | |
|
8cd601a777 | |
|
0b2c7ecfe1 | |
|
be6b30bba6 | |
|
993cf78a38 | |
|
f7c6e7d164 | |
|
e7ddfdf311 | |
|
dab79020f4 | |
|
a6639b5380 | |
|
06e922c511 | |
|
93c196acaa | |
|
e034486aaa | |
|
824b078eac | |
|
cf93327ed6 | |
|
6cfe8c2ba4 | |
|
b6bc816daf | |
|
a73b6a9f4a | |
|
f6cb595709 | |
|
afe36c630d | |
|
5850c5b06d | |
|
b0d0eb3a11 | |
|
21d09f81e5 | |
|
8b5e74048e | |
|
201f5433e1 | |
|
8cbe2475e3 | |
|
930f7eda00 | |
|
a932fa1428 | |
|
3412eabcff | |
|
e47a37e3f0 |
11
.envrc
11
.envrc
|
@ -1,3 +1,8 @@
|
||||||
[ -d pyenv ] || virtualenv -p python3 pyenv
|
if ! [ -d .venv ]
|
||||||
. pyenv/bin/activate
|
then
|
||||||
pip install -r requirements.txt
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
pip install -e '.[dev]'
|
||||||
|
else
|
||||||
|
. .venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
|
@ -4,5 +4,6 @@ htmlcov/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
pyenv/
|
.eggs/
|
||||||
/preserves
|
.venv/
|
||||||
|
/preserves
|
||||||
|
|
28
Makefile
28
Makefile
|
@ -1,3 +1,5 @@
|
||||||
|
PACKAGEVERSION := $(shell ./print-package-version)
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -6,12 +8,28 @@ clean:
|
||||||
rm -f .coverage
|
rm -f .coverage
|
||||||
rm -rf *.egg-info build dist
|
rm -rf *.egg-info build dist
|
||||||
|
|
||||||
# sudo apt install python3-wheel twine
|
tag:
|
||||||
publish: build
|
git tag v$(PACKAGEVERSION)
|
||||||
|
|
||||||
|
publish: clean build
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
|
|
||||||
build: clean
|
build: build-only
|
||||||
python3 setup.py sdist bdist_wheel
|
|
||||||
|
build-only: dist/syndicate-py-$(PACKAGEVERSION).tar.gz
|
||||||
|
|
||||||
|
dist/syndicate-py-$(PACKAGEVERSION).tar.gz:
|
||||||
|
python3 -m build
|
||||||
|
|
||||||
veryclean: clean
|
veryclean: clean
|
||||||
rm -rf pyenv
|
rm -rf .venv
|
||||||
|
|
||||||
|
PROTOCOLS_BRANCH=main
|
||||||
|
pull-protocols:
|
||||||
|
git subtree pull -P syndicate/protocols \
|
||||||
|
-m 'Merge latest changes from the syndicate-protocols repository' \
|
||||||
|
git@git.syndicate-lang.org:syndicate-lang/syndicate-protocols \
|
||||||
|
$(PROTOCOLS_BRANCH)
|
||||||
|
|
||||||
|
chat.bin: chat.prs
|
||||||
|
preserves-schemac .:chat.prs > $@
|
||||||
|
|
14
README.md
14
README.md
|
@ -8,9 +8,13 @@ or
|
||||||
|
|
||||||
git clone https://git.syndicate-lang.org/syndicate-lang/syndicate-py
|
git clone https://git.syndicate-lang.org/syndicate-lang/syndicate-py
|
||||||
cd syndicate-py
|
cd syndicate-py
|
||||||
virtualenv -p python3 pyenv
|
python -m venv .venv
|
||||||
. pyenv/bin/activate
|
. .venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -e '.[dev]'
|
||||||
|
|
||||||
|
See also
|
||||||
|
[syndicate-py-packaging](https://git.syndicate-lang.org/syndicate-lang/syndicate-py-packaging)
|
||||||
|
for Debian packaging scripts.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
@ -19,10 +23,10 @@ Start a Syndicate broker (such as
|
||||||
|
|
||||||
Find the line of broker output giving the root capability:
|
Find the line of broker output giving the root capability:
|
||||||
|
|
||||||
... rootcap=<ref "syndicate" [] #x"a6480df5306611ddd0d3882b546e1977"> ...
|
... rootcap=<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}> ...
|
||||||
|
|
||||||
Then, run [chat.py](chat.py) several times in several separate windows:
|
Then, run [chat.py](chat.py) several times in several separate windows:
|
||||||
|
|
||||||
python chat.py \
|
python chat.py \
|
||||||
--address '<tcp "localhost" 8001>' \
|
--address '<tcp "localhost" 8001>' \
|
||||||
--cap '<ref "syndicate" [] #x"a6480df5306611ddd0d3882b546e1977">'
|
--cap '<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>'
|
||||||
|
|
51
bidi-gc.py
51
bidi-gc.py
|
@ -3,7 +3,7 @@ import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
import syndicate
|
import syndicate
|
||||||
from syndicate import patterns as P, actor, dataspace, Record, Embedded
|
from syndicate import patterns as P, actor, dataspace, Record, Embedded, turn
|
||||||
from syndicate.during import Handler
|
from syndicate.during import Handler
|
||||||
from syndicate.schema import sturdy
|
from syndicate.schema import sturdy
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ parser = argparse.ArgumentParser(description='Test bidirectional object referenc
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument('--address', metavar='\'<tcp "HOST" PORT>\'',
|
parser.add_argument('--address', metavar='\'<tcp "HOST" PORT>\'',
|
||||||
help='transport address of the server',
|
help='transport address of the server',
|
||||||
default='<ws "ws://localhost:8001/">')
|
default='<ws "ws://localhost:9001/">')
|
||||||
parser.add_argument('--cap', metavar='\'<ref ...>\'',
|
parser.add_argument('--cap', metavar='\'<ref ...>\'',
|
||||||
help='capability for the dataspace on the server',
|
help='capability for the dataspace on the server',
|
||||||
default='<ref "syndicate" [] #[pkgN9TBmEd3Q04grVG4Zdw==]>')
|
default='<ref "syndicate" [] #[acowDB2/oI+6aSEC3YIxGg==]>')
|
||||||
parser.add_argument('--start',
|
parser.add_argument('--start',
|
||||||
help='make this instance kick off the procedure',
|
help='make this instance kick off the procedure',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
@ -41,46 +41,65 @@ args = parser.parse_args()
|
||||||
#
|
#
|
||||||
# ----Three()--->
|
# ----Three()--->
|
||||||
|
|
||||||
|
#
|
||||||
|
# Here's a trace from a live session of this running against syndicate-rs:
|
||||||
|
#
|
||||||
|
# B --> server: [[1, <assert <Boot #:⌜141/402:00007f3e50021ef0⌝> 3>]]
|
||||||
|
#
|
||||||
|
# A --> server: [[1, <assert <Observe <rec Boot [<bind <_>>]> #:⌜151/422:00007f3e50025090⌝> 3>]]
|
||||||
|
# A <-- server: [[1, <assert [#:⌜141/402:00007f3e50021ef0⌝] 633>]]
|
||||||
|
# A --> server: [[2, <assert <One #:⌜151/422:00007f3e5c009b00⌝> 5>]]
|
||||||
|
#
|
||||||
|
# B <-- server: [[1, <assert <One #:⌜151/422:00007f3e5c009b00⌝> 643>]]
|
||||||
|
# B --> server: [[1, <retract 3>], [2, <assert <Two> 5>]]
|
||||||
|
#
|
||||||
|
# A <-- server: [[2, <assert <Two> 653>]]
|
||||||
|
# A <-- server: [[1, <retract 633>]]
|
||||||
|
# A --> server: [[2, <message <Three>>]]
|
||||||
|
#
|
||||||
|
# B <-- server: [[1, <message <Three>>]]
|
||||||
|
#
|
||||||
|
|
||||||
Boot = Record.makeConstructor('Boot', 'b')
|
Boot = Record.makeConstructor('Boot', 'b')
|
||||||
One = Record.makeConstructor('One', 'a')
|
One = Record.makeConstructor('One', 'a')
|
||||||
Two = Record.makeConstructor('Two', '')
|
Two = Record.makeConstructor('Two', '')
|
||||||
Three = Record.makeConstructor('Three', '')
|
Three = Record.makeConstructor('Three', '')
|
||||||
|
|
||||||
@actor.run_system(name = 'bidi-gc', debug = False)
|
@actor.run_system(name = 'bidi-gc', debug = False)
|
||||||
def main(turn):
|
def main():
|
||||||
root_facet = turn._facet
|
root_facet = turn.active_facet()
|
||||||
|
|
||||||
@syndicate.relay.connect(turn, args.address, sturdy.SturdyRef.decode(syndicate.parse(args.cap)),
|
@syndicate.relay.connect(args.address, sturdy.SturdyRef.decode(syndicate.parse(args.cap)),
|
||||||
on_disconnected = lambda _relay, _did_connect: sys.exit(1))
|
on_disconnected = lambda _relay, _did_connect: sys.exit(1))
|
||||||
def on_connected(turn, ds):
|
def on_connected(ds):
|
||||||
if args.start:
|
if args.start:
|
||||||
# We are "A".
|
# We are "A".
|
||||||
|
|
||||||
@dataspace.observe(turn, ds, P.rec('Boot', P.CAPTURE))
|
@dataspace.observe(ds, P.rec('Boot', P.CAPTURE))
|
||||||
@Handler().add_handler
|
@Handler().add_handler
|
||||||
def on_b(turn, b):
|
def on_b(b):
|
||||||
print('A got B', b)
|
print('A got B', b)
|
||||||
@Handler().add_handler
|
@Handler().add_handler
|
||||||
def a(turn, two):
|
def a(two):
|
||||||
print('A got assertion:', two)
|
print('A got assertion:', two)
|
||||||
turn.send(b.embeddedValue, Three())
|
turn.send(b.embeddedValue, Three())
|
||||||
def on_two_retracted(turn):
|
def on_two_retracted():
|
||||||
print('Assertion', two, 'from B went')
|
print('Assertion', two, 'from B went')
|
||||||
turn.retract(one_handle)
|
turn.retract(one_handle)
|
||||||
return on_two_retracted
|
return on_two_retracted
|
||||||
one_handle = turn.publish(b.embeddedValue, One(Embedded(turn.ref(a))))
|
one_handle = turn.publish(b.embeddedValue, One(Embedded(turn.ref(a))))
|
||||||
return lambda turn: print('B\'s Boot record went')
|
return lambda: print('B\'s Boot record went')
|
||||||
else:
|
else:
|
||||||
# We are "B".
|
# We are "B".
|
||||||
|
|
||||||
@Handler().add_handler
|
@Handler().add_handler
|
||||||
def b(turn, one):
|
def b(one):
|
||||||
print('B got assertion:', one)
|
print('B got assertion:', one)
|
||||||
print('boot_handle =', boot_handle)
|
print('boot_handle =', boot_handle)
|
||||||
turn.retract(boot_handle)
|
turn.retract(boot_handle)
|
||||||
turn.publish(One._a(one).embeddedValue, Two())
|
turn.publish(One._a(one).embeddedValue, Two())
|
||||||
return lambda turn: print('B facet stopping')
|
return lambda: print('B facet stopping')
|
||||||
@b.msg_handler
|
@b.msg_handler
|
||||||
def b_msg(turn, three):
|
def b_msg(three):
|
||||||
print('B got message: ', three)
|
print('B got message:', three)
|
||||||
boot_handle = turn.publish(ds, Boot(Embedded(turn.ref(b))))
|
boot_handle = turn.publish(ds, Boot(Embedded(turn.ref(b))))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
´³bundle·µ³chat„´³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€„„„„
|
|
@ -0,0 +1,4 @@
|
||||||
|
version 1 .
|
||||||
|
|
||||||
|
Present = <Present @username string>.
|
||||||
|
Says = <Says @who string @what string>.
|
37
chat.py
37
chat.py
|
@ -3,45 +3,50 @@ import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
import syndicate
|
import syndicate
|
||||||
from syndicate import patterns as P, actor, dataspace
|
from syndicate import patterns as P, actor, dataspace, turn
|
||||||
from syndicate.schema import simpleChatProtocol, sturdy
|
|
||||||
|
from syndicate.schema import sturdy
|
||||||
|
|
||||||
|
from preserves.schema import load_schema_file
|
||||||
|
simpleChatProtocol = load_schema_file('./chat.bin').chat
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Simple dataspace-server-mediated text chat.',
|
parser = argparse.ArgumentParser(description='Simple dataspace-server-mediated text chat.',
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument('--address', metavar='\'<tcp "HOST" PORT>\'',
|
parser.add_argument('--address', metavar='\'<tcp "HOST" PORT>\'',
|
||||||
help='transport address of the server',
|
help='transport address of the server',
|
||||||
default='<ws "ws://localhost:8001/">')
|
default='<ws "ws://localhost:9001/">')
|
||||||
parser.add_argument('--cap', metavar='\'<ref ...>\'',
|
parser.add_argument('--cap', metavar='\'<ref ...>\'',
|
||||||
help='capability for the dataspace on the server',
|
help='capability for the dataspace on the server',
|
||||||
default='<ref "syndicate" [] #[pkgN9TBmEd3Q04grVG4Zdw==]>')
|
default='<ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}>')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
Present = simpleChatProtocol.Present
|
Present = simpleChatProtocol.Present
|
||||||
Says = simpleChatProtocol.Says
|
Says = simpleChatProtocol.Says
|
||||||
|
|
||||||
@actor.run_system(name = 'chat', debug = False)
|
@actor.run_system(name = 'chat', debug = False)
|
||||||
def main(turn):
|
def main():
|
||||||
root_facet = turn._facet
|
root_facet = turn.active_facet()
|
||||||
|
|
||||||
|
@syndicate.relay.connect(args.address, sturdy.SturdyRef.decode(syndicate.parse(args.cap)))
|
||||||
|
def on_connected(ds):
|
||||||
|
turn.on_stop(lambda: turn.stop(root_facet))
|
||||||
|
|
||||||
@syndicate.relay.connect(turn, args.address, sturdy.SturdyRef.decode(syndicate.parse(args.cap)))
|
|
||||||
def on_connected(turn, ds):
|
|
||||||
me = 'user_' + str(random.randint(10, 1000))
|
me = 'user_' + str(random.randint(10, 1000))
|
||||||
|
|
||||||
turn.publish(ds, Present(me))
|
turn.publish(ds, Present(me))
|
||||||
|
|
||||||
@dataspace.during(turn, ds, P.rec('Present', P.CAPTURE), inert_ok=True)
|
@dataspace.during(ds, P.rec('Present', P.CAPTURE), inert_ok=True)
|
||||||
def on_presence(turn, who):
|
def on_presence(who):
|
||||||
print('%s joined' % (who,))
|
print('%s joined' % (who,))
|
||||||
turn.on_stop(lambda turn: print('%s left' % (who,)))
|
turn.on_stop(lambda: print('%s left' % (who,)))
|
||||||
|
|
||||||
@dataspace.on_message(turn, ds, P.rec('Says', P.CAPTURE, P.CAPTURE))
|
@dataspace.on_message(ds, P.rec('Says', P.CAPTURE, P.CAPTURE))
|
||||||
def on_says(turn, who, what):
|
def on_says(who, what):
|
||||||
print('%s says %r' % (who, what))
|
print('%s says %r' % (who, what))
|
||||||
|
|
||||||
@turn.linked_task()
|
@turn.linked_task()
|
||||||
async def accept_input(f):
|
async def accept_input(f):
|
||||||
reader = asyncio.StreamReader()
|
reader = asyncio.StreamReader()
|
||||||
await actor.find_loop().connect_read_pipe(lambda: asyncio.StreamReaderProtocol(reader), sys.stdin)
|
await f.loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(reader), sys.stdin)
|
||||||
while line := (await reader.readline()).decode('utf-8'):
|
while line := (await reader.readline()).decode('utf-8'):
|
||||||
actor.Turn.external(f, lambda turn: turn.send(ds, Says(me, line.strip())))
|
turn.external(f, lambda: turn.send(ds, Says(me, line.strip())))
|
||||||
actor.Turn.external(f, lambda turn: turn.stop(root_facet))
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
let ?root_ds = dataspace
|
||||||
|
<require-service <relay-listener <tcp "0.0.0.0" 9001> $gatekeeper>>
|
||||||
|
<bind <ref { oid: "syndicate" key: #x"" }> $root_ds #f>
|
8
inf.py
8
inf.py
|
@ -1,9 +1,9 @@
|
||||||
from syndicate import relay
|
from syndicate import relay, turn
|
||||||
from syndicate.during import During
|
from syndicate.during import During
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@relay.service(name='inf', debug=True)
|
@relay.service(name='inf', debug=True)
|
||||||
@During().add_handler
|
@During().add_handler
|
||||||
def main(turn, args):
|
def main(args):
|
||||||
logging.info(f'in main {turn}, {args}')
|
logging.info(f'in main {args}')
|
||||||
turn.on_stop(lambda turn: logging.info(f'args retracted {args}'))
|
turn.on_stop(lambda: logging.info(f'args retracted {args}'))
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<require-service <daemon inf>>
|
||||||
|
<daemon inf {
|
||||||
|
argv: "python inf.py"
|
||||||
|
protocol: application/syndicate
|
||||||
|
}>
|
||||||
|
? <service-object <daemon inf> ?cap> [
|
||||||
|
$cap += =here-is-your-configuration!
|
||||||
|
$cap += =here-is-another-configuration!
|
||||||
|
]
|
32
ovlinfo.py
32
ovlinfo.py
|
@ -1,32 +0,0 @@
|
||||||
import sys
|
|
||||||
import asyncio
|
|
||||||
import random
|
|
||||||
import threading
|
|
||||||
import syndicate.mini.core as S
|
|
||||||
|
|
||||||
OverlayLink = S.Record.makeConstructor('OverlayLink', 'downNode upNode')
|
|
||||||
|
|
||||||
conn = S.Connection.from_url(sys.argv[1])
|
|
||||||
|
|
||||||
uplinks = {}
|
|
||||||
def add_uplink(turn, src, tgt):
|
|
||||||
uplinks[src] = tgt
|
|
||||||
summarise_uplinks()
|
|
||||||
def del_uplink(turn, src, tgt):
|
|
||||||
del uplinks[src]
|
|
||||||
summarise_uplinks()
|
|
||||||
def summarise_uplinks():
|
|
||||||
print(repr(uplinks))
|
|
||||||
|
|
||||||
with conn.turn() as t:
|
|
||||||
with conn.actor().react(t) as facet:
|
|
||||||
facet.add(S.Observe(OverlayLink(S.CAPTURE, S.CAPTURE)),
|
|
||||||
on_add=add_uplink,
|
|
||||||
on_del=del_uplink)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.set_debug(True)
|
|
||||||
loop.run_until_complete(conn.reconnecting_main(loop))
|
|
||||||
loop.stop()
|
|
||||||
loop.run_forever()
|
|
||||||
loop.close()
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
. ./.envrc
|
||||||
|
exec python -c \
|
||||||
|
'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])'
|
|
@ -0,0 +1,50 @@
|
||||||
|
[project]
|
||||||
|
name = "syndicate-py"
|
||||||
|
version = "0.19.1"
|
||||||
|
description = "Syndicated Actor model and Syndicate network protocol for Python 3"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.6, <4"
|
||||||
|
license = {text = "GPL-3.0-or-later"}
|
||||||
|
|
||||||
|
authors = [
|
||||||
|
{name = "Tony Garnock-Jones", email = "tonyg@leastfixedpoint.com"},
|
||||||
|
]
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
]
|
||||||
|
|
||||||
|
# "websockets" isn't listed here, but if you want to use relay.WebsocketTunnelRelay,
|
||||||
|
# you will need it.
|
||||||
|
dependencies = [
|
||||||
|
"preserves",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://git.syndicate-lang.org/syndicate-lang/syndicate-py"
|
||||||
|
Issues = "https://git.syndicate-lang.org/syndicate-lang/syndicate-py/issues"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"build",
|
||||||
|
"twine",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = [
|
||||||
|
"syndicate",
|
||||||
|
"syndicate.protocols",
|
||||||
|
"syndicate.protocols.schemas",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
"syndicate.protocols" = ["*.bin"]
|
||||||
|
"syndicate.protocols.schemas" = ["*.prs"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "setuptools-scm"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
|
@ -1,2 +0,0 @@
|
||||||
websockets
|
|
||||||
preserves
|
|
24
setup.py
24
setup.py
|
@ -1,24 +0,0 @@
|
||||||
try:
|
|
||||||
from setuptools import setup
|
|
||||||
except ImportError:
|
|
||||||
from distutils.core import setup
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="syndicate-py",
|
|
||||||
version="0.2.0",
|
|
||||||
author="Tony Garnock-Jones",
|
|
||||||
author_email="tonyg@leastfixedpoint.com",
|
|
||||||
license="GNU General Public License v3 or later (GPLv3+)",
|
|
||||||
classifiers=[
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Intended Audience :: Developers",
|
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
],
|
|
||||||
packages=["syndicate"],
|
|
||||||
url="https://git.syndicate-lang.org/syndicate-lang/syndicate-py",
|
|
||||||
description="Syndicated Actor model and Syndicate network protocol for Python 3",
|
|
||||||
install_requires=['websockets', 'preserves'],
|
|
||||||
python_requires=">=3.6, <4",
|
|
||||||
)
|
|
|
@ -3,4 +3,48 @@ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||||
# This is 'import *' in order to effectively re-export preserves as part of this module's API.
|
# This is 'import *' in order to effectively re-export preserves as part of this module's API.
|
||||||
from preserves import *
|
from preserves import *
|
||||||
|
|
||||||
|
def __setup():
|
||||||
|
from .actor import _active, Turn
|
||||||
|
from .metapy import staticproperty
|
||||||
|
from types import FunctionType
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class turn:
|
||||||
|
@staticproperty
|
||||||
|
def active():
|
||||||
|
t = getattr(_active, 'turn', False)
|
||||||
|
if t is False:
|
||||||
|
t = _active.turn = None
|
||||||
|
return t
|
||||||
|
|
||||||
|
@staticproperty
|
||||||
|
def log():
|
||||||
|
return turn.active.log
|
||||||
|
|
||||||
|
def run(facet, action):
|
||||||
|
return Turn.run(facet, action)
|
||||||
|
|
||||||
|
def external(facet, action, loop=None):
|
||||||
|
return Turn.external(facet, action, loop=loop)
|
||||||
|
|
||||||
|
def active_facet():
|
||||||
|
return turn.active._facet
|
||||||
|
|
||||||
|
def install_definition(name, definition):
|
||||||
|
def handler(*args, **kwargs):
|
||||||
|
return definition(turn.active, *args, **kwargs)
|
||||||
|
setattr(turn, name, handler)
|
||||||
|
|
||||||
|
for (name, definition) in Turn.__dict__.items():
|
||||||
|
if name[0] == '_':
|
||||||
|
continue
|
||||||
|
elif type(definition) == FunctionType:
|
||||||
|
install_definition(name, definition)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return turn
|
||||||
|
|
||||||
|
turn = __setup()
|
||||||
|
|
||||||
from . import relay
|
from . import relay
|
||||||
|
|
|
@ -3,10 +3,13 @@ import inspect
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import threading
|
||||||
|
|
||||||
from preserves import Embedded, preserve
|
from preserves import Embedded, preserve
|
||||||
|
|
||||||
from .idgen import IdGenerator
|
from .idgen import IdGenerator
|
||||||
|
from .metapy import staticproperty
|
||||||
|
from .dataflow import Graph, Field
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -14,31 +17,67 @@ _next_actor_number = IdGenerator()
|
||||||
_next_handle = IdGenerator()
|
_next_handle = IdGenerator()
|
||||||
_next_facet_id = IdGenerator()
|
_next_facet_id = IdGenerator()
|
||||||
|
|
||||||
|
_active = threading.local()
|
||||||
|
_active.turn = None
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def run_system(**kwargs):
|
def run_system(**kwargs):
|
||||||
return lambda boot_proc: start_actor_system(boot_proc, **kwargs)
|
return lambda boot_proc: System().run(boot_proc, **kwargs)
|
||||||
|
|
||||||
def start_actor_system(boot_proc, debug = False, name = None, configure_logging = True):
|
class System:
|
||||||
if configure_logging:
|
def __init__(self, loop = None):
|
||||||
logging.basicConfig(level = logging.DEBUG if debug else logging.INFO)
|
self.loop = loop or asyncio.get_event_loop()
|
||||||
loop = asyncio.get_event_loop()
|
self.inhabitant_count = 0
|
||||||
if debug:
|
self.exit_signal = asyncio.Queue()
|
||||||
loop.set_debug(True)
|
|
||||||
queue_task(lambda: Actor(boot_proc, name = name), loop = loop)
|
|
||||||
loop.run_forever()
|
|
||||||
while asyncio.all_tasks(loop):
|
|
||||||
loop.stop()
|
|
||||||
loop.run_forever()
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
def adjust_engine_inhabitant_count(delta):
|
def run(self, boot_proc, debug = None, name = None, configure_logging = True):
|
||||||
loop = asyncio.get_running_loop()
|
if configure_logging:
|
||||||
if not hasattr(loop, '__syndicate_inhabitant_count'):
|
logging.basicConfig(level = logging.DEBUG if debug else logging.INFO)
|
||||||
loop.__syndicate_inhabitant_count = 0
|
if debug:
|
||||||
loop.__syndicate_inhabitant_count = loop.__syndicate_inhabitant_count + delta
|
self.loop.set_debug(True)
|
||||||
if loop.__syndicate_inhabitant_count == 0:
|
self.queue_task(lambda: Actor(boot_proc, system = self, name = name))
|
||||||
log.debug('Inhabitant count reached zero')
|
|
||||||
loop.stop()
|
# From Python 3.12, we may be able to use:
|
||||||
|
# asyncio.run(self._run, debug=debug, loop_factory=lambda: self.loop)
|
||||||
|
# but until then:
|
||||||
|
with asyncio.Runner(debug=debug, loop_factory=lambda: self.loop) as r:
|
||||||
|
return r.run(self._run())
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
try:
|
||||||
|
await self.exit_signal.get()
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
log.debug('System._run main loop exit')
|
||||||
|
|
||||||
|
def adjust_engine_inhabitant_count(self, delta):
|
||||||
|
self.inhabitant_count = self.inhabitant_count + delta
|
||||||
|
if self.inhabitant_count == 0:
|
||||||
|
log.debug('Inhabitant count reached zero')
|
||||||
|
self.exit_signal.put_nowait(())
|
||||||
|
|
||||||
|
def queue_task(self, thunk):
|
||||||
|
async def task():
|
||||||
|
try:
|
||||||
|
await ensure_awaitable(thunk())
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
return self.loop.create_task(task())
|
||||||
|
|
||||||
|
def queue_task_threadsafe(self, thunk):
|
||||||
|
async def task():
|
||||||
|
try:
|
||||||
|
await ensure_awaitable(thunk())
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
return self.loop.call_soon_threadsafe(lambda: asyncio.run_coroutine_threadsafe(task(), self.loop))
|
||||||
|
|
||||||
|
async def ensure_awaitable(value):
|
||||||
|
if inspect.isawaitable(value):
|
||||||
|
return await value
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
def remove_noerror(collection, item):
|
def remove_noerror(collection, item):
|
||||||
try:
|
try:
|
||||||
|
@ -47,16 +86,18 @@ def remove_noerror(collection, item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Actor:
|
class Actor:
|
||||||
def __init__(self, boot_proc, name = None, initial_assertions = {}, daemon = False):
|
def __init__(self, boot_proc, system, name = None, initial_assertions = {}, daemon = False):
|
||||||
self.name = name or 'a' + str(next(_next_actor_number))
|
self.name = name or 'a' + str(next(_next_actor_number))
|
||||||
|
self._system = system
|
||||||
self._daemon = daemon
|
self._daemon = daemon
|
||||||
if not daemon:
|
if not daemon:
|
||||||
adjust_engine_inhabitant_count(1)
|
system.adjust_engine_inhabitant_count(1)
|
||||||
self.root = Facet(self, None)
|
self.root = Facet(self, None)
|
||||||
self.outbound = initial_assertions or {}
|
self.outbound = initial_assertions or {}
|
||||||
self.exit_reason = None # None -> running, True -> terminated OK, exn -> error
|
self.exit_reason = None # None -> running, True -> terminated OK, exn -> error
|
||||||
self.exit_hooks = []
|
self.exit_hooks = []
|
||||||
self._log = None
|
self._log = None
|
||||||
|
self._dataflow_graph = None
|
||||||
Turn.run(Facet(self, self.root, set(self.outbound.keys())), stop_if_inert_after(boot_proc))
|
Turn.run(Facet(self, self.root, set(self.outbound.keys())), stop_if_inert_after(boot_proc))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -70,7 +111,7 @@ class Actor:
|
||||||
def daemon(self, value):
|
def daemon(self, value):
|
||||||
if self._daemon != value:
|
if self._daemon != value:
|
||||||
self._daemon = value
|
self._daemon = value
|
||||||
adjust_engine_inhabitant_count(-1 if value else 1)
|
self._system.adjust_engine_inhabitant_count(-1 if value else 1)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alive(self):
|
def alive(self):
|
||||||
|
@ -82,27 +123,33 @@ class Actor:
|
||||||
self._log = logging.getLogger('syndicate.Actor.%s' % (self.name,))
|
self._log = logging.getLogger('syndicate.Actor.%s' % (self.name,))
|
||||||
return self._log
|
return self._log
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dataflow_graph(self):
|
||||||
|
if self._dataflow_graph is None:
|
||||||
|
self._dataflow_graph = Graph()
|
||||||
|
return self._dataflow_graph
|
||||||
|
|
||||||
def at_exit(self, hook):
|
def at_exit(self, hook):
|
||||||
self.exit_hooks.append(hook)
|
self.exit_hooks.append(hook)
|
||||||
|
|
||||||
def cancel_at_exit(self, hook):
|
def cancel_at_exit(self, hook):
|
||||||
remove_noerror(self.exit_hooks, hook)
|
remove_noerror(self.exit_hooks, hook)
|
||||||
|
|
||||||
def terminate(self, turn, exit_reason):
|
def _repair_dataflow_graph(self):
|
||||||
|
if self._dataflow_graph is not None:
|
||||||
|
self._dataflow_graph.repair_damage(lambda a: a())
|
||||||
|
|
||||||
|
def _terminate(self, exit_reason):
|
||||||
if self.exit_reason is not None: return
|
if self.exit_reason is not None: return
|
||||||
self.log.debug('Terminating %r with exit_reason %r', self, exit_reason)
|
self.log.debug('Terminating %r with exit_reason %r', self, exit_reason)
|
||||||
self.exit_reason = exit_reason
|
self.exit_reason = exit_reason
|
||||||
if exit_reason != True:
|
if exit_reason != True:
|
||||||
self.log.error('crashed: %s' % (exit_reason,))
|
self.log.error('crashed: %s' % (exit_reason,))
|
||||||
for h in self.exit_hooks:
|
for h in self.exit_hooks:
|
||||||
h(turn)
|
h()
|
||||||
def finish_termination():
|
self.root._terminate(exit_reason == True)
|
||||||
Turn.run(self.root,
|
if not self._daemon:
|
||||||
lambda turn: self.root._terminate(turn, exit_reason == True),
|
self._system.adjust_engine_inhabitant_count(-1)
|
||||||
zombie_turn = True)
|
|
||||||
if not self._daemon:
|
|
||||||
adjust_engine_inhabitant_count(-1)
|
|
||||||
queue_task(finish_termination)
|
|
||||||
|
|
||||||
def _pop_outbound(self, handle, clear_from_source_facet):
|
def _pop_outbound(self, handle, clear_from_source_facet):
|
||||||
e = self.outbound.pop(handle)
|
e = self.outbound.pop(handle)
|
||||||
|
@ -114,6 +161,10 @@ class Actor:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
class Facet:
|
class Facet:
|
||||||
|
@staticproperty
|
||||||
|
def active():
|
||||||
|
return _active.turn._facet
|
||||||
|
|
||||||
def __init__(self, actor, parent, initial_handles=None):
|
def __init__(self, actor, parent, initial_handles=None):
|
||||||
self.id = next(_next_facet_id)
|
self.id = next(_next_facet_id)
|
||||||
self.actor = actor
|
self.actor = actor
|
||||||
|
@ -126,10 +177,17 @@ class Facet:
|
||||||
self.linked_tasks = []
|
self.linked_tasks = []
|
||||||
self.alive = True
|
self.alive = True
|
||||||
self.inert_check_preventers = 0
|
self.inert_check_preventers = 0
|
||||||
|
self._log = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def log(self):
|
def log(self):
|
||||||
return self.actor.log
|
if self._log is None:
|
||||||
|
if self.parent is None:
|
||||||
|
p = self.actor.log
|
||||||
|
else:
|
||||||
|
p = self.parent.log
|
||||||
|
self._log = p.getChild(str(self.id))
|
||||||
|
return self._log
|
||||||
|
|
||||||
def _repr_labels(self):
|
def _repr_labels(self):
|
||||||
pieces = []
|
pieces = []
|
||||||
|
@ -150,6 +208,15 @@ class Facet:
|
||||||
def cancel_on_stop(self, a):
|
def cancel_on_stop(self, a):
|
||||||
remove_noerror(self.shutdown_actions, a)
|
remove_noerror(self.shutdown_actions, a)
|
||||||
|
|
||||||
|
def on_stop_or_crash(self, a):
|
||||||
|
def cleanup():
|
||||||
|
self.cancel_on_stop(cleanup)
|
||||||
|
self.actor.cancel_at_exit(cleanup)
|
||||||
|
a()
|
||||||
|
self.on_stop(cleanup)
|
||||||
|
self.actor.at_exit(cleanup)
|
||||||
|
return cleanup
|
||||||
|
|
||||||
def isinert(self):
|
def isinert(self):
|
||||||
return \
|
return \
|
||||||
len(self.children) == 0 and \
|
len(self.children) == 0 and \
|
||||||
|
@ -167,27 +234,43 @@ class Facet:
|
||||||
self.inert_check_preventers = self.inert_check_preventers - 1
|
self.inert_check_preventers = self.inert_check_preventers - 1
|
||||||
return disarm
|
return disarm
|
||||||
|
|
||||||
def linked_task(self, coro_fn, loop = None):
|
@property
|
||||||
|
def loop(self):
|
||||||
|
return self.actor._system.loop
|
||||||
|
|
||||||
|
def linked_task(self, coro_fn, run_in_executor=False):
|
||||||
task = None
|
task = None
|
||||||
def cancel_linked_task(turn):
|
if run_in_executor:
|
||||||
|
inner_coro_fn = coro_fn
|
||||||
|
async def outer_coro_fn(facet):
|
||||||
|
await self.loop.run_in_executor(None, lambda: inner_coro_fn(facet))
|
||||||
|
coro_fn = outer_coro_fn
|
||||||
|
@self.on_stop_or_crash
|
||||||
|
def cancel_linked_task():
|
||||||
nonlocal task
|
nonlocal task
|
||||||
if task is not None:
|
if task is not None:
|
||||||
remove_noerror(self.linked_tasks, task)
|
remove_noerror(self.linked_tasks, task)
|
||||||
task.cancel()
|
task.cancel()
|
||||||
task = None
|
task = None
|
||||||
self.cancel_on_stop(cancel_linked_task)
|
|
||||||
self.actor.cancel_at_exit(cancel_linked_task)
|
|
||||||
async def guarded_task():
|
async def guarded_task():
|
||||||
|
should_terminate_facet = True
|
||||||
try:
|
try:
|
||||||
await coro_fn(self)
|
if await coro_fn(self) is True:
|
||||||
|
should_terminate_facet = False
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
Turn.external(self, cancel_linked_task)
|
if should_terminate_facet:
|
||||||
task = find_loop(loop).create_task(guarded_task())
|
Turn.external(self, lambda: Turn.active.stop())
|
||||||
|
else:
|
||||||
|
Turn.external(self, cancel_linked_task)
|
||||||
|
task = self.loop.create_task(guarded_task())
|
||||||
self.linked_tasks.append(task)
|
self.linked_tasks.append(task)
|
||||||
self.on_stop(cancel_linked_task)
|
|
||||||
self.actor.at_exit(cancel_linked_task)
|
|
||||||
|
|
||||||
def _terminate(self, turn, orderly):
|
def _terminate(self, orderly):
|
||||||
if not self.alive: return
|
if not self.alive: return
|
||||||
self.log.debug('%s terminating %r', 'orderly' if orderly else 'disorderly', self)
|
self.log.debug('%s terminating %r', 'orderly' if orderly else 'disorderly', self)
|
||||||
self.alive = False
|
self.alive = False
|
||||||
|
@ -196,12 +279,14 @@ class Facet:
|
||||||
if parent:
|
if parent:
|
||||||
parent.children.remove(self)
|
parent.children.remove(self)
|
||||||
|
|
||||||
with ActiveFacet(turn, self):
|
with ActiveFacet(self):
|
||||||
for child in list(self.children):
|
for child in list(self.children):
|
||||||
child._terminate(turn, orderly)
|
child._terminate(orderly)
|
||||||
if orderly:
|
if orderly:
|
||||||
for h in self.shutdown_actions:
|
with ActiveFacet(self.parent or self):
|
||||||
h(turn)
|
for h in self.shutdown_actions:
|
||||||
|
h()
|
||||||
|
turn = Turn.active
|
||||||
for h in self.handles:
|
for h in self.handles:
|
||||||
# Optimization: don't clear from source facet, the source facet is us and we're
|
# Optimization: don't clear from source facet, the source facet is us and we're
|
||||||
# about to clear our handles in one fell swoop.
|
# about to clear our handles in one fell swoop.
|
||||||
|
@ -211,15 +296,13 @@ class Facet:
|
||||||
if orderly:
|
if orderly:
|
||||||
if parent:
|
if parent:
|
||||||
if parent.isinert():
|
if parent.isinert():
|
||||||
Turn.run(parent, lambda turn: parent._terminate(turn, True))
|
parent._terminate(True)
|
||||||
else:
|
else:
|
||||||
Turn.run(self.actor.root,
|
self.actor._terminate(True)
|
||||||
lambda turn: self.actor.terminate(turn, True),
|
|
||||||
zombie_turn = True)
|
|
||||||
|
|
||||||
class ActiveFacet:
|
class ActiveFacet:
|
||||||
def __init__(self, turn, facet):
|
def __init__(self, facet):
|
||||||
self.turn = turn
|
self.turn = Turn.active
|
||||||
self.outer_facet = None
|
self.outer_facet = None
|
||||||
self.inner_facet = facet
|
self.inner_facet = facet
|
||||||
|
|
||||||
|
@ -232,26 +315,17 @@ class ActiveFacet:
|
||||||
self.turn._facet = self.outer_facet
|
self.turn._facet = self.outer_facet
|
||||||
self.outer_facet = None
|
self.outer_facet = None
|
||||||
|
|
||||||
async def ensure_awaitable(value):
|
|
||||||
if inspect.isawaitable(value):
|
|
||||||
return await value
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
def find_loop(loop = None):
|
def find_loop(loop = None):
|
||||||
return asyncio.get_running_loop() if loop is None else loop
|
return asyncio.get_running_loop() if loop is None else loop
|
||||||
|
|
||||||
def queue_task(thunk, loop = None):
|
|
||||||
async def task():
|
|
||||||
await ensure_awaitable(thunk())
|
|
||||||
return find_loop(loop).create_task(task())
|
|
||||||
|
|
||||||
def queue_task_threadsafe(thunk, loop = None):
|
|
||||||
async def task():
|
|
||||||
await ensure_awaitable(thunk())
|
|
||||||
return asyncio.run_coroutine_threadsafe(task(), find_loop(loop))
|
|
||||||
|
|
||||||
class Turn:
|
class Turn:
|
||||||
|
@staticproperty
|
||||||
|
def active():
|
||||||
|
t = getattr(_active, 'turn', False)
|
||||||
|
if t is False:
|
||||||
|
t = _active.turn = None
|
||||||
|
return t
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls, facet, action, zombie_turn = False):
|
def run(cls, facet, action, zombie_turn = False):
|
||||||
if not zombie_turn:
|
if not zombie_turn:
|
||||||
|
@ -259,20 +333,27 @@ class Turn:
|
||||||
if not facet.alive: return
|
if not facet.alive: return
|
||||||
turn = cls(facet)
|
turn = cls(facet)
|
||||||
try:
|
try:
|
||||||
action(turn)
|
saved = Turn.active
|
||||||
|
_active.turn = turn
|
||||||
|
try:
|
||||||
|
action()
|
||||||
|
facet.actor._repair_dataflow_graph()
|
||||||
|
finally:
|
||||||
|
_active.turn = saved
|
||||||
except:
|
except:
|
||||||
ei = sys.exc_info()
|
ei = sys.exc_info()
|
||||||
facet.log.error('%s', ''.join(traceback.format_exception(*ei)))
|
facet.log.error('%s', ''.join(traceback.format_exception(*ei)))
|
||||||
Turn.run(facet.actor.root, lambda turn: facet.actor.terminate(turn, ei[1]))
|
Turn.run(facet.actor.root, lambda: facet.actor._terminate(ei[1]))
|
||||||
else:
|
else:
|
||||||
turn._deliver()
|
turn._deliver()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def external(cls, facet, action, loop = None):
|
def external(cls, facet, action, loop = None):
|
||||||
return queue_task_threadsafe(lambda: cls.run(facet, action), loop)
|
return facet.actor._system.queue_task_threadsafe(lambda: cls.run(facet, action))
|
||||||
|
|
||||||
def __init__(self, facet):
|
def __init__(self, facet):
|
||||||
self._facet = facet
|
self._facet = facet
|
||||||
|
self._system = facet.actor._system
|
||||||
self.queues = {}
|
self.queues = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -285,48 +366,69 @@ class Turn:
|
||||||
# this actually can work as a decorator as well as a normal method!
|
# this actually can work as a decorator as well as a normal method!
|
||||||
def facet(self, boot_proc):
|
def facet(self, boot_proc):
|
||||||
new_facet = Facet(self._facet.actor, self._facet)
|
new_facet = Facet(self._facet.actor, self._facet)
|
||||||
with ActiveFacet(self, new_facet):
|
with ActiveFacet(new_facet):
|
||||||
stop_if_inert_after(boot_proc)(self)
|
stop_if_inert_after(boot_proc)()
|
||||||
return new_facet
|
return new_facet
|
||||||
|
|
||||||
def prevent_inert_check(self):
|
def prevent_inert_check(self):
|
||||||
return self._facet.prevent_inert_check()
|
return self._facet.prevent_inert_check()
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def linked_task(self, loop = None):
|
def linked_task(self, **kwargs):
|
||||||
return lambda coro_fn: self._facet.linked_task(coro_fn, loop = loop)
|
return lambda coro_fn: self._facet.linked_task(coro_fn, **kwargs)
|
||||||
|
|
||||||
def stop(self, facet = None, continuation = None):
|
def stop(self, facet = None, continuation = None):
|
||||||
if facet is None:
|
if facet is None:
|
||||||
facet = self._facet
|
facet = self._facet
|
||||||
def action(turn):
|
if continuation is not None:
|
||||||
facet._terminate(turn, True)
|
facet.on_stop(continuation)
|
||||||
if continuation is not None:
|
facet._terminate(True)
|
||||||
continuation(turn)
|
|
||||||
self._enqueue(facet.parent, action)
|
|
||||||
|
|
||||||
# can also be used as a decorator
|
# can also be used as a decorator
|
||||||
def on_stop(self, a):
|
def on_stop(self, a):
|
||||||
self._facet.on_stop(a)
|
self._facet.on_stop(a)
|
||||||
|
|
||||||
|
# can also be used as a decorator
|
||||||
|
def on_stop_or_crash(self, a):
|
||||||
|
self._facet.on_stop_or_crash(a)
|
||||||
|
|
||||||
def spawn(self, boot_proc, name = None, initial_handles = None, daemon = False):
|
def spawn(self, boot_proc, name = None, initial_handles = None, daemon = False):
|
||||||
def action(turn):
|
def action():
|
||||||
new_outbound = {}
|
new_outbound = {}
|
||||||
if initial_handles is not None:
|
if initial_handles is not None:
|
||||||
for handle in initial_handles:
|
for handle in initial_handles:
|
||||||
new_outbound[handle] = \
|
new_outbound[handle] = \
|
||||||
self._facet.actor._pop_outbound(handle, clear_from_source_facet=True)
|
self._facet.actor._pop_outbound(handle, clear_from_source_facet=True)
|
||||||
queue_task(lambda: Actor(boot_proc,
|
self._system.queue_task(lambda: Actor(boot_proc,
|
||||||
name = name,
|
system = self._system,
|
||||||
initial_assertions = new_outbound,
|
name = name,
|
||||||
daemon = daemon))
|
initial_assertions = new_outbound,
|
||||||
|
daemon = daemon))
|
||||||
self._enqueue(self._facet, action)
|
self._enqueue(self._facet, action)
|
||||||
|
|
||||||
def stop_actor(self):
|
def stop_actor(self):
|
||||||
self._enqueue(self._facet.actor.root, lambda turn: self._facet.actor.terminate(turn, True))
|
self._enqueue(self._facet.actor.root, lambda: self._facet.actor._terminate(True))
|
||||||
|
|
||||||
def crash(self, exn):
|
def crash(self, exn):
|
||||||
self._enqueue(self._facet.actor.root, lambda turn: self._facet.actor.terminate(turn, exn))
|
self._enqueue(self._facet.actor.root, lambda: self._facet.actor._terminate(exn))
|
||||||
|
|
||||||
|
def field(self, initial_value=None, name=None):
|
||||||
|
return Field(self._facet.actor.dataflow_graph, initial_value, name)
|
||||||
|
|
||||||
|
# can also be used as a decorator
|
||||||
|
def dataflow(self, a):
|
||||||
|
f = self._facet
|
||||||
|
f.prevent_inert_check()
|
||||||
|
def subject():
|
||||||
|
if not f.alive: return
|
||||||
|
with ActiveFacet(f):
|
||||||
|
a()
|
||||||
|
f.on_stop(lambda: f.actor.dataflow_graph.forget_subject(subject))
|
||||||
|
f.actor.dataflow_graph.with_subject(subject, lambda: subject())
|
||||||
|
|
||||||
|
def publish_dataflow(self, assertion_function):
|
||||||
|
endpoint = DataflowPublication(assertion_function)
|
||||||
|
self.dataflow(lambda: endpoint.update())
|
||||||
|
|
||||||
def publish(self, ref, assertion):
|
def publish(self, ref, assertion):
|
||||||
handle = next(_next_handle)
|
handle = next(_next_handle)
|
||||||
|
@ -340,10 +442,10 @@ class Turn:
|
||||||
e = OutboundAssertion(facet, handle, ref)
|
e = OutboundAssertion(facet, handle, ref)
|
||||||
facet.actor.outbound[handle] = e
|
facet.actor.outbound[handle] = e
|
||||||
facet.handles.add(handle)
|
facet.handles.add(handle)
|
||||||
def action(turn):
|
def action():
|
||||||
e.established = True
|
e.established = True
|
||||||
self.log.debug('%r <-- publish %r handle %r', ref, assertion, handle)
|
self.log.debug('%r <-- publish %r handle %r', ref, assertion, handle)
|
||||||
ref.entity.on_publish(turn, assertion, handle)
|
ref.entity.on_publish(assertion, handle)
|
||||||
self._enqueue(ref.facet, action)
|
self._enqueue(ref.facet, action)
|
||||||
|
|
||||||
def retract(self, handle):
|
def retract(self, handle):
|
||||||
|
@ -353,67 +455,99 @@ class Turn:
|
||||||
self._retract(e)
|
self._retract(e)
|
||||||
|
|
||||||
def replace(self, ref, handle, assertion):
|
def replace(self, ref, handle, assertion):
|
||||||
new_handle = None if assertion is None else self.publish(ref, assertion)
|
if assertion is None or ref is None:
|
||||||
|
new_handle = None
|
||||||
|
else:
|
||||||
|
new_handle = self.publish(ref, assertion)
|
||||||
self.retract(handle)
|
self.retract(handle)
|
||||||
return new_handle
|
return new_handle
|
||||||
|
|
||||||
def _retract(self, e):
|
def _retract(self, e):
|
||||||
# Assumes e has already been removed from self._facet.actor.outbound and the
|
# Assumes e has already been removed from self._facet.actor.outbound and the
|
||||||
# appropriate set of handles
|
# appropriate set of handles
|
||||||
def action(turn):
|
def action():
|
||||||
if e.established:
|
if e.established:
|
||||||
e.established = False
|
e.established = False
|
||||||
self.log.debug('%r <-- retract handle %r', e.ref, e.handle)
|
self.log.debug('%r <-- retract handle %r', e.ref, e.handle)
|
||||||
e.ref.entity.on_retract(turn, e.handle)
|
e.ref.entity.on_retract(e.handle)
|
||||||
self._enqueue(e.ref.facet, action)
|
self._enqueue(e.ref.facet, action)
|
||||||
|
|
||||||
def sync(self, ref, k):
|
def sync(self, ref, k):
|
||||||
class SyncContinuation(Entity):
|
class SyncContinuation(Entity):
|
||||||
def on_message(self, turn, _value):
|
def on_message(self, _value):
|
||||||
k(turn)
|
k()
|
||||||
self._sync(ref, self.ref(SyncContinuation()))
|
self._sync(ref, self.ref(SyncContinuation()))
|
||||||
|
|
||||||
def _sync(self, ref, peer):
|
def _sync(self, ref, peer):
|
||||||
peer = preserve(peer)
|
peer = preserve(peer)
|
||||||
def action(turn):
|
def action():
|
||||||
self.log.debug('%r <-- sync peer %r', ref, peer)
|
self.log.debug('%r <-- sync peer %r', ref, peer)
|
||||||
ref.entity.on_sync(turn, peer)
|
ref.entity.on_sync(peer)
|
||||||
self._enqueue(ref.facet, action)
|
self._enqueue(ref.facet, action)
|
||||||
|
|
||||||
def send(self, ref, message):
|
def send(self, ref, message):
|
||||||
# TODO: attenuation
|
# TODO: attenuation
|
||||||
message = preserve(message)
|
message = preserve(message)
|
||||||
def action(turn):
|
def action():
|
||||||
self.log.debug('%r <-- message %r', ref, message)
|
self.log.debug('%r <-- message %r', ref, message)
|
||||||
ref.entity.on_message(turn, message)
|
ref.entity.on_message(message)
|
||||||
self._enqueue(ref.facet, action)
|
self._enqueue(ref.facet, action)
|
||||||
|
|
||||||
|
# decorator
|
||||||
|
def after(self, delay_seconds):
|
||||||
|
def decorate(action):
|
||||||
|
@self.linked_task()
|
||||||
|
async def task(facet):
|
||||||
|
await asyncio.sleep(delay_seconds)
|
||||||
|
Turn.external(facet, action)
|
||||||
|
return decorate
|
||||||
|
|
||||||
def _enqueue(self, target_facet, action):
|
def _enqueue(self, target_facet, action):
|
||||||
if target_facet not in self.queues:
|
target_actor = target_facet.actor
|
||||||
self.queues[target_facet] = []
|
if target_actor not in self.queues:
|
||||||
self.queues[target_facet].append(action)
|
self.queues[target_actor] = []
|
||||||
|
self.queues[target_actor].append((target_facet, action))
|
||||||
|
|
||||||
def _deliver(self):
|
def _deliver(self):
|
||||||
for (facet, q) in self.queues.items():
|
for (actor, q) in self.queues.items():
|
||||||
# Stupid python scoping bites again
|
# Stupid python scoping bites again
|
||||||
def make_deliver_q(facet, q): # gratuitous
|
def make_deliver_q(actor, q): # gratuitous
|
||||||
def deliver_q(turn):
|
def deliver_q():
|
||||||
for action in q:
|
turn = Turn.active
|
||||||
action(turn)
|
saved_facet = turn._facet
|
||||||
return lambda: Turn.run(facet, deliver_q)
|
for (facet, action) in q:
|
||||||
queue_task(make_deliver_q(facet, q))
|
turn._facet = facet
|
||||||
|
action()
|
||||||
|
turn._facet = saved_facet
|
||||||
|
return lambda: Turn.run(actor.root, deliver_q)
|
||||||
|
self._system.queue_task(make_deliver_q(actor, q))
|
||||||
self.queues = {}
|
self.queues = {}
|
||||||
|
|
||||||
def stop_if_inert_after(action):
|
def stop_if_inert_after(action):
|
||||||
def wrapped_action(turn):
|
def wrapped_action():
|
||||||
action(turn)
|
turn = Turn.active
|
||||||
def check_action(turn):
|
action()
|
||||||
|
def check_action():
|
||||||
if (turn._facet.parent is not None and not turn._facet.parent.alive) \
|
if (turn._facet.parent is not None and not turn._facet.parent.alive) \
|
||||||
or turn._facet.isinert():
|
or turn._facet.isinert():
|
||||||
turn.stop()
|
turn.stop()
|
||||||
turn._enqueue(turn._facet, check_action)
|
turn._enqueue(turn._facet, check_action)
|
||||||
return wrapped_action
|
return wrapped_action
|
||||||
|
|
||||||
|
class DataflowPublication:
|
||||||
|
def __init__(self, assertion_function):
|
||||||
|
self.assertion_function = assertion_function
|
||||||
|
self.handle = None
|
||||||
|
self.target = None
|
||||||
|
self.assertion = None
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
(next_target, next_assertion) = self.assertion_function() or (None, None)
|
||||||
|
if next_target != self.target or next_assertion != self.assertion_function:
|
||||||
|
self.target = next_target
|
||||||
|
self.assertion = next_assertion
|
||||||
|
self.handle = Turn.active.replace(self.target, self.handle, self.assertion)
|
||||||
|
|
||||||
class Ref:
|
class Ref:
|
||||||
def __init__(self, facet, entity):
|
def __init__(self, facet, entity):
|
||||||
self.facet = facet
|
self.facet = facet
|
||||||
|
@ -435,27 +569,35 @@ class OutboundAssertion:
|
||||||
|
|
||||||
# Can act as a mixin
|
# Can act as a mixin
|
||||||
class Entity:
|
class Entity:
|
||||||
def on_publish(self, turn, v, handle):
|
def on_publish(self, v, handle):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_retract(self, turn, handle):
|
def on_retract(self, handle):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_message(self, turn, v):
|
def on_message(self, v):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_sync(self, turn, peer):
|
def on_sync(self, peer):
|
||||||
turn.send(peer, True)
|
Turn.active.send(peer, True)
|
||||||
|
|
||||||
_inert_actor = None
|
_inert_actor = None
|
||||||
_inert_facet = None
|
_inert_facet = None
|
||||||
_inert_ref = None
|
_inert_ref = None
|
||||||
_inert_entity = Entity()
|
_inert_entity = Entity()
|
||||||
def __boot_inert(turn):
|
def __boot_inert():
|
||||||
global _inert_actor, _inert_facet, _inert_ref
|
global _inert_actor, _inert_facet, _inert_ref
|
||||||
_inert_actor = turn._facet.actor
|
_inert_actor = Turn.active._facet.actor
|
||||||
_inert_facet = turn._facet
|
_inert_facet = Turn.active._facet
|
||||||
_inert_ref = turn.ref(_inert_entity)
|
_inert_ref = Turn.active.ref(_inert_entity)
|
||||||
async def __run_inert():
|
async def __run_inert():
|
||||||
Actor(__boot_inert, name = '_inert_actor')
|
Actor(__boot_inert, system = System(), name = '_inert_actor')
|
||||||
asyncio.get_event_loop().run_until_complete(__run_inert())
|
def __setup_inert():
|
||||||
|
def setup_main():
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
loop.run_until_complete(__run_inert())
|
||||||
|
loop.close()
|
||||||
|
t = threading.Thread(target=setup_main)
|
||||||
|
t.start()
|
||||||
|
t.join()
|
||||||
|
__setup_inert()
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
from . import mapset
|
||||||
|
|
||||||
|
class Graph:
|
||||||
|
def __init__(self):
|
||||||
|
self.edges_forward = {}
|
||||||
|
self.edges_reverse = {}
|
||||||
|
self.damaged_nodes = set()
|
||||||
|
self.active_subject = None
|
||||||
|
|
||||||
|
def with_subject(self, subject_id, f):
|
||||||
|
old_subject = self.active_subject
|
||||||
|
self.active_subject = subject_id
|
||||||
|
try:
|
||||||
|
return f()
|
||||||
|
finally:
|
||||||
|
self.active_subject = old_subject
|
||||||
|
|
||||||
|
def record_observation(self, object_id):
|
||||||
|
if self.active_subject is not None:
|
||||||
|
mapset.add(self.edges_forward, object_id, self.active_subject)
|
||||||
|
mapset.add(self.edges_reverse, self.active_subject, object_id)
|
||||||
|
|
||||||
|
def record_damage(self, object_id):
|
||||||
|
self.damaged_nodes.add(object_id)
|
||||||
|
|
||||||
|
def forget_subject(self, subject_id):
|
||||||
|
for oid in self.edges_reverse.pop(subject_id, set()):
|
||||||
|
mapset.discard(self.edges_forward, oid, subject_id)
|
||||||
|
|
||||||
|
def observers_of(self, object_id):
|
||||||
|
return list(self.edges_forward.get(object_id, []))
|
||||||
|
|
||||||
|
def repair_damage(self, repair_fn):
|
||||||
|
repaired_this_round = set()
|
||||||
|
while True:
|
||||||
|
workset = self.damaged_nodes - repaired_this_round
|
||||||
|
self.damaged_nodes = set()
|
||||||
|
|
||||||
|
if not workset:
|
||||||
|
break
|
||||||
|
|
||||||
|
repaired_this_round = repaired_this_round | workset
|
||||||
|
|
||||||
|
updated_subjects = set()
|
||||||
|
for object_id in workset:
|
||||||
|
for subject_id in self.observers_of(object_id):
|
||||||
|
if subject_id not in updated_subjects:
|
||||||
|
updated_subjects.add(subject_id)
|
||||||
|
self.forget_subject(subject_id)
|
||||||
|
self.with_subject(subject_id, lambda: repair_fn(subject_id))
|
||||||
|
|
||||||
|
__nextFieldId = 0
|
||||||
|
|
||||||
|
class Field:
|
||||||
|
def __init__(self, graph, initial=None, name=None):
|
||||||
|
global __nextFieldId
|
||||||
|
self.id = name
|
||||||
|
if self.id is None:
|
||||||
|
self.id = str(__nextFieldId)
|
||||||
|
__nextFieldId = __nextFieldId + 1
|
||||||
|
self.graph = graph
|
||||||
|
self._value = initial
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
self.graph.record_observation(self)
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, new_value):
|
||||||
|
if self._value != new_value:
|
||||||
|
self.graph.record_damage(self)
|
||||||
|
self._value = new_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update(self):
|
||||||
|
self.graph.record_damage(self)
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
self.graph.record_damage(self)
|
|
@ -1,17 +1,18 @@
|
||||||
from .schema import dataspace
|
from .schema import dataspace
|
||||||
from .during import During
|
from .during import During
|
||||||
|
from . import turn
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def observe(turn, ds, pattern):
|
def observe(ds, pattern):
|
||||||
def publish_observer(entity):
|
def publish_observer(entity):
|
||||||
turn.publish(ds, dataspace.Observe(pattern, turn.ref(entity)))
|
turn.publish(ds, dataspace.Observe(pattern, turn.ref(entity)))
|
||||||
return entity
|
return entity
|
||||||
return publish_observer
|
return publish_observer
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def on_message(turn, ds, pattern, *args, **kwargs):
|
def on_message(ds, pattern, *args, **kwargs):
|
||||||
return lambda on_msg: observe(turn, ds, pattern)(During(*args, **kwargs).msg_handler(on_msg))
|
return lambda on_msg: observe(ds, pattern)(During(*args, **kwargs).msg_handler(on_msg))
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def during(turn, ds, pattern, *args, **kwargs):
|
def during(ds, pattern, *args, **kwargs):
|
||||||
return lambda on_add: observe(turn, ds, pattern)(During(*args, **kwargs).add_handler(on_add))
|
return lambda on_add: observe(ds, pattern)(During(*args, **kwargs).add_handler(on_add))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from . import actor
|
from . import turn, actor
|
||||||
|
|
||||||
def _ignore(*args, **kwargs):
|
def _ignore(*args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _default_sync(turn, peer):
|
def _default_sync(peer):
|
||||||
turn.send(peer, True)
|
turn.send(peer, True)
|
||||||
|
|
||||||
class Handler(actor.Entity):
|
class Handler(actor.Entity):
|
||||||
|
@ -27,21 +27,21 @@ class Handler(actor.Entity):
|
||||||
def _wrap_add_handler(self, handler):
|
def _wrap_add_handler(self, handler):
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def on_publish(self, turn, v, handle):
|
def on_publish(self, v, handle):
|
||||||
retraction_handler = self._on_add(turn, *self._wrap(v))
|
retraction_handler = self._on_add(*self._wrap(v))
|
||||||
if retraction_handler is not None:
|
if retraction_handler is not None:
|
||||||
self.retraction_handlers[handle] = retraction_handler
|
self.retraction_handlers[handle] = retraction_handler
|
||||||
|
|
||||||
def on_retract(self, turn, handle):
|
def on_retract(self, handle):
|
||||||
retraction_handler = self.retraction_handlers.pop(handle, None)
|
retraction_handler = self.retraction_handlers.pop(handle, None)
|
||||||
if retraction_handler is not None:
|
if retraction_handler is not None:
|
||||||
retraction_handler(turn)
|
retraction_handler()
|
||||||
|
|
||||||
def on_message(self, turn, v):
|
def on_message(self, v):
|
||||||
self._on_msg(turn, *self._wrap(v))
|
self._on_msg(*self._wrap(v))
|
||||||
|
|
||||||
def on_sync(self, turn, peer):
|
def on_sync(self, peer):
|
||||||
self._on_sync(turn, peer)
|
self._on_sync(peer)
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def add_handler(self, on_add):
|
def add_handler(self, on_add):
|
||||||
|
@ -60,13 +60,13 @@ class Handler(actor.Entity):
|
||||||
|
|
||||||
class During(Handler):
|
class During(Handler):
|
||||||
def _wrap_add_handler(self, handler):
|
def _wrap_add_handler(self, handler):
|
||||||
def facet_handler(turn, *args):
|
def facet_handler(*args):
|
||||||
@turn.facet
|
@turn.facet
|
||||||
def facet(turn):
|
def facet():
|
||||||
if self.inert_ok:
|
if self.inert_ok:
|
||||||
turn.prevent_inert_check()
|
turn.prevent_inert_check()
|
||||||
maybe_stop_action = handler(turn, *args)
|
maybe_stop_action = handler(*args)
|
||||||
if maybe_stop_action is not None:
|
if maybe_stop_action is not None:
|
||||||
turn.on_stop(maybe_stop_action)
|
turn.on_stop(maybe_stop_action)
|
||||||
return lambda turn: turn.stop(facet)
|
return lambda: turn.stop(facet)
|
||||||
return facet_handler
|
return facet_handler
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
from .schema import gatekeeper
|
from .schema import gatekeeper
|
||||||
from .during import During
|
from .during import During
|
||||||
|
from . import turn
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def resolve(turn, gk, cap, *args, **kwargs):
|
def resolve(gk, cap, *args, **kwargs):
|
||||||
def configure_handler(handler):
|
def configure_handler(handler):
|
||||||
def unwrapping_handler(turn, wrapped_ref):
|
def unwrapping_handler(r):
|
||||||
return handler(turn, wrapped_ref.embeddedValue)
|
resolved = gatekeeper.Resolved.decode(r)
|
||||||
return _resolve(turn, gk, cap)(During(*args, **kwargs).add_handler(unwrapping_handler))
|
if resolved.VARIANT.name == 'accepted':
|
||||||
|
return handler(resolved.responderSession)
|
||||||
|
raise Exception('Could not resolve reference: ' + repr(resolved))
|
||||||
|
return _resolve(gk, cap)(During(*args, **kwargs).add_handler(unwrapping_handler))
|
||||||
return configure_handler
|
return configure_handler
|
||||||
|
|
||||||
# decorator
|
# decorator
|
||||||
def _resolve(turn, gk, cap):
|
def _resolve(gk, cap):
|
||||||
def publish_resolution_request(entity):
|
def publish_resolution_request(entity):
|
||||||
turn.publish(gk, gatekeeper.Resolve(cap, turn.ref(entity)))
|
turn.publish(gk, gatekeeper.Resolve(cap, turn.ref(entity)))
|
||||||
return entity
|
return entity
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
def add(m, k, v):
|
||||||
|
s = m.get(k)
|
||||||
|
if s is None:
|
||||||
|
s = set()
|
||||||
|
m[k] = s
|
||||||
|
s.add(v)
|
||||||
|
|
||||||
|
def discard(m, k, v):
|
||||||
|
s = m.get(k)
|
||||||
|
if s is None:
|
||||||
|
return
|
||||||
|
s.discard(v)
|
||||||
|
if not s:
|
||||||
|
m.pop(k)
|
|
@ -0,0 +1,15 @@
|
||||||
|
class staticproperty:
|
||||||
|
"""For use as @staticproperty, like @property, but for static properties of classes.
|
||||||
|
Read-only for now."""
|
||||||
|
def __init__(self, getter):
|
||||||
|
self.getter = getter
|
||||||
|
def __get__(self, inst, cls=None):
|
||||||
|
return self.getter()
|
||||||
|
|
||||||
|
class classproperty:
|
||||||
|
"""For use as @classproperty, like @property, but for class-side properties of classes.
|
||||||
|
Read-only for now."""
|
||||||
|
def __init__(self, getter):
|
||||||
|
self.getter = getter
|
||||||
|
def __get__(self, inst, cls=None):
|
||||||
|
return self.getter(cls)
|
|
@ -1,32 +1,111 @@
|
||||||
from .schema import dataspacePatterns as P
|
from .schema import dataspacePatterns as P
|
||||||
from . import Symbol
|
from . import Symbol, Record
|
||||||
|
from preserves import preserve
|
||||||
|
|
||||||
_dict = dict ## we're about to shadow the builtin
|
_dict = dict ## we're about to shadow the builtin
|
||||||
|
|
||||||
_ = P.Pattern.DDiscard(P.DDiscard())
|
_ = P.Pattern.discard()
|
||||||
|
|
||||||
def bind(p):
|
def bind(p):
|
||||||
return P.Pattern.DBind(P.DBind(p))
|
return P.Pattern.bind(p)
|
||||||
|
|
||||||
CAPTURE = bind(_)
|
CAPTURE = bind(_)
|
||||||
|
|
||||||
|
class unquote:
|
||||||
|
def __init__(self, pattern):
|
||||||
|
self.pattern = pattern
|
||||||
|
def __escape_schema__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
uCAPTURE = unquote(CAPTURE)
|
||||||
|
u_ = unquote(_)
|
||||||
|
|
||||||
|
# Given
|
||||||
|
#
|
||||||
|
# Run = <run @name string @input any @output any>
|
||||||
|
#
|
||||||
|
# then these all produce the same pattern:
|
||||||
|
#
|
||||||
|
# P.rec('Observe', P.quote(P.rec('run', P.lit('N'), P.uCAPTURE, P.bind(P.u_))), P._)
|
||||||
|
#
|
||||||
|
# P.rec('Observe', P.quote(P.quote(Run('N', P.unquote(P.uCAPTURE), P.unquote(P.bind(P.u_))))), P._)
|
||||||
|
#
|
||||||
|
# P.quote(Record(Symbol('Observe'),
|
||||||
|
# [P.quote(Run('N', P.unquote(P.uCAPTURE), P.unquote(P.bind(P.u_)))),
|
||||||
|
# P.u_]))
|
||||||
|
|
||||||
|
# Simple, stupid single-level quasiquotation.
|
||||||
|
def quote(p):
|
||||||
|
if isinstance(p, unquote):
|
||||||
|
return p.pattern
|
||||||
|
p = preserve(p)
|
||||||
|
if isinstance(p, list) or isinstance(p, tuple):
|
||||||
|
return arr(*map(quote, p))
|
||||||
|
elif isinstance(p, set) or isinstance(p, frozenset):
|
||||||
|
raise Exception('Cannot represent literal set in dataspace pattern')
|
||||||
|
elif isinstance(p, _dict):
|
||||||
|
return dict(*((k, quote(pp)) for (k, pp) in p.items()))
|
||||||
|
elif isinstance(p, Record):
|
||||||
|
return _rec(p.key, *map(quote, p.fields))
|
||||||
|
else:
|
||||||
|
return P.Pattern.lit(P.AnyAtom.decode(p))
|
||||||
|
|
||||||
def lit(v):
|
def lit(v):
|
||||||
return P.Pattern.DLit(P.DLit(v))
|
if isinstance(v, list) or isinstance(v, tuple):
|
||||||
|
return arr(*map(lit, v))
|
||||||
|
elif isinstance(v, set) or isinstance(v, frozenset):
|
||||||
|
raise Exception('Cannot represent literal set in dataspace pattern')
|
||||||
|
elif isinstance(v, _dict):
|
||||||
|
return dict(*((k, lit(vv)) for (k, vv) in v.items()))
|
||||||
|
elif isinstance(v, Record):
|
||||||
|
return _rec(v.key, *map(lit, v.fields))
|
||||||
|
else:
|
||||||
|
return P.Pattern.lit(P.AnyAtom.decode(v))
|
||||||
|
|
||||||
|
def seq_entries(seq):
|
||||||
|
entries = {}
|
||||||
|
for i, p in enumerate(seq):
|
||||||
|
if p.VARIANT != P.Pattern.discard.VARIANT:
|
||||||
|
entries[i] = p
|
||||||
|
np = len(seq)
|
||||||
|
if np > 0 and (np - 1) not in entries:
|
||||||
|
entries[np - 1] = P.Pattern.discard()
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def unlit_seq(entries):
|
||||||
|
seq = []
|
||||||
|
if len(entries) > 0:
|
||||||
|
try:
|
||||||
|
max_k = max(entries.keys())
|
||||||
|
except TypeError:
|
||||||
|
raise Exception('Pattern entries do not represent a gap-free sequence')
|
||||||
|
for i in range(max_k + 1):
|
||||||
|
seq.append(unlit(entries[i]))
|
||||||
|
return seq
|
||||||
|
|
||||||
|
def unlit(p):
|
||||||
|
if not hasattr(p, 'VARIANT'):
|
||||||
|
p = P.Pattern.decode(p)
|
||||||
|
if p.VARIANT == P.Pattern.lit.VARIANT:
|
||||||
|
return p.value.value
|
||||||
|
if p.VARIANT != P.Pattern.group.VARIANT:
|
||||||
|
raise Exception('Pattern does not represent a literal value')
|
||||||
|
if p.type.VARIANT == P.GroupType.rec.VARIANT:
|
||||||
|
return Record(p.type.label, unlit_seq(p.entries))
|
||||||
|
if p.type.VARIANT == P.GroupType.arr.VARIANT:
|
||||||
|
return list(unlit_seq(p.entries))
|
||||||
|
if p.type.VARIANT == P.GroupType.dict.VARIANT:
|
||||||
|
return _dict(map(lambda kv: (kv[0], unlit(kv[1])), p.entries.items()))
|
||||||
|
raise Exception('unreachable')
|
||||||
|
|
||||||
def rec(labelstr, *members):
|
def rec(labelstr, *members):
|
||||||
return _rec(Symbol(labelstr), *members)
|
return _rec(Symbol(labelstr), *members)
|
||||||
|
|
||||||
def _rec(label, *members):
|
def _rec(label, *members):
|
||||||
return P.Pattern.DCompound(P.DCompound.rec(
|
return P.Pattern.group(P.GroupType.rec(label), seq_entries(members))
|
||||||
P.CRec(label, len(members)),
|
|
||||||
_dict(enumerate(members))))
|
|
||||||
|
|
||||||
def arr(*members):
|
def arr(*members):
|
||||||
return P.Pattern.DCompound(P.DCompound.arr(
|
return P.Pattern.group(P.GroupType.arr(), seq_entries(members))
|
||||||
P.CArr(len(members)),
|
|
||||||
_dict(enumerate(members))))
|
|
||||||
|
|
||||||
def dict(*kvs):
|
def dict(*kvs):
|
||||||
return P.Pattern.DCompound(P.DCompound.dict(
|
return P.Pattern.group(P.GroupType.dict(), _dict(kvs))
|
||||||
P.CDict(),
|
|
||||||
_dict(kvs)))
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
all: schema-bundle.bin
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f schema-bundle.bin
|
||||||
|
|
||||||
|
schema-bundle.bin: schemas/*.prs
|
||||||
|
preserves-schemac schemas > $@.tmp
|
||||||
|
mv $@.tmp $@
|
|
@ -0,0 +1,44 @@
|
||||||
|
´³bundle·µ³tcp„´³schema·³version°³definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³
SignedInteger„„„„„³ TcpRemote´³rec´³lit³
|
||||||
|
tcp-remote„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³
SignedInteger„„„„„³TcpPeerInfo´³rec´³lit³tcp-peer„´³tupleµ´³named³handle´³embedded³any„„´³named³local´³refµ„³TcpLocal„„´³named³remote´³refµ„³ TcpRemote„„„„„„³embeddedType´³refµ³ EntityRef„³Cap„„„µ³http„´³schema·³version°³definitions·³Chunk´³orµµ±string´³atom³String„„µ±bytes´³atom³
|
||||||
|
ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³
|
||||||
|
QueryValue´³orµµ±string´³atom³String„„µ±file´³rec´³lit³file„´³tupleµ´³named³filename´³atom³String„„´³named³headers´³refµ„³Headers„„´³named³body´³atom³
|
||||||
|
ByteString„„„„„„„„³HostPattern´³orµµ±host´³atom³String„„µ±any´³lit€„„„„³HttpBinding´³rec´³lit³ http-bind„´³tupleµ´³named³host´³refµ„³HostPattern„„´³named³port´³atom³
SignedInteger„„´³named³method´³refµ„³
MethodPattern„„´³named³path´³refµ„³PathPattern„„´³named³handler´³embedded´³refµ„³HttpRequest„„„„„„³HttpContext´³rec´³lit³request„´³tupleµ´³named³req´³refµ„³HttpRequest„„´³named³res´³embedded´³refµ„³HttpResponse„„„„„„³HttpRequest´³rec´³lit³http-request„´³tupleµ´³named³sequenceNumber´³atom³
SignedInteger„„´³named³host´³refµ„³RequestHost„„´³named³port´³atom³
SignedInteger„„´³named³method´³atom³Symbol„„´³named³path´³seqof´³atom³String„„„´³named³headers´³refµ„³Headers„„´³named³query´³dictof´³atom³Symbol„´³seqof´³refµ„³
|
||||||
|
QueryValue„„„„´³named³body´³refµ„³RequestBody„„„„„³HttpService´³rec´³lit³http-service„´³tupleµ´³named³host´³refµ„³HostPattern„„´³named³port´³atom³
SignedInteger„„´³named³method´³refµ„³
MethodPattern„„´³named³path´³refµ„³PathPattern„„„„„³PathPattern´³seqof´³refµ„³PathPatternElement„„³RequestBody´³orµµ±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µ„³Sink„„„„„„„µ±StreamError´³refµ„³StreamError„„µ±credit´³rec´³lit³credit„´³tupleµ´³named³amount´³refµ„³CreditAmount„„´³named³mode´³refµ„³Mode„„„„„„„„³LineMode´³orµµ±lf´³lit³lf„„µ±crlf´³lit³crlf„„„„³StreamError´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„„„„³CreditAmount´³orµµ±count´³atom³
SignedInteger„„µ± unbounded´³lit³ unbounded„„„„³StreamConnection´³rec´³lit³stream-connection„´³tupleµ´³named³source´³embedded´³refµ„³Source„„„´³named³sink´³embedded´³refµ„³Sink„„„´³named³spec³any„„„„³StreamListenerError´³rec´³lit³stream-listener-error„´³tupleµ´³named³spec³any„´³named³message´³atom³String„„„„„³StreamListenerReady´³rec´³lit³stream-listener-ready„´³tupleµ´³named³spec³any„„„„„³embeddedType´³refµ³ EntityRef„³Cap„„„µ³sturdy„´³schema·³version°³definitions·³Lit´³rec´³lit³lit„´³tupleµ´³named³value³any„„„„³Oid´³atom³
SignedInteger„³Alts´³rec´³lit³or„´³tupleµ´³named³alternatives´³seqof´³refµ„³Rewrite„„„„„„³PAnd´³rec´³lit³and„´³tupleµ´³named³patterns´³seqof´³refµ„³Pattern„„„„„„³PNot´³rec´³lit³not„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³TRef´³rec´³lit³ref„´³tupleµ´³named³binding´³atom³
SignedInteger„„„„„³PAtom´³orµµ±Boolean´³lit³Boolean„„µ±Double´³lit³Double„„µ±
SignedInteger´³lit³
SignedInteger„„µ±String´³lit³String„„µ±
|
||||||
|
ByteString´³lit³
|
||||||
|
ByteString„„µ±Symbol´³lit³Symbol„„„„³PBind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Caveat´³orµµ±Rewrite´³refµ„³Rewrite„„µ±Alts´³refµ„³Alts„„µ±Reject´³refµ„³Reject„„µ±unknown³any„„„³Reject´³rec´³lit³reject„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„³Pattern´³orµµ±PDiscard´³refµ„³PDiscard„„µ±PAtom´³refµ„³PAtom„„µ± PEmbedded´³refµ„³ PEmbedded„„µ±PBind´³refµ„³PBind„„µ±PAnd´³refµ„³PAnd„„µ±PNot´³refµ„³PNot„„µ±Lit´³refµ„³Lit„„µ± PCompound´³refµ„³ PCompound„„„„³Rewrite´³rec´³lit³rewrite„´³tupleµ´³named³pattern´³refµ„³Pattern„„´³named³template´³refµ„³Template„„„„„³WireRef´³orµµ±mine´³tupleµ´³lit° |