mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 03:23:09 +03:00
Merge branch 'master' into release/next-userspace
This commit is contained in:
commit
75acc5aeb5
21
.travis.yml
21
.travis.yml
@ -1,6 +1,13 @@
|
|||||||
|
stages:
|
||||||
|
- compile
|
||||||
|
# Don't run the combine stage in pull requests, because deploy is disabled there.
|
||||||
|
- name: combine
|
||||||
|
if: type != pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- stage: compile
|
||||||
|
os: linux
|
||||||
language: nix
|
language: nix
|
||||||
nix: 2.3.6
|
nix: 2.3.6
|
||||||
before_install:
|
before_install:
|
||||||
@ -15,7 +22,8 @@ jobs:
|
|||||||
- make release
|
- make release
|
||||||
- sh/ci-tests
|
- sh/ci-tests
|
||||||
|
|
||||||
- os: linux
|
- stage: compile
|
||||||
|
os: linux
|
||||||
language: generic
|
language: generic
|
||||||
env: STACK_YAML=pkg/hs/stack.yaml
|
env: STACK_YAML=pkg/hs/stack.yaml
|
||||||
cache:
|
cache:
|
||||||
@ -32,7 +40,8 @@ jobs:
|
|||||||
- stack test
|
- stack test
|
||||||
- sh/release-king-linux64-dynamic
|
- sh/release-king-linux64-dynamic
|
||||||
|
|
||||||
- os: osx
|
- stage: compile
|
||||||
|
os: osx
|
||||||
language: generic
|
language: generic
|
||||||
sudo: required
|
sudo: required
|
||||||
env: STACK_YAML=pkg/hs/stack.yaml
|
env: STACK_YAML=pkg/hs/stack.yaml
|
||||||
@ -50,6 +59,12 @@ jobs:
|
|||||||
- stack test
|
- stack test
|
||||||
- sh/release-king-darwin-dynamic
|
- sh/release-king-darwin-dynamic
|
||||||
|
|
||||||
|
- stage: combine
|
||||||
|
os: linux
|
||||||
|
language: generic
|
||||||
|
script:
|
||||||
|
- sh/combine-release-builds
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- skip_cleanup: true
|
- skip_cleanup: true
|
||||||
provider: gcs
|
provider: gcs
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f738f60e9e028081864f317106f623d2f21a5fe5c2f6fdd83576e22d21a8c6a6
|
oid sha256:35d8930b9b35364605196d99766ec713154af9105ce7b9fabfaa50e8ca29a5fd
|
||||||
size 14862847
|
size 4448128
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9fbfbe75a6685df444621ebd27677716fd0abf7113020f3274c3b5c209e3616e
|
oid sha256:e5c82dea80aa7c5593f43fa4294db7974211abceedd907663da73889857642e7
|
||||||
size 1304972
|
size 1309381
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:59786d78805460632c4de60275b994260d255be7b721ccf47140d7647a46e66c
|
oid sha256:ecf3f8593815742e409008421f318b664124e672b1eecd131e4a1e49864a1c2a
|
||||||
size 6244195
|
size 6175676
|
||||||
|
@ -13,7 +13,6 @@ let
|
|||||||
murmur3-src = deps.murmur3.src;
|
murmur3-src = deps.murmur3.src;
|
||||||
scrypt-src = deps.scrypt.src;
|
scrypt-src = deps.scrypt.src;
|
||||||
secp256k1-src = deps.secp256k1.src;
|
secp256k1-src = deps.secp256k1.src;
|
||||||
sni-src = deps.sni.src;
|
|
||||||
softfloat3-src = deps.softfloat3.src;
|
softfloat3-src = deps.softfloat3.src;
|
||||||
uv-src = deps.uv.src;
|
uv-src = deps.uv.src;
|
||||||
};
|
};
|
||||||
|
7
nix/cachix/tests.nix
Normal file
7
nix/cachix/tests.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
let
|
||||||
|
ops = import ../ops/default.nix {};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
results = ops.test;
|
||||||
|
fakebus = ops.bus;
|
||||||
|
}
|
@ -5,7 +5,6 @@ rec {
|
|||||||
murmur3 = import ./deps/murmur3/cross.nix { inherit crossenv; };
|
murmur3 = import ./deps/murmur3/cross.nix { inherit crossenv; };
|
||||||
uv = import ./deps/uv/cross.nix { inherit crossenv; };
|
uv = import ./deps/uv/cross.nix { inherit crossenv; };
|
||||||
ed25519 = import ./deps/ed25519/cross.nix { inherit crossenv; };
|
ed25519 = import ./deps/ed25519/cross.nix { inherit crossenv; };
|
||||||
sni = import ./deps/sni/cross.nix { inherit crossenv; };
|
|
||||||
scrypt = import ./deps/scrypt/cross.nix { inherit crossenv; };
|
scrypt = import ./deps/scrypt/cross.nix { inherit crossenv; };
|
||||||
softfloat3 = import ./deps/softfloat3/cross.nix { inherit crossenv; };
|
softfloat3 = import ./deps/softfloat3/cross.nix { inherit crossenv; };
|
||||||
secp256k1 = import ./deps/secp256k1/cross.nix { inherit crossenv; };
|
secp256k1 = import ./deps/secp256k1/cross.nix { inherit crossenv; };
|
||||||
|
@ -10,7 +10,7 @@ let
|
|||||||
|
|
||||||
libs =
|
libs =
|
||||||
with pkgs;
|
with pkgs;
|
||||||
[ openssl zlib curl gmp scrypt libsigsegv ncurses openssl zlib lmdb ];
|
[ openssl curl gmp scrypt libsigsegv openssl zlib lmdb ];
|
||||||
|
|
||||||
osx =
|
osx =
|
||||||
with pkgs;
|
with pkgs;
|
||||||
@ -20,7 +20,7 @@ let
|
|||||||
|
|
||||||
vendor =
|
vendor =
|
||||||
with deps;
|
with deps;
|
||||||
[ argon2 ed25519 h2o murmur3 scrypt secp256k1 sni softfloat3 uv ent ge-additions ivory-header ca-header ];
|
[ argon2 ed25519 h2o murmur3 scrypt secp256k1 softfloat3 uv ent ge-additions ivory-header ca-header ];
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ rec {
|
|||||||
murmur3 = import ./murmur3 { inherit pkgs; };
|
murmur3 = import ./murmur3 { inherit pkgs; };
|
||||||
uv = import ./uv { inherit pkgs; };
|
uv = import ./uv { inherit pkgs; };
|
||||||
ed25519 = import ./ed25519 { inherit pkgs; };
|
ed25519 = import ./ed25519 { inherit pkgs; };
|
||||||
sni = import ./sni { inherit pkgs; };
|
|
||||||
scrypt = import ./scrypt { inherit pkgs; };
|
scrypt = import ./scrypt { inherit pkgs; };
|
||||||
softfloat3 = import ./softfloat3 { inherit pkgs; };
|
softfloat3 = import ./softfloat3 { inherit pkgs; };
|
||||||
secp256k1 = import ./secp256k1 { inherit pkgs; };
|
secp256k1 = import ./secp256k1 { inherit pkgs; };
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
source $stdenv/setup
|
|
||||||
|
|
||||||
CFLAGS="-O3 -Wall -ffast-math -Wno-unused-const-variable"
|
|
||||||
|
|
||||||
echo $CC $CFLAGS -c $src/src/tls.c -o tls.o
|
|
||||||
$CC $CFLAGS -c $src/src/tls.c -o tls.o
|
|
||||||
|
|
||||||
echo $AR rcs libsni.a tls.o
|
|
||||||
$AR rcs libsni.a tls.o
|
|
||||||
|
|
||||||
mkdir -p $out/{lib,include}
|
|
||||||
cp libsni.a $out/lib/
|
|
||||||
cp $src/src/tls.h $out/include/
|
|
@ -1,18 +0,0 @@
|
|||||||
{ crossenv }:
|
|
||||||
|
|
||||||
crossenv.make_derivation rec {
|
|
||||||
name = "sni";
|
|
||||||
builder = ./builder.sh;
|
|
||||||
|
|
||||||
CC = "${crossenv.host}-gcc";
|
|
||||||
AR = "${crossenv.host}-ar";
|
|
||||||
|
|
||||||
src = crossenv.nixpkgs.fetchFromGitHub {
|
|
||||||
owner = "urbit";
|
|
||||||
repo = "sniproxy";
|
|
||||||
rev = "173beb88ee62bddd13874ca04ab338cdec704928";
|
|
||||||
sha256 = "1ib6p7vhpvbg6d5a2aimppsb09kjg4px4vlw5h3ys9zf9c1if5z4";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
{ pkgs }:
|
|
||||||
|
|
||||||
pkgs.stdenv.mkDerivation rec {
|
|
||||||
name = "sni";
|
|
||||||
builder = ./builder.sh;
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "urbit";
|
|
||||||
repo = "sniproxy";
|
|
||||||
rev = "173beb88ee62bddd13874ca04ab338cdec704928";
|
|
||||||
sha256 = "1ib6p7vhpvbg6d5a2aimppsb09kjg4px4vlw5h3ys9zf9c1if5z4";
|
|
||||||
};
|
|
||||||
}
|
|
@ -24,10 +24,6 @@ rec {
|
|||||||
inherit crossenv;
|
inherit crossenv;
|
||||||
};
|
};
|
||||||
|
|
||||||
ncurses = import ./pkgs/ncurses {
|
|
||||||
inherit crossenv;
|
|
||||||
};
|
|
||||||
|
|
||||||
pdcurses = import ./pkgs/pdcurses {
|
pdcurses = import ./pkgs/pdcurses {
|
||||||
inherit crossenv;
|
inherit crossenv;
|
||||||
};
|
};
|
||||||
|
@ -10,10 +10,10 @@ let
|
|||||||
tlon = import ../pkgs { inherit pkgs; };
|
tlon = import ../pkgs { inherit pkgs; };
|
||||||
arvo = tlon.arvo;
|
arvo = tlon.arvo;
|
||||||
urbit = tlon.urbit;
|
urbit = tlon.urbit;
|
||||||
herb = tlon.herb;
|
herb = tlon.herb;
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
import ./fakeship {
|
import ./fakeship {
|
||||||
inherit pkgs arvo pill ship herb urbit;
|
inherit pkgs arvo pill ship urbit herb;
|
||||||
}
|
}
|
||||||
|
@ -31,16 +31,16 @@ let
|
|||||||
ship = "zod";
|
ship = "zod";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
rec {
|
||||||
|
|
||||||
bus = import ./fakeship {
|
bus = import ./fakeship {
|
||||||
inherit pkgs herb urbit arvo;
|
inherit pkgs herb urbit arvo;
|
||||||
pill = bootsolid;
|
pill = bootsolid;
|
||||||
ship = "bus";
|
ship = "bus";
|
||||||
};
|
};
|
||||||
|
|
||||||
in
|
|
||||||
|
|
||||||
rec {
|
|
||||||
|
|
||||||
test = import ./test {
|
test = import ./test {
|
||||||
inherit pkgs herb urbit;
|
inherit pkgs herb urbit;
|
||||||
ship = bus;
|
ship = bus;
|
||||||
|
@ -13,7 +13,7 @@ check () {
|
|||||||
[ 3 -eq "$(herb $out -d 3)" ]
|
[ 3 -eq "$(herb $out -d 3)" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
if check
|
if check && sleep 10 && check
|
||||||
then
|
then
|
||||||
echo "Boot success." >&2
|
echo "Boot success." >&2
|
||||||
herb $out -p hood -d '+hood/exit' || true
|
herb $out -p hood -d '+hood/exit' || true
|
||||||
|
@ -7,13 +7,13 @@ chmod -R u+rw ./pier
|
|||||||
|
|
||||||
$URBIT -d ./pier
|
$URBIT -d ./pier
|
||||||
|
|
||||||
cleanup () {
|
shutdown () {
|
||||||
if [ -e ./pier/.vere.lock ]
|
if [ -e ./pier/.vere.lock ]
|
||||||
then kill $(< ./pier/.vere.lock) || true;
|
then kill $(< ./pier/.vere.lock) || true;
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup EXIT
|
trap shutdown EXIT
|
||||||
|
|
||||||
# update pill strategy to ensure correct staging
|
# update pill strategy to ensure correct staging
|
||||||
#
|
#
|
||||||
@ -75,6 +75,8 @@ herb ./pier -p hood -d "+hood/unmount %stage"
|
|||||||
|
|
||||||
herb ./pier -P solid.pill -d '+solid /=stage=/sys, =dub &'
|
herb ./pier -P solid.pill -d '+solid /=stage=/sys, =dub &'
|
||||||
|
|
||||||
|
herb ./pier -p hood -d '+hood/exit' || true
|
||||||
|
|
||||||
mv solid.pill $out
|
mv solid.pill $out
|
||||||
|
|
||||||
set +x
|
set +x
|
||||||
|
@ -21,7 +21,7 @@ let
|
|||||||
mkUrbit = { debug }:
|
mkUrbit = { debug }:
|
||||||
import ./urbit {
|
import ./urbit {
|
||||||
inherit pkgs ent debug ge-additions libaes_siv;
|
inherit pkgs ent debug ge-additions libaes_siv;
|
||||||
inherit (deps) argon2 murmur3 uv ed25519 sni scrypt softfloat3;
|
inherit (deps) argon2 murmur3 uv ed25519 scrypt softfloat3;
|
||||||
inherit (deps) secp256k1 h2o ivory-header ca-header;
|
inherit (deps) secp256k1 h2o ivory-header ca-header;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
debug,
|
debug,
|
||||||
argon2, ed25519, ent, ge-additions, libaes_siv, h2o, murmur3, scrypt, secp256k1, sni, softfloat3, uv, ivory-header, ca-header
|
argon2, ed25519, ent, ge-additions, libaes_siv, h2o, murmur3, scrypt, secp256k1, softfloat3, uv, ivory-header, ca-header
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -23,10 +23,10 @@ let
|
|||||||
|
|
||||||
deps =
|
deps =
|
||||||
with pkgs;
|
with pkgs;
|
||||||
[ curl gmp sigseg ncurses openssl zlib lmdb ];
|
[ curl gmp sigseg openssl zlib lmdb ];
|
||||||
|
|
||||||
vendor =
|
vendor =
|
||||||
[ argon2 softfloat3 ed25519 ent ge-additions libaes_siv h2o scrypt uv murmur3 secp256k1 sni ivory-header ca-header ];
|
[ argon2 softfloat3 ed25519 ent ge-additions libaes_siv h2o scrypt uv murmur3 secp256k1 ivory-header ca-header ];
|
||||||
|
|
||||||
urbit = pkgs.stdenv.mkDerivation {
|
urbit = pkgs.stdenv.mkDerivation {
|
||||||
inherit name meta;
|
inherit name meta;
|
||||||
|
@ -12,11 +12,11 @@ let
|
|||||||
|
|
||||||
crossdeps =
|
crossdeps =
|
||||||
with env;
|
with env;
|
||||||
[ curl libgmp libsigsegv ncurses openssl zlib lmdb ];
|
[ curl libgmp libsigsegv openssl zlib lmdb ];
|
||||||
|
|
||||||
vendor =
|
vendor =
|
||||||
with deps;
|
with deps;
|
||||||
[ argon2 softfloat3 ed25519 ge-additions libaes_siv h2o scrypt uv murmur3 secp256k1 sni ivory-header ca-header ];
|
[ argon2 softfloat3 ed25519 ge-additions libaes_siv h2o scrypt uv murmur3 secp256k1 ivory-header ca-header ];
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ env.make_derivation {
|
|||||||
MEMORY_DEBUG = debug;
|
MEMORY_DEBUG = debug;
|
||||||
CPU_DEBUG = debug;
|
CPU_DEBUG = debug;
|
||||||
EVENT_TIME_DEBUG = false;
|
EVENT_TIME_DEBUG = false;
|
||||||
NCURSES = env.ncurses;
|
|
||||||
|
|
||||||
name = "${name}-${env_name}";
|
name = "${name}-${env_name}";
|
||||||
exename = name;
|
exename = name;
|
||||||
|
@ -17,6 +17,5 @@ bash ./configure
|
|||||||
make build/urbit build/urbit-worker -j8
|
make build/urbit build/urbit-worker -j8
|
||||||
|
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp -r $NCURSES/share/terminfo $out/bin/$exename-terminfo
|
|
||||||
cp ./build/urbit $out/bin/$exename
|
cp ./build/urbit $out/bin/$exename
|
||||||
cp ./build/urbit-worker $out/bin/$exename-worker
|
cp ./build/urbit-worker $out/bin/$exename-worker
|
||||||
|
@ -12,5 +12,5 @@ import ./default.nix {
|
|||||||
inherit (tlon)
|
inherit (tlon)
|
||||||
ent ge-additions libaes_siv;
|
ent ge-additions libaes_siv;
|
||||||
inherit (deps)
|
inherit (deps)
|
||||||
argon2 ed25519 h2o murmur3 scrypt secp256k1 sni softfloat3 uv ivory-header ca-header;
|
argon2 ed25519 h2o murmur3 scrypt secp256k1 softfloat3 uv ivory-header ca-header;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ let
|
|||||||
|
|
||||||
builds-for-platform = plat:
|
builds-for-platform = plat:
|
||||||
plat.deps // {
|
plat.deps // {
|
||||||
inherit (plat.env) curl libgmp libsigsegv ncurses openssl zlib lmdb;
|
inherit (plat.env) curl libgmp libsigsegv openssl zlib lmdb;
|
||||||
inherit (plat.env) cmake_toolchain;
|
inherit (plat.env) cmake_toolchain;
|
||||||
ent = ent plat;
|
ent = ent plat;
|
||||||
ge-additions = ge-additions plat;
|
ge-additions = ge-additions plat;
|
||||||
|
@ -69,13 +69,37 @@
|
|||||||
|= [ovo=ovum ken=*]
|
|= [ovo=ovum ken=*]
|
||||||
[~ (slum ken [now ovo])]
|
[~ (slum ken [now ovo])]
|
||||||
::
|
::
|
||||||
:: our boot-ova is a list containing one massive formula:
|
:: boot-one: lifecycle formula (from +brass)
|
||||||
|
::
|
||||||
|
=/ boot-one
|
||||||
|
=> [boot-formula=** full-sequence=**]
|
||||||
|
!= =+ [state-gate main-sequence]=.*(full-sequence boot-formula)
|
||||||
|
|-
|
||||||
|
?@ main-sequence
|
||||||
|
state-gate
|
||||||
|
%= $
|
||||||
|
main-sequence +.main-sequence
|
||||||
|
state-gate .*(state-gate [%9 2 %10 [6 %1 -.main-sequence] %0 1])
|
||||||
|
==
|
||||||
|
::
|
||||||
|
:: kernel-formula
|
||||||
::
|
::
|
||||||
:: We evaluate :arvo-formula (for jet registration),
|
:: We evaluate :arvo-formula (for jet registration),
|
||||||
:: then ignore the result and produce :installed
|
:: then ignore the result and produce .installed
|
||||||
|
::
|
||||||
|
=/ kernel-formula
|
||||||
|
[%7 arvo-formula %1 installed]
|
||||||
|
::
|
||||||
|
:: boot-two: startup formula
|
||||||
|
::
|
||||||
|
=/ boot-two
|
||||||
|
=> [kernel-formula=** main-sequence=**]
|
||||||
|
!= [.*(0 kernel-formula) main-sequence]
|
||||||
|
::
|
||||||
|
:: boot-ova
|
||||||
::
|
::
|
||||||
=/ boot-ova=(list)
|
=/ boot-ova=(list)
|
||||||
[[%7 arvo-formula %1 installed] ~]
|
[boot-one boot-two kernel-formula ~]
|
||||||
::
|
::
|
||||||
:: a pill is a 3-tuple of event-lists: [boot kernel userspace]
|
:: a pill is a 3-tuple of event-lists: [boot kernel userspace]
|
||||||
::
|
::
|
||||||
|
1
pkg/hs/.gitignore
vendored
Normal file
1
pkg/hs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
stack.yaml.lock
|
@ -19,7 +19,7 @@ dependencies:
|
|||||||
- transformers
|
- transformers
|
||||||
- transformers-compat
|
- transformers-compat
|
||||||
- unordered-containers
|
- unordered-containers
|
||||||
- urbit-king
|
- urbit-noun
|
||||||
|
|
||||||
default-extensions:
|
default-extensions:
|
||||||
- ApplicativeDo
|
- ApplicativeDo
|
||||||
|
3
pkg/hs/racquire/.gitignore
vendored
Normal file
3
pkg/hs/racquire/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.stack-work
|
||||||
|
*.cabal
|
||||||
|
test/gold/*.writ
|
21
pkg/hs/racquire/LICENSE
Normal file
21
pkg/hs/racquire/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 urbit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
66
pkg/hs/racquire/package.yaml
Normal file
66
pkg/hs/racquire/package.yaml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
name: racquire
|
||||||
|
version: 0.10.4
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: lib
|
||||||
|
ghc-options:
|
||||||
|
- -fwarn-incomplete-patterns
|
||||||
|
- -fwarn-unused-binds
|
||||||
|
- -fwarn-unused-imports
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- mtl
|
||||||
|
- unliftio-core
|
||||||
|
- resourcet
|
||||||
|
- exceptions
|
||||||
|
- rio
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
- ApplicativeDo
|
||||||
|
- BangPatterns
|
||||||
|
- BlockArguments
|
||||||
|
- ConstraintKinds
|
||||||
|
- DataKinds
|
||||||
|
- DefaultSignatures
|
||||||
|
- DeriveAnyClass
|
||||||
|
- DeriveDataTypeable
|
||||||
|
- DeriveFoldable
|
||||||
|
- DeriveGeneric
|
||||||
|
- DeriveTraversable
|
||||||
|
- DerivingStrategies
|
||||||
|
- EmptyCase
|
||||||
|
- EmptyDataDecls
|
||||||
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
|
- FunctionalDependencies
|
||||||
|
- GADTs
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
|
- LambdaCase
|
||||||
|
- MagicHash
|
||||||
|
- MultiParamTypeClasses
|
||||||
|
- NamedFieldPuns
|
||||||
|
- NoImplicitPrelude
|
||||||
|
- NumericUnderscores
|
||||||
|
- OverloadedStrings
|
||||||
|
- PackageImports
|
||||||
|
- PartialTypeSignatures
|
||||||
|
- PatternSynonyms
|
||||||
|
- QuasiQuotes
|
||||||
|
- Rank2Types
|
||||||
|
- RankNTypes
|
||||||
|
- RecordWildCards
|
||||||
|
- ScopedTypeVariables
|
||||||
|
- StandaloneDeriving
|
||||||
|
- TemplateHaskell
|
||||||
|
- TupleSections
|
||||||
|
- TypeApplications
|
||||||
|
- TypeFamilies
|
||||||
|
- TypeOperators
|
||||||
|
- UnboxedTuples
|
||||||
|
- UnicodeSyntax
|
||||||
|
- ViewPatterns
|
@ -3,10 +3,15 @@ resolver: lts-14.21
|
|||||||
packages:
|
packages:
|
||||||
- lmdb-static
|
- lmdb-static
|
||||||
- proto
|
- proto
|
||||||
|
- racquire
|
||||||
- terminal-progress-bar
|
- terminal-progress-bar
|
||||||
- urbit-atom
|
- urbit-atom
|
||||||
- urbit-azimuth
|
- urbit-azimuth
|
||||||
|
- urbit-eventlog-lmdb
|
||||||
- urbit-king
|
- urbit-king
|
||||||
|
- urbit-termsize
|
||||||
|
- urbit-noun
|
||||||
|
- urbit-noun-core
|
||||||
|
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- flat-0.3.4@sha256:002a0e0ae656ea8cc02a772d0bcb6ea7dbd7f2e79070959cc748ad1e7138eb38
|
- flat-0.3.4@sha256:002a0e0ae656ea8cc02a772d0bcb6ea7dbd7f2e79070959cc748ad1e7138eb38
|
||||||
|
3
pkg/hs/urbit-eventlog-lmdb/.gitignore
vendored
Normal file
3
pkg/hs/urbit-eventlog-lmdb/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.stack-work
|
||||||
|
*.cabal
|
||||||
|
test/gold/*.writ
|
21
pkg/hs/urbit-eventlog-lmdb/LICENSE
Normal file
21
pkg/hs/urbit-eventlog-lmdb/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 urbit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -4,27 +4,51 @@
|
|||||||
TODO Effects storage logic is messy.
|
TODO Effects storage logic is messy.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Log ( EventLog, identity, nextEv, lastEv
|
module Urbit.EventLog.LMDB
|
||||||
, new, existing
|
( LogIdentity(..)
|
||||||
, streamEvents, appendEvents, trimEvents
|
, EventLog
|
||||||
, streamEffectsRows, writeEffectsRow
|
, identity
|
||||||
) where
|
, nextEv
|
||||||
|
, lastEv
|
||||||
|
, new
|
||||||
|
, existing
|
||||||
|
, streamEvents
|
||||||
|
, appendEvents
|
||||||
|
, trimEvents
|
||||||
|
, streamEffectsRows
|
||||||
|
, writeEffectsRow
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.Prelude hiding (init)
|
import ClassyPrelude
|
||||||
|
|
||||||
import Data.Conduit
|
|
||||||
import Data.RAcquire
|
import Data.RAcquire
|
||||||
import Database.LMDB.Raw
|
import Database.LMDB.Raw
|
||||||
import Foreign.Marshal.Alloc
|
|
||||||
import Foreign.Ptr
|
|
||||||
import Urbit.Vere.Pier.Types
|
|
||||||
|
|
||||||
import Foreign.Storable (peek, poke, sizeOf)
|
import Data.Conduit (ConduitT, yield)
|
||||||
|
import Foreign.Marshal.Alloc (allocaBytes)
|
||||||
|
import Foreign.Ptr (Ptr, castPtr, nullPtr)
|
||||||
|
import Foreign.Storable (peek, poke, sizeOf)
|
||||||
|
import RIO (HasLogFunc, RIO, display, logDebug, runRIO)
|
||||||
|
import Urbit.Noun (DecodeErr, Noun, Ship)
|
||||||
|
import Urbit.Noun (deriveNoun, fromNounExn, toNoun)
|
||||||
|
import Urbit.Noun (cueBS, jamBS)
|
||||||
|
|
||||||
import qualified Data.ByteString.Unsafe as BU
|
import qualified Data.ByteString.Unsafe as BU
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
|
|
||||||
|
|
||||||
|
-- Public Types ----------------------------------------------------------------
|
||||||
|
|
||||||
|
data LogIdentity = LogIdentity
|
||||||
|
{ who :: Ship
|
||||||
|
, isFake :: Bool
|
||||||
|
, lifecycleLen :: Word
|
||||||
|
} deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
deriveNoun ''LogIdentity
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
type Env = MDB_env
|
type Env = MDB_env
|
||||||
@ -34,29 +58,29 @@ type Dbi = MDB_dbi
|
|||||||
type Cur = MDB_cursor
|
type Cur = MDB_cursor
|
||||||
|
|
||||||
data EventLog = EventLog
|
data EventLog = EventLog
|
||||||
{ env :: Env
|
{ env :: Env
|
||||||
, _metaTbl :: Dbi
|
, _metaTbl :: Dbi
|
||||||
, eventsTbl :: Dbi
|
, eventsTbl :: Dbi
|
||||||
, effectsTbl :: Dbi
|
, effectsTbl :: Dbi
|
||||||
, identity :: LogIdentity
|
, identity :: LogIdentity
|
||||||
, numEvents :: IORef EventId
|
, numEvents :: TVar Word64
|
||||||
}
|
}
|
||||||
|
|
||||||
nextEv :: EventLog -> RIO e EventId
|
nextEv :: EventLog -> STM Word64
|
||||||
nextEv = fmap succ . readIORef . numEvents
|
nextEv = fmap (+1) . lastEv
|
||||||
|
|
||||||
lastEv :: EventLog -> RIO e EventId
|
lastEv :: EventLog -> STM Word64
|
||||||
lastEv = readIORef . numEvents
|
lastEv = readTVar . numEvents
|
||||||
|
|
||||||
data EventLogExn
|
data EventLogExn
|
||||||
= NoLogIdentity
|
= NoLogIdentity
|
||||||
| MissingEvent EventId
|
| MissingEvent Word64
|
||||||
| BadNounInLogIdentity ByteString DecodeErr ByteString
|
| BadNounInLogIdentity ByteString DecodeErr ByteString
|
||||||
| BadKeyInEventLog
|
| BadKeyInEventLog
|
||||||
| BadWriteLogIdentity LogIdentity
|
| BadWriteLogIdentity LogIdentity
|
||||||
| BadWriteEvent EventId
|
| BadWriteEvent Word64
|
||||||
| BadWriteEffect EventId
|
| BadWriteEffect Word64
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
|
|
||||||
-- Instances -------------------------------------------------------------------
|
-- Instances -------------------------------------------------------------------
|
||||||
@ -64,6 +88,12 @@ data EventLogExn
|
|||||||
instance Exception EventLogExn where
|
instance Exception EventLogExn where
|
||||||
|
|
||||||
|
|
||||||
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
io :: MonadIO m => IO a -> m a
|
||||||
|
io = liftIO
|
||||||
|
|
||||||
|
|
||||||
-- Open/Close an Event Log -----------------------------------------------------
|
-- Open/Close an Event Log -----------------------------------------------------
|
||||||
|
|
||||||
rawOpen :: MonadIO m => FilePath -> m Env
|
rawOpen :: MonadIO m => FilePath -> m Env
|
||||||
@ -82,7 +112,7 @@ create dir id = do
|
|||||||
(m, e, f) <- createTables env
|
(m, e, f) <- createTables env
|
||||||
clearEvents env e
|
clearEvents env e
|
||||||
writeIdent env m id
|
writeIdent env m id
|
||||||
EventLog env m e f id <$> newIORef 0
|
EventLog env m e f id <$> newTVarIO 0
|
||||||
where
|
where
|
||||||
createTables env =
|
createTables env =
|
||||||
rwith (writeTxn env) $ \txn -> io $
|
rwith (writeTxn env) $ \txn -> io $
|
||||||
@ -98,7 +128,7 @@ open dir = do
|
|||||||
id <- getIdent env m
|
id <- getIdent env m
|
||||||
logDebug $ display (pack @Text $ "Log Identity: " <> show id)
|
logDebug $ display (pack @Text $ "Log Identity: " <> show id)
|
||||||
numEvs <- getNumEvents env e
|
numEvs <- getNumEvents env e
|
||||||
EventLog env m e f id <$> newIORef numEvs
|
EventLog env m e f id <$> newTVarIO numEvs
|
||||||
where
|
where
|
||||||
openTables env =
|
openTables env =
|
||||||
rwith (writeTxn env) $ \txn -> io $
|
rwith (writeTxn env) $ \txn -> io $
|
||||||
@ -227,10 +257,10 @@ clearEvents env eventsTbl =
|
|||||||
|
|
||||||
appendEvents :: EventLog -> Vector ByteString -> RIO e ()
|
appendEvents :: EventLog -> Vector ByteString -> RIO e ()
|
||||||
appendEvents log !events = do
|
appendEvents log !events = do
|
||||||
numEvs <- readIORef (numEvents log)
|
numEvs <- atomically $ readTVar (numEvents log)
|
||||||
next <- pure (numEvs + 1)
|
next <- pure (numEvs + 1)
|
||||||
doAppend $ zip [next..] $ toList events
|
doAppend $ zip [next..] $ toList events
|
||||||
writeIORef (numEvents log) (numEvs + word (length events))
|
atomically $ writeTVar (numEvents log) (numEvs + word (length events))
|
||||||
where
|
where
|
||||||
flags = compileWriteFlags [MDB_NOOVERWRITE]
|
flags = compileWriteFlags [MDB_NOOVERWRITE]
|
||||||
doAppend = \kvs ->
|
doAppend = \kvs ->
|
||||||
@ -240,21 +270,20 @@ appendEvents log !events = do
|
|||||||
True -> pure ()
|
True -> pure ()
|
||||||
False -> throwIO (BadWriteEvent k)
|
False -> throwIO (BadWriteEvent k)
|
||||||
|
|
||||||
writeEffectsRow :: EventLog -> EventId -> ByteString -> RIO e ()
|
writeEffectsRow :: MonadIO m => EventLog -> Word64 -> ByteString -> m ()
|
||||||
writeEffectsRow log k v = do
|
writeEffectsRow log k v = io $ runRIO () $ do
|
||||||
rwith (writeTxn $ env log) $ \txn ->
|
let flags = compileWriteFlags []
|
||||||
putBytes flags txn (effectsTbl log) k v >>= \case
|
rwith (writeTxn $ env log) $ \txn ->
|
||||||
True -> pure ()
|
putBytes flags txn (effectsTbl log) k v >>= \case
|
||||||
False -> throwIO (BadWriteEffect k)
|
True -> pure ()
|
||||||
where
|
False -> throwIO (BadWriteEffect k)
|
||||||
flags = compileWriteFlags []
|
|
||||||
|
|
||||||
|
|
||||||
-- Read Events -----------------------------------------------------------------
|
-- Read Events -----------------------------------------------------------------
|
||||||
|
|
||||||
trimEvents :: HasLogFunc e => EventLog -> Word64 -> RIO e ()
|
trimEvents :: HasLogFunc e => EventLog -> Word64 -> RIO e ()
|
||||||
trimEvents log start = do
|
trimEvents log start = do
|
||||||
last <- lastEv log
|
last <- atomically (lastEv log)
|
||||||
rwith (writeTxn $ env log) $ \txn ->
|
rwith (writeTxn $ env log) $ \txn ->
|
||||||
for_ [start..last] $ \eId ->
|
for_ [start..last] $ \eId ->
|
||||||
withWordPtr eId $ \pKey -> do
|
withWordPtr eId $ \pKey -> do
|
||||||
@ -262,23 +291,21 @@ trimEvents log start = do
|
|||||||
found <- io $ mdb_del txn (eventsTbl log) key Nothing
|
found <- io $ mdb_del txn (eventsTbl log) key Nothing
|
||||||
unless found $
|
unless found $
|
||||||
throwIO (MissingEvent eId)
|
throwIO (MissingEvent eId)
|
||||||
writeIORef (numEvents log) (pred start)
|
atomically $ writeTVar (numEvents log) (pred start)
|
||||||
|
|
||||||
streamEvents :: HasLogFunc e
|
streamEvents :: MonadIO m => EventLog -> Word64 -> ConduitT () ByteString m ()
|
||||||
=> EventLog -> Word64
|
|
||||||
-> ConduitT () ByteString (RIO e) ()
|
|
||||||
streamEvents log first = do
|
streamEvents log first = do
|
||||||
batch <- lift $ readBatch log first
|
batch <- io $ runRIO () $ readBatch log first
|
||||||
unless (null batch) $ do
|
unless (null batch) $ do
|
||||||
for_ batch yield
|
for_ batch yield
|
||||||
streamEvents log (first + word (length batch))
|
streamEvents log (first + word (length batch))
|
||||||
|
|
||||||
streamEffectsRows :: ∀e. HasLogFunc e
|
streamEffectsRows :: ∀e. HasLogFunc e
|
||||||
=> EventLog -> EventId
|
=> EventLog -> Word64
|
||||||
-> ConduitT () (Word64, ByteString) (RIO e) ()
|
-> ConduitT () (Word64, ByteString) (RIO e) ()
|
||||||
streamEffectsRows log = go
|
streamEffectsRows log = go
|
||||||
where
|
where
|
||||||
go :: EventId -> ConduitT () (Word64, ByteString) (RIO e) ()
|
go :: Word64 -> ConduitT () (Word64, ByteString) (RIO e) ()
|
||||||
go next = do
|
go next = do
|
||||||
batch <- lift $ readRowsBatch (env log) (effectsTbl log) next
|
batch <- lift $ readRowsBatch (env log) (effectsTbl log) next
|
||||||
unless (null batch) $ do
|
unless (null batch) $ do
|
||||||
@ -294,12 +321,12 @@ readBatch :: EventLog -> Word64 -> RIO e (V.Vector ByteString)
|
|||||||
readBatch log first = start
|
readBatch log first = start
|
||||||
where
|
where
|
||||||
start = do
|
start = do
|
||||||
last <- lastEv log
|
last <- atomically (lastEv log)
|
||||||
if (first > last)
|
if (first > last)
|
||||||
then pure mempty
|
then pure mempty
|
||||||
else readRows $ fromIntegral $ min 1000 $ ((last+1) - first)
|
else readRows $ fromIntegral $ min 1000 $ ((last+1) - first)
|
||||||
|
|
||||||
assertFound :: EventId -> Bool -> RIO e ()
|
assertFound :: Word64 -> Bool -> RIO e ()
|
||||||
assertFound id found = do
|
assertFound id found = do
|
||||||
unless found $ throwIO $ MissingEvent id
|
unless found $ throwIO $ MissingEvent id
|
||||||
|
|
71
pkg/hs/urbit-eventlog-lmdb/package.yaml
Normal file
71
pkg/hs/urbit-eventlog-lmdb/package.yaml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
name: urbit-eventlog-lmdb
|
||||||
|
version: 0.10.4
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: lib
|
||||||
|
ghc-options:
|
||||||
|
- -fwarn-incomplete-patterns
|
||||||
|
- -fwarn-unused-binds
|
||||||
|
- -fwarn-unused-imports
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- classy-prelude
|
||||||
|
- stm
|
||||||
|
- rio
|
||||||
|
- vector
|
||||||
|
- bytestring
|
||||||
|
- lmdb-static
|
||||||
|
- conduit
|
||||||
|
- racquire
|
||||||
|
- urbit-noun-core
|
||||||
|
- urbit-noun
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
- ApplicativeDo
|
||||||
|
- BangPatterns
|
||||||
|
- BlockArguments
|
||||||
|
- ConstraintKinds
|
||||||
|
- DataKinds
|
||||||
|
- DefaultSignatures
|
||||||
|
- DeriveAnyClass
|
||||||
|
- DeriveDataTypeable
|
||||||
|
- DeriveFoldable
|
||||||
|
- DeriveGeneric
|
||||||
|
- DeriveTraversable
|
||||||
|
- DerivingStrategies
|
||||||
|
- EmptyCase
|
||||||
|
- EmptyDataDecls
|
||||||
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
|
- FunctionalDependencies
|
||||||
|
- GADTs
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
|
- LambdaCase
|
||||||
|
- MagicHash
|
||||||
|
- MultiParamTypeClasses
|
||||||
|
- NamedFieldPuns
|
||||||
|
- NoImplicitPrelude
|
||||||
|
- NumericUnderscores
|
||||||
|
- OverloadedStrings
|
||||||
|
- PackageImports
|
||||||
|
- PartialTypeSignatures
|
||||||
|
- PatternSynonyms
|
||||||
|
- QuasiQuotes
|
||||||
|
- Rank2Types
|
||||||
|
- RankNTypes
|
||||||
|
- RecordWildCards
|
||||||
|
- ScopedTypeVariables
|
||||||
|
- StandaloneDeriving
|
||||||
|
- TemplateHaskell
|
||||||
|
- TupleSections
|
||||||
|
- TypeApplications
|
||||||
|
- TypeFamilies
|
||||||
|
- TypeOperators
|
||||||
|
- UnboxedTuples
|
||||||
|
- UnicodeSyntax
|
||||||
|
- ViewPatterns
|
119
pkg/hs/urbit-king/TODO.md
Normal file
119
pkg/hs/urbit-king/TODO.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# New IPC Protocol
|
||||||
|
|
||||||
|
Stubbed out:
|
||||||
|
|
||||||
|
- [x] Handle replacement events (stubbed out now b/c interface can't
|
||||||
|
handle unparsed nouns)
|
||||||
|
- [x] Handle IPC errors by killing serf process.
|
||||||
|
- [x] Handle `peek` and `pack` in `swimming` flow.
|
||||||
|
- [x] Documentation for `Urbit.Vere.Serf.IPC`.
|
||||||
|
- [x] Unstub slog/stder/dead callbacks on serf config.
|
||||||
|
- [x] Remove GoodParse hack in newRunCompute.
|
||||||
|
- [x] Bring back tank printing.
|
||||||
|
- [x] Handle serf stderr message correctly.
|
||||||
|
- [x] Bring back `logEvent`.
|
||||||
|
- [x] Snapshots should block until that event is commited to disk.
|
||||||
|
- [x] Hook up error callbacks to IO Drivers.
|
||||||
|
- [x] Do something useful with error callbacks from IO Drivers.
|
||||||
|
|
||||||
|
Bugs:
|
||||||
|
|
||||||
|
- [x] In non-daemon mode, serf slogs/stderr output that happens *before*
|
||||||
|
the terminal connects should still go to stderr.
|
||||||
|
- [x] Serf stderr should also be send (along with slogs) to all connected
|
||||||
|
terminals.
|
||||||
|
- [x] `king new` should reject pier directories that already exist.
|
||||||
|
- [x] In non-daemon-mode, ^D doesn't bring down Urbit properly.
|
||||||
|
- [x] Spinner updated multiple times with the same event, and this causes
|
||||||
|
logging of events to contain duplicates.
|
||||||
|
|
||||||
|
King-Haskell specific features:
|
||||||
|
|
||||||
|
- [x] Re-implement `collectFX` flow in Serf/Pier.
|
||||||
|
- [x] Hook up `collectFX` to CLI.
|
||||||
|
- [ ] Get `collect-all-fx` flow working again.
|
||||||
|
|
||||||
|
Performance:
|
||||||
|
|
||||||
|
- [x] Batching during replay.
|
||||||
|
- [x] Batching during normal operation.
|
||||||
|
|
||||||
|
Optimization:
|
||||||
|
|
||||||
|
- [x] IO Driver Event Prioritization
|
||||||
|
|
||||||
|
Polish:
|
||||||
|
|
||||||
|
- [x] Cleanup batching flow.
|
||||||
|
- [x] Think through how to shutdown the serf on exception.
|
||||||
|
- [x] King should shutdown promptly on ^C. Always takes 2s in practice.
|
||||||
|
- [x] Bring back progress bars.
|
||||||
|
- [x] Make sure replay progress bars go to stderr.
|
||||||
|
- [x] Logging for new IPC flow.
|
||||||
|
- [x] Logging for boot sequence.
|
||||||
|
- [x] Take snapshots on clean shutdown.
|
||||||
|
|
||||||
|
# Misc Bugs
|
||||||
|
|
||||||
|
- [ ] `king run --collect-fx` flag does nothing. Remove or implement.
|
||||||
|
- [x] Handle ^C in connected terminals. It should interrupt current
|
||||||
|
event (send SIGINT to serf, which will cause the current event to
|
||||||
|
fail promptly).
|
||||||
|
- [x] The terminal driver seems to have a race condition when spinner
|
||||||
|
changed too quickly.
|
||||||
|
|
||||||
|
|
||||||
|
# Finding the Serf Executable
|
||||||
|
|
||||||
|
- [ ] Right now, `urbit-worker` is found by looking it up in the PATH. This
|
||||||
|
is wrong, but what is right?
|
||||||
|
|
||||||
|
|
||||||
|
# Take Advantage of New IPC Features
|
||||||
|
|
||||||
|
- [ ] Hook up `scry` to drivers.
|
||||||
|
- Any immediate applications of this?
|
||||||
|
|
||||||
|
- [ ] Allow scrys to go into the %work batching flow for better latency.
|
||||||
|
|
||||||
|
- Handle event errors in other cases:
|
||||||
|
- [ ] Ames packet failures should print (but not too often).
|
||||||
|
- [ ] Incoming Http requests should produce 500 responses.
|
||||||
|
- [ ] Terminal event errors should be printed in connected terminals.
|
||||||
|
- [ ] Http client responses should be retried.
|
||||||
|
|
||||||
|
|
||||||
|
# Further IO Driver Startup Flow Betterment
|
||||||
|
|
||||||
|
Implement Pier-wide process start events
|
||||||
|
|
||||||
|
- [x] Handle %vega and exit effects.
|
||||||
|
- [x] Handle %trim effect
|
||||||
|
- [x] Inject entropy event on pier start: ``[//arvo [%wack ENT]]`
|
||||||
|
- [ ] Verbose flag: `-v` injects `[%verb ~]`
|
||||||
|
- [ ] CLI event injection: `-I file-path`. The `file-path` is a jammed
|
||||||
|
noun representing an event: `[wire card]`.
|
||||||
|
1. Just parse it as an `Ev` for now.
|
||||||
|
2. Make the serf IPC code not care about the shape of events and effects.
|
||||||
|
3. Support invalid events throughout the system (use `Lenient`?)
|
||||||
|
|
||||||
|
# Polish
|
||||||
|
|
||||||
|
- [x] Goot logging output in non-verbose mode.
|
||||||
|
- [x] Command-Line flag to re-enable verbose output.
|
||||||
|
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
|
||||||
|
- [x] ShutdownSTM action that's passed to the terminal driver should
|
||||||
|
live in `KingEnv` and should be available to all drivers.
|
||||||
|
- [ ] Break most logic from `Main.hs` out into modules.
|
||||||
|
- [ ] Simplify `Main.hs` flows.
|
||||||
|
- [ ] Cleanup Terminal Driver code.
|
||||||
|
- [x] Spin off `racquire` into it's own package.
|
||||||
|
- [x] Spin off `urbit-noun-core` and `urbit-noun` packages.
|
||||||
|
- [x] Spin off `urbit-eventlog-lmdb` into it's own package.
|
||||||
|
- [ ] Spin off `Urbit.Vere.Serf` into it's own package
|
||||||
|
- Make it care less about the shape of events and effects.
|
||||||
|
- [ ] Spin off per-pier logic into it's own package.
|
||||||
|
- Probably `urbit-pier`
|
@ -79,7 +79,10 @@ instance FromNoun H.StdMethod where
|
|||||||
-- Http Server Configuration ---------------------------------------------------
|
-- Http Server Configuration ---------------------------------------------------
|
||||||
|
|
||||||
newtype PEM = PEM { unPEM :: Wain }
|
newtype PEM = PEM { unPEM :: Wain }
|
||||||
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
|
deriving newtype (Eq, Ord, ToNoun, FromNoun)
|
||||||
|
|
||||||
|
instance Show PEM where
|
||||||
|
show _ = "\"PEM (secret)\""
|
||||||
|
|
||||||
type Key = PEM
|
type Key = PEM
|
||||||
type Cert = PEM
|
type Cert = PEM
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
-}
|
-}
|
||||||
module Urbit.Arvo.Effect where
|
module Urbit.Arvo.Effect where
|
||||||
|
|
||||||
|
import Urbit.Noun.Time
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Time
|
|
||||||
|
|
||||||
import Urbit.Arvo.Common (KingId(..), ServId(..))
|
import Urbit.Arvo.Common (KingId(..), ServId(..))
|
||||||
import Urbit.Arvo.Common (Header, HttpEvent, HttpServerConf, Method, Mime)
|
import Urbit.Arvo.Common (Header, HttpEvent, HttpServerConf, Method, Mime)
|
||||||
@ -82,22 +82,6 @@ data SyncEf
|
|||||||
deriveNoun ''SyncEf
|
deriveNoun ''SyncEf
|
||||||
|
|
||||||
|
|
||||||
-- UDP Effects -----------------------------------------------------------------
|
|
||||||
|
|
||||||
{-|
|
|
||||||
%init -- "I don't think that's something that can happen"
|
|
||||||
%west -- "Those also shouldn't happen"
|
|
||||||
%woot -- "Those also shouldn't happen"
|
|
||||||
-}
|
|
||||||
data AmesEf
|
|
||||||
= AmesEfInit Path ()
|
|
||||||
| AmesEfWest Path Ship Path Noun
|
|
||||||
| AmesEfWoot Path Ship (Maybe (Maybe (Term, [Tank])))
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
deriveNoun ''AmesEf
|
|
||||||
|
|
||||||
|
|
||||||
-- Timer Effects ---------------------------------------------------------------
|
-- Timer Effects ---------------------------------------------------------------
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
@ -171,7 +155,6 @@ data VaneEf
|
|||||||
| VEHttpClient HttpClientEf
|
| VEHttpClient HttpClientEf
|
||||||
| VEHttpServer HttpServerEf
|
| VEHttpServer HttpServerEf
|
||||||
| VEBehn BehnEf
|
| VEBehn BehnEf
|
||||||
| VEAmes AmesEf
|
|
||||||
| VETerm TermEf
|
| VETerm TermEf
|
||||||
| VEClay SyncEf
|
| VEClay SyncEf
|
||||||
| VESync SyncEf
|
| VESync SyncEf
|
||||||
@ -203,3 +186,10 @@ instance FromNoun Ef where
|
|||||||
ReOrg "" s "vega" p _ -> fail "%vega effect expects nil value"
|
ReOrg "" s "vega" p _ -> fail "%vega effect expects nil value"
|
||||||
ReOrg "" s tag p val -> EfVane <$> parseNoun (toNoun (s, tag, p, val))
|
ReOrg "" s tag p val -> EfVane <$> parseNoun (toNoun (s, tag, p, val))
|
||||||
ReOrg _ _ _ _ _ -> fail "Non-empty first path-element"
|
ReOrg _ _ _ _ _ -> fail "Non-empty first path-element"
|
||||||
|
|
||||||
|
summarizeEffect :: Lenient Ef -> Text
|
||||||
|
summarizeEffect ef =
|
||||||
|
fromNoun (toNoun ef) & \case
|
||||||
|
Nothing -> "//invalid %effect"
|
||||||
|
Just (pax :: [Cord], tag :: Cord, val :: Noun) ->
|
||||||
|
"/" <> intercalate "/" (unCord <$> pax) <> " %" <> unCord tag
|
||||||
|
@ -202,9 +202,16 @@ deriveNoun ''AmesEv
|
|||||||
|
|
||||||
-- Arvo Events -----------------------------------------------------------------
|
-- Arvo Events -----------------------------------------------------------------
|
||||||
|
|
||||||
|
newtype Entropy = Entropy { entropyBits :: Word512 }
|
||||||
|
deriving newtype (Eq, Ord, FromNoun, ToNoun)
|
||||||
|
|
||||||
|
instance Show Entropy where
|
||||||
|
show = const "\"ENTROPY (secret)\""
|
||||||
|
|
||||||
|
|
||||||
data ArvoEv
|
data ArvoEv
|
||||||
= ArvoEvWhom () Ship
|
= ArvoEvWhom () Ship
|
||||||
| ArvoEvWack () Word512
|
| ArvoEvWack () Entropy
|
||||||
| ArvoEvWarn Path Noun
|
| ArvoEvWarn Path Noun
|
||||||
| ArvoEvCrud Path Noun
|
| ArvoEvCrud Path Noun
|
||||||
| ArvoEvVeer Atom Noun
|
| ArvoEvVeer Atom Noun
|
||||||
@ -350,6 +357,7 @@ instance FromNoun Ev where
|
|||||||
ReOrg "vane" s t p v -> fmap EvVane $ parseNoun $ toNoun (s,t,p,v)
|
ReOrg "vane" s t p v -> fmap EvVane $ parseNoun $ toNoun (s,t,p,v)
|
||||||
ReOrg _ _ _ _ _ -> fail "First path-elem must be ?($ %vane)"
|
ReOrg _ _ _ _ _ -> fail "First path-elem must be ?($ %vane)"
|
||||||
|
|
||||||
|
|
||||||
-- Short Event Names -----------------------------------------------------------
|
-- Short Event Names -----------------------------------------------------------
|
||||||
|
|
||||||
{-
|
{-
|
||||||
@ -374,3 +382,10 @@ getSpinnerNameForEvent = \case
|
|||||||
where
|
where
|
||||||
isRet (TermEvBelt _ (Ret ())) = True
|
isRet (TermEvBelt _ (Ret ())) = True
|
||||||
isRet _ = False
|
isRet _ = False
|
||||||
|
|
||||||
|
summarizeEvent :: Ev -> Text
|
||||||
|
summarizeEvent ev =
|
||||||
|
fromNoun (toNoun ev) & \case
|
||||||
|
Nothing -> "//invalid %event"
|
||||||
|
Just (pax :: [Cord], tag :: Cord, val :: Noun) ->
|
||||||
|
"/" <> intercalate "/" (unCord <$> pax) <> " %" <> unCord tag
|
||||||
|
@ -4,7 +4,14 @@
|
|||||||
ships. Do it or strip it out.
|
ships. Do it or strip it out.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.King.API (King(..), kingAPI, readPortsFile) where
|
module Urbit.King.API
|
||||||
|
( King(..)
|
||||||
|
, TermConn
|
||||||
|
, TermConnAPI
|
||||||
|
, kingAPI
|
||||||
|
, readPortsFile
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
@ -12,7 +19,7 @@ import Urbit.Prelude
|
|||||||
import Network.Socket (Socket)
|
import Network.Socket (Socket)
|
||||||
import Prelude (read)
|
import Prelude (read)
|
||||||
import Urbit.Arvo (Belt)
|
import Urbit.Arvo (Belt)
|
||||||
import Urbit.King.App (HasConfigDir(..))
|
import Urbit.King.App (HasPierPath(..))
|
||||||
|
|
||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
import qualified Network.Wai as W
|
import qualified Network.Wai as W
|
||||||
@ -43,16 +50,16 @@ data King = King
|
|||||||
{-|
|
{-|
|
||||||
Get the filepath of the urbit config directory and the ports file.
|
Get the filepath of the urbit config directory and the ports file.
|
||||||
-}
|
-}
|
||||||
portsFilePath :: HasConfigDir e => RIO e (FilePath, FilePath)
|
portsFilePath :: HasPierPath e => RIO e (FilePath, FilePath)
|
||||||
portsFilePath = do
|
portsFilePath = do
|
||||||
dir <- view configDirL
|
dir <- view pierPathL
|
||||||
fil <- pure (dir </> ".king.ports")
|
fil <- pure (dir </> ".king.ports")
|
||||||
pure (dir, fil)
|
pure (dir, fil)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Write the ports file.
|
Write the ports file.
|
||||||
-}
|
-}
|
||||||
portsFile :: HasConfigDir e => Word -> RAcquire e (FilePath, FilePath)
|
portsFile :: HasPierPath e => Word -> RAcquire e (FilePath, FilePath)
|
||||||
portsFile por =
|
portsFile por =
|
||||||
mkRAcquire mkFile (removeFile . snd)
|
mkRAcquire mkFile (removeFile . snd)
|
||||||
where
|
where
|
||||||
@ -65,7 +72,7 @@ portsFile por =
|
|||||||
{-|
|
{-|
|
||||||
Get the HTTP port for the running Urbit daemon.
|
Get the HTTP port for the running Urbit daemon.
|
||||||
-}
|
-}
|
||||||
readPortsFile :: HasConfigDir e => RIO e (Maybe Word)
|
readPortsFile :: HasPierPath e => RIO e (Maybe Word)
|
||||||
readPortsFile = do
|
readPortsFile = do
|
||||||
(_, fil) <- portsFilePath
|
(_, fil) <- portsFilePath
|
||||||
bs <- readFile fil
|
bs <- readFile fil
|
||||||
@ -86,7 +93,7 @@ kingServer is =
|
|||||||
{-|
|
{-|
|
||||||
Start the HTTP server and write to the ports file.
|
Start the HTTP server and write to the ports file.
|
||||||
-}
|
-}
|
||||||
kingAPI :: (HasConfigDir e, HasLogFunc e)
|
kingAPI :: (HasPierPath e, HasLogFunc e)
|
||||||
=> RAcquire e King
|
=> RAcquire e King
|
||||||
kingAPI = do
|
kingAPI = do
|
||||||
(port, sock) <- io $ W.openFreePort
|
(port, sock) <- io $ W.openFreePort
|
||||||
|
@ -2,139 +2,192 @@
|
|||||||
Code for setting up the RIO environment.
|
Code for setting up the RIO environment.
|
||||||
-}
|
-}
|
||||||
module Urbit.King.App
|
module Urbit.King.App
|
||||||
( App
|
( KingEnv
|
||||||
, runApp
|
, runKingEnvStderr
|
||||||
, runAppLogFile
|
, runKingEnvLogFile
|
||||||
, runAppNoLog
|
, runKingEnvNoLog
|
||||||
, runPierApp
|
, kingEnvKillSignal
|
||||||
, HasConfigDir(..)
|
, killKingActionL
|
||||||
, HasStderrLogFunc(..)
|
, onKillKingSigL
|
||||||
) where
|
, PierEnv
|
||||||
|
, runPierEnv
|
||||||
|
, killPierActionL
|
||||||
|
, onKillPierSigL
|
||||||
|
, HasStderrLogFunc(..)
|
||||||
|
, HasKingId(..)
|
||||||
|
, HasProcId(..)
|
||||||
|
, HasKingEnv(..)
|
||||||
|
, HasPierEnv(..)
|
||||||
|
, module Urbit.King.Config
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.King.Config
|
import Urbit.King.Config
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
|
||||||
import System.Directory (createDirectoryIfMissing, getHomeDirectory)
|
import System.Directory (createDirectoryIfMissing, getHomeDirectory)
|
||||||
|
import System.Posix.Internals (c_getpid)
|
||||||
|
import System.Posix.Types (CPid(..))
|
||||||
|
import System.Random (randomIO)
|
||||||
|
import Urbit.King.App.Class (HasStderrLogFunc(..))
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class HasConfigDir a where
|
-- KingEnv ---------------------------------------------------------------------
|
||||||
configDirL ∷ Lens' a FilePath
|
|
||||||
|
|
||||||
class HasStderrLogFunc a where
|
class HasKingId a where
|
||||||
stderrLogFuncL :: Lens' a LogFunc
|
kingIdL :: Lens' a Word16
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
class HasProcId a where
|
||||||
|
procIdL :: Lens' a Int32
|
||||||
|
|
||||||
data App = App
|
class (HasLogFunc a, HasStderrLogFunc a, HasKingId a, HasProcId a)
|
||||||
{ _appLogFunc :: !LogFunc
|
=> HasKingEnv a
|
||||||
, _appStderrLogFunc :: !LogFunc
|
where
|
||||||
}
|
kingEnvL :: Lens' a KingEnv
|
||||||
|
|
||||||
makeLenses ''App
|
data KingEnv = KingEnv
|
||||||
|
{ _kingEnvLogFunc :: !LogFunc
|
||||||
|
, _kingEnvStderrLogFunc :: !LogFunc
|
||||||
|
, _kingEnvKingId :: !Word16
|
||||||
|
, _kingEnvProcId :: !Int32
|
||||||
|
, _kingEnvKillSignal :: !(TMVar ())
|
||||||
|
}
|
||||||
|
|
||||||
instance HasLogFunc App where
|
makeLenses ''KingEnv
|
||||||
logFuncL = appLogFunc
|
|
||||||
|
|
||||||
instance HasStderrLogFunc App where
|
instance HasKingEnv KingEnv where
|
||||||
stderrLogFuncL = appStderrLogFunc
|
kingEnvL = id
|
||||||
|
|
||||||
runApp :: RIO App a -> IO a
|
instance HasLogFunc KingEnv where
|
||||||
runApp inner = do
|
logFuncL = kingEnvLogFunc
|
||||||
logOptions <- logOptionsHandle stderr True
|
|
||||||
<&> setLogUseTime True
|
|
||||||
<&> setLogUseLoc False
|
|
||||||
|
|
||||||
withLogFunc logOptions $ \logFunc ->
|
instance HasStderrLogFunc KingEnv where
|
||||||
runRIO (App logFunc logFunc) inner
|
stderrLogFuncL = kingEnvStderrLogFunc
|
||||||
|
|
||||||
runAppLogFile :: RIO App a -> IO a
|
instance HasProcId KingEnv where
|
||||||
runAppLogFile inner =
|
procIdL = kingEnvProcId
|
||||||
withLogFileHandle $ \h -> do
|
|
||||||
logOptions <- logOptionsHandle h True
|
|
||||||
<&> setLogUseTime True
|
|
||||||
<&> setLogUseLoc False
|
|
||||||
stderrLogOptions <- logOptionsHandle stderr True
|
|
||||||
<&> setLogUseTime False
|
|
||||||
<&> setLogUseLoc False
|
|
||||||
|
|
||||||
withLogFunc stderrLogOptions $ \stderrLogFunc ->
|
instance HasKingId KingEnv where
|
||||||
withLogFunc logOptions $ \logFunc ->
|
kingIdL = kingEnvKingId
|
||||||
runRIO (App logFunc stderrLogFunc) inner
|
|
||||||
|
|
||||||
|
-- Running KingEnvs ------------------------------------------------------------
|
||||||
|
|
||||||
|
runKingEnvStderr :: Bool -> RIO KingEnv a -> IO a
|
||||||
|
runKingEnvStderr verb inner = do
|
||||||
|
logOptions <-
|
||||||
|
logOptionsHandle stderr verb <&> setLogUseTime True <&> setLogUseLoc False
|
||||||
|
|
||||||
|
withLogFunc logOptions $ \logFunc -> runKingEnv logFunc logFunc inner
|
||||||
|
|
||||||
|
runKingEnvLogFile :: Bool -> RIO KingEnv a -> IO a
|
||||||
|
runKingEnvLogFile verb inner = withLogFileHandle $ \h -> do
|
||||||
|
logOptions <-
|
||||||
|
logOptionsHandle h verb <&> setLogUseTime True <&> setLogUseLoc False
|
||||||
|
stderrLogOptions <-
|
||||||
|
logOptionsHandle stderr verb <&> setLogUseTime False <&> setLogUseLoc False
|
||||||
|
|
||||||
|
withLogFunc stderrLogOptions $ \stderrLogFunc -> withLogFunc logOptions
|
||||||
|
$ \logFunc -> runKingEnv logFunc stderrLogFunc inner
|
||||||
|
|
||||||
withLogFileHandle :: (Handle -> IO a) -> IO a
|
withLogFileHandle :: (Handle -> IO a) -> IO a
|
||||||
withLogFileHandle act = do
|
withLogFileHandle act = do
|
||||||
home <- getHomeDirectory
|
home <- getHomeDirectory
|
||||||
let logDir = home </> ".urbit"
|
let logDir = home </> ".urbit"
|
||||||
createDirectoryIfMissing True logDir
|
createDirectoryIfMissing True logDir
|
||||||
withFile (logDir </> "king.log") AppendMode $ \handle -> do
|
withFile (logDir </> "king.log") AppendMode $ \handle -> do
|
||||||
hSetBuffering handle LineBuffering
|
hSetBuffering handle LineBuffering
|
||||||
act handle
|
act handle
|
||||||
|
|
||||||
runAppNoLog :: RIO App a -> IO a
|
runKingEnvNoLog :: RIO KingEnv a -> IO a
|
||||||
runAppNoLog act =
|
runKingEnvNoLog act = withFile "/dev/null" AppendMode $ \handle -> do
|
||||||
withFile "/dev/null" AppendMode $ \handle -> do
|
logOptions <- logOptionsHandle handle True
|
||||||
logOptions <- logOptionsHandle handle True
|
withLogFunc logOptions $ \logFunc -> runKingEnv logFunc logFunc act
|
||||||
withLogFunc logOptions $ \logFunc ->
|
|
||||||
runRIO (App logFunc logFunc) act
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
runKingEnv :: LogFunc -> LogFunc -> RIO KingEnv a -> IO a
|
||||||
|
runKingEnv logFunc stderr action = do
|
||||||
|
kid <- randomIO
|
||||||
|
CPid pid <- c_getpid
|
||||||
|
kil <- newEmptyTMVarIO
|
||||||
|
runRIO (KingEnv logFunc stderr kid pid kil) action
|
||||||
|
|
||||||
-- | A PierApp is like an App, except that it also provides a PierConfig
|
|
||||||
data PierApp = PierApp
|
|
||||||
{ _pierAppLogFunc :: !LogFunc
|
|
||||||
, _pierAppStderrLogFunc :: !LogFunc
|
|
||||||
, _pierAppPierConfig :: !PierConfig
|
|
||||||
, _pierAppNetworkConfig :: !NetworkConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
makeLenses ''PierApp
|
-- KingEnv Utils ---------------------------------------------------------------
|
||||||
|
|
||||||
instance HasStderrLogFunc PierApp where
|
onKillKingSigL :: HasKingEnv e => Getter e (STM ())
|
||||||
stderrLogFuncL = pierAppStderrLogFunc
|
onKillKingSigL = kingEnvL . kingEnvKillSignal . to readTMVar
|
||||||
|
|
||||||
instance HasLogFunc PierApp where
|
killKingActionL :: HasKingEnv e => Getter e (STM ())
|
||||||
logFuncL = pierAppLogFunc
|
killKingActionL =
|
||||||
|
kingEnvL . kingEnvKillSignal . to (\kil -> void (tryPutTMVar kil ()))
|
||||||
|
|
||||||
instance HasPierConfig PierApp where
|
|
||||||
pierConfigL = pierAppPierConfig
|
|
||||||
|
|
||||||
instance HasNetworkConfig PierApp where
|
-- PierEnv ---------------------------------------------------------------------
|
||||||
networkConfigL = pierAppNetworkConfig
|
|
||||||
|
|
||||||
instance HasConfigDir PierApp where
|
class (HasKingEnv a, HasPierConfig a, HasNetworkConfig a) => HasPierEnv a where
|
||||||
configDirL = pierAppPierConfig . pcPierPath
|
pierEnvL :: Lens' a PierEnv
|
||||||
|
|
||||||
runPierApp :: PierConfig -> NetworkConfig -> Bool -> RIO PierApp a -> IO a
|
data PierEnv = PierEnv
|
||||||
runPierApp pierConfig networkConfig daemon inner =
|
{ _pierEnvKingEnv :: !KingEnv
|
||||||
if daemon
|
, _pierEnvPierConfig :: !PierConfig
|
||||||
then execStderr
|
, _pierEnvNetworkConfig :: !NetworkConfig
|
||||||
else withLogFileHandle execFile
|
, _pierEnvKillSignal :: !(TMVar ())
|
||||||
where
|
}
|
||||||
execStderr = do
|
|
||||||
logOptions <- logOptionsHandle stderr True
|
|
||||||
<&> setLogUseTime True
|
|
||||||
<&> setLogUseLoc False
|
|
||||||
|
|
||||||
withLogFunc logOptions $ \logFunc ->
|
makeLenses ''PierEnv
|
||||||
go $ PierApp { _pierAppLogFunc = logFunc
|
|
||||||
, _pierAppStderrLogFunc = logFunc
|
|
||||||
, _pierAppPierConfig = pierConfig
|
|
||||||
, _pierAppNetworkConfig = networkConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
execFile logHandle = do
|
instance HasKingEnv PierEnv where
|
||||||
logOptions <- logOptionsHandle logHandle True
|
kingEnvL = pierEnvKingEnv
|
||||||
<&> setLogUseTime True
|
|
||||||
<&> setLogUseLoc False
|
instance HasPierEnv PierEnv where
|
||||||
logStderrOptions <- logOptionsHandle stderr True
|
pierEnvL = id
|
||||||
<&> setLogUseTime False
|
|
||||||
<&> setLogUseLoc False
|
instance HasKingId PierEnv where
|
||||||
withLogFunc logStderrOptions $ \logStderr ->
|
kingIdL = kingEnvL . kingEnvKingId
|
||||||
withLogFunc logOptions $ \logFunc ->
|
|
||||||
go $ PierApp { _pierAppLogFunc = logFunc
|
instance HasStderrLogFunc PierEnv where
|
||||||
, _pierAppStderrLogFunc = logStderr
|
stderrLogFuncL = kingEnvL . stderrLogFuncL
|
||||||
, _pierAppPierConfig = pierConfig
|
|
||||||
, _pierAppNetworkConfig = networkConfig
|
instance HasLogFunc PierEnv where
|
||||||
}
|
logFuncL = kingEnvL . logFuncL
|
||||||
go app = runRIO app inner
|
|
||||||
|
instance HasPierPath PierEnv where
|
||||||
|
pierPathL = pierEnvPierConfig . pierPathL
|
||||||
|
|
||||||
|
instance HasDryRun PierEnv where
|
||||||
|
dryRunL = pierEnvPierConfig . dryRunL
|
||||||
|
|
||||||
|
instance HasPierConfig PierEnv where
|
||||||
|
pierConfigL = pierEnvPierConfig
|
||||||
|
|
||||||
|
instance HasNetworkConfig PierEnv where
|
||||||
|
networkConfigL = pierEnvNetworkConfig
|
||||||
|
|
||||||
|
instance HasProcId PierEnv where
|
||||||
|
procIdL = kingEnvL . kingEnvProcId
|
||||||
|
|
||||||
|
|
||||||
|
-- PierEnv Utils ---------------------------------------------------------------
|
||||||
|
|
||||||
|
onKillPierSigL :: HasPierEnv e => Getter e (STM ())
|
||||||
|
onKillPierSigL = pierEnvL . pierEnvKillSignal . to readTMVar
|
||||||
|
|
||||||
|
killPierActionL :: HasPierEnv e => Getter e (STM ())
|
||||||
|
killPierActionL =
|
||||||
|
pierEnvL . pierEnvKillSignal . to (\kil -> void (tryPutTMVar kil ()))
|
||||||
|
|
||||||
|
|
||||||
|
-- Running Pier Envs -----------------------------------------------------------
|
||||||
|
|
||||||
|
runPierEnv
|
||||||
|
:: PierConfig -> NetworkConfig -> TMVar () -> RIO PierEnv a -> RIO KingEnv a
|
||||||
|
runPierEnv pierConfig networkConfig vKill action = do
|
||||||
|
app <- ask
|
||||||
|
|
||||||
|
let pierEnv = PierEnv { _pierEnvKingEnv = app
|
||||||
|
, _pierEnvPierConfig = pierConfig
|
||||||
|
, _pierEnvNetworkConfig = networkConfig
|
||||||
|
, _pierEnvKillSignal = vKill
|
||||||
|
}
|
||||||
|
|
||||||
|
io (runRIO pierEnv action)
|
||||||
|
15
pkg/hs/urbit-king/lib/Urbit/King/App/Class.hs
Normal file
15
pkg/hs/urbit-king/lib/Urbit/King/App/Class.hs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{-|
|
||||||
|
Code for setting up the RIO environment.
|
||||||
|
-}
|
||||||
|
module Urbit.King.App.Class
|
||||||
|
( HasStderrLogFunc(..)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
|
||||||
|
-- KingEnv ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
class HasStderrLogFunc a where
|
||||||
|
stderrLogFuncL :: Lens' a LogFunc
|
@ -15,6 +15,12 @@ import System.Environment (getProgName)
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
data KingOpts = KingOpts
|
||||||
|
{ koSharedHttpPort :: Maybe Word16
|
||||||
|
, koSharedHttpsPort :: Maybe Word16
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
data Opts = Opts
|
data Opts = Opts
|
||||||
{ oQuiet :: Bool
|
{ oQuiet :: Bool
|
||||||
, oHashless :: Bool
|
, oHashless :: Bool
|
||||||
@ -23,6 +29,9 @@ data Opts = Opts
|
|||||||
, oDryFrom :: Maybe Word64
|
, oDryFrom :: Maybe Word64
|
||||||
, oVerbose :: Bool
|
, oVerbose :: Bool
|
||||||
, oAmesPort :: Maybe Word16
|
, oAmesPort :: Maybe Word16
|
||||||
|
, oNoAmes :: Bool
|
||||||
|
, oNoHttp :: Bool
|
||||||
|
, oNoHttps :: Bool
|
||||||
, oTrace :: Bool
|
, oTrace :: Bool
|
||||||
, oCollectFx :: Bool
|
, oCollectFx :: Bool
|
||||||
, oLocalhost :: Bool
|
, oLocalhost :: Bool
|
||||||
@ -31,6 +40,7 @@ data Opts = Opts
|
|||||||
, oHttpPort :: Maybe Word16
|
, oHttpPort :: Maybe Word16
|
||||||
, oHttpsPort :: Maybe Word16
|
, oHttpsPort :: Maybe Word16
|
||||||
, oLoopbackPort :: Maybe Word16
|
, oLoopbackPort :: Maybe Word16
|
||||||
|
, oSerfExe :: Maybe Text
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
@ -93,7 +103,7 @@ data Bug
|
|||||||
|
|
||||||
data Cmd
|
data Cmd
|
||||||
= CmdNew New Opts
|
= CmdNew New Opts
|
||||||
| CmdRun Run Opts Bool
|
| CmdRun KingOpts [(Run, Opts, Bool)]
|
||||||
| CmdBug Bug
|
| CmdBug Bug
|
||||||
| CmdCon FilePath
|
| CmdCon FilePath
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
@ -221,6 +231,24 @@ opts = do
|
|||||||
<> help "Ames port"
|
<> help "Ames port"
|
||||||
<> hidden
|
<> hidden
|
||||||
|
|
||||||
|
oNoAmes <-
|
||||||
|
switch
|
||||||
|
$ long "no-ames"
|
||||||
|
<> help "Run with Ames disabled."
|
||||||
|
<> hidden
|
||||||
|
|
||||||
|
oNoHttp <-
|
||||||
|
switch
|
||||||
|
$ long "no-http"
|
||||||
|
<> help "Run with HTTP disabled."
|
||||||
|
<> hidden
|
||||||
|
|
||||||
|
oNoHttps <-
|
||||||
|
switch
|
||||||
|
$ long "no-https"
|
||||||
|
<> help "Run with HTTPS disabled."
|
||||||
|
<> hidden
|
||||||
|
|
||||||
oHttpPort <-
|
oHttpPort <-
|
||||||
optional
|
optional
|
||||||
$ option auto
|
$ option auto
|
||||||
@ -245,13 +273,18 @@ opts = do
|
|||||||
<> help "Localhost-only HTTP port"
|
<> help "Localhost-only HTTP port"
|
||||||
<> hidden
|
<> hidden
|
||||||
|
|
||||||
-- Always disable hashboard. Right now, urbit is almost unusable with this
|
oSerfExe <-
|
||||||
-- flag enabled and it is disabled in vere.
|
optional
|
||||||
let oHashless = True
|
$ strOption
|
||||||
-- oHashless <- switch $ short 'S'
|
$ metavar "PATH"
|
||||||
-- <> long "hashless"
|
<> long "serf"
|
||||||
-- <> help "Disable battery hashing"
|
<> help "Path to Serf"
|
||||||
-- <> hidden
|
<> hidden
|
||||||
|
|
||||||
|
oHashless <- switch $ short 'S'
|
||||||
|
<> long "hashless"
|
||||||
|
<> help "Disable battery hashing (Ignored for now)"
|
||||||
|
<> hidden
|
||||||
|
|
||||||
oQuiet <- switch $ short 'q'
|
oQuiet <- switch $ short 'q'
|
||||||
<> long "quiet"
|
<> long "quiet"
|
||||||
@ -307,15 +340,33 @@ opts = do
|
|||||||
newShip :: Parser Cmd
|
newShip :: Parser Cmd
|
||||||
newShip = CmdNew <$> new <*> opts
|
newShip = CmdNew <$> new <*> opts
|
||||||
|
|
||||||
|
runOneShip :: Parser (Run, Opts, Bool)
|
||||||
|
runOneShip = (,,) <$> fmap Run pierPath <*> opts <*> df
|
||||||
|
where
|
||||||
|
df = switch (short 'd' <> long "daemon" <> help "Daemon mode" <> hidden)
|
||||||
|
|
||||||
|
kingOpts :: Parser KingOpts
|
||||||
|
kingOpts = do
|
||||||
|
koSharedHttpPort <-
|
||||||
|
optional
|
||||||
|
$ option auto
|
||||||
|
$ metavar "PORT"
|
||||||
|
<> long "shared-http-port"
|
||||||
|
<> help "HTTP port"
|
||||||
|
<> hidden
|
||||||
|
|
||||||
|
koSharedHttpsPort <-
|
||||||
|
optional
|
||||||
|
$ option auto
|
||||||
|
$ metavar "PORT"
|
||||||
|
<> long "shared-https-port"
|
||||||
|
<> help "HTTPS port"
|
||||||
|
<> hidden
|
||||||
|
|
||||||
|
pure (KingOpts{..})
|
||||||
|
|
||||||
runShip :: Parser Cmd
|
runShip :: Parser Cmd
|
||||||
runShip = do
|
runShip = CmdRun <$> kingOpts <*> some runOneShip
|
||||||
rPierPath <- pierPath
|
|
||||||
o <- opts
|
|
||||||
daemon <- switch $ short 'd'
|
|
||||||
<> long "daemon"
|
|
||||||
<> help "Daemon mode"
|
|
||||||
<> hidden
|
|
||||||
pure (CmdRun (Run{..}) o daemon)
|
|
||||||
|
|
||||||
valPill :: Parser Bug
|
valPill :: Parser Bug
|
||||||
valPill = do
|
valPill = do
|
||||||
|
@ -1,29 +1,40 @@
|
|||||||
{-|
|
{-|
|
||||||
Pier Configuration
|
Pier Configuration
|
||||||
-}
|
-}
|
||||||
module Urbit.King.Config where
|
module Urbit.King.Config where
|
||||||
|
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
import qualified Urbit.Vere.Serf as Serf
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
All the configuration data revolving around a ship and the current
|
All the configuration data revolving around a ship and the current
|
||||||
execution options.
|
execution options.
|
||||||
-}
|
-}
|
||||||
data PierConfig = PierConfig
|
data PierConfig = PierConfig
|
||||||
{ _pcPierPath :: FilePath
|
{ _pcPierPath :: FilePath
|
||||||
, _pcDryRun :: Bool
|
, _pcDryRun :: Bool
|
||||||
} deriving (Show)
|
, _pcSerfExe :: Text
|
||||||
|
, _pcSerfFlags :: [Serf.Flag]
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
makeLenses ''PierConfig
|
makeLenses ''PierConfig
|
||||||
|
|
||||||
class HasPierConfig env where
|
class HasPierPath a where
|
||||||
pierConfigL :: Lens' env PierConfig
|
pierPathL :: Lens' a FilePath
|
||||||
|
|
||||||
pierPathL ∷ HasPierConfig a => Lens' a FilePath
|
class HasDryRun a where
|
||||||
pierPathL = pierConfigL . pcPierPath
|
dryRunL :: Lens' a Bool
|
||||||
|
|
||||||
|
class (HasPierPath a, HasDryRun a) => HasPierConfig a where
|
||||||
|
pierConfigL :: Lens' a PierConfig
|
||||||
|
|
||||||
|
instance HasPierPath PierConfig where
|
||||||
|
pierPathL = pcPierPath
|
||||||
|
|
||||||
|
instance HasDryRun PierConfig where
|
||||||
|
dryRunL = pcDryRun
|
||||||
|
|
||||||
dryRunL :: HasPierConfig a => Lens' a Bool
|
|
||||||
dryRunL = pierConfigL . pcDryRun
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -36,6 +47,9 @@ data NetMode
|
|||||||
data NetworkConfig = NetworkConfig
|
data NetworkConfig = NetworkConfig
|
||||||
{ _ncNetMode :: NetMode
|
{ _ncNetMode :: NetMode
|
||||||
, _ncAmesPort :: Maybe Word16
|
, _ncAmesPort :: Maybe Word16
|
||||||
|
, _ncNoAmes :: Bool
|
||||||
|
, _ncNoHttp :: Bool
|
||||||
|
, _ncNoHttps :: Bool
|
||||||
, _ncHttpPort :: Maybe Word16
|
, _ncHttpPort :: Maybe Word16
|
||||||
, _ncHttpsPort :: Maybe Word16
|
, _ncHttpsPort :: Maybe Word16
|
||||||
, _ncLocalPort :: Maybe Word16
|
, _ncLocalPort :: Maybe Word16
|
||||||
|
@ -10,14 +10,15 @@ import Urbit.Prelude
|
|||||||
|
|
||||||
import Data.Conduit
|
import Data.Conduit
|
||||||
import Urbit.Arvo
|
import Urbit.Arvo
|
||||||
import Urbit.Time
|
import Urbit.Noun.Time
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Monad.Trans.Maybe (MaybeT(..))
|
import Control.Monad.Trans.Maybe (MaybeT(..))
|
||||||
import Urbit.Vere.Log (EventLog)
|
import Urbit.EventLog.LMDB (EventLog)
|
||||||
|
|
||||||
import qualified Data.Conduit.Combinators as C
|
import qualified Data.Conduit.Combinators as C
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ run log = do
|
|||||||
hSetEcho stdin False
|
hSetEcho stdin False
|
||||||
logInfo $ displayShow (Log.identity log)
|
logInfo $ displayShow (Log.identity log)
|
||||||
let cycle = fromIntegral $ lifecycleLen $ Log.identity log
|
let cycle = fromIntegral $ lifecycleLen $ Log.identity log
|
||||||
las <- Log.lastEv log
|
las <- atomically (Log.lastEv log)
|
||||||
loop cycle las las
|
loop cycle las las
|
||||||
where
|
where
|
||||||
failRead cur =
|
failRead cur =
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
{-|
|
{- |
|
||||||
King Haskell Entry Point
|
# Signal Handling (SIGTERM, SIGINT)
|
||||||
|
|
||||||
|
We handle SIGTERM by causing the main thread to raise a `UserInterrupt`
|
||||||
|
exception. This is the same behavior as SIGINT (the signal sent upon
|
||||||
|
`CTRL-C`).
|
||||||
|
|
||||||
|
The main thread is therefore responsible for handling this exception
|
||||||
|
and causing everything to shut down properly.
|
||||||
|
|
||||||
|
# Crashing and Shutting Down
|
||||||
|
|
||||||
|
Rule number one: The King never crashes.
|
||||||
|
|
||||||
|
This rule is asperational at the moment, but it needs to become as
|
||||||
|
close to truth as possible. Shut down ships in extreme cases, but
|
||||||
|
never let the king go down.
|
||||||
|
-}
|
||||||
|
|
||||||
|
{-
|
||||||
|
TODO These some old scribbled notes. They don't belong here
|
||||||
|
anymore. Do something about it.
|
||||||
|
|
||||||
# Event Pruning
|
# Event Pruning
|
||||||
|
|
||||||
@ -62,18 +82,18 @@ import Urbit.Arvo
|
|||||||
import Urbit.King.Config
|
import Urbit.King.Config
|
||||||
import Urbit.Vere.Dawn
|
import Urbit.Vere.Dawn
|
||||||
import Urbit.Vere.Pier
|
import Urbit.Vere.Pier
|
||||||
|
import Urbit.Vere.Eyre.Multi (multiEyre, MultiEyreApi, MultiEyreConf(..))
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
import Urbit.Vere.Serf
|
import Urbit.Vere.Serf
|
||||||
|
import Urbit.King.App
|
||||||
|
|
||||||
import Control.Concurrent (myThreadId)
|
import Control.Concurrent (myThreadId)
|
||||||
import Control.Exception (AsyncException(UserInterrupt))
|
import Control.Exception (AsyncException(UserInterrupt))
|
||||||
import Control.Lens ((&))
|
import Control.Lens ((&))
|
||||||
import System.Process (system)
|
import System.Process (system)
|
||||||
import Text.Show.Pretty (pPrint)
|
import Text.Show.Pretty (pPrint)
|
||||||
import Urbit.King.App (runApp, runAppLogFile, runAppNoLog, runPierApp)
|
|
||||||
import Urbit.King.App (HasConfigDir(..), HasStderrLogFunc(..))
|
|
||||||
import Urbit.Noun.Conversions (cordToUW)
|
import Urbit.Noun.Conversions (cordToUW)
|
||||||
import Urbit.Time (Wen)
|
import Urbit.Noun.Time (Wen)
|
||||||
import Urbit.Vere.LockFile (lockFile)
|
import Urbit.Vere.LockFile (lockFile)
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
@ -82,18 +102,14 @@ import qualified Network.HTTP.Client as C
|
|||||||
import qualified System.Posix.Signals as Sys
|
import qualified System.Posix.Signals as Sys
|
||||||
import qualified System.ProgressBar as PB
|
import qualified System.ProgressBar as PB
|
||||||
import qualified System.Random as Sys
|
import qualified System.Random as Sys
|
||||||
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
import qualified Urbit.King.CLI as CLI
|
import qualified Urbit.King.CLI as CLI
|
||||||
import qualified Urbit.King.EventBrowser as EventBrowser
|
import qualified Urbit.King.EventBrowser as EventBrowser
|
||||||
import qualified Urbit.Ob as Ob
|
import qualified Urbit.Ob as Ob
|
||||||
import qualified Urbit.Vere.Log as Log
|
|
||||||
import qualified Urbit.Vere.Pier as Pier
|
import qualified Urbit.Vere.Pier as Pier
|
||||||
import qualified Urbit.Vere.Serf as Serf
|
import qualified Urbit.Vere.Serf as Serf
|
||||||
import qualified Urbit.Vere.Term as Term
|
import qualified Urbit.Vere.Term as Term
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
zod :: Ship
|
|
||||||
zod = 0
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -103,28 +119,33 @@ removeFileIfExists pax = do
|
|||||||
when exists $ do
|
when exists $ do
|
||||||
removeFile pax
|
removeFile pax
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
toSerfFlags :: CLI.Opts -> Serf.Flags
|
-- Compile CLI Flags to Pier Configuration -------------------------------------
|
||||||
|
|
||||||
|
{-
|
||||||
|
TODO: This is not all of the flags.
|
||||||
|
Urbit is basically useless with hashboard, so we ignore that flag.
|
||||||
|
-}
|
||||||
|
toSerfFlags :: CLI.Opts -> [Serf.Flag]
|
||||||
toSerfFlags CLI.Opts{..} = catMaybes m
|
toSerfFlags CLI.Opts{..} = catMaybes m
|
||||||
where
|
where
|
||||||
-- TODO: This is not all the flags.
|
m = [ setFrom oQuiet Serf.Quiet
|
||||||
m = [ from oQuiet Serf.Quiet
|
, setFrom oTrace Serf.Trace
|
||||||
, from oTrace Serf.Trace
|
, setFrom (oHashless || True) Serf.Hashless
|
||||||
, from oHashless Serf.Hashless
|
, setFrom oQuiet Serf.Quiet
|
||||||
, from oQuiet Serf.Quiet
|
, setFrom oVerbose Serf.Verbose
|
||||||
, from oVerbose Serf.Verbose
|
, setFrom (oDryRun || isJust oDryFrom) Serf.DryRun
|
||||||
, from (oDryRun || isJust oDryFrom) Serf.DryRun
|
|
||||||
]
|
]
|
||||||
from True flag = Just flag
|
setFrom True flag = Just flag
|
||||||
from False _ = Nothing
|
setFrom False _ = Nothing
|
||||||
|
|
||||||
|
|
||||||
toPierConfig :: FilePath -> CLI.Opts -> PierConfig
|
toPierConfig :: FilePath -> CLI.Opts -> PierConfig
|
||||||
toPierConfig pierPath CLI.Opts {..} = PierConfig { .. }
|
toPierConfig pierPath o@(CLI.Opts{..}) = PierConfig { .. }
|
||||||
where
|
where
|
||||||
_pcPierPath = pierPath
|
_pcPierPath = pierPath
|
||||||
_pcDryRun = oDryRun || isJust oDryFrom
|
_pcDryRun = oDryRun || isJust oDryFrom
|
||||||
|
_pcSerfExe = fromMaybe "urbit-worker" oSerfExe
|
||||||
|
_pcSerfFlags = toSerfFlags o
|
||||||
|
|
||||||
toNetworkConfig :: CLI.Opts -> NetworkConfig
|
toNetworkConfig :: CLI.Opts -> NetworkConfig
|
||||||
toNetworkConfig CLI.Opts {..} = NetworkConfig { .. }
|
toNetworkConfig CLI.Opts {..} = NetworkConfig { .. }
|
||||||
@ -143,157 +164,187 @@ toNetworkConfig CLI.Opts {..} = NetworkConfig { .. }
|
|||||||
_ncHttpPort = oHttpPort
|
_ncHttpPort = oHttpPort
|
||||||
_ncHttpsPort = oHttpsPort
|
_ncHttpsPort = oHttpsPort
|
||||||
_ncLocalPort = oLoopbackPort
|
_ncLocalPort = oLoopbackPort
|
||||||
|
_ncNoAmes = oNoAmes
|
||||||
|
_ncNoHttp = oNoHttp
|
||||||
|
_ncNoHttps = oNoHttps
|
||||||
|
|
||||||
tryBootFromPill :: ( HasLogFunc e, HasNetworkConfig e, HasPierConfig e
|
logStderr :: HasStderrLogFunc e => RIO LogFunc a -> RIO e a
|
||||||
, HasConfigDir e, HasStderrLogFunc e
|
logStderr action = do
|
||||||
)
|
logFunc <- view stderrLogFuncL
|
||||||
=> Bool -> Pill -> Bool -> Serf.Flags -> Ship
|
runRIO logFunc action
|
||||||
-> LegacyBootEvent
|
|
||||||
-> RIO e ()
|
|
||||||
tryBootFromPill oExit pill lite flags ship boot = do
|
|
||||||
mStart <- newEmptyMVar
|
|
||||||
runOrExitImmediately bootedPier oExit mStart
|
|
||||||
where
|
|
||||||
bootedPier = do
|
|
||||||
view pierPathL >>= lockFile
|
|
||||||
rio $ logTrace "Starting boot"
|
|
||||||
sls <- Pier.booted pill lite flags ship boot
|
|
||||||
rio $ logTrace "Completed boot"
|
|
||||||
pure sls
|
|
||||||
|
|
||||||
runOrExitImmediately :: ( HasLogFunc e, HasNetworkConfig e, HasPierConfig e
|
logSlogs :: HasStderrLogFunc e => RIO e (TVar (Text -> IO ()))
|
||||||
, HasConfigDir e
|
logSlogs = logStderr $ do
|
||||||
)
|
env <- ask
|
||||||
=> RAcquire e (Serf e, Log.EventLog, SerfState)
|
newTVarIO (runRIO env . logOther "serf" . display . T.strip)
|
||||||
-> Bool
|
|
||||||
-> MVar ()
|
|
||||||
-> RIO e ()
|
|
||||||
runOrExitImmediately getPier oExit mStart =
|
|
||||||
rwith getPier $ if oExit then shutdownImmediately else runPier
|
|
||||||
where
|
|
||||||
shutdownImmediately (serf, log, ss) = do
|
|
||||||
logTrace "Sending shutdown signal"
|
|
||||||
logTrace $ displayShow ss
|
|
||||||
|
|
||||||
-- Why is this here? Do I need to force a snapshot to happen?
|
tryBootFromPill
|
||||||
io $ threadDelay 500000
|
:: Bool
|
||||||
|
-> Pill
|
||||||
|
-> Bool
|
||||||
|
-> Ship
|
||||||
|
-> LegacyBootEvent
|
||||||
|
-> MultiEyreApi
|
||||||
|
-> RIO PierEnv ()
|
||||||
|
tryBootFromPill oExit pill lite ship boot multi = do
|
||||||
|
mStart <- newEmptyMVar
|
||||||
|
vSlog <- logSlogs
|
||||||
|
runOrExitImmediately vSlog (bootedPier vSlog) oExit mStart multi
|
||||||
|
where
|
||||||
|
bootedPier vSlog = do
|
||||||
|
view pierPathL >>= lockFile
|
||||||
|
rio $ logDebug "Starting boot"
|
||||||
|
sls <- Pier.booted vSlog pill lite ship boot
|
||||||
|
rio $ logDebug "Completed boot"
|
||||||
|
pure sls
|
||||||
|
|
||||||
ss <- shutdown serf 0
|
runOrExitImmediately
|
||||||
logTrace $ displayShow ss
|
:: TVar (Text -> IO ())
|
||||||
logTrace "Shutdown!"
|
-> RAcquire PierEnv (Serf, Log.EventLog)
|
||||||
|
-> Bool
|
||||||
|
-> MVar ()
|
||||||
|
-> MultiEyreApi
|
||||||
|
-> RIO PierEnv ()
|
||||||
|
runOrExitImmediately vSlog getPier oExit mStart multi = do
|
||||||
|
rwith getPier (if oExit then shutdownImmediately else runPier)
|
||||||
|
where
|
||||||
|
shutdownImmediately :: (Serf, Log.EventLog) -> RIO PierEnv ()
|
||||||
|
shutdownImmediately (serf, log) = do
|
||||||
|
logDebug "Sending shutdown signal"
|
||||||
|
Serf.stop serf
|
||||||
|
logDebug "Shutdown!"
|
||||||
|
|
||||||
runPier sls = do
|
runPier :: (Serf, Log.EventLog) -> RIO PierEnv ()
|
||||||
runRAcquire $ Pier.pier sls mStart
|
runPier serfLog = do
|
||||||
|
runRAcquire (Pier.pier serfLog vSlog mStart multi)
|
||||||
|
|
||||||
tryPlayShip :: ( HasStderrLogFunc e, HasLogFunc e, HasNetworkConfig e
|
tryPlayShip
|
||||||
, HasPierConfig e, HasConfigDir e
|
:: Bool
|
||||||
)
|
-> Bool
|
||||||
=> Bool -> Bool -> Maybe Word64 -> Serf.Flags -> MVar () -> RIO e ()
|
-> Maybe Word64
|
||||||
tryPlayShip exitImmediately fullReplay playFrom flags mStart = do
|
-> MVar ()
|
||||||
when fullReplay wipeSnapshot
|
-> MultiEyreApi
|
||||||
runOrExitImmediately resumeShip exitImmediately mStart
|
-> RIO PierEnv ()
|
||||||
where
|
tryPlayShip exitImmediately fullReplay playFrom mStart multi = do
|
||||||
wipeSnapshot = do
|
when fullReplay wipeSnapshot
|
||||||
shipPath <- view pierPathL
|
vSlog <- logSlogs
|
||||||
logTrace "wipeSnapshot"
|
runOrExitImmediately vSlog (resumeShip vSlog) exitImmediately mStart multi
|
||||||
logDebug $ display $ pack @Text ("Wiping " <> north shipPath)
|
where
|
||||||
logDebug $ display $ pack @Text ("Wiping " <> south shipPath)
|
wipeSnapshot = do
|
||||||
removeFileIfExists (north shipPath)
|
shipPath <- view pierPathL
|
||||||
removeFileIfExists (south shipPath)
|
logDebug "wipeSnapshot"
|
||||||
|
logDebug $ display $ pack @Text ("Wiping " <> north shipPath)
|
||||||
|
logDebug $ display $ pack @Text ("Wiping " <> south shipPath)
|
||||||
|
removeFileIfExists (north shipPath)
|
||||||
|
removeFileIfExists (south shipPath)
|
||||||
|
|
||||||
north shipPath = shipPath <> "/.urb/chk/north.bin"
|
north shipPath = shipPath <> "/.urb/chk/north.bin"
|
||||||
south shipPath = shipPath <> "/.urb/chk/south.bin"
|
south shipPath = shipPath <> "/.urb/chk/south.bin"
|
||||||
|
|
||||||
resumeShip = do
|
resumeShip :: TVar (Text -> IO ()) -> RAcquire PierEnv (Serf, Log.EventLog)
|
||||||
view pierPathL >>= lockFile
|
resumeShip vSlog = do
|
||||||
rio $ logTrace "RESUMING SHIP"
|
view pierPathL >>= lockFile
|
||||||
sls <- Pier.resumed playFrom flags
|
rio $ logDebug "RESUMING SHIP"
|
||||||
rio $ logTrace "SHIP RESUMED"
|
sls <- Pier.resumed vSlog playFrom
|
||||||
pure sls
|
rio $ logDebug "SHIP RESUMED"
|
||||||
|
pure sls
|
||||||
|
|
||||||
runRAcquire :: (MonadUnliftIO (m e), MonadIO (m e), MonadReader e (m e))
|
runRAcquire :: (MonadUnliftIO (m e), MonadIO (m e), MonadReader e (m e))
|
||||||
=> RAcquire e a -> m e a
|
=> RAcquire e a -> m e a
|
||||||
runRAcquire act = rwith act pure
|
runRAcquire act = rwith act pure
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
checkEvs :: forall e. HasLogFunc e => FilePath -> Word64 -> Word64 -> RIO e ()
|
checkEvs :: FilePath -> Word64 -> Word64 -> RIO KingEnv ()
|
||||||
checkEvs pierPath first last = do
|
checkEvs pierPath first last = do
|
||||||
rwith (Log.existing logPath) $ \log -> do
|
rwith (Log.existing logPath) $ \log -> do
|
||||||
let ident = Log.identity log
|
let ident = Log.identity log
|
||||||
let pbSty = PB.defStyle { PB.stylePostfix = PB.exact }
|
let pbSty = PB.defStyle { PB.stylePostfix = PB.exact }
|
||||||
logTrace (displayShow ident)
|
logDebug (displayShow ident)
|
||||||
|
|
||||||
last <- Log.lastEv log <&> \lastReal -> min last lastReal
|
last <- atomically $ Log.lastEv log <&> \lastReal -> min last lastReal
|
||||||
|
|
||||||
let evCount = fromIntegral (last - first)
|
let evCount = fromIntegral (last - first)
|
||||||
|
|
||||||
pb <- PB.newProgressBar pbSty 10 (PB.Progress 1 evCount ())
|
pb <- PB.newProgressBar pbSty 10 (PB.Progress 1 evCount ())
|
||||||
|
|
||||||
runConduit $ Log.streamEvents log first
|
runConduit $ Log.streamEvents log first .| showEvents
|
||||||
.| showEvents pb first (fromIntegral $ lifecycleLen ident)
|
pb
|
||||||
where
|
first
|
||||||
logPath :: FilePath
|
(fromIntegral $ lifecycleLen ident)
|
||||||
logPath = pierPath <> "/.urb/log"
|
where
|
||||||
|
logPath :: FilePath
|
||||||
|
logPath = pierPath <> "/.urb/log"
|
||||||
|
|
||||||
showEvents :: PB.ProgressBar () -> EventId -> EventId
|
showEvents
|
||||||
-> ConduitT ByteString Void (RIO e) ()
|
:: PB.ProgressBar ()
|
||||||
showEvents pb eId _ | eId > last = pure ()
|
-> EventId
|
||||||
showEvents pb eId cycle = await >>= \case
|
-> EventId
|
||||||
Nothing -> do
|
-> ConduitT ByteString Void (RIO KingEnv) ()
|
||||||
lift $ PB.killProgressBar pb
|
showEvents pb eId _ | eId > last = pure ()
|
||||||
lift $ logTrace "Everything checks out."
|
showEvents pb eId cycle = await >>= \case
|
||||||
Just bs -> do
|
Nothing -> do
|
||||||
lift $ PB.incProgress pb 1
|
lift $ PB.killProgressBar pb
|
||||||
lift $ do
|
lift $ logDebug "Everything checks out."
|
||||||
n <- io $ cueBSExn bs
|
Just bs -> do
|
||||||
when (eId > cycle) $ do
|
lift $ PB.incProgress pb 1
|
||||||
(mug, wen, evNoun) <- unpackJob n
|
lift $ do
|
||||||
fromNounErr evNoun & \case
|
n <- io $ cueBSExn bs
|
||||||
Left err -> logError (displayShow (eId, err))
|
when (eId > cycle) $ do
|
||||||
Right (_ ∷ Ev) -> pure ()
|
(mug, wen, evNoun) <- unpackJob n
|
||||||
showEvents pb (succ eId) cycle
|
fromNounErr evNoun & \case
|
||||||
|
Left err -> logError (displayShow (eId, err))
|
||||||
|
Right (_ :: Ev) -> pure ()
|
||||||
|
showEvents pb (succ eId) cycle
|
||||||
|
|
||||||
|
unpackJob :: Noun -> RIO KingEnv (Mug, Wen, Noun)
|
||||||
|
unpackJob = io . fromNounExn
|
||||||
|
|
||||||
unpackJob :: Noun -> RIO e (Mug, Wen, Noun)
|
|
||||||
unpackJob = io . fromNounExn
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
collectAllFx :: FilePath -> RIO KingEnv ()
|
||||||
|
collectAllFx = error "TODO"
|
||||||
|
|
||||||
|
{-
|
||||||
{-|
|
{-|
|
||||||
This runs the serf at `$top/.tmpdir`, but we disable snapshots,
|
This runs the serf at `$top/.tmpdir`, but we disable snapshots,
|
||||||
so this should never actually be created. We just do this to avoid
|
so this should never actually be created. We just do this to avoid
|
||||||
letting the serf use an existing snapshot.
|
letting the serf use an existing snapshot.
|
||||||
-}
|
-}
|
||||||
collectAllFx :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
collectAllFx :: FilePath -> RIO KingEnv ()
|
||||||
collectAllFx top = do
|
collectAllFx top = do
|
||||||
logTrace $ display $ pack @Text top
|
logDebug $ display $ pack @Text top
|
||||||
rwith collectedFX $ \() ->
|
vSlog <- logSlogs
|
||||||
logTrace "Done collecting effects!"
|
rwith (collectedFX vSlog) $ \() ->
|
||||||
|
logDebug "Done collecting effects!"
|
||||||
where
|
where
|
||||||
tmpDir :: FilePath
|
tmpDir :: FilePath
|
||||||
tmpDir = top </> ".tmpdir"
|
tmpDir = top </> ".tmpdir"
|
||||||
|
|
||||||
collectedFX :: RAcquire e ()
|
collectedFX :: TVar (Text -> IO ()) -> RAcquire KingEnv ()
|
||||||
collectedFX = do
|
collectedFX vSlog = do
|
||||||
lockFile top
|
lockFile top
|
||||||
log <- Log.existing (top <> "/.urb/log")
|
log <- Log.existing (top <> "/.urb/log")
|
||||||
serf <- Serf.run (Serf.Config tmpDir serfFlags)
|
serf <- Pier.runSerf vSlog tmpDir serfFlags
|
||||||
rio $ Serf.collectFX serf log
|
rio $ Serf.collectFX serf log
|
||||||
|
|
||||||
serfFlags :: Serf.Flags
|
serfFlags :: [Serf.Flag]
|
||||||
serfFlags = [Serf.Hashless, Serf.DryRun]
|
serfFlags = [Serf.Hashless, Serf.DryRun]
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
replayPartEvs :: ∀e. (HasStderrLogFunc e, HasLogFunc e)
|
replayPartEvs :: FilePath -> Word64 -> RIO KingEnv ()
|
||||||
=> FilePath -> Word64 -> RIO e ()
|
|
||||||
replayPartEvs top last = do
|
replayPartEvs top last = do
|
||||||
logTrace $ display $ pack @Text top
|
logDebug $ display $ pack @Text top
|
||||||
fetchSnapshot
|
fetchSnapshot
|
||||||
rwith replayedEvs $ \() ->
|
rwith replayedEvs $ \() ->
|
||||||
logTrace "Done replaying events!"
|
logDebug "Done replaying events!"
|
||||||
where
|
where
|
||||||
fetchSnapshot :: RIO e ()
|
fetchSnapshot :: RIO KingEnv ()
|
||||||
fetchSnapshot = do
|
fetchSnapshot = do
|
||||||
snap <- Pier.getSnapshot top last
|
snap <- Pier.getSnapshot top last
|
||||||
case snap of
|
case snap of
|
||||||
@ -305,20 +356,28 @@ replayPartEvs top last = do
|
|||||||
tmpDir :: FilePath
|
tmpDir :: FilePath
|
||||||
tmpDir = top </> ".partial-replay" </> show last
|
tmpDir = top </> ".partial-replay" </> show last
|
||||||
|
|
||||||
replayedEvs :: RAcquire e ()
|
replayedEvs :: RAcquire KingEnv ()
|
||||||
replayedEvs = do
|
replayedEvs = do
|
||||||
lockFile top
|
lockFile top
|
||||||
log <- Log.existing (top <> "/.urb/log")
|
log <- Log.existing (top <> "/.urb/log")
|
||||||
serf <- Serf.run (Serf.Config tmpDir serfFlags)
|
let onSlog = print
|
||||||
|
let onStdr = print
|
||||||
|
let onDead = error "DIED"
|
||||||
|
let config = Serf.Config "urbit-worker" tmpDir serfFlags onSlog onStdr onDead
|
||||||
|
(serf, info) <- io (Serf.start config)
|
||||||
rio $ do
|
rio $ do
|
||||||
ss <- Serf.replay serf log $ Just last
|
eSs <- Serf.execReplay serf log (Just last)
|
||||||
Serf.snapshot serf ss
|
case eSs of
|
||||||
|
Left bail -> error (show bail)
|
||||||
|
Right 0 -> io (Serf.snapshot serf)
|
||||||
|
Right num -> pure ()
|
||||||
io $ threadDelay 500000 -- Copied from runOrExitImmediately
|
io $ threadDelay 500000 -- Copied from runOrExitImmediately
|
||||||
pure ()
|
pure ()
|
||||||
|
|
||||||
serfFlags :: Serf.Flags
|
serfFlags :: [Serf.Flag]
|
||||||
serfFlags = [Serf.Hashless]
|
serfFlags = [Serf.Hashless]
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
@ -326,84 +385,98 @@ replayPartEvs top last = do
|
|||||||
-}
|
-}
|
||||||
testPill :: HasLogFunc e => FilePath -> Bool -> Bool -> RIO e ()
|
testPill :: HasLogFunc e => FilePath -> Bool -> Bool -> RIO e ()
|
||||||
testPill pax showPil showSeq = do
|
testPill pax showPil showSeq = do
|
||||||
putStrLn "Reading pill file."
|
logDebug "Reading pill file."
|
||||||
pillBytes <- readFile pax
|
pillBytes <- readFile pax
|
||||||
|
|
||||||
putStrLn "Cueing pill file."
|
logDebug "Cueing pill file."
|
||||||
pillNoun <- io $ cueBS pillBytes & either throwIO pure
|
pillNoun <- io $ cueBS pillBytes & either throwIO pure
|
||||||
|
|
||||||
putStrLn "Parsing pill file."
|
logDebug "Parsing pill file."
|
||||||
pill <- fromNounErr pillNoun & either (throwIO . uncurry ParseErr) pure
|
pill <- fromNounErr pillNoun & either (throwIO . uncurry ParseErr) pure
|
||||||
|
|
||||||
putStrLn "Using pill to generate boot sequence."
|
logDebug "Using pill to generate boot sequence."
|
||||||
bootSeq <- generateBootSeq zod pill False (Fake $ Ship 0)
|
bootSeq <- genBootSeq (Ship 0) pill False (Fake (Ship 0))
|
||||||
|
|
||||||
putStrLn "Validate jam/cue and toNoun/fromNoun on pill value"
|
logDebug "Validate jam/cue and toNoun/fromNoun on pill value"
|
||||||
reJam <- validateNounVal pill
|
reJam <- validateNounVal pill
|
||||||
|
|
||||||
putStrLn "Checking if round-trip matches input file:"
|
logDebug "Checking if round-trip matches input file:"
|
||||||
unless (reJam == pillBytes) $ do
|
unless (reJam == pillBytes) $ do
|
||||||
putStrLn " Our jam does not match the file...\n"
|
logDebug " Our jam does not match the file...\n"
|
||||||
putStrLn " This is surprising, but it is probably okay."
|
logDebug " This is surprising, but it is probably okay."
|
||||||
|
|
||||||
when showPil $ do
|
when showPil $ do
|
||||||
putStrLn "\n\n== Pill ==\n"
|
logDebug "\n\n== Pill ==\n"
|
||||||
io $ pPrint pill
|
io $ pPrint pill
|
||||||
|
|
||||||
when showSeq $ do
|
when showSeq $ do
|
||||||
putStrLn "\n\n== Boot Sequence ==\n"
|
logDebug "\n\n== Boot Sequence ==\n"
|
||||||
io $ pPrint bootSeq
|
io $ pPrint bootSeq
|
||||||
|
|
||||||
validateNounVal :: (HasLogFunc e, Eq a, ToNoun a, FromNoun a)
|
validateNounVal :: (HasLogFunc e, Eq a, ToNoun a, FromNoun a)
|
||||||
=> a -> RIO e ByteString
|
=> a -> RIO e ByteString
|
||||||
validateNounVal inpVal = do
|
validateNounVal inpVal = do
|
||||||
putStrLn " jam"
|
logDebug " jam"
|
||||||
inpByt <- evaluate $ jamBS $ toNoun inpVal
|
inpByt <- evaluate $ jamBS $ toNoun inpVal
|
||||||
|
|
||||||
putStrLn " cue"
|
logDebug " cue"
|
||||||
outNon <- cueBS inpByt & either throwIO pure
|
outNon <- cueBS inpByt & either throwIO pure
|
||||||
|
|
||||||
putStrLn " fromNoun"
|
logDebug " fromNoun"
|
||||||
outVal <- fromNounErr outNon & either (throwIO . uncurry ParseErr) pure
|
outVal <- fromNounErr outNon & either (throwIO . uncurry ParseErr) pure
|
||||||
|
|
||||||
putStrLn " toNoun"
|
logDebug " toNoun"
|
||||||
outNon <- evaluate (toNoun outVal)
|
outNon <- evaluate (toNoun outVal)
|
||||||
|
|
||||||
putStrLn " jam"
|
logDebug " jam"
|
||||||
outByt <- evaluate $ jamBS outNon
|
outByt <- evaluate $ jamBS outNon
|
||||||
|
|
||||||
putStrLn "Checking if: x == cue (jam x)"
|
logDebug "Checking if: x == cue (jam x)"
|
||||||
unless (inpVal == outVal) $
|
unless (inpVal == outVal) $
|
||||||
error "Value fails test: x == cue (jam x)"
|
error "Value fails test: x == cue (jam x)"
|
||||||
|
|
||||||
putStrLn "Checking if: jam x == jam (cue (jam x))"
|
logDebug "Checking if: jam x == jam (cue (jam x))"
|
||||||
unless (inpByt == outByt) $
|
unless (inpByt == outByt) $
|
||||||
error "Value fails test: jam x == jam (cue (jam x))"
|
error "Value fails test: jam x == jam (cue (jam x))"
|
||||||
|
|
||||||
pure outByt
|
pure outByt
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
pillFrom :: CLI.PillSource -> RIO e Pill
|
pillFrom :: CLI.PillSource -> RIO KingEnv Pill
|
||||||
|
pillFrom = \case
|
||||||
|
CLI.PillSourceFile pillPath -> do
|
||||||
|
logDebug $ display $ "boot: reading pill from " ++ (pack pillPath :: Text)
|
||||||
|
io (loadFile pillPath >>= either throwIO pure)
|
||||||
|
|
||||||
pillFrom (CLI.PillSourceFile pillPath) = do
|
CLI.PillSourceURL url -> do
|
||||||
putStrLn $ "boot: reading pill from " ++ pack pillPath
|
logDebug $ display $ "boot: retrieving pill from " ++ (pack url :: Text)
|
||||||
io (loadFile pillPath >>= either throwIO pure)
|
-- Get the jamfile with the list of stars accepting comets right now.
|
||||||
|
manager <- io $ C.newManager tlsManagerSettings
|
||||||
|
request <- io $ C.parseRequest url
|
||||||
|
response <- io $ C.httpLbs (C.setRequestCheckStatus request) manager
|
||||||
|
let body = toStrict $ C.responseBody response
|
||||||
|
|
||||||
pillFrom (CLI.PillSourceURL url) = do
|
noun <- cueBS body & either throwIO pure
|
||||||
putStrLn $ "boot: retrieving pill from " ++ pack url
|
fromNounErr noun & either (throwIO . uncurry ParseErr) pure
|
||||||
-- Get the jamfile with the list of stars accepting comets right now.
|
|
||||||
manager <- io $ C.newManager tlsManagerSettings
|
|
||||||
request <- io $ C.parseRequest url
|
|
||||||
response <- io $ C.httpLbs (C.setRequestCheckStatus request) manager
|
|
||||||
let body = toStrict $ C.responseBody response
|
|
||||||
|
|
||||||
noun <- cueBS body & either throwIO pure
|
newShip :: CLI.New -> CLI.Opts -> RIO KingEnv ()
|
||||||
fromNounErr noun & either (throwIO . uncurry ParseErr) pure
|
newShip CLI.New{..} opts = do
|
||||||
|
{-
|
||||||
|
TODO XXX HACK
|
||||||
|
|
||||||
newShip :: forall e. HasLogFunc e => CLI.New -> CLI.Opts -> RIO e ()
|
Because the "new ship" flow *may* automatically start the ship,
|
||||||
newShip CLI.New{..} opts
|
we need to create this, but it's not actually correct.
|
||||||
| CLI.BootComet <- nBootType = do
|
|
||||||
|
The right solution is to separate out the "new ship" flow from the
|
||||||
|
"run ship" flow, and possibly sequence them from the outside if
|
||||||
|
that's really needed.
|
||||||
|
-}
|
||||||
|
multi <- multiEyre (MultiEyreConf Nothing Nothing True)
|
||||||
|
|
||||||
|
case nBootType of
|
||||||
|
CLI.BootComet -> do
|
||||||
pill <- pillFrom nPillSource
|
pill <- pillFrom nPillSource
|
||||||
putStrLn "boot: retrieving list of stars currently accepting comets"
|
putStrLn "boot: retrieving list of stars currently accepting comets"
|
||||||
starList <- dawnCometList
|
starList <- dawnCometList
|
||||||
@ -413,14 +486,14 @@ newShip CLI.New{..} opts
|
|||||||
eny <- io $ Sys.randomIO
|
eny <- io $ Sys.randomIO
|
||||||
let seed = mineComet (Set.fromList starList) eny
|
let seed = mineComet (Set.fromList starList) eny
|
||||||
putStrLn ("boot: found comet " ++ renderShip (sShip seed))
|
putStrLn ("boot: found comet " ++ renderShip (sShip seed))
|
||||||
bootFromSeed pill seed
|
bootFromSeed multi pill seed
|
||||||
|
|
||||||
| CLI.BootFake name <- nBootType = do
|
CLI.BootFake name -> do
|
||||||
pill <- pillFrom nPillSource
|
pill <- pillFrom nPillSource
|
||||||
ship <- shipFrom name
|
ship <- shipFrom name
|
||||||
runTryBootFromPill pill name ship (Fake ship)
|
runTryBootFromPill multi pill name ship (Fake ship)
|
||||||
|
|
||||||
| CLI.BootFromKeyfile keyFile <- nBootType = do
|
CLI.BootFromKeyfile keyFile -> do
|
||||||
text <- readFileUtf8 keyFile
|
text <- readFileUtf8 keyFile
|
||||||
asAtom <- case cordToUW (Cord $ T.strip text) of
|
asAtom <- case cordToUW (Cord $ T.strip text) of
|
||||||
Nothing -> error "Couldn't parse keyfile. Hint: keyfiles start with 0w?"
|
Nothing -> error "Couldn't parse keyfile. Hint: keyfiles start with 0w?"
|
||||||
@ -433,10 +506,10 @@ newShip CLI.New{..} opts
|
|||||||
|
|
||||||
pill <- pillFrom nPillSource
|
pill <- pillFrom nPillSource
|
||||||
|
|
||||||
bootFromSeed pill seed
|
bootFromSeed multi pill seed
|
||||||
|
|
||||||
where
|
where
|
||||||
shipFrom :: Text -> RIO e Ship
|
shipFrom :: Text -> RIO KingEnv Ship
|
||||||
shipFrom name = case Ob.parsePatp name of
|
shipFrom name = case Ob.parsePatp name of
|
||||||
Left x -> error "Invalid ship name"
|
Left x -> error "Invalid ship name"
|
||||||
Right p -> pure $ Ship $ fromIntegral $ Ob.fromPatp p
|
Right p -> pure $ Ship $ fromIntegral $ Ob.fromPatp p
|
||||||
@ -446,7 +519,7 @@ newShip CLI.New{..} opts
|
|||||||
Just x -> x
|
Just x -> x
|
||||||
Nothing -> "./" <> unpack name
|
Nothing -> "./" <> unpack name
|
||||||
|
|
||||||
nameFromShip :: Ship -> RIO e Text
|
nameFromShip :: Ship -> RIO KingEnv Text
|
||||||
nameFromShip s = name
|
nameFromShip s = name
|
||||||
where
|
where
|
||||||
nameWithSig = Ob.renderPatp $ Ob.patp $ fromIntegral s
|
nameWithSig = Ob.renderPatp $ Ob.patp $ fromIntegral s
|
||||||
@ -454,8 +527,8 @@ newShip CLI.New{..} opts
|
|||||||
Nothing -> error "Urbit.ob didn't produce string with ~"
|
Nothing -> error "Urbit.ob didn't produce string with ~"
|
||||||
Just x -> pure x
|
Just x -> pure x
|
||||||
|
|
||||||
bootFromSeed :: Pill -> Seed -> RIO e ()
|
bootFromSeed :: MultiEyreApi -> Pill -> Seed -> RIO KingEnv ()
|
||||||
bootFromSeed pill seed = do
|
bootFromSeed multi pill seed = do
|
||||||
ethReturn <- dawnVent seed
|
ethReturn <- dawnVent seed
|
||||||
|
|
||||||
case ethReturn of
|
case ethReturn of
|
||||||
@ -463,43 +536,51 @@ newShip CLI.New{..} opts
|
|||||||
Right dawn -> do
|
Right dawn -> do
|
||||||
let ship = sShip $ dSeed dawn
|
let ship = sShip $ dSeed dawn
|
||||||
name <- nameFromShip ship
|
name <- nameFromShip ship
|
||||||
runTryBootFromPill pill name ship (Dawn dawn)
|
runTryBootFromPill multi pill name ship (Dawn dawn)
|
||||||
|
|
||||||
flags = toSerfFlags opts
|
|
||||||
|
|
||||||
-- Now that we have all the information for running an application with a
|
-- Now that we have all the information for running an application with a
|
||||||
-- PierConfig, do so.
|
-- PierConfig, do so.
|
||||||
runTryBootFromPill pill name ship bootEvent = do
|
runTryBootFromPill multi pill name ship bootEvent = do
|
||||||
|
vKill <- view kingEnvKillSignal
|
||||||
let pierConfig = toPierConfig (pierPath name) opts
|
let pierConfig = toPierConfig (pierPath name) opts
|
||||||
let networkConfig = toNetworkConfig opts
|
let networkConfig = toNetworkConfig opts
|
||||||
io $ runPierApp pierConfig networkConfig True $
|
runPierEnv pierConfig networkConfig vKill $
|
||||||
tryBootFromPill True pill nLite flags ship bootEvent
|
tryBootFromPill True pill nLite ship bootEvent multi
|
||||||
------ tryBootFromPill (CLI.oExit opts) pill nLite flags ship bootEvent
|
------ tryBootFromPill (CLI.oExit opts) pill nLite flags ship bootEvent
|
||||||
|
|
||||||
|
runShipEnv :: CLI.Run -> CLI.Opts -> TMVar () -> RIO PierEnv a -> RIO KingEnv a
|
||||||
|
runShipEnv (CLI.Run pierPath) opts vKill act = do
|
||||||
|
runPierEnv pierConfig netConfig vKill act
|
||||||
|
where
|
||||||
|
pierConfig = toPierConfig pierPath opts
|
||||||
|
netConfig = toNetworkConfig opts
|
||||||
|
|
||||||
runShip :: CLI.Run -> CLI.Opts -> Bool -> IO ()
|
runShip
|
||||||
runShip (CLI.Run pierPath) opts daemon = do
|
:: CLI.Run -> CLI.Opts -> Bool -> MultiEyreApi -> RIO PierEnv ()
|
||||||
tid <- myThreadId
|
runShip (CLI.Run pierPath) opts daemon multi = do
|
||||||
let onTermExit = throwTo tid UserInterrupt
|
mStart <- newEmptyMVar
|
||||||
mStart <- newEmptyMVar
|
|
||||||
if daemon
|
if daemon
|
||||||
then runPier mStart
|
then runPier mStart
|
||||||
else do
|
else do
|
||||||
|
-- Wait until the pier has started up, then connect a terminal. If
|
||||||
|
-- the terminal ever shuts down, ask the ship to go down.
|
||||||
connectionThread <- async $ do
|
connectionThread <- async $ do
|
||||||
readMVar mStart
|
readMVar mStart
|
||||||
finally (runAppNoLog $ connTerm pierPath) onTermExit
|
finally (connTerm pierPath) $ do
|
||||||
finally (runPier mStart) (cancel connectionThread)
|
view killPierActionL >>= atomically
|
||||||
|
|
||||||
|
-- Run the pier until it finishes, and then kill the terminal.
|
||||||
|
finally (runPier mStart) $ do
|
||||||
|
cancel connectionThread
|
||||||
where
|
where
|
||||||
runPier mStart =
|
runPier :: MVar () -> RIO PierEnv ()
|
||||||
runPierApp pierConfig networkConfig daemon $
|
runPier mStart = do
|
||||||
tryPlayShip
|
tryPlayShip
|
||||||
(CLI.oExit opts)
|
(CLI.oExit opts)
|
||||||
(CLI.oFullReplay opts)
|
(CLI.oFullReplay opts)
|
||||||
(CLI.oDryFrom opts)
|
(CLI.oDryFrom opts)
|
||||||
(toSerfFlags opts)
|
mStart
|
||||||
mStart
|
multi
|
||||||
pierConfig = toPierConfig pierPath opts
|
|
||||||
networkConfig = toNetworkConfig opts
|
|
||||||
|
|
||||||
|
|
||||||
startBrowser :: HasLogFunc e => FilePath -> RIO e ()
|
startBrowser :: HasLogFunc e => FilePath -> RIO e ()
|
||||||
@ -540,33 +621,200 @@ checkComet = do
|
|||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
args <- CLI.parseArgs
|
||||||
|
|
||||||
|
hSetBuffering stdout NoBuffering
|
||||||
|
setupSignalHandlers
|
||||||
|
|
||||||
|
runKingEnv args $ case args of
|
||||||
|
CLI.CmdRun ko ships -> runShips ko ships
|
||||||
|
CLI.CmdNew n o -> newShip n o
|
||||||
|
CLI.CmdBug (CLI.CollectAllFX pax ) -> collectAllFx pax
|
||||||
|
CLI.CmdBug (CLI.EventBrowser pax ) -> startBrowser pax
|
||||||
|
CLI.CmdBug (CLI.ValidatePill pax pil s) -> testPill pax pil s
|
||||||
|
CLI.CmdBug (CLI.ValidateEvents pax f l) -> checkEvs pax f l
|
||||||
|
CLI.CmdBug (CLI.ValidateFX pax f l) -> checkFx pax f l
|
||||||
|
CLI.CmdBug (CLI.ReplayEvents pax l ) -> replayPartEvs pax l
|
||||||
|
CLI.CmdBug (CLI.CheckDawn pax ) -> checkDawn pax
|
||||||
|
CLI.CmdBug CLI.CheckComet -> checkComet
|
||||||
|
CLI.CmdCon pier -> connTerm pier
|
||||||
|
|
||||||
|
where
|
||||||
|
runKingEnv args =
|
||||||
|
let verb = verboseLogging args
|
||||||
|
in if willRunTerminal args
|
||||||
|
then runKingEnvLogFile verb
|
||||||
|
else runKingEnvStderr verb
|
||||||
|
|
||||||
|
setupSignalHandlers = do
|
||||||
mainTid <- myThreadId
|
mainTid <- myThreadId
|
||||||
|
let onKillSig = throwTo mainTid UserInterrupt
|
||||||
|
for_ [Sys.sigTERM, Sys.sigINT] $ \sig -> do
|
||||||
|
Sys.installHandler sig (Sys.Catch onKillSig) Nothing
|
||||||
|
|
||||||
hSetBuffering stdout NoBuffering
|
verboseLogging :: CLI.Cmd -> Bool
|
||||||
|
verboseLogging = \case
|
||||||
|
CLI.CmdRun ko ships -> any CLI.oVerbose (ships <&> \(_, o, _) -> o)
|
||||||
|
_ -> False
|
||||||
|
|
||||||
let onTermSig = throwTo mainTid UserInterrupt
|
willRunTerminal :: CLI.Cmd -> Bool
|
||||||
|
willRunTerminal = \case
|
||||||
|
CLI.CmdCon _ -> True
|
||||||
|
CLI.CmdRun ko [(_,_,daemon)] -> not daemon
|
||||||
|
CLI.CmdRun ko _ -> False
|
||||||
|
_ -> False
|
||||||
|
|
||||||
Sys.installHandler Sys.sigTERM (Sys.Catch onTermSig) Nothing
|
|
||||||
|
|
||||||
CLI.parseArgs >>= \case
|
{-
|
||||||
CLI.CmdRun r o d -> runShip r o d
|
Runs a ship but restarts it if it crashes or shuts down on it's own.
|
||||||
CLI.CmdNew n o -> runApp $ newShip n o
|
|
||||||
CLI.CmdBug (CLI.CollectAllFX pax) -> runApp $ collectAllFx pax
|
Once `waitForKillRequ` returns, the ship will be terminated and this
|
||||||
CLI.CmdBug (CLI.EventBrowser pax) -> runApp $ startBrowser pax
|
routine will exit.
|
||||||
CLI.CmdBug (CLI.ValidatePill pax pil s) -> runApp $ testPill pax pil s
|
|
||||||
CLI.CmdBug (CLI.ValidateEvents pax f l) -> runApp $ checkEvs pax f l
|
TODO Use logging system instead of printing.
|
||||||
CLI.CmdBug (CLI.ValidateFX pax f l) -> runApp $ checkFx pax f l
|
-}
|
||||||
CLI.CmdBug (CLI.ReplayEvents pax l) -> runApp $ replayPartEvs pax l
|
runShipRestarting
|
||||||
CLI.CmdBug (CLI.CheckDawn pax) -> runApp $ checkDawn pax
|
:: CLI.Run -> CLI.Opts -> MultiEyreApi -> RIO KingEnv ()
|
||||||
CLI.CmdBug CLI.CheckComet -> runApp $ checkComet
|
runShipRestarting r o multi = do
|
||||||
CLI.CmdCon pier -> runAppLogFile $ connTerm pier
|
let pier = pack (CLI.rPierPath r)
|
||||||
|
loop = runShipRestarting r o multi
|
||||||
|
|
||||||
|
onKill <- view onKillKingSigL
|
||||||
|
vKillPier <- newEmptyTMVarIO
|
||||||
|
|
||||||
|
tid <- asyncBound $ runShipEnv r o vKillPier $ runShip r o True multi
|
||||||
|
|
||||||
|
let onShipExit = Left <$> waitCatchSTM tid
|
||||||
|
onKillRequ = Right <$> onKill
|
||||||
|
|
||||||
|
atomically (onShipExit <|> onKillRequ) >>= \case
|
||||||
|
Left exit -> do
|
||||||
|
case exit of
|
||||||
|
Left err -> logError $ display (tshow err <> ": " <> pier)
|
||||||
|
Right () ->
|
||||||
|
logError $ display ("Ship exited on it's own. Why? " <> pier)
|
||||||
|
threadDelay 250_000
|
||||||
|
loop
|
||||||
|
Right () -> do
|
||||||
|
logTrace $ display (pier <> " shutdown requested")
|
||||||
|
race_ (wait tid) $ do
|
||||||
|
threadDelay 5_000_000
|
||||||
|
logDebug $ display (pier <> " not down after 5s, killing with fire.")
|
||||||
|
cancel tid
|
||||||
|
logTrace $ display ("Ship terminated: " <> pier)
|
||||||
|
|
||||||
|
{-
|
||||||
|
TODO This is messy and shared a lot of logic with `runShipRestarting`.
|
||||||
|
-}
|
||||||
|
runShipNoRestart
|
||||||
|
:: CLI.Run -> CLI.Opts -> Bool -> MultiEyreApi -> RIO KingEnv ()
|
||||||
|
runShipNoRestart r o d multi = do
|
||||||
|
vKill <- view kingEnvKillSignal -- killing ship same as killing king
|
||||||
|
tid <- asyncBound (runShipEnv r o vKill $ runShip r o d multi)
|
||||||
|
onKill <- view onKillKingSigL
|
||||||
|
|
||||||
|
let pier = pack (CLI.rPierPath r)
|
||||||
|
|
||||||
|
let onShipExit = Left <$> waitCatchSTM tid
|
||||||
|
onKillRequ = Right <$> onKill
|
||||||
|
|
||||||
|
atomically (onShipExit <|> onKillRequ) >>= \case
|
||||||
|
Left (Left err) -> do
|
||||||
|
logError $ display (tshow err <> ": " <> pier)
|
||||||
|
Left (Right ()) -> do
|
||||||
|
logError $ display (pier <> " exited on it's own. Why?")
|
||||||
|
Right () -> do
|
||||||
|
logTrace $ display (pier <> " shutdown requested")
|
||||||
|
race_ (wait tid) $ do
|
||||||
|
threadDelay 5_000_000
|
||||||
|
logTrace $ display (pier <> " not down after 5s, killing with fire.")
|
||||||
|
cancel tid
|
||||||
|
logTrace $ display (pier <> " terminated.")
|
||||||
|
|
||||||
|
runShips :: CLI.KingOpts -> [(CLI.Run, CLI.Opts, Bool)] -> RIO KingEnv ()
|
||||||
|
runShips CLI.KingOpts {..} ships = do
|
||||||
|
let meConf = MultiEyreConf
|
||||||
|
{ mecHttpPort = fromIntegral <$> koSharedHttpPort
|
||||||
|
, mecHttpsPort = fromIntegral <$> koSharedHttpsPort
|
||||||
|
, mecLocalhostOnly = False -- TODO Localhost-only needs to be
|
||||||
|
-- a king-wide option.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-
|
||||||
|
TODO Need to rework RIO environment to fix this. Should have a
|
||||||
|
bunch of nested contexts:
|
||||||
|
|
||||||
|
- King has started. King has Id. Logging available.
|
||||||
|
- In running environment. MultiEyre and global config available.
|
||||||
|
- In pier environment: pier path and config available.
|
||||||
|
- In running ship environment: serf state, event queue available.
|
||||||
|
-}
|
||||||
|
multi <- multiEyre meConf
|
||||||
|
|
||||||
|
go multi ships
|
||||||
|
where
|
||||||
|
go :: MultiEyreApi -> [(CLI.Run, CLI.Opts, Bool)] -> RIO KingEnv ()
|
||||||
|
go me = \case
|
||||||
|
[] -> pure ()
|
||||||
|
[rod] -> runSingleShip rod me
|
||||||
|
ships -> runMultipleShips (ships <&> \(r, o, _) -> (r, o)) me
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO Duplicated logic.
|
||||||
|
runSingleShip :: (CLI.Run, CLI.Opts, Bool) -> MultiEyreApi -> RIO KingEnv ()
|
||||||
|
runSingleShip (r, o, d) multi = do
|
||||||
|
shipThread <- async (runShipNoRestart r o d multi)
|
||||||
|
|
||||||
|
{-
|
||||||
|
Wait for the ship to go down.
|
||||||
|
|
||||||
|
Since `waitCatch` will never throw an exception, the `onException`
|
||||||
|
block will only happen if this thread is killed with an async
|
||||||
|
exception. The one we expect is `UserInterrupt` which will be raised
|
||||||
|
on this thread upon SIGKILL or SIGTERM.
|
||||||
|
|
||||||
|
If this thread is killed, we first ask the ship to go down, wait
|
||||||
|
for the ship to actually go down, and then go down ourselves.
|
||||||
|
-}
|
||||||
|
onException (void $ waitCatch shipThread) $ do
|
||||||
|
logTrace "KING IS GOING DOWN"
|
||||||
|
atomically =<< view killKingActionL
|
||||||
|
waitCatch shipThread
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
|
||||||
|
runMultipleShips :: [(CLI.Run, CLI.Opts)] -> MultiEyreApi -> RIO KingEnv ()
|
||||||
|
runMultipleShips ships multi = do
|
||||||
|
shipThreads <- for ships $ \(r, o) -> do
|
||||||
|
async (runShipRestarting r o multi)
|
||||||
|
|
||||||
|
{-
|
||||||
|
Since `spin` never returns, this will run until the main
|
||||||
|
thread is killed with an async exception. The one we expect is
|
||||||
|
`UserInterrupt` which will be raised on this thread upon SIGKILL
|
||||||
|
or SIGTERM.
|
||||||
|
|
||||||
|
Once that happens, we send a shutdown signal which will cause all
|
||||||
|
ships to be shut down, and then we `wait` for them to finish before
|
||||||
|
returning.
|
||||||
|
|
||||||
|
This is different than the single-ship flow, because ships never
|
||||||
|
go down on their own in this flow. If they go down, they just bring
|
||||||
|
themselves back up.
|
||||||
|
-}
|
||||||
|
let spin = forever (threadDelay maxBound)
|
||||||
|
finally spin $ do
|
||||||
|
logTrace "KING IS GOING DOWN"
|
||||||
|
view killKingActionL >>= atomically
|
||||||
|
for_ shipThreads waitCatch
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
connTerm :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
connTerm :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
||||||
connTerm pier =
|
connTerm = Term.runTerminalClient
|
||||||
Term.runTerminalClient pier
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -1,87 +1,162 @@
|
|||||||
{-|
|
{-|
|
||||||
Ames IO Driver -- UDP
|
Ames IO Driver
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Ames (ames) where
|
module Urbit.Vere.Ames (ames, ames', PacketOutcome(..)) where
|
||||||
|
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
|
||||||
import Control.Monad.Extra hiding (mapM_)
|
import Network.Socket hiding (recvFrom, sendTo)
|
||||||
import Network.Socket hiding (recvFrom, sendTo)
|
import Urbit.Arvo hiding (Fake)
|
||||||
import Network.Socket.ByteString
|
|
||||||
import Urbit.Arvo hiding (Fake)
|
|
||||||
import Urbit.King.Config
|
import Urbit.King.Config
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import qualified Data.ByteString as BS
|
import Urbit.King.App (HasKingId(..), HasPierEnv(..))
|
||||||
import qualified Data.Map as M
|
import Urbit.Vere.Ames.DNS (NetworkMode(..), ResolvServ(..))
|
||||||
import qualified Urbit.Ob as Ob
|
import Urbit.Vere.Ames.DNS (galaxyPort, resolvServ)
|
||||||
import qualified Urbit.Time as Time
|
import Urbit.Vere.Ames.UDP (UdpServ(..), fakeUdpServ, realUdpServ)
|
||||||
|
|
||||||
|
|
||||||
|
-- Constants -------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | How many unprocessed ames packets to allow in the queue before we start
|
||||||
|
-- dropping incoming packets.
|
||||||
|
queueBound :: Word
|
||||||
|
queueBound = 1000
|
||||||
|
|
||||||
|
-- | How often, measured in number of packets dropped, we should announce packet
|
||||||
|
-- loss.
|
||||||
|
packetsDroppedPerComplaint :: Word
|
||||||
|
packetsDroppedPerComplaint = 1000
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
data AmesDrv = AmesDrv
|
data AmesDrv = AmesDrv
|
||||||
{ aTurfs :: TVar (Maybe [Turf])
|
{ aTurfs :: TVar (Maybe [Turf])
|
||||||
, aGalaxies :: IORef (M.Map Galaxy (Async (), TQueue ByteString))
|
, aDropped :: TVar Word
|
||||||
, aSocket :: TVar (Maybe Socket)
|
, aUdpServ :: UdpServ
|
||||||
, aListener :: Async ()
|
, aResolvr :: ResolvServ
|
||||||
, aSendingQueue :: TQueue (SockAddr, ByteString)
|
, aRecvTid :: Async ()
|
||||||
, aSendingThread :: Async ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data NetworkMode = Fake | Localhost | Real | NoNetwork
|
data PacketOutcome
|
||||||
deriving (Eq, Ord, Show)
|
= Intake
|
||||||
|
| Ouster
|
||||||
|
|
||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
galaxyPort :: NetworkMode -> Galaxy -> PortNumber
|
|
||||||
galaxyPort Fake (Patp g) = fromIntegral g + 31337
|
|
||||||
galaxyPort Localhost (Patp g) = fromIntegral g + 13337
|
|
||||||
galaxyPort Real (Patp g) = fromIntegral g + 13337
|
|
||||||
galaxyPort NoNetwork _ = fromIntegral 0
|
|
||||||
|
|
||||||
listenPort :: NetworkMode -> Ship -> PortNumber
|
listenPort :: NetworkMode -> Ship -> PortNumber
|
||||||
listenPort m s | s < 256 = galaxyPort m (fromIntegral s)
|
listenPort m s | s < 256 = galaxyPort m (fromIntegral s)
|
||||||
listenPort m _ = 0
|
listenPort m _ = 0 -- I don't care, just give me any port.
|
||||||
|
|
||||||
localhost :: HostAddress
|
localhost :: HostAddress
|
||||||
localhost = tupleToHostAddress (127,0,0,1)
|
localhost = tupleToHostAddress (127, 0, 0, 1)
|
||||||
|
|
||||||
inaddrAny :: HostAddress
|
inaddrAny :: HostAddress
|
||||||
inaddrAny = tupleToHostAddress (0,0,0,0)
|
inaddrAny = tupleToHostAddress (0, 0, 0, 0)
|
||||||
|
|
||||||
okayFakeAddr :: AmesDest -> Bool
|
modeAddress :: NetworkMode -> Maybe HostAddress
|
||||||
okayFakeAddr = \case
|
modeAddress = \case
|
||||||
EachYes _ -> True
|
Fake -> Just localhost
|
||||||
EachNo (Jammed (AAIpv4 (Ipv4 a) _)) -> a == localhost
|
Localhost -> Just localhost
|
||||||
EachNo (Jammed (AAVoid v)) -> absurd v
|
Real -> Just inaddrAny
|
||||||
|
NoNetwork -> Nothing
|
||||||
|
|
||||||
localhostSockAddr :: NetworkMode -> AmesDest -> SockAddr
|
okFakeAddr :: AmesDest -> Bool
|
||||||
localhostSockAddr mode = \case
|
okFakeAddr = \case
|
||||||
EachYes g -> SockAddrInet (galaxyPort mode g) localhost
|
EachYes _ -> True
|
||||||
EachNo (Jammed (AAIpv4 _ p)) -> SockAddrInet (fromIntegral p) localhost
|
EachNo (Jammed (AAIpv4 (Ipv4 a) _)) -> a == localhost
|
||||||
EachNo (Jammed (AAVoid v)) -> absurd v
|
EachNo (Jammed (AAVoid v )) -> absurd v
|
||||||
|
|
||||||
|
localAddr :: NetworkMode -> AmesDest -> SockAddr
|
||||||
|
localAddr mode = \case
|
||||||
|
EachYes g -> SockAddrInet (galaxyPort mode g) localhost
|
||||||
|
EachNo (Jammed (AAIpv4 _ p)) -> SockAddrInet (fromIntegral p) localhost
|
||||||
|
EachNo (Jammed (AAVoid v )) -> absurd v
|
||||||
|
|
||||||
bornEv :: KingId -> Ev
|
bornEv :: KingId -> Ev
|
||||||
bornEv inst =
|
bornEv inst = EvBlip $ BlipEvNewt $ NewtEvBorn (fromIntegral inst, ()) ()
|
||||||
EvBlip $ BlipEvNewt $ NewtEvBorn (fromIntegral inst, ()) ()
|
|
||||||
|
|
||||||
hearEv :: PortNumber -> HostAddress -> ByteString -> Ev
|
hearEv :: PortNumber -> HostAddress -> ByteString -> Ev
|
||||||
hearEv p a bs =
|
hearEv p a bs =
|
||||||
EvBlip $ BlipEvAmes $ AmesEvHear () dest (MkBytes bs)
|
EvBlip $ BlipEvAmes $ AmesEvHear () dest (MkBytes bs)
|
||||||
where
|
where
|
||||||
dest = EachNo $ Jammed $ AAIpv4 (Ipv4 a) (fromIntegral p)
|
dest = EachNo $ Jammed $ AAIpv4 (Ipv4 a) (fromIntegral p)
|
||||||
|
|
||||||
_turfText :: Turf -> Text
|
|
||||||
_turfText = intercalate "." . reverse . fmap unCord . unTurf
|
|
||||||
|
|
||||||
renderGalaxy :: Galaxy -> Text
|
|
||||||
renderGalaxy = Ob.renderPatp . Ob.patp . fromIntegral . unPatp
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
netMode :: HasNetworkConfig e => Bool -> RIO e NetworkMode
|
||||||
|
netMode isFake = do
|
||||||
|
netMode <- view (networkConfigL . ncNetMode)
|
||||||
|
noAmes <- view (networkConfigL . ncNoAmes)
|
||||||
|
pure $ case (noAmes, isFake, netMode) of
|
||||||
|
(True, _ , _ ) -> NoNetwork
|
||||||
|
(_ , _ , NMNone ) -> NoNetwork
|
||||||
|
(_ , True, _ ) -> Fake
|
||||||
|
(_ , _ , NMNormal ) -> Real
|
||||||
|
(_ , _ , NMLocalhost) -> Localhost
|
||||||
|
|
||||||
|
udpPort :: HasNetworkConfig e => Bool -> Ship -> RIO e PortNumber
|
||||||
|
udpPort isFake who = do
|
||||||
|
mode <- netMode isFake
|
||||||
|
mPort <- view (networkConfigL . ncAmesPort)
|
||||||
|
pure $ maybe (listenPort mode who) fromIntegral mPort
|
||||||
|
|
||||||
|
udpServ :: (HasLogFunc e, HasNetworkConfig e) => Bool -> Ship -> RIO e UdpServ
|
||||||
|
udpServ isFake who = do
|
||||||
|
mode <- netMode isFake
|
||||||
|
port <- udpPort isFake who
|
||||||
|
case modeAddress mode of
|
||||||
|
Nothing -> fakeUdpServ
|
||||||
|
Just host -> realUdpServ port host
|
||||||
|
|
||||||
|
_bornFailed :: e -> WorkError -> IO ()
|
||||||
|
_bornFailed env _ = runRIO env $ do
|
||||||
|
pure () -- TODO What can we do?
|
||||||
|
|
||||||
|
ames'
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> Ship
|
||||||
|
-> Bool
|
||||||
|
-> (Text -> RIO e ())
|
||||||
|
-> RIO e ([Ev], RAcquire e (DriverApi NewtEf))
|
||||||
|
ames' who isFake stderr = do
|
||||||
|
-- Unfortunately, we cannot use TBQueue because the only behavior
|
||||||
|
-- provided for when full is to block the writer. The implementation
|
||||||
|
-- below uses materially the same data structures as TBQueue, however.
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
avail :: TVar Word <- newTVarIO queueBound
|
||||||
|
let
|
||||||
|
enqueuePacket p = do
|
||||||
|
vail <- readTVar avail
|
||||||
|
if vail > 0
|
||||||
|
then do
|
||||||
|
modifyTVar avail (subtract 1)
|
||||||
|
writeTQueue ventQ p
|
||||||
|
pure Intake
|
||||||
|
else do
|
||||||
|
_ <- readTQueue ventQ
|
||||||
|
writeTQueue ventQ p
|
||||||
|
pure Ouster
|
||||||
|
dequeuePacket = do
|
||||||
|
pM <- tryReadTQueue ventQ
|
||||||
|
when (isJust pM) $ modifyTVar avail (+ 1)
|
||||||
|
pure pM
|
||||||
|
|
||||||
|
env <- ask
|
||||||
|
let (bornEvs, startDriver) = ames env who isFake enqueuePacket stderr
|
||||||
|
|
||||||
|
let runDriver = do
|
||||||
|
diOnEffect <- startDriver
|
||||||
|
let diEventSource = fmap RRWork <$> dequeuePacket
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
|
pure (bornEvs, runDriver)
|
||||||
|
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
inst -- Process instance number.
|
inst -- Process instance number.
|
||||||
who -- Which ship are we?
|
who -- Which ship are we?
|
||||||
@ -93,229 +168,81 @@ renderGalaxy = Ob.renderPatp . Ob.patp . fromIntegral . unPatp
|
|||||||
|
|
||||||
TODO verify that the KingIds match on effects.
|
TODO verify that the KingIds match on effects.
|
||||||
-}
|
-}
|
||||||
ames :: forall e. (HasLogFunc e, HasNetworkConfig e)
|
ames
|
||||||
=> KingId -> Ship -> Bool -> QueueEv
|
:: forall e
|
||||||
-> (Text -> RIO e ())
|
. (HasLogFunc e, HasNetworkConfig e, HasKingId e)
|
||||||
-> ([Ev], RAcquire e (EffCb e NewtEf))
|
=> e
|
||||||
ames inst who isFake enqueueEv stderr =
|
-> Ship
|
||||||
(initialEvents, runAmes)
|
-> Bool
|
||||||
where
|
-> (EvErr -> STM PacketOutcome)
|
||||||
initialEvents :: [Ev]
|
-> (Text -> RIO e ())
|
||||||
initialEvents = [bornEv inst]
|
-> ([Ev], RAcquire e (NewtEf -> IO ()))
|
||||||
|
ames env who isFake enqueueEv stderr = (initialEvents, runAmes)
|
||||||
|
where
|
||||||
|
king = fromIntegral (env ^. kingIdL)
|
||||||
|
|
||||||
runAmes :: RAcquire e (EffCb e NewtEf)
|
initialEvents :: [Ev]
|
||||||
runAmes = do
|
initialEvents = [bornEv king]
|
||||||
drv <- mkRAcquire start stop
|
|
||||||
pure (handleEffect drv)
|
|
||||||
|
|
||||||
start :: RIO e AmesDrv
|
runAmes :: RAcquire e (NewtEf -> IO ())
|
||||||
start = do
|
runAmes = do
|
||||||
aTurfs <- newTVarIO Nothing
|
mode <- rio (netMode isFake)
|
||||||
aGalaxies <- newIORef mempty
|
drv <- mkRAcquire start stop
|
||||||
aSocket <- newTVarIO Nothing
|
pure (handleEffect drv mode)
|
||||||
bindSock aSocket
|
|
||||||
aListener <- async (waitPacket aSocket)
|
|
||||||
aSendingQueue <- newTQueueIO
|
|
||||||
aSendingThread <- async (sendingThread aSendingQueue aSocket)
|
|
||||||
pure $ AmesDrv{..}
|
|
||||||
|
|
||||||
netMode :: RIO e NetworkMode
|
start :: HasLogFunc e => RIO e AmesDrv
|
||||||
netMode = do
|
start = do
|
||||||
if isFake
|
aTurfs <- newTVarIO Nothing
|
||||||
then pure Fake
|
aDropped <- newTVarIO 0
|
||||||
else view (networkConfigL . ncNetMode) >>= \case
|
aUdpServ <- udpServ isFake who
|
||||||
NMNormal -> pure Real
|
aRecvTid <- queuePacketsThread aDropped aUdpServ
|
||||||
NMLocalhost -> pure Localhost
|
aResolvr <- resolvServ aTurfs (usSend aUdpServ) stderr
|
||||||
NMNone -> pure NoNetwork
|
pure (AmesDrv { .. })
|
||||||
|
|
||||||
stop :: AmesDrv -> RIO e ()
|
hearFailed _ = pure ()
|
||||||
stop AmesDrv{..} = do
|
|
||||||
readIORef aGalaxies >>= mapM_ (cancel . fst)
|
|
||||||
|
|
||||||
cancel aSendingThread
|
queuePacketsThread :: HasLogFunc e => TVar Word -> UdpServ -> RIO e (Async ())
|
||||||
cancel aListener
|
queuePacketsThread dropCtr UdpServ {..} = async $ forever $ do
|
||||||
socket <- atomically $ readTVar aSocket
|
outcome <- atomically $ do
|
||||||
io $ maybeM (pure ()) (close') (pure socket)
|
(p, a, b) <- usRecv
|
||||||
|
enqueueEv (EvErr (hearEv p a b) hearFailed)
|
||||||
|
case outcome of
|
||||||
|
Intake -> pure ()
|
||||||
|
Ouster -> do
|
||||||
|
d <- atomically $ do
|
||||||
|
d <- readTVar dropCtr
|
||||||
|
writeTVar dropCtr (d + 1)
|
||||||
|
pure d
|
||||||
|
when (d `rem` packetsDroppedPerComplaint == 0) $
|
||||||
|
logWarn "ames: queue full; dropping inbound packets"
|
||||||
|
|
||||||
bindSock :: TVar (Maybe Socket) -> RIO e ()
|
stop :: AmesDrv -> RIO e ()
|
||||||
bindSock socketVar = getBindAddr >>= doBindSocket
|
stop AmesDrv {..} = io $ do
|
||||||
where
|
usKill aUdpServ
|
||||||
getBindAddr = netMode >>= \case
|
rsKill aResolvr
|
||||||
Fake -> pure $ Just localhost
|
cancel aRecvTid
|
||||||
Localhost -> pure $ Just localhost
|
|
||||||
Real -> pure $ Just inaddrAny
|
|
||||||
NoNetwork -> pure Nothing
|
|
||||||
|
|
||||||
doBindSocket :: Maybe HostAddress -> RIO e ()
|
handleEffect :: AmesDrv -> NetworkMode -> NewtEf -> IO ()
|
||||||
doBindSocket Nothing = atomically $ writeTVar socketVar Nothing
|
handleEffect drv@AmesDrv {..} mode = runRIO env . \case
|
||||||
doBindSocket (Just bindAddr) = do
|
NewtEfTurf (_id, ()) turfs -> do
|
||||||
mode <- netMode
|
atomically $ writeTVar aTurfs (Just turfs)
|
||||||
mPort <- view (networkConfigL . ncAmesPort)
|
|
||||||
let ourPort = maybe (listenPort mode who) fromIntegral mPort
|
|
||||||
s <- io $ socket AF_INET Datagram defaultProtocol
|
|
||||||
|
|
||||||
logTrace $ displayShow ("(ames) Binding to port ", ourPort)
|
NewtEfSend (_id, ()) dest (MkBytes bs) -> do
|
||||||
let addr = SockAddrInet ourPort bindAddr
|
atomically (readTVar aTurfs) >>= \case
|
||||||
() <- io $ bind s addr
|
Nothing -> pure ()
|
||||||
|
Just turfs -> sendPacket drv mode dest bs
|
||||||
|
|
||||||
atomically $ writeTVar socketVar (Just s)
|
sendPacket :: AmesDrv -> NetworkMode -> AmesDest -> ByteString -> RIO e ()
|
||||||
|
sendPacket AmesDrv {..} mode dest byt = do
|
||||||
|
let to adr = io (usSend aUdpServ adr byt)
|
||||||
|
|
||||||
waitPacket :: TVar (Maybe Socket) -> RIO e ()
|
case (mode, dest) of
|
||||||
waitPacket socketVar = do
|
(NoNetwork, _ ) -> pure ()
|
||||||
(atomically $ readTVar socketVar) >>= \case
|
(Fake , _ ) -> when (okFakeAddr dest) $ to (localAddr Fake dest)
|
||||||
Nothing -> pure ()
|
(Localhost, _ ) -> to (localAddr Localhost dest)
|
||||||
Just s -> do
|
(Real , ra) -> ra & \case
|
||||||
res <- io $ tryIOError $ recvFrom s 4096
|
EachYes gala -> io (rsSend aResolvr gala byt)
|
||||||
case res of
|
EachNo addr -> to (ipv4Addr addr)
|
||||||
Left exn -> do
|
|
||||||
-- When we have a socket exception, we need to rebuild the
|
|
||||||
-- socket.
|
|
||||||
logTrace $ displayShow ("(ames) Socket exception. Rebinding.")
|
|
||||||
bindSock socketVar
|
|
||||||
Right (bs, addr) -> do
|
|
||||||
logTrace $ displayShow ("(ames) Received packet from ", addr)
|
|
||||||
case addr of
|
|
||||||
SockAddrInet p a -> atomically (enqueueEv $ hearEv p a bs)
|
|
||||||
_ -> pure ()
|
|
||||||
|
|
||||||
waitPacket socketVar
|
ipv4Addr (Jammed (AAVoid v )) = absurd v
|
||||||
|
ipv4Addr (Jammed (AAIpv4 a p)) = SockAddrInet (fromIntegral p) (unIpv4 a)
|
||||||
|
|
||||||
handleEffect :: AmesDrv -> NewtEf -> RIO e ()
|
|
||||||
handleEffect drv@AmesDrv{..} = \case
|
|
||||||
NewtEfTurf (_id, ()) turfs -> do
|
|
||||||
atomically $ writeTVar aTurfs (Just turfs)
|
|
||||||
|
|
||||||
NewtEfSend (_id, ()) dest (MkBytes bs) -> do
|
|
||||||
atomically (readTVar aTurfs) >>= \case
|
|
||||||
Nothing -> pure ()
|
|
||||||
Just turfs -> do
|
|
||||||
mode <- netMode
|
|
||||||
(sendPacket drv mode dest bs)
|
|
||||||
|
|
||||||
sendPacket :: AmesDrv -> NetworkMode -> AmesDest -> ByteString -> RIO e ()
|
|
||||||
|
|
||||||
sendPacket AmesDrv{..} NoNetwork dest bs = pure ()
|
|
||||||
|
|
||||||
sendPacket AmesDrv{..} Fake dest bs = do
|
|
||||||
when (okayFakeAddr dest) $ atomically $
|
|
||||||
writeTQueue aSendingQueue ((localhostSockAddr Fake dest), bs)
|
|
||||||
|
|
||||||
-- In localhost only mode, regardless of the actual destination, send it to
|
|
||||||
-- localhost.
|
|
||||||
sendPacket AmesDrv{..} Localhost dest bs = atomically $
|
|
||||||
writeTQueue aSendingQueue ((localhostSockAddr Localhost dest), bs)
|
|
||||||
|
|
||||||
sendPacket AmesDrv{..} Real (EachYes galaxy) bs = do
|
|
||||||
galaxies <- readIORef aGalaxies
|
|
||||||
queue <- case M.lookup galaxy galaxies of
|
|
||||||
Just (_, queue) -> pure queue
|
|
||||||
Nothing -> do
|
|
||||||
inQueue <- newTQueueIO
|
|
||||||
thread <- async $ galaxyResolver galaxy aTurfs inQueue aSendingQueue
|
|
||||||
modifyIORef (aGalaxies) (M.insert galaxy (thread, inQueue))
|
|
||||||
pure inQueue
|
|
||||||
|
|
||||||
atomically $ writeTQueue queue bs
|
|
||||||
|
|
||||||
sendPacket AmesDrv{..} Real (EachNo (Jammed (AAIpv4 a p))) bs = do
|
|
||||||
let addr = SockAddrInet (fromIntegral p) (unIpv4 a)
|
|
||||||
atomically $ writeTQueue aSendingQueue (addr, bs)
|
|
||||||
|
|
||||||
sendPacket AmesDrv{..} Real (EachNo (Jammed (AAVoid v))) bs = do
|
|
||||||
pure (absurd v)
|
|
||||||
|
|
||||||
-- An outbound queue of messages. We can only write to a socket from one
|
|
||||||
-- thread, so coalesce those writes here.
|
|
||||||
sendingThread :: TQueue (SockAddr, ByteString)
|
|
||||||
-> TVar (Maybe Socket)
|
|
||||||
-> RIO e ()
|
|
||||||
sendingThread queue socketVar = forever $
|
|
||||||
do
|
|
||||||
(dest, bs) <- atomically $ readTQueue queue
|
|
||||||
logTrace $ displayShow ("(ames) Sending packet to ", dest)
|
|
||||||
sendAll bs dest
|
|
||||||
where
|
|
||||||
sendAll bs dest = do
|
|
||||||
mybSocket <- atomically $ readTVar socketVar
|
|
||||||
case mybSocket of
|
|
||||||
Nothing -> pure ()
|
|
||||||
Just socket -> do
|
|
||||||
bytesSent <- io $ sendTo socket bs dest
|
|
||||||
when (bytesSent /= BS.length bs) $ do
|
|
||||||
sendAll (drop bytesSent bs) dest
|
|
||||||
|
|
||||||
-- Asynchronous thread per galaxy which handles domain resolution, and can
|
|
||||||
-- block its own queue of ByteStrings to send.
|
|
||||||
--
|
|
||||||
-- Maybe perform the resolution asynchronously, injecting into the resolver
|
|
||||||
-- queue as a message.
|
|
||||||
--
|
|
||||||
-- TODO: Figure out how the real haskell time library works.
|
|
||||||
galaxyResolver :: Galaxy -> TVar (Maybe [Turf]) -> TQueue ByteString
|
|
||||||
-> TQueue (SockAddr, ByteString)
|
|
||||||
-> RIO e ()
|
|
||||||
galaxyResolver galaxy turfVar incoming outgoing =
|
|
||||||
loop Nothing Time.unixEpoch
|
|
||||||
where
|
|
||||||
loop :: Maybe SockAddr -> Time.Wen -> RIO e ()
|
|
||||||
loop lastGalaxyIP lastLookupTime = do
|
|
||||||
packet <- atomically $ readTQueue incoming
|
|
||||||
|
|
||||||
checkIP lastGalaxyIP lastLookupTime >>= \case
|
|
||||||
(Nothing, t) -> do
|
|
||||||
-- We've failed to lookup the IP. Drop the outbound packet
|
|
||||||
-- because we have no IP for our galaxy, including possible
|
|
||||||
-- previous IPs.
|
|
||||||
logDebug $ displayShow
|
|
||||||
("(ames) Dropping packet; no ip for galaxy ", galaxy)
|
|
||||||
loop Nothing t
|
|
||||||
(Just ip, t) -> do
|
|
||||||
queueSendToGalaxy ip packet
|
|
||||||
loop (Just ip) t
|
|
||||||
|
|
||||||
checkIP :: Maybe SockAddr -> Time.Wen
|
|
||||||
-> RIO e (Maybe SockAddr, Time.Wen)
|
|
||||||
checkIP lastIP lastLookupTime = do
|
|
||||||
current <- io $ Time.now
|
|
||||||
if (Time.gap current lastLookupTime ^. Time.secs) < 300
|
|
||||||
then pure (lastIP, lastLookupTime)
|
|
||||||
else do
|
|
||||||
toCheck <- fromMaybe [] <$> atomically (readTVar turfVar)
|
|
||||||
mybIp <- resolveFirstIP lastIP toCheck
|
|
||||||
timeAfterResolution <- io $ Time.now
|
|
||||||
pure (mybIp, timeAfterResolution)
|
|
||||||
|
|
||||||
resolveFirstIP :: Maybe SockAddr -> [Turf] -> RIO e (Maybe SockAddr)
|
|
||||||
resolveFirstIP prevIP [] = do
|
|
||||||
stderr $ "ames: czar at " ++ renderGalaxy galaxy ++ ": not found"
|
|
||||||
logDebug $ displayShow
|
|
||||||
("(ames) Failed to lookup IP for ", galaxy)
|
|
||||||
pure prevIP
|
|
||||||
|
|
||||||
resolveFirstIP prevIP (x:xs) = do
|
|
||||||
hostname <- buildDNS galaxy x
|
|
||||||
let portstr = show $ galaxyPort Real galaxy
|
|
||||||
listIPs <- io $ getAddrInfo Nothing (Just hostname) (Just portstr)
|
|
||||||
case listIPs of
|
|
||||||
[] -> resolveFirstIP prevIP xs
|
|
||||||
(y:ys) -> do
|
|
||||||
let sockaddr = Just $ addrAddress y
|
|
||||||
when (sockaddr /= prevIP) $
|
|
||||||
stderr $ "ames: czar " ++ renderGalaxy galaxy ++ ": ip " ++
|
|
||||||
(tshow $ addrAddress y)
|
|
||||||
logDebug $ displayShow
|
|
||||||
("(ames) Looked up ", hostname, portstr, y)
|
|
||||||
pure sockaddr
|
|
||||||
|
|
||||||
buildDNS :: Galaxy -> Turf -> RIO e String
|
|
||||||
buildDNS (Patp g) turf = do
|
|
||||||
let nameWithSig = Ob.renderPatp $ Ob.patp $ fromIntegral g
|
|
||||||
name <- case stripPrefix "~" nameWithSig of
|
|
||||||
Nothing -> error "Urbit.ob didn't produce string with ~"
|
|
||||||
Just x -> pure (unpack x)
|
|
||||||
pure $ name ++ "." ++ (unpack $ _turfText turf)
|
|
||||||
|
|
||||||
queueSendToGalaxy :: SockAddr -> ByteString -> RIO e ()
|
|
||||||
queueSendToGalaxy inet packet = do
|
|
||||||
atomically $ writeTQueue outgoing (inet, packet)
|
|
||||||
|
217
pkg/hs/urbit-king/lib/Urbit/Vere/Ames/DNS.hs
Normal file
217
pkg/hs/urbit-king/lib/Urbit/Vere/Ames/DNS.hs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
{-|
|
||||||
|
Handles sending packets to galaxies. We need to get their IP addresses
|
||||||
|
from DNS, which is more complicated.
|
||||||
|
|
||||||
|
-- Asynchronous thread per galaxy which handles domain resolution, and can
|
||||||
|
-- block its own queue of ByteStrings to send.
|
||||||
|
--
|
||||||
|
-- Maybe perform the resolution asynchronously, injecting into the resolver
|
||||||
|
-- queue as a message.
|
||||||
|
--
|
||||||
|
-- TODO: Figure out how the real haskell time library works.
|
||||||
|
|
||||||
|
-- We've failed to lookup the IP. Drop the outbound packet
|
||||||
|
-- because we have no IP for our galaxy, including possible
|
||||||
|
-- previous IPs.
|
||||||
|
|
||||||
|
{-
|
||||||
|
- Sending Packets to Galaxies.
|
||||||
|
- Each galaxy has it's own DNS resolution thread.
|
||||||
|
- Initially, no threads are started.
|
||||||
|
- To send a message to a galaxy,
|
||||||
|
- Check to see if it already has a resolution thread.
|
||||||
|
- If it does, pass the packet to that thread.
|
||||||
|
- If it doesn't, start a new thread and give it the packet.
|
||||||
|
- Galaxy resolution threads work as follows:
|
||||||
|
- First, they are given:
|
||||||
|
- They know which galaxy they are responsible for.
|
||||||
|
- They have access to the turfs TVar (shared state with Ames driver).
|
||||||
|
- They can be given packets (to be send to their galaxy).
|
||||||
|
- They must be given a way to send UDP packets.
|
||||||
|
- Next, we loop forever
|
||||||
|
- In the loop we track:
|
||||||
|
- the last-known IP address.
|
||||||
|
- the time when we last looked up the IP address.
|
||||||
|
- We wait to be given a packet.
|
||||||
|
- We get the IP address.
|
||||||
|
- If we looked up the IP address in the last 5 minute, use the
|
||||||
|
cached IP address.
|
||||||
|
- Just use the one from last time.
|
||||||
|
- Otherwise,
|
||||||
|
- Do a DNS lookup.
|
||||||
|
- Go through the turf list one item at a time.
|
||||||
|
- Try each one.
|
||||||
|
- If it resolves to one-or-more IP addresses,
|
||||||
|
- Use the first one.
|
||||||
|
- If it resolves to zero IP addresses, move on to the next turf.
|
||||||
|
- If none of the turfs can be used to resolve the IP address,
|
||||||
|
then we don't know where the galaxy is.
|
||||||
|
- Drop the packet.
|
||||||
|
-}
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Ames.DNS
|
||||||
|
( NetworkMode(..)
|
||||||
|
, ResolvServ(..)
|
||||||
|
, resolvServ
|
||||||
|
, galaxyPort
|
||||||
|
, renderGalaxy
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
import Network.Socket hiding (recvFrom, sendTo)
|
||||||
|
import Urbit.Arvo hiding (Fake)
|
||||||
|
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import qualified Urbit.Noun.Time as Time
|
||||||
|
import qualified Urbit.Ob as Ob
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data NetworkMode = Fake | Localhost | Real | NoNetwork
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
data ResolvServ = ResolvServ
|
||||||
|
{ rsSend :: Galaxy -> ByteString -> IO ()
|
||||||
|
, rsKill :: IO ()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
galaxyPort :: NetworkMode -> Galaxy -> PortNumber
|
||||||
|
galaxyPort Fake (Patp g) = fromIntegral g + 31337
|
||||||
|
galaxyPort Localhost (Patp g) = fromIntegral g + 13337
|
||||||
|
galaxyPort Real (Patp g) = fromIntegral g + 13337
|
||||||
|
galaxyPort NoNetwork _ = fromIntegral 0
|
||||||
|
|
||||||
|
turfText :: Turf -> Text
|
||||||
|
turfText = intercalate "." . reverse . fmap unCord . unTurf
|
||||||
|
|
||||||
|
renderGalaxy :: Galaxy -> Text
|
||||||
|
renderGalaxy = Ob.renderPatp . Ob.patp . fromIntegral . unPatp
|
||||||
|
|
||||||
|
galaxyHostname :: Galaxy -> Turf -> Text
|
||||||
|
galaxyHostname g t = galaName g ++ "." ++ turfText t
|
||||||
|
where
|
||||||
|
stripSig :: Text -> Text
|
||||||
|
stripSig inp = fromMaybe inp (stripPrefix "~" inp)
|
||||||
|
|
||||||
|
galaName :: Galaxy -> Text
|
||||||
|
galaName = stripSig . renderGalaxy
|
||||||
|
|
||||||
|
resolv :: Galaxy -> [Turf] -> IO (Maybe (Turf, Text, PortNumber, SockAddr))
|
||||||
|
resolv gal = go
|
||||||
|
where
|
||||||
|
go = \case
|
||||||
|
[] -> pure Nothing
|
||||||
|
turf : turfs -> do
|
||||||
|
let host = galaxyHostname gal turf
|
||||||
|
port = galaxyPort Real gal
|
||||||
|
getAddrInfo Nothing (Just (unpack host)) (Just (show port)) >>= \case
|
||||||
|
[] -> go turfs
|
||||||
|
ip : _ -> pure $ Just (turf, host, port, addrAddress ip)
|
||||||
|
|
||||||
|
doResolv
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> Galaxy
|
||||||
|
-> (Time.Wen, Maybe SockAddr)
|
||||||
|
-> [Turf]
|
||||||
|
-> (Text -> RIO e ())
|
||||||
|
-> RIO e (Maybe SockAddr, Time.Wen)
|
||||||
|
doResolv gal (prevWen, prevIP) turfs stderr = do
|
||||||
|
current <- io $ Time.now
|
||||||
|
if (Time.gap current prevWen ^. Time.secs) < 300
|
||||||
|
then pure (prevIP, prevWen)
|
||||||
|
else do
|
||||||
|
tim <- io (Time.now)
|
||||||
|
io (resolv gal turfs) >>= \case
|
||||||
|
Nothing -> do
|
||||||
|
stderr $ "ames: czar at " ++ galStr ++ ": not found"
|
||||||
|
logDebug $ displayShow ("(ames) Failed to lookup IP for ", gal)
|
||||||
|
pure (prevIP, tim)
|
||||||
|
Just (turf, host, port, addr) -> do
|
||||||
|
when (Just addr /= prevIP) (printCzar addr)
|
||||||
|
logDebug $ displayShow ("(ames) Looked up ", host, port, turf, addr)
|
||||||
|
pure (Just addr, tim)
|
||||||
|
where
|
||||||
|
galStr = renderGalaxy gal
|
||||||
|
printCzar addr = stderr $ "ames: czar " ++ galStr ++ ": ip " ++ tshow addr
|
||||||
|
|
||||||
|
|
||||||
|
resolvWorker
|
||||||
|
:: forall e
|
||||||
|
. HasLogFunc e
|
||||||
|
=> Galaxy
|
||||||
|
-> TVar (Maybe [Turf])
|
||||||
|
-> TVar (Time.Wen, Maybe SockAddr)
|
||||||
|
-> STM ByteString
|
||||||
|
-> (SockAddr -> ByteString -> IO ())
|
||||||
|
-> (Text -> RIO e ())
|
||||||
|
-> RIO e (Async ())
|
||||||
|
resolvWorker gal vTurfs vLast waitMsg send stderr = async (forever go)
|
||||||
|
where
|
||||||
|
logDrop =
|
||||||
|
logDebug $ displayShow ("(ames) Dropping packet; no ip for galaxy ", gal)
|
||||||
|
|
||||||
|
go :: RIO e ()
|
||||||
|
go = do
|
||||||
|
(packt, turfs, (lastTime, lastAddr)) <- atomically
|
||||||
|
((,,) <$> waitMsg <*> readTVar vTurfs <*> readTVar vLast)
|
||||||
|
|
||||||
|
(newAddr, newTime) <- doResolv gal
|
||||||
|
(lastTime, lastAddr)
|
||||||
|
(fromMaybe [] turfs)
|
||||||
|
stderr
|
||||||
|
|
||||||
|
maybe logDrop (\ip -> io (send ip packt)) newAddr
|
||||||
|
|
||||||
|
atomically $ writeTVar vLast (newTime, newAddr)
|
||||||
|
|
||||||
|
|
||||||
|
resolvServ
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> TVar (Maybe [Turf])
|
||||||
|
-> (SockAddr -> ByteString -> IO ())
|
||||||
|
-> (Text -> RIO e ())
|
||||||
|
-> RIO e ResolvServ
|
||||||
|
resolvServ vTurfs send stderr = do
|
||||||
|
vGala <- newTVarIO (mempty :: Map Galaxy (Async (), TQueue ByteString))
|
||||||
|
vDead <- newTVarIO False
|
||||||
|
envir <- ask
|
||||||
|
|
||||||
|
let spawnWorker :: Galaxy -> IO (Async (), TQueue ByteString)
|
||||||
|
spawnWorker gal = runRIO envir $ do
|
||||||
|
que <- newTQueueIO
|
||||||
|
las <- newTVarIO (Time.unixEpoch, Nothing)
|
||||||
|
tid <- resolvWorker gal vTurfs las (readTQueue que) send stderr
|
||||||
|
pure (tid, que)
|
||||||
|
|
||||||
|
let getWorker :: Galaxy -> IO (Async (), TQueue ByteString)
|
||||||
|
getWorker gal = do
|
||||||
|
(fmap (lookup gal) $ atomically $ readTVar vGala) >>= \case
|
||||||
|
Just (tid, que) -> do
|
||||||
|
pure (tid, que)
|
||||||
|
Nothing -> do
|
||||||
|
(tid, que) <- spawnWorker gal
|
||||||
|
atomically $ modifyTVar' vGala (M.insert gal (tid, que))
|
||||||
|
pure (tid, que)
|
||||||
|
|
||||||
|
let doSend :: Galaxy -> ByteString -> IO ()
|
||||||
|
doSend gal byt = do
|
||||||
|
dead <- atomically (readTVar vDead)
|
||||||
|
unless dead $ do
|
||||||
|
(_, que) <- getWorker gal
|
||||||
|
atomically (writeTQueue que byt)
|
||||||
|
|
||||||
|
let doKill :: IO ()
|
||||||
|
doKill = do
|
||||||
|
galas <- atomically $ do
|
||||||
|
writeTVar vDead True
|
||||||
|
readTVar vGala
|
||||||
|
for_ galas (cancel . fst)
|
||||||
|
|
||||||
|
pure (ResolvServ doSend doKill)
|
243
pkg/hs/urbit-king/lib/Urbit/Vere/Ames/UDP.hs
Normal file
243
pkg/hs/urbit-king/lib/Urbit/Vere/Ames/UDP.hs
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
{- |
|
||||||
|
Raw UDP Server used by Ames driver.
|
||||||
|
|
||||||
|
1. Opens a UDP socket and makes sure that it stays open.
|
||||||
|
|
||||||
|
- If can't open the port, wait and try again repeatedly.
|
||||||
|
- If there is an error reading or writting from the open socket,
|
||||||
|
close it and open another.
|
||||||
|
|
||||||
|
2. Receives packets from the socket.
|
||||||
|
|
||||||
|
- When packets come in from the socket, they go into a bounded queue.
|
||||||
|
- If the queue is full, the packet is dropped.
|
||||||
|
- If the socket is closed, wait and try again repeatedly.
|
||||||
|
- `usRecv` gets the first packet from the queue.
|
||||||
|
|
||||||
|
3. Sends packets to the socket.
|
||||||
|
|
||||||
|
- Packets sent to `usSend` enter a bounded queue.
|
||||||
|
- If that queue is full, the packet is dropped.
|
||||||
|
- Packets are taken off the queue one at a time.
|
||||||
|
- If the socket is closed (or broken), the packet is dropped.
|
||||||
|
|
||||||
|
4. Runs until `usKill` is run, then all threads are killed and the
|
||||||
|
socket is closed.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Ames.UDP
|
||||||
|
( UdpServ(..)
|
||||||
|
, fakeUdpServ
|
||||||
|
, realUdpServ
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
import Network.Socket hiding (recvFrom, sendTo)
|
||||||
|
|
||||||
|
import Control.Monad.STM (retry)
|
||||||
|
import Network.Socket.ByteString (recvFrom, sendTo)
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data UdpServ = UdpServ
|
||||||
|
{ usSend :: SockAddr -> ByteString -> IO ()
|
||||||
|
, usRecv :: STM (PortNumber, HostAddress, ByteString)
|
||||||
|
, usKill :: IO ()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Writes to queue and returns `True` unless the queue is full, then do
|
||||||
|
nothing and return `False`.
|
||||||
|
-}
|
||||||
|
tryWriteTBQueue :: TBQueue x -> x -> STM Bool
|
||||||
|
tryWriteTBQueue q x = do
|
||||||
|
isFullTBQueue q >>= \case
|
||||||
|
True -> pure False
|
||||||
|
False -> writeTBQueue q x $> True
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Open a UDP socket and bind it to a port
|
||||||
|
-}
|
||||||
|
doBind :: PortNumber -> HostAddress -> IO (Either IOError Socket)
|
||||||
|
doBind por hos = tryIOError $ do
|
||||||
|
sok <- io $ socket AF_INET Datagram defaultProtocol
|
||||||
|
() <- io $ bind sok (SockAddrInet por hos)
|
||||||
|
pure sok
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Open a UDP socket and bind it to a port.
|
||||||
|
|
||||||
|
If this fails, wait 250ms and repeat forever.
|
||||||
|
-}
|
||||||
|
forceBind :: HasLogFunc e => PortNumber -> HostAddress -> RIO e Socket
|
||||||
|
forceBind por hos = go
|
||||||
|
where
|
||||||
|
go = do
|
||||||
|
logDebug (display ("AMES: UDP: Opening socket on port " <> tshow por))
|
||||||
|
io (doBind por hos) >>= \case
|
||||||
|
Right sk -> do
|
||||||
|
logDebug (display ("AMES: UDP: Opened socket on port " <> tshow por))
|
||||||
|
pure sk
|
||||||
|
Left err -> do
|
||||||
|
logDebug (display ("AMES: UDP: " <> tshow err))
|
||||||
|
logDebug ("AMES: UDP: Failed to open UDP socket. Waiting")
|
||||||
|
threadDelay 250_000
|
||||||
|
go
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Attempt to send a packet to a socket.
|
||||||
|
|
||||||
|
If it fails, return `False`. Otherwise, return `True`.
|
||||||
|
-}
|
||||||
|
sendPacket :: HasLogFunc e => ByteString -> SockAddr -> Socket -> RIO e Bool
|
||||||
|
sendPacket fullBytes adr sok = do
|
||||||
|
logDebug $ displayShow ("AMES", "UDP", "Sending packet.")
|
||||||
|
res <- io $ tryIOError $ go fullBytes
|
||||||
|
case res of
|
||||||
|
Left err -> do
|
||||||
|
logError $ displayShow ("AMES", "UDP", "Failed to send packet", err)
|
||||||
|
pure False
|
||||||
|
Right () -> do
|
||||||
|
logDebug $ displayShow ("AMES", "UDP", "Packet sent.")
|
||||||
|
pure True
|
||||||
|
where
|
||||||
|
go byt = do
|
||||||
|
sent <- sendTo sok byt adr
|
||||||
|
when (sent /= length byt) $ do
|
||||||
|
go (drop sent byt)
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Attempt to receive a packet from a socket.
|
||||||
|
|
||||||
|
- If an exception is throw, return `Left exn`.
|
||||||
|
- If it wasn't an IPv4 packet, return `Right Nothing`.
|
||||||
|
- Otherwise, return `Right (Just packet)`.
|
||||||
|
-}
|
||||||
|
recvPacket
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> Socket
|
||||||
|
-> RIO e (Either IOError (Maybe (ByteString, PortNumber, HostAddress)))
|
||||||
|
recvPacket sok = do
|
||||||
|
io (tryIOError $ recvFrom sok 4096) <&> \case
|
||||||
|
Left exn -> Left exn
|
||||||
|
Right (b, SockAddrInet p a) -> Right (Just (b, p, a))
|
||||||
|
Right (_, _ ) -> Right Nothing
|
||||||
|
|
||||||
|
|
||||||
|
-- Fake Server for No-Networking Mode ------------------------------------------
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Fake UDP API for no-networking configurations.
|
||||||
|
-}
|
||||||
|
fakeUdpServ :: HasLogFunc e => RIO e UdpServ
|
||||||
|
fakeUdpServ = do
|
||||||
|
logDebug $ displayShow ("AMES", "UDP", "\"Starting\" fake UDP server.")
|
||||||
|
pure UdpServ { .. }
|
||||||
|
where
|
||||||
|
usSend = \_ _ -> pure ()
|
||||||
|
usRecv = retry
|
||||||
|
usKill = pure ()
|
||||||
|
|
||||||
|
|
||||||
|
-- Real Server -----------------------------------------------------------------
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Real UDP server. See module-level docs.
|
||||||
|
-}
|
||||||
|
realUdpServ
|
||||||
|
:: forall e . HasLogFunc e => PortNumber -> HostAddress -> RIO e UdpServ
|
||||||
|
realUdpServ por hos = do
|
||||||
|
logDebug $ displayShow ("AMES", "UDP", "Starting real UDP server.")
|
||||||
|
|
||||||
|
env <- ask
|
||||||
|
|
||||||
|
vSock <- newTVarIO Nothing
|
||||||
|
vFail <- newEmptyTMVarIO
|
||||||
|
qSend <- newTBQueueIO 100 -- TODO Tuning
|
||||||
|
qRecv <- newTBQueueIO 100 -- TODO Tuning
|
||||||
|
|
||||||
|
{-
|
||||||
|
If reading or writing to a socket fails, unbind it and tell the
|
||||||
|
socket-open thread to close it and open another.
|
||||||
|
|
||||||
|
This is careful about edge-cases. In any of these cases, do nothing.
|
||||||
|
|
||||||
|
- If vSock isn't set to the socket we used, do nothing.
|
||||||
|
- If vFail is already set (another thread signaled failure already).
|
||||||
|
-}
|
||||||
|
let signalBrokenSocket :: Socket -> RIO e ()
|
||||||
|
signalBrokenSocket sock = do
|
||||||
|
logDebug $ displayShow ("AMES", "UDP"
|
||||||
|
, "Socket broken. Requesting new socket"
|
||||||
|
)
|
||||||
|
atomically $ do
|
||||||
|
mSock <- readTVar vSock
|
||||||
|
mFail <- tryReadTMVar vFail
|
||||||
|
when (mSock == Just sock && mFail == Nothing) $ do
|
||||||
|
putTMVar vFail sock
|
||||||
|
writeTVar vSock Nothing
|
||||||
|
|
||||||
|
enqueueRecvPacket :: PortNumber -> HostAddress -> ByteString -> RIO e ()
|
||||||
|
enqueueRecvPacket p a b = do
|
||||||
|
did <- atomically (tryWriteTBQueue qRecv (p, a, b))
|
||||||
|
when (did == False) $ do
|
||||||
|
logWarn $ displayShow $ ("AMES", "UDP",)
|
||||||
|
"Dropping inbound packet because queue is full."
|
||||||
|
|
||||||
|
enqueueSendPacket :: SockAddr -> ByteString -> RIO e ()
|
||||||
|
enqueueSendPacket a b = do
|
||||||
|
did <- atomically (tryWriteTBQueue qSend (a, b))
|
||||||
|
when (did == False) $ do
|
||||||
|
logWarn "AMES: UDP: Dropping outbound packet because queue is full."
|
||||||
|
|
||||||
|
tOpen <- async $ forever $ do
|
||||||
|
sk <- forceBind por hos
|
||||||
|
atomically (writeTVar vSock (Just sk))
|
||||||
|
broken <- atomically (takeTMVar vFail)
|
||||||
|
logWarn "AMES: UDP: Closing broken socket."
|
||||||
|
io (close broken)
|
||||||
|
|
||||||
|
tSend <- async $ forever $ join $ atomically $ do
|
||||||
|
(adr, byt) <- readTBQueue qSend
|
||||||
|
readTVar vSock <&> \case
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just sk -> do
|
||||||
|
okay <- sendPacket byt adr sk
|
||||||
|
unless okay (signalBrokenSocket sk)
|
||||||
|
|
||||||
|
tRecv <- async $ forever $ do
|
||||||
|
atomically (readTVar vSock) >>= \case
|
||||||
|
Nothing -> threadDelay 100_000
|
||||||
|
Just sk -> do
|
||||||
|
recvPacket sk >>= \case
|
||||||
|
Left exn -> do
|
||||||
|
logError "AMES: UDP: Failed to receive packet"
|
||||||
|
signalBrokenSocket sk
|
||||||
|
Right Nothing -> do
|
||||||
|
logError "AMES: UDP: Dropping non-ipv4 packet"
|
||||||
|
pure ()
|
||||||
|
Right (Just (b, p, a)) -> do
|
||||||
|
logDebug "AMES: UDP: Received packet."
|
||||||
|
enqueueRecvPacket p a b
|
||||||
|
|
||||||
|
let shutdown = do
|
||||||
|
logDebug "AMES: UDP: Shutting down. (killing threads)"
|
||||||
|
cancel tOpen
|
||||||
|
cancel tSend
|
||||||
|
cancel tRecv
|
||||||
|
logDebug "AMES: UDP: Shutting down. (closing socket)"
|
||||||
|
io $ join $ atomically $ do
|
||||||
|
res <- readTVar vSock <&> maybe (pure ()) close
|
||||||
|
writeTVar vSock Nothing
|
||||||
|
pure res
|
||||||
|
|
||||||
|
pure $ UdpServ { usSend = \a b -> runRIO env (enqueueSendPacket a b)
|
||||||
|
, usRecv = readTBQueue qRecv
|
||||||
|
, usKill = runRIO env shutdown
|
||||||
|
}
|
@ -2,21 +2,33 @@
|
|||||||
Behn: Timer Driver
|
Behn: Timer Driver
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Behn (behn) where
|
module Urbit.Vere.Behn (behn, DriverApi(..), behn') where
|
||||||
|
|
||||||
import Urbit.Arvo hiding (Behn)
|
import Urbit.Arvo hiding (Behn)
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Urbit.Time (Wen)
|
import Urbit.King.App (HasKingId(..), HasPierEnv(..))
|
||||||
import Urbit.Timer (Timer)
|
import Urbit.Noun.Time (Wen)
|
||||||
|
import Urbit.Timer (Timer)
|
||||||
|
|
||||||
import qualified Urbit.Time as Time
|
import qualified Urbit.Noun.Time as Time
|
||||||
import qualified Urbit.Timer as Timer
|
import qualified Urbit.Timer as Timer
|
||||||
|
|
||||||
|
|
||||||
-- Behn Stuff ------------------------------------------------------------------
|
-- Behn Stuff ------------------------------------------------------------------
|
||||||
|
|
||||||
|
behn' :: HasPierEnv e => RIO e ([Ev], RAcquire e (DriverApi BehnEf))
|
||||||
|
behn' = do
|
||||||
|
env <- ask
|
||||||
|
pure ([bornEv (fromIntegral (env ^. kingIdL))], runDriver env)
|
||||||
|
where
|
||||||
|
runDriver env = do
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
diOnEffect <- liftAcquire (behn env (writeTQueue ventQ))
|
||||||
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
bornEv :: KingId -> Ev
|
bornEv :: KingId -> Ev
|
||||||
bornEv king = EvBlip $ BlipEvBehn $ BehnEvBorn (king, ()) ()
|
bornEv king = EvBlip $ BlipEvBehn $ BehnEvBorn (king, ()) ()
|
||||||
|
|
||||||
@ -25,16 +37,22 @@ wakeEv = EvBlip $ BlipEvBehn $ BehnEvWake () ()
|
|||||||
|
|
||||||
sysTime = view Time.systemTime
|
sysTime = view Time.systemTime
|
||||||
|
|
||||||
behn :: KingId -> QueueEv -> ([Ev], Acquire (EffCb e BehnEf))
|
wakeErr :: WorkError -> IO ()
|
||||||
behn king enqueueEv =
|
wakeErr _ = pure ()
|
||||||
(initialEvents, runBehn)
|
|
||||||
where
|
|
||||||
initialEvents = [bornEv king]
|
|
||||||
|
|
||||||
runBehn :: Acquire (EffCb e BehnEf)
|
behn
|
||||||
|
:: HasKingId e
|
||||||
|
=> e
|
||||||
|
-> (EvErr -> STM ())
|
||||||
|
-> Acquire (BehnEf -> IO ())
|
||||||
|
behn env enqueueEv = runBehn
|
||||||
|
where
|
||||||
|
king = fromIntegral (env ^. kingIdL)
|
||||||
|
|
||||||
|
runBehn :: Acquire (BehnEf -> IO ())
|
||||||
runBehn = do
|
runBehn = do
|
||||||
tim <- mkAcquire Timer.init Timer.stop
|
tim <- mkAcquire Timer.init Timer.stop
|
||||||
pure (handleEf tim)
|
pure (runRIO env . handleEf tim)
|
||||||
|
|
||||||
handleEf :: Timer -> BehnEf -> RIO e ()
|
handleEf :: Timer -> BehnEf -> RIO e ()
|
||||||
handleEf b = io . \case
|
handleEf b = io . \case
|
||||||
@ -45,4 +63,4 @@ behn king enqueueEv =
|
|||||||
doze :: Timer -> Maybe Wen -> IO ()
|
doze :: Timer -> Maybe Wen -> IO ()
|
||||||
doze tim = \case
|
doze tim = \case
|
||||||
Nothing -> Timer.stop tim
|
Nothing -> Timer.stop tim
|
||||||
Just t -> Timer.start tim (sysTime t) $ atomically (enqueueEv wakeEv)
|
Just t -> Timer.start tim (sysTime t) $ atomically (enqueueEv (EvErr wakeEv wakeErr))
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
UNIX Filesystem Driver
|
UNIX Filesystem Driver
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Clay (clay) where
|
module Urbit.Vere.Clay
|
||||||
|
( clay
|
||||||
|
, clay'
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.Arvo hiding (Term)
|
import Urbit.Arvo hiding (Term)
|
||||||
import Urbit.King.Config
|
import Urbit.King.App
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
@ -112,26 +116,52 @@ buildActionListFromDifferences fp snapshot = do
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
clay :: forall e. (HasPierConfig e, HasLogFunc e)
|
_boatFailed :: e -> WorkError -> IO ()
|
||||||
=> KingId -> QueueEv -> ([Ev], RAcquire e (EffCb e SyncEf))
|
_boatFailed env _ = runRIO env $ do
|
||||||
clay king enqueueEv =
|
pure () -- TODO What can we do?
|
||||||
|
|
||||||
|
clay'
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> RIO e ([Ev], RAcquire e (DriverApi SyncEf))
|
||||||
|
clay' = do
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
env <- ask
|
||||||
|
|
||||||
|
let (bornEvs, startDriver) = clay env (writeTQueue ventQ)
|
||||||
|
|
||||||
|
let runDriver = do
|
||||||
|
diOnEffect <- startDriver
|
||||||
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
|
pure (bornEvs, runDriver)
|
||||||
|
|
||||||
|
clay
|
||||||
|
:: forall e
|
||||||
|
. (HasPierConfig e, HasLogFunc e, HasKingId e)
|
||||||
|
=> e
|
||||||
|
-> (EvErr -> STM ())
|
||||||
|
-> ([Ev], RAcquire e (SyncEf -> IO ()))
|
||||||
|
clay env plan =
|
||||||
(initialEvents, runSync)
|
(initialEvents, runSync)
|
||||||
where
|
where
|
||||||
initialEvents = [
|
king = fromIntegral (env ^. kingIdL)
|
||||||
EvBlip $ BlipEvBoat $ BoatEvBoat () ()
|
|
||||||
-- TODO: In the case of -A, we need to read all the data from the
|
|
||||||
-- specified directory and shove it into an %into event.
|
|
||||||
]
|
|
||||||
|
|
||||||
runSync :: RAcquire e (EffCb e SyncEf)
|
boatEv = EvBlip $ BlipEvBoat $ BoatEvBoat () ()
|
||||||
|
|
||||||
|
-- TODO: In the case of -A, we need to read all the data from the
|
||||||
|
-- specified directory and shove it into an %into event.
|
||||||
|
initialEvents = [boatEv]
|
||||||
|
|
||||||
|
runSync :: RAcquire e (SyncEf -> IO ())
|
||||||
runSync = handleEffect <$> mkRAcquire start stop
|
runSync = handleEffect <$> mkRAcquire start stop
|
||||||
|
|
||||||
start :: RIO e ClayDrv
|
start :: RIO e ClayDrv
|
||||||
start = ClayDrv <$> newTVarIO mempty
|
start = ClayDrv <$> newTVarIO mempty
|
||||||
stop c = pure ()
|
stop c = pure ()
|
||||||
|
|
||||||
handleEffect :: ClayDrv -> SyncEf -> RIO e ()
|
handleEffect :: ClayDrv -> SyncEf -> IO ()
|
||||||
handleEffect cd = \case
|
handleEffect cd = runRIO env . \case
|
||||||
SyncEfHill _ mountPoints -> do
|
SyncEfHill _ mountPoints -> do
|
||||||
logDebug $ displayShow ("(clay) known mount points:", mountPoints)
|
logDebug $ displayShow ("(clay) known mount points:", mountPoints)
|
||||||
pierPath <- view pierPathL
|
pierPath <- view pierPathL
|
||||||
@ -151,8 +181,15 @@ clay king enqueueEv =
|
|||||||
logDebug $ displayShow ("(clay) dirk actions: ", actions)
|
logDebug $ displayShow ("(clay) dirk actions: ", actions)
|
||||||
|
|
||||||
let !intoList = map (actionsToInto dir) actions
|
let !intoList = map (actionsToInto dir) actions
|
||||||
atomically $ enqueueEv $ EvBlip $ BlipEvSync $
|
|
||||||
SyncEvInto (Some (king, ())) desk False intoList
|
let syncEv = EvBlip
|
||||||
|
$ BlipEvSync
|
||||||
|
$ SyncEvInto (Some (king, ())) desk False intoList
|
||||||
|
|
||||||
|
let syncFailed _ = pure ()
|
||||||
|
|
||||||
|
atomically $ plan (EvErr syncEv syncFailed)
|
||||||
|
|
||||||
|
|
||||||
atomically $ modifyTVar
|
atomically $ modifyTVar
|
||||||
(cdMountPoints cd)
|
(cdMountPoints cd)
|
||||||
|
364
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre.hs
Normal file
364
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre.hs
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
{-|
|
||||||
|
Eyre: Http Server Driver
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre
|
||||||
|
( eyre
|
||||||
|
, eyre'
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude hiding (Builder)
|
||||||
|
|
||||||
|
import Urbit.Arvo hiding (ServerId, reqUrl, secure)
|
||||||
|
import Urbit.King.App (HasKingId(..), HasPierEnv(..))
|
||||||
|
import Urbit.King.Config
|
||||||
|
import Urbit.Vere.Eyre.Multi
|
||||||
|
import Urbit.Vere.Eyre.PortsFile
|
||||||
|
import Urbit.Vere.Eyre.Serv
|
||||||
|
import Urbit.Vere.Eyre.Service
|
||||||
|
import Urbit.Vere.Eyre.Wai
|
||||||
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
|
import Data.PEM (pemParseBS, pemWriteBS)
|
||||||
|
import RIO.Prelude (decodeUtf8Lenient)
|
||||||
|
import System.Random (randomIO)
|
||||||
|
import Urbit.Vere.Http (convertHeaders, unconvertHeaders)
|
||||||
|
|
||||||
|
import qualified Network.HTTP.Types as H
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type HasShipEnv e = (HasLogFunc e, HasNetworkConfig e, HasPierConfig e)
|
||||||
|
|
||||||
|
type ReqId = UD
|
||||||
|
|
||||||
|
newtype Drv = Drv (MVar (Maybe Serv))
|
||||||
|
|
||||||
|
data SockOpts = SockOpts
|
||||||
|
{ soLocalhost :: Bool
|
||||||
|
, soWhich :: ServPort
|
||||||
|
}
|
||||||
|
|
||||||
|
data PortsToTry = PortsToTry
|
||||||
|
{ pttSec :: SockOpts
|
||||||
|
, pttIns :: SockOpts
|
||||||
|
, pttLop :: SockOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
data Serv = Serv
|
||||||
|
{ sServId :: ServId
|
||||||
|
, sConfig :: HttpServerConf
|
||||||
|
, sLop :: ServApi
|
||||||
|
, sIns :: ServApi
|
||||||
|
, sSec :: Maybe ServApi
|
||||||
|
, sPorts :: Ports
|
||||||
|
, sPortsFile :: FilePath
|
||||||
|
, sLiveReqs :: TVar LiveReqs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Utilities for Constructing Events -------------------------------------------
|
||||||
|
|
||||||
|
servEv :: HttpServerEv -> Ev
|
||||||
|
servEv = EvBlip . BlipEvHttpServer
|
||||||
|
|
||||||
|
bornEv :: KingId -> Ev
|
||||||
|
bornEv king = servEv $ HttpServerEvBorn (king, ()) ()
|
||||||
|
|
||||||
|
liveEv :: ServId -> Ports -> Ev
|
||||||
|
liveEv sId Ports {..} = servEv $ HttpServerEvLive (sId, ()) pHttp pHttps
|
||||||
|
|
||||||
|
cancelEv :: ServId -> ReqId -> EvErr
|
||||||
|
cancelEv sId reqId =
|
||||||
|
EvErr (servEv (HttpServerEvCancelRequest (sId, reqId, 1, ()) ())) cancelFailed
|
||||||
|
|
||||||
|
cancelFailed :: WorkError -> IO ()
|
||||||
|
cancelFailed _ = pure ()
|
||||||
|
|
||||||
|
reqEv :: ServId -> ReqId -> WhichServer -> Address -> HttpRequest -> Ev
|
||||||
|
reqEv sId reqId which addr req = case which of
|
||||||
|
Loopback -> servEv $ HttpServerEvRequestLocal (sId, reqId, 1, ())
|
||||||
|
$ HttpServerReq False addr req
|
||||||
|
_ -> servEv $ HttpServerEvRequest (sId, reqId, 1, ())
|
||||||
|
$ HttpServerReq (which == Secure) addr req
|
||||||
|
|
||||||
|
|
||||||
|
-- Based on Pier+Config, which ports should each server run? -------------------
|
||||||
|
|
||||||
|
httpServerPorts :: HasShipEnv e => Bool -> RIO e PortsToTry
|
||||||
|
httpServerPorts fak = do
|
||||||
|
ins <- view (networkConfigL . ncHttpPort . to (fmap fromIntegral))
|
||||||
|
sec <- view (networkConfigL . ncHttpsPort . to (fmap fromIntegral))
|
||||||
|
lop <- view (networkConfigL . ncLocalPort . to (fmap fromIntegral))
|
||||||
|
localMode <- view (networkConfigL . ncNetMode . to (== NMLocalhost))
|
||||||
|
|
||||||
|
let local = localMode || fak
|
||||||
|
|
||||||
|
let pttSec = case (sec, fak) of
|
||||||
|
(Just p , _ ) -> SockOpts local (SPChoices $ singleton p)
|
||||||
|
(Nothing, False) -> SockOpts local (SPChoices (443 :| [8443 .. 8453]))
|
||||||
|
(Nothing, True ) -> SockOpts local (SPChoices (8443 :| [8444 .. 8453]))
|
||||||
|
|
||||||
|
let pttIns = case (ins, fak) of
|
||||||
|
(Just p , _ ) -> SockOpts local (SPChoices $ singleton p)
|
||||||
|
(Nothing, False) -> SockOpts local (SPChoices (80 :| [8080 .. 8090]))
|
||||||
|
(Nothing, True ) -> SockOpts local (SPChoices (8080 :| [8081 .. 8090]))
|
||||||
|
|
||||||
|
let pttLop = case (lop, fak) of
|
||||||
|
(Just p , _) -> SockOpts local (SPChoices $ singleton p)
|
||||||
|
(Nothing, _) -> SockOpts local SPAnyPort
|
||||||
|
|
||||||
|
pure (PortsToTry { .. })
|
||||||
|
|
||||||
|
|
||||||
|
-- Convert Between Urbit and WAI types. ----------------------------------------
|
||||||
|
|
||||||
|
parseTlsConfig :: (Key, Cert) -> Maybe TlsConfig
|
||||||
|
parseTlsConfig (PEM key, PEM certs) = do
|
||||||
|
let (cerByt, keyByt) = (wainBytes certs, wainBytes key)
|
||||||
|
pems <- pemParseBS cerByt & either (const Nothing) Just
|
||||||
|
(cert, chain) <- case pems of
|
||||||
|
[] -> Nothing
|
||||||
|
p : ps -> pure (pemWriteBS p, pemWriteBS <$> ps)
|
||||||
|
pure $ TlsConfig keyByt cert chain
|
||||||
|
where
|
||||||
|
wainBytes :: Wain -> ByteString
|
||||||
|
wainBytes = encodeUtf8 . unWain
|
||||||
|
|
||||||
|
parseHttpEvent :: HttpEvent -> [RespAct]
|
||||||
|
parseHttpEvent = \case
|
||||||
|
Start h b True -> [RAFull (hSta h) (hHdr h) (fByt $ fromMaybe "" b)]
|
||||||
|
Start h b False -> [RAHead (hSta h) (hHdr h) (fByt $ fromMaybe "" b)]
|
||||||
|
Cancel () -> [RADone]
|
||||||
|
Continue b done -> toList (RABloc . fByt <$> b)
|
||||||
|
<> if done then [RADone] else []
|
||||||
|
where
|
||||||
|
hHdr :: ResponseHeader -> [H.Header]
|
||||||
|
hHdr = unconvertHeaders . headers
|
||||||
|
|
||||||
|
hSta :: ResponseHeader -> H.Status
|
||||||
|
hSta = toEnum . fromIntegral . statusCode
|
||||||
|
|
||||||
|
fByt :: File -> ByteString
|
||||||
|
fByt = unOcts . unFile
|
||||||
|
|
||||||
|
requestEvent :: ServId -> WhichServer -> Word64 -> ReqInfo -> Ev
|
||||||
|
requestEvent srvId which reqId ReqInfo{..} = reqEv srvId reqUd which riAdr evReq
|
||||||
|
where
|
||||||
|
evBod = bodFile riBod
|
||||||
|
evHdr = convertHeaders riHdr
|
||||||
|
evUrl = Cord (decodeUtf8Lenient riUrl)
|
||||||
|
evReq = HttpRequest riMet evUrl evHdr evBod
|
||||||
|
reqUd = fromIntegral reqId
|
||||||
|
|
||||||
|
bodFile :: ByteString -> Maybe File
|
||||||
|
bodFile "" = Nothing
|
||||||
|
bodFile bs = Just $ File $ Octs bs
|
||||||
|
|
||||||
|
|
||||||
|
-- Running Servers -------------------------------------------------------------
|
||||||
|
|
||||||
|
execRespActs :: HasLogFunc e => Drv -> Ship -> Word64 -> HttpEvent -> RIO e ()
|
||||||
|
execRespActs (Drv v) who reqId ev = readMVar v >>= \case
|
||||||
|
Nothing -> logError "Got a response to a request that does not exist."
|
||||||
|
Just sv -> do
|
||||||
|
logDebug $ displayShow ev
|
||||||
|
for_ (parseHttpEvent ev) $ \act -> do
|
||||||
|
atomically (routeRespAct who (sLiveReqs sv) reqId act)
|
||||||
|
|
||||||
|
startServ
|
||||||
|
:: (HasPierConfig e, HasLogFunc e, HasNetworkConfig e)
|
||||||
|
=> MultiEyreApi
|
||||||
|
-> Ship
|
||||||
|
-> Bool
|
||||||
|
-> HttpServerConf
|
||||||
|
-> (EvErr -> STM ())
|
||||||
|
-> RIO e Serv
|
||||||
|
startServ multi who isFake conf plan = do
|
||||||
|
logDebug (displayShow ("EYRE", "startServ"))
|
||||||
|
|
||||||
|
let vLive = meaLive multi
|
||||||
|
|
||||||
|
srvId <- io $ ServId . UV . fromIntegral <$> (randomIO :: IO Word32)
|
||||||
|
|
||||||
|
let mTls = hscSecure conf >>= parseTlsConfig
|
||||||
|
|
||||||
|
mCre <- mTls & \case
|
||||||
|
Nothing -> pure Nothing
|
||||||
|
Just tc -> configCreds tc & \case
|
||||||
|
Right rs -> pure (Just (tc, rs))
|
||||||
|
Left err -> do
|
||||||
|
logError "Couldn't Load TLS Credentials."
|
||||||
|
pure Nothing
|
||||||
|
|
||||||
|
ptt <- httpServerPorts isFake
|
||||||
|
|
||||||
|
{-
|
||||||
|
TODO If configuration requests a redirect, get the HTTPS port (if
|
||||||
|
configuration specifies a specific port, use that. Otherwise, wait
|
||||||
|
for the HTTPS server to start and then use the port that it chose).
|
||||||
|
and run an HTTP server that simply redirects to the HTTPS server.
|
||||||
|
-}
|
||||||
|
let secRedi = Nothing
|
||||||
|
|
||||||
|
let soHost :: SockOpts -> ServHost
|
||||||
|
soHost so = if soLocalhost so then SHLocalhost else SHAnyHostOk
|
||||||
|
|
||||||
|
noHttp <- view (networkConfigL . ncNoHttp)
|
||||||
|
noHttps <- view (networkConfigL . ncNoHttps)
|
||||||
|
|
||||||
|
let reqEvFailed _ = pure ()
|
||||||
|
|
||||||
|
let onReq :: WhichServer -> Ship -> Word64 -> ReqInfo -> STM ()
|
||||||
|
onReq which _ship reqId reqInfo =
|
||||||
|
plan $ EvErr (requestEvent srvId which reqId reqInfo) reqEvFailed
|
||||||
|
|
||||||
|
let onKilReq :: Ship -> Word64 -> STM ()
|
||||||
|
onKilReq _ship = plan . cancelEv srvId . fromIntegral
|
||||||
|
|
||||||
|
logDebug (displayShow ("EYRE", "joinMultiEyre", who, mTls, mCre))
|
||||||
|
|
||||||
|
atomically (joinMultiEyre multi who mCre onReq onKilReq)
|
||||||
|
|
||||||
|
logDebug $ displayShow ("EYRE", "Starting loopback server")
|
||||||
|
lop <- serv vLive $ ServConf
|
||||||
|
{ scHost = soHost (pttLop ptt)
|
||||||
|
, scPort = soWhich (pttLop ptt)
|
||||||
|
, scRedi = Nothing
|
||||||
|
, scFake = False
|
||||||
|
, scType = STHttp who $ ReqApi
|
||||||
|
{ rcReq = onReq Loopback
|
||||||
|
, rcKil = onKilReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug $ displayShow ("EYRE", "Starting insecure server")
|
||||||
|
ins <- serv vLive $ ServConf
|
||||||
|
{ scHost = soHost (pttIns ptt)
|
||||||
|
, scPort = soWhich (pttIns ptt)
|
||||||
|
, scRedi = secRedi
|
||||||
|
, scFake = noHttp
|
||||||
|
, scType = STHttp who $ ReqApi
|
||||||
|
{ rcReq = onReq Insecure
|
||||||
|
, rcKil = onKilReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mSec <- for mTls $ \tls -> do
|
||||||
|
logDebug "Starting secure server"
|
||||||
|
serv vLive $ ServConf
|
||||||
|
{ scHost = soHost (pttSec ptt)
|
||||||
|
, scPort = soWhich (pttSec ptt)
|
||||||
|
, scRedi = Nothing
|
||||||
|
, scFake = noHttps
|
||||||
|
, scType = STHttps who tls $ ReqApi
|
||||||
|
{ rcReq = onReq Secure
|
||||||
|
, rcKil = onKilReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pierPath <- view pierPathL
|
||||||
|
|
||||||
|
lopPor <- atomically (fmap fromIntegral $ saPor lop)
|
||||||
|
insPor <- atomically (fmap fromIntegral $ saPor ins)
|
||||||
|
secPor <- for mSec (fmap fromIntegral . atomically . saPor)
|
||||||
|
|
||||||
|
let por = Ports secPor insPor lopPor
|
||||||
|
fil = pierPath <> "/.http.ports"
|
||||||
|
|
||||||
|
logDebug $ displayShow ("EYRE", "All Servers Started.", srvId, por, fil)
|
||||||
|
|
||||||
|
pure (Serv srvId conf lop ins mSec por fil vLive)
|
||||||
|
|
||||||
|
|
||||||
|
-- Eyre Driver -----------------------------------------------------------------
|
||||||
|
|
||||||
|
_bornFailed :: e -> WorkError -> IO ()
|
||||||
|
_bornFailed env _ = runRIO env $ do
|
||||||
|
pure () -- TODO What should this do?
|
||||||
|
|
||||||
|
eyre'
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> MultiEyreApi
|
||||||
|
-> Ship
|
||||||
|
-> Bool
|
||||||
|
-> RIO e ([Ev], RAcquire e (DriverApi HttpServerEf))
|
||||||
|
eyre' multi who isFake = do
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
env <- ask
|
||||||
|
|
||||||
|
let (bornEvs, startDriver) = eyre env multi who (writeTQueue ventQ) isFake
|
||||||
|
|
||||||
|
let runDriver = do
|
||||||
|
diOnEffect <- startDriver
|
||||||
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
|
pure (bornEvs, runDriver)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Eyre -- HTTP Server Driver
|
||||||
|
|
||||||
|
Inject born events.
|
||||||
|
Until born events succeeds, ignore effects.
|
||||||
|
Wait until born event callbacks invoked.
|
||||||
|
If success, signal success.
|
||||||
|
If failure, try again several times.
|
||||||
|
If still failure, bring down ship.
|
||||||
|
Once born event succeeds:
|
||||||
|
- Begin normal operation (start accepting requests)
|
||||||
|
-}
|
||||||
|
eyre
|
||||||
|
:: forall e
|
||||||
|
. (HasPierEnv e)
|
||||||
|
=> e
|
||||||
|
-> MultiEyreApi
|
||||||
|
-> Ship
|
||||||
|
-> (EvErr -> STM ())
|
||||||
|
-> Bool
|
||||||
|
-> ([Ev], RAcquire e (HttpServerEf -> IO ()))
|
||||||
|
eyre env multi who plan isFake = (initialEvents, runHttpServer)
|
||||||
|
where
|
||||||
|
king = fromIntegral (env ^. kingIdL)
|
||||||
|
|
||||||
|
initialEvents :: [Ev]
|
||||||
|
initialEvents = [bornEv king]
|
||||||
|
|
||||||
|
runHttpServer :: RAcquire e (HttpServerEf -> IO ())
|
||||||
|
runHttpServer = handleEf <$> mkRAcquire
|
||||||
|
(Drv <$> newMVar Nothing)
|
||||||
|
(\(Drv v) -> stopService v kill >>= fromEither)
|
||||||
|
|
||||||
|
kill :: HasLogFunc e => Serv -> RIO e ()
|
||||||
|
kill Serv{..} = do
|
||||||
|
atomically (leaveMultiEyre multi who)
|
||||||
|
atomically (saKil sLop)
|
||||||
|
atomically (saKil sIns)
|
||||||
|
for_ sSec (\sec -> atomically (saKil sec))
|
||||||
|
io (removePortsFile sPortsFile)
|
||||||
|
|
||||||
|
restart :: Drv -> HttpServerConf -> RIO e Serv
|
||||||
|
restart (Drv var) conf = do
|
||||||
|
logDebug "Restarting http server"
|
||||||
|
let startAct = startServ multi who isFake conf plan
|
||||||
|
res <- fromEither =<< restartService var startAct kill
|
||||||
|
logDebug "Done restating http server"
|
||||||
|
pure res
|
||||||
|
|
||||||
|
liveFailed _ = pure ()
|
||||||
|
|
||||||
|
handleEf :: Drv -> HttpServerEf -> IO ()
|
||||||
|
handleEf drv = runRIO env . \case
|
||||||
|
HSESetConfig (i, ()) conf -> do
|
||||||
|
logDebug (displayShow ("EYRE", "%set-config"))
|
||||||
|
Serv {..} <- restart drv conf
|
||||||
|
logDebug (displayShow ("EYRE", "%set-config", "Sending %live"))
|
||||||
|
atomically $ plan (EvErr (liveEv sServId sPorts) liveFailed)
|
||||||
|
logDebug "Write ports file"
|
||||||
|
io (writePortsFile sPortsFile sPorts)
|
||||||
|
HSEResponse (i, req, _seq, ()) ev -> do
|
||||||
|
logDebug (displayShow ("EYRE", "%response"))
|
||||||
|
execRespActs drv who (fromIntegral req) ev
|
131
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Multi.hs
Normal file
131
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Multi.hs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
{-|
|
||||||
|
Eyre: Http Server Driver
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre.Multi
|
||||||
|
( WhichServer(..)
|
||||||
|
, MultiEyreConf(..)
|
||||||
|
, OnMultiReq
|
||||||
|
, OnMultiKil
|
||||||
|
, MultiEyreApi(..)
|
||||||
|
, joinMultiEyre
|
||||||
|
, leaveMultiEyre
|
||||||
|
, multiEyre
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude hiding (Builder)
|
||||||
|
|
||||||
|
import Urbit.Arvo hiding (ServerId, reqUrl, secure)
|
||||||
|
import Urbit.Vere.Eyre.Serv
|
||||||
|
import Urbit.Vere.Eyre.Wai
|
||||||
|
|
||||||
|
import Network.TLS (Credential)
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data WhichServer = Secure | Insecure | Loopback
|
||||||
|
deriving (Eq)
|
||||||
|
|
||||||
|
data MultiEyreConf = MultiEyreConf
|
||||||
|
{ mecHttpsPort :: Maybe Port
|
||||||
|
, mecHttpPort :: Maybe Port
|
||||||
|
, mecLocalhostOnly :: Bool
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
type OnMultiReq = WhichServer -> Ship -> Word64 -> ReqInfo -> STM ()
|
||||||
|
|
||||||
|
type OnMultiKil = Ship -> Word64 -> STM ()
|
||||||
|
|
||||||
|
data MultiEyreApi = MultiEyreApi
|
||||||
|
{ meaConf :: MultiEyreConf
|
||||||
|
, meaLive :: TVar LiveReqs
|
||||||
|
, meaPlan :: TVar (Map Ship OnMultiReq)
|
||||||
|
, meaCanc :: TVar (Map Ship OnMultiKil)
|
||||||
|
, meaTlsC :: TVar (Map Ship (TlsConfig, Credential))
|
||||||
|
, meaKill :: STM ()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Multi-Tenet HTTP ------------------------------------------------------------
|
||||||
|
|
||||||
|
joinMultiEyre
|
||||||
|
:: MultiEyreApi
|
||||||
|
-> Ship
|
||||||
|
-> Maybe (TlsConfig, Credential)
|
||||||
|
-> OnMultiReq
|
||||||
|
-> OnMultiKil
|
||||||
|
-> STM ()
|
||||||
|
joinMultiEyre api who mTls onReq onKil = do
|
||||||
|
modifyTVar' (meaPlan api) (insertMap who onReq)
|
||||||
|
modifyTVar' (meaCanc api) (insertMap who onKil)
|
||||||
|
for_ mTls $ \creds -> do
|
||||||
|
modifyTVar' (meaTlsC api) (insertMap who creds)
|
||||||
|
|
||||||
|
leaveMultiEyre :: MultiEyreApi -> Ship -> STM ()
|
||||||
|
leaveMultiEyre MultiEyreApi {..} who = do
|
||||||
|
modifyTVar' meaCanc (deleteMap who)
|
||||||
|
modifyTVar' meaPlan (deleteMap who)
|
||||||
|
modifyTVar' meaTlsC (deleteMap who)
|
||||||
|
|
||||||
|
multiEyre :: HasLogFunc e => MultiEyreConf -> RIO e MultiEyreApi
|
||||||
|
multiEyre conf@MultiEyreConf {..} = do
|
||||||
|
logDebug (displayShow ("EYRE", "MULTI", conf))
|
||||||
|
|
||||||
|
vLive <- io emptyLiveReqs >>= newTVarIO
|
||||||
|
vPlan <- newTVarIO mempty
|
||||||
|
vCanc <- newTVarIO (mempty :: Map Ship (Ship -> Word64 -> STM ()))
|
||||||
|
vTlsC <- newTVarIO mempty
|
||||||
|
|
||||||
|
let host = if mecLocalhostOnly then SHLocalhost else SHAnyHostOk
|
||||||
|
|
||||||
|
let onReq :: WhichServer -> Ship -> Word64 -> ReqInfo -> STM ()
|
||||||
|
onReq which who reqId reqInfo = do
|
||||||
|
plan <- readTVar vPlan
|
||||||
|
lookup who plan & \case
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just cb -> cb which who reqId reqInfo
|
||||||
|
|
||||||
|
let onKil :: Ship -> Word64 -> STM ()
|
||||||
|
onKil who reqId = do
|
||||||
|
canc <- readTVar vCanc
|
||||||
|
lookup who canc & \case
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just cb -> cb who reqId
|
||||||
|
|
||||||
|
mIns <- for mecHttpPort $ \por -> do
|
||||||
|
logDebug (displayShow ("EYRE", "MULTI", "HTTP", por))
|
||||||
|
serv vLive $ ServConf
|
||||||
|
{ scHost = host
|
||||||
|
, scPort = SPChoices $ singleton $ fromIntegral por
|
||||||
|
, scRedi = Nothing -- TODO
|
||||||
|
, scFake = False
|
||||||
|
, scType = STMultiHttp $ ReqApi
|
||||||
|
{ rcReq = onReq Insecure
|
||||||
|
, rcKil = onKil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mSec <- for mecHttpsPort $ \por -> do
|
||||||
|
logDebug (displayShow ("EYRE", "MULTI", "HTTPS", por))
|
||||||
|
serv vLive $ ServConf
|
||||||
|
{ scHost = host
|
||||||
|
, scPort = SPChoices $ singleton $ fromIntegral por
|
||||||
|
, scRedi = Nothing
|
||||||
|
, scFake = False
|
||||||
|
, scType = STMultiHttps (MTC vTlsC) $ ReqApi
|
||||||
|
{ rcReq = onReq Secure
|
||||||
|
, rcKil = onKil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pure $ MultiEyreApi
|
||||||
|
{ meaLive = vLive
|
||||||
|
, meaPlan = vPlan
|
||||||
|
, meaCanc = vCanc
|
||||||
|
, meaTlsC = vTlsC
|
||||||
|
, meaConf = conf
|
||||||
|
, meaKill = traverse_ saKil (toList mIns <> toList mSec)
|
||||||
|
}
|
44
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/PortsFile.hs
Normal file
44
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/PortsFile.hs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{-|
|
||||||
|
Eyre: Http Server Driver
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre.PortsFile
|
||||||
|
( Ports(..)
|
||||||
|
, writePortsFile
|
||||||
|
, removePortsFile
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
import System.Directory (doesFileExist, removeFile)
|
||||||
|
import Urbit.Arvo (Port(unPort))
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data Ports = Ports
|
||||||
|
{ pHttps :: Maybe Port
|
||||||
|
, pHttp :: Port
|
||||||
|
, pLoop :: Port
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
|
||||||
|
-- Creating and Deleting `.http.ports` files. ----------------------------------
|
||||||
|
|
||||||
|
portsFileText :: Ports -> Text
|
||||||
|
portsFileText Ports {..} = unlines $ catMaybes
|
||||||
|
[ pHttps <&> \p -> (tshow p <> " secure public")
|
||||||
|
, Just (tshow (unPort pHttp) <> " insecure public")
|
||||||
|
, Just (tshow (unPort pLoop) <> " insecure loopback")
|
||||||
|
]
|
||||||
|
|
||||||
|
removePortsFile :: FilePath -> IO ()
|
||||||
|
removePortsFile pax = do
|
||||||
|
doesFileExist pax >>= \case
|
||||||
|
True -> removeFile pax
|
||||||
|
False -> pure ()
|
||||||
|
|
||||||
|
writePortsFile :: FilePath -> Ports -> IO ()
|
||||||
|
writePortsFile f = writeFile f . encodeUtf8 . portsFileText
|
356
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Serv.hs
Normal file
356
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Serv.hs
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
{-|
|
||||||
|
Runs a single HTTP (or HTTPS) server for the eyre driver.
|
||||||
|
|
||||||
|
A server is given:
|
||||||
|
|
||||||
|
- A port, or a range or ports.
|
||||||
|
- Opens a socket on one of those ports.
|
||||||
|
- If this fails, try again repeatedly.
|
||||||
|
- Once a socket is opened, runs an HTTP server on the specified port.
|
||||||
|
- Once the server is up, calls a callback with the port that was opened.
|
||||||
|
- Once we have chosen a port, we commit to that port (ignoring the
|
||||||
|
original range).
|
||||||
|
- If the socket ever goes down, keep trying to reopen that port forever.
|
||||||
|
- When the server is shutdown, make sure the socket is closed.
|
||||||
|
|
||||||
|
TODO How to detect socket closed during server run?
|
||||||
|
-}
|
||||||
|
|
||||||
|
{-# OPTIONS_GHC -Wno-deprecations #-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre.Serv
|
||||||
|
( ServApi(..)
|
||||||
|
, TlsConfig(..)
|
||||||
|
, MultiTlsConfig(..)
|
||||||
|
, ReqApi(..)
|
||||||
|
, ServType(..)
|
||||||
|
, ServPort(..)
|
||||||
|
, ServHost(..)
|
||||||
|
, ServConf(..)
|
||||||
|
, configCreds
|
||||||
|
, serv
|
||||||
|
, fakeServ
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude hiding (Builder)
|
||||||
|
|
||||||
|
import Data.Default (def)
|
||||||
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
|
import Network.TLS (Credential, Credentials(..), ServerHooks(..))
|
||||||
|
import Network.TLS (credentialLoadX509ChainFromMemory)
|
||||||
|
import RIO.Prelude (decodeUtf8Lenient)
|
||||||
|
|
||||||
|
import qualified Control.Monad.STM as STM
|
||||||
|
import qualified Data.Char as C
|
||||||
|
import qualified Network.Socket as Net
|
||||||
|
import qualified Network.Wai as W
|
||||||
|
import qualified Network.Wai.Handler.Warp as W
|
||||||
|
import qualified Network.Wai.Handler.WarpTLS as W
|
||||||
|
import qualified Urbit.Ob as Ob
|
||||||
|
import qualified Urbit.Vere.Eyre.Wai as E
|
||||||
|
|
||||||
|
|
||||||
|
-- Internal Types --------------------------------------------------------------
|
||||||
|
|
||||||
|
data ServApi = ServApi
|
||||||
|
{ saKil :: STM ()
|
||||||
|
, saPor :: STM W.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
data TlsConfig = TlsConfig
|
||||||
|
{ tcPrKey :: ByteString
|
||||||
|
, tcCerti :: ByteString
|
||||||
|
, tcChain :: [ByteString]
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
newtype MultiTlsConfig = MTC (TVar (Map Ship (TlsConfig, Credential)))
|
||||||
|
|
||||||
|
instance Show MultiTlsConfig where
|
||||||
|
show = const "MultiTlsConfig"
|
||||||
|
|
||||||
|
data ReqApi = ReqApi
|
||||||
|
{ rcReq :: Ship -> Word64 -> E.ReqInfo -> STM ()
|
||||||
|
, rcKil :: Ship -> Word64 -> STM ()
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Show ReqApi where
|
||||||
|
show = const "ReqApi"
|
||||||
|
|
||||||
|
data ServType
|
||||||
|
= STHttp Ship ReqApi
|
||||||
|
| STHttps Ship TlsConfig ReqApi
|
||||||
|
| STMultiHttp ReqApi
|
||||||
|
| STMultiHttps MultiTlsConfig ReqApi
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data ServPort
|
||||||
|
= SPAnyPort
|
||||||
|
| SPChoices (NonEmpty W.Port)
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data ServHost
|
||||||
|
= SHLocalhost
|
||||||
|
| SHAnyHostOk
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data ServConf = ServConf
|
||||||
|
{ scType :: ServType
|
||||||
|
, scHost :: ServHost
|
||||||
|
, scPort :: ServPort
|
||||||
|
, scRedi :: Maybe W.Port
|
||||||
|
, scFake :: Bool
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
|
||||||
|
-- Opening Sockets -------------------------------------------------------------
|
||||||
|
|
||||||
|
getBindAddr :: String -> W.Port -> IO Net.SockAddr
|
||||||
|
getBindAddr hos por =
|
||||||
|
Net.getAddrInfo Nothing (Just hos) (Just (show por)) >>= \case
|
||||||
|
[] -> error "this should never happen."
|
||||||
|
x : _ -> pure (Net.addrAddress x)
|
||||||
|
|
||||||
|
bindListenPort :: String -> W.Port -> Net.Socket -> IO Net.PortNumber
|
||||||
|
bindListenPort hos por sok = do
|
||||||
|
Net.bind sok =<< getBindAddr hos por
|
||||||
|
Net.listen sok 1
|
||||||
|
Net.socketPort sok
|
||||||
|
|
||||||
|
tcpSocket :: IO (Either IOError Net.Socket)
|
||||||
|
tcpSocket =
|
||||||
|
tryIOError (Net.socket Net.AF_INET Net.Stream Net.defaultProtocol)
|
||||||
|
|
||||||
|
tryOpen :: String -> W.Port -> IO (Either IOError (W.Port, Net.Socket))
|
||||||
|
tryOpen hos por =
|
||||||
|
tcpSocket >>= \case
|
||||||
|
Left exn -> pure (Left exn)
|
||||||
|
Right sok -> tryIOError (bindListenPort hos por sok) >>= \case
|
||||||
|
Left exn -> Net.close sok $> Left exn
|
||||||
|
Right por -> pure (Right (fromIntegral por, sok))
|
||||||
|
|
||||||
|
openFreePort :: String -> IO (Either IOError (W.Port, Net.Socket))
|
||||||
|
openFreePort hos = do
|
||||||
|
tcpSocket >>= \case
|
||||||
|
Left exn -> pure (Left exn)
|
||||||
|
Right sok -> tryIOError (doBind sok) >>= \case
|
||||||
|
Left exn -> Net.close sok $> Left exn
|
||||||
|
Right ps -> pure (Right ps)
|
||||||
|
where
|
||||||
|
doBind sok = do
|
||||||
|
adr <- Net.inet_addr hos
|
||||||
|
Net.bind sok (Net.SockAddrInet Net.defaultPort adr)
|
||||||
|
Net.listen sok 1
|
||||||
|
port <- Net.socketPort sok
|
||||||
|
pure (fromIntegral port, sok)
|
||||||
|
|
||||||
|
retry :: HasLogFunc e => RIO e (Either IOError a) -> RIO e a
|
||||||
|
retry act = act >>= \case
|
||||||
|
Right res -> pure res
|
||||||
|
Left exn -> do
|
||||||
|
logDbg ctx ("Failed to open ports. Waiting 5s, then trying again.", exn)
|
||||||
|
threadDelay 5_000_000
|
||||||
|
retry act
|
||||||
|
where
|
||||||
|
ctx = ["EYRE", "SERV", "retry"]
|
||||||
|
|
||||||
|
tryOpenChoices
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> String
|
||||||
|
-> NonEmpty W.Port
|
||||||
|
-> RIO e (Either IOError (W.Port, Net.Socket))
|
||||||
|
tryOpenChoices hos = go
|
||||||
|
where
|
||||||
|
go (p :| ps) = do
|
||||||
|
logDebug (displayShow ("EYRE", "Trying to open port.", p))
|
||||||
|
io (tryOpen hos p) >>= \case
|
||||||
|
Left err -> do
|
||||||
|
logError (displayShow ("EYRE", "Failed to open port.", p))
|
||||||
|
case ps of
|
||||||
|
[] -> pure (Left err)
|
||||||
|
q : qs -> go (q :| qs)
|
||||||
|
Right (p, s) -> do
|
||||||
|
pure (Right (p, s))
|
||||||
|
|
||||||
|
tryOpenAny
|
||||||
|
:: HasLogFunc e => String -> RIO e (Either IOError (W.Port, Net.Socket))
|
||||||
|
tryOpenAny hos = do
|
||||||
|
let ctx = ["EYRE", "SERV", "tryOpenAny"]
|
||||||
|
logDbg ctx "Asking the OS for any free port."
|
||||||
|
io (openFreePort hos) >>= \case
|
||||||
|
Left exn -> pure (Left exn)
|
||||||
|
Right (p, s) -> do
|
||||||
|
pure (Right (p, s))
|
||||||
|
|
||||||
|
logDbg :: (HasLogFunc e, Show a) => [Text] -> a -> RIO e ()
|
||||||
|
logDbg ctx msg = logDebug (prefix <> suffix)
|
||||||
|
where
|
||||||
|
prefix = display (concat $ fmap (<> ": ") ctx)
|
||||||
|
suffix = displayShow msg
|
||||||
|
|
||||||
|
forceOpenSocket
|
||||||
|
:: forall e
|
||||||
|
. HasLogFunc e
|
||||||
|
=> ServHost
|
||||||
|
-> ServPort
|
||||||
|
-> RAcquire e (W.Port, Net.Socket)
|
||||||
|
forceOpenSocket hos por = mkRAcquire opn kil
|
||||||
|
where
|
||||||
|
kil = io . Net.close . snd
|
||||||
|
|
||||||
|
opn = do
|
||||||
|
let ctx = ["EYRE", "SERV", "forceOpenSocket"]
|
||||||
|
logDbg ctx (hos, por)
|
||||||
|
(p, s) <- retry $ case por of
|
||||||
|
SPAnyPort -> tryOpenAny bind
|
||||||
|
SPChoices ps -> tryOpenChoices bind ps
|
||||||
|
logDbg ctx ("Opened port.", p)
|
||||||
|
pure (p, s)
|
||||||
|
|
||||||
|
bind = case hos of
|
||||||
|
SHLocalhost -> "127.0.0.1"
|
||||||
|
SHAnyHostOk -> "0.0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
-- Starting WAI ----------------------------------------------------------------
|
||||||
|
|
||||||
|
hostShip :: Maybe ByteString -> IO Ship
|
||||||
|
hostShip Nothing = error "Request must contain HOST header."
|
||||||
|
hostShip (Just bs) = byteShip (hedLabel bs) & \case
|
||||||
|
Left err -> error ("Bad host prefix. Must be a ship name: " <> unpack err)
|
||||||
|
Right sp -> pure sp
|
||||||
|
where
|
||||||
|
byteShip = fmap (fromIntegral . Ob.fromPatp) . bytePatp
|
||||||
|
bytePatp = Ob.parsePatp . decodeUtf8Lenient
|
||||||
|
hedLabel = fst . break (== fromIntegral (C.ord '.'))
|
||||||
|
|
||||||
|
onSniHdr
|
||||||
|
:: HasLogFunc e => e -> MultiTlsConfig -> Maybe String -> IO Credentials
|
||||||
|
onSniHdr env (MTC mtls) mHos = do
|
||||||
|
tabl <- atomically (readTVar mtls)
|
||||||
|
runRIO env $ logDbg ctx (tabl, mHos)
|
||||||
|
ship <- hostShip (encodeUtf8 . pack <$> mHos)
|
||||||
|
runRIO env $ logDbg ctx ship
|
||||||
|
tcfg <- lookup ship tabl & maybe (notRunning ship) (pure . snd)
|
||||||
|
runRIO env $ logDbg ctx tcfg
|
||||||
|
pure (Credentials [tcfg])
|
||||||
|
where
|
||||||
|
notRunning ship = error ("Ship not running: ~" <> show ship)
|
||||||
|
ctx = ["EYRE", "HTTPS", "SNI"]
|
||||||
|
|
||||||
|
startServer
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> ServType
|
||||||
|
-> ServHost
|
||||||
|
-> W.Port
|
||||||
|
-> Net.Socket
|
||||||
|
-> Maybe W.Port
|
||||||
|
-> TVar E.LiveReqs
|
||||||
|
-> RIO e ()
|
||||||
|
startServer typ hos por sok red vLive = do
|
||||||
|
envir <- ask
|
||||||
|
|
||||||
|
let host = case hos of
|
||||||
|
SHLocalhost -> "127.0.0.1"
|
||||||
|
SHAnyHostOk -> "*"
|
||||||
|
|
||||||
|
let opts =
|
||||||
|
W.defaultSettings
|
||||||
|
& W.setHost host
|
||||||
|
& W.setPort (fromIntegral por)
|
||||||
|
& W.setTimeout (5 * 60)
|
||||||
|
|
||||||
|
let runAppl who = E.app envir who vLive
|
||||||
|
reqShip = hostShip . W.requestHeaderHost
|
||||||
|
|
||||||
|
case typ of
|
||||||
|
STHttp who api -> do
|
||||||
|
let app = runAppl who (rcReq api who) (rcKil api who)
|
||||||
|
io (W.runSettingsSocket opts sok app)
|
||||||
|
|
||||||
|
STHttps who TlsConfig {..} api -> do
|
||||||
|
let tls = W.tlsSettingsChainMemory tcCerti tcChain tcPrKey
|
||||||
|
let app = runAppl who (rcReq api who) (rcKil api who)
|
||||||
|
io (W.runTLSSocket tls opts sok app)
|
||||||
|
|
||||||
|
STMultiHttp api -> do
|
||||||
|
let app req resp = do
|
||||||
|
who <- reqShip req
|
||||||
|
runAppl who (rcReq api who) (rcKil api who) req resp
|
||||||
|
io (W.runSettingsSocket opts sok app)
|
||||||
|
|
||||||
|
STMultiHttps mtls api -> do
|
||||||
|
TlsConfig {..} <- atomically (getFirstTlsConfig mtls)
|
||||||
|
|
||||||
|
let sni = def { onServerNameIndication = onSniHdr envir mtls }
|
||||||
|
|
||||||
|
let tlsSing = (W.tlsSettingsChainMemory tcCerti tcChain tcPrKey)
|
||||||
|
let tlsMany = tlsSing { W.tlsServerHooks = sni }
|
||||||
|
|
||||||
|
let ctx = ["EYRE", "HTTPS", "REQ"]
|
||||||
|
|
||||||
|
let
|
||||||
|
app = \req resp -> do
|
||||||
|
runRIO envir $ logDbg ctx "Got request"
|
||||||
|
who <- reqShip req
|
||||||
|
runRIO envir $ logDbg ctx ("Parsed HOST", who)
|
||||||
|
runAppl who (rcReq api who) (rcKil api who) req resp
|
||||||
|
|
||||||
|
io (W.runTLSSocket tlsMany opts sok app)
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
configCreds :: TlsConfig -> Either Text Credential
|
||||||
|
configCreds TlsConfig {..} =
|
||||||
|
credentialLoadX509ChainFromMemory tcCerti tcChain tcPrKey & \case
|
||||||
|
Left str -> Left (pack str)
|
||||||
|
Right rs -> Right rs
|
||||||
|
|
||||||
|
fakeServ :: HasLogFunc e => ServConf -> RIO e ServApi
|
||||||
|
fakeServ conf = do
|
||||||
|
let por = fakePort (scPort conf)
|
||||||
|
logDebug (displayShow ("EYRE", "SERV", "Running Fake Server", por))
|
||||||
|
pure $ ServApi
|
||||||
|
{ saKil = pure ()
|
||||||
|
, saPor = pure por
|
||||||
|
}
|
||||||
|
where
|
||||||
|
fakePort :: ServPort -> W.Port
|
||||||
|
fakePort SPAnyPort = 55555
|
||||||
|
fakePort (SPChoices (x :| _)) = x
|
||||||
|
|
||||||
|
getFirstTlsConfig :: MultiTlsConfig -> STM TlsConfig
|
||||||
|
getFirstTlsConfig (MTC var) = do
|
||||||
|
map <- readTVar var
|
||||||
|
case toList map of
|
||||||
|
[] -> STM.retry
|
||||||
|
x:_ -> pure (fst x)
|
||||||
|
|
||||||
|
realServ :: HasLogFunc e => TVar E.LiveReqs -> ServConf -> RIO e ServApi
|
||||||
|
realServ vLive conf@ServConf {..} = do
|
||||||
|
logDebug (displayShow ("EYRE", "SERV", "Running Real Server"))
|
||||||
|
kil <- newEmptyTMVarIO
|
||||||
|
por <- newEmptyTMVarIO
|
||||||
|
|
||||||
|
tid <- async (runServ por)
|
||||||
|
_ <- async (atomically (takeTMVar kil) >> cancel tid)
|
||||||
|
|
||||||
|
pure $ ServApi
|
||||||
|
{ saKil = void (tryPutTMVar kil ())
|
||||||
|
, saPor = readTMVar por
|
||||||
|
}
|
||||||
|
where
|
||||||
|
runServ vPort = do
|
||||||
|
logDebug (displayShow ("EYRE", "SERV", "runServ"))
|
||||||
|
rwith (forceOpenSocket scHost scPort) $ \(por, sok) -> do
|
||||||
|
atomically (putTMVar vPort por)
|
||||||
|
startServer scType scHost por sok scRedi vLive
|
||||||
|
|
||||||
|
serv :: HasLogFunc e => TVar E.LiveReqs -> ServConf -> RIO e ServApi
|
||||||
|
serv vLive conf = do
|
||||||
|
if scFake conf
|
||||||
|
then fakeServ conf
|
||||||
|
else realServ vLive conf
|
67
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Service.hs
Normal file
67
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Service.hs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{-|
|
||||||
|
Eyre: Http Server Driver
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre.Service
|
||||||
|
( restartService
|
||||||
|
, stopService
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
|
||||||
|
-- Generic Service Stop/Restart -- Using an MVar for Atomicity -----------------
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Restart a running service.
|
||||||
|
|
||||||
|
This can probably be made simpler, but it
|
||||||
|
|
||||||
|
- Sets the MVar to Nothing if there was an exception while starting
|
||||||
|
or stopping the service.
|
||||||
|
|
||||||
|
- Keeps the MVar lock until the restart process finishes.
|
||||||
|
-}
|
||||||
|
restartService
|
||||||
|
:: forall e s
|
||||||
|
. HasLogFunc e
|
||||||
|
=> MVar (Maybe s)
|
||||||
|
-> RIO e s
|
||||||
|
-> (s -> RIO e ())
|
||||||
|
-> RIO e (Either SomeException s)
|
||||||
|
restartService vServ sstart kkill = do
|
||||||
|
logDebug "restartService"
|
||||||
|
modifyMVar vServ $ \case
|
||||||
|
Nothing -> doStart
|
||||||
|
Just sv -> doRestart sv
|
||||||
|
where
|
||||||
|
doRestart :: s -> RIO e (Maybe s, Either SomeException s)
|
||||||
|
doRestart serv = do
|
||||||
|
logDebug "doStart"
|
||||||
|
try (kkill serv) >>= \case
|
||||||
|
Left exn -> pure (Nothing, Left exn)
|
||||||
|
Right () -> doStart
|
||||||
|
|
||||||
|
doStart :: RIO e (Maybe s, Either SomeException s)
|
||||||
|
doStart = do
|
||||||
|
logDebug "doStart"
|
||||||
|
try sstart <&> \case
|
||||||
|
Right s -> (Just s, Right s)
|
||||||
|
Left exn -> (Nothing, Left exn)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Stop a running service. Do nothing if it's already stopped.
|
||||||
|
-}
|
||||||
|
stopService
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> MVar (Maybe s)
|
||||||
|
-> (s -> RIO e ())
|
||||||
|
-> RIO e (Either SomeException ())
|
||||||
|
stopService vServ kkill = do
|
||||||
|
logDebug "stopService"
|
||||||
|
modifyMVar vServ $ \case
|
||||||
|
Nothing -> pure (Nothing, Right ())
|
||||||
|
Just sv -> do
|
||||||
|
res <- try (kkill sv)
|
||||||
|
pure (Nothing, res)
|
229
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Wai.hs
Normal file
229
pkg/hs/urbit-king/lib/Urbit/Vere/Eyre/Wai.hs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
{-|
|
||||||
|
WAI Application for `eyre` driver.
|
||||||
|
|
||||||
|
# Request Lifecycles
|
||||||
|
|
||||||
|
- Requests come in, are given an identifier and are passed to a callback.
|
||||||
|
|
||||||
|
- When requests timeout, the identifier is passed to anothing callback.
|
||||||
|
|
||||||
|
- The server pulls response actions, and passes them to the associated
|
||||||
|
request.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Eyre.Wai
|
||||||
|
( RespAct(..)
|
||||||
|
, RespApi(..)
|
||||||
|
, LiveReqs(..)
|
||||||
|
, ReqInfo(..)
|
||||||
|
, emptyLiveReqs
|
||||||
|
, routeRespAct
|
||||||
|
, rmLiveReq
|
||||||
|
, newLiveReq
|
||||||
|
, app
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude hiding (Builder)
|
||||||
|
|
||||||
|
import Data.Binary.Builder (Builder, fromByteString)
|
||||||
|
import Data.Bits (shiftL, (.|.))
|
||||||
|
import Data.Conduit (ConduitT, Flush(Chunk, Flush), yield)
|
||||||
|
import Network.Socket (SockAddr(..))
|
||||||
|
import System.Random (newStdGen, randoms)
|
||||||
|
import Urbit.Arvo (Address(..), Ipv4(..), Ipv6(..), Method)
|
||||||
|
|
||||||
|
import qualified Network.HTTP.Types as H
|
||||||
|
import qualified Network.Wai as W
|
||||||
|
import qualified Network.Wai.Conduit as W
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data RespAct
|
||||||
|
= RAFull H.Status [H.Header] ByteString
|
||||||
|
| RAHead H.Status [H.Header] ByteString
|
||||||
|
| RABloc ByteString
|
||||||
|
| RADone
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
data RespApi = RespApi
|
||||||
|
{ raAct :: RespAct -> STM Bool
|
||||||
|
, raKil :: STM ()
|
||||||
|
}
|
||||||
|
|
||||||
|
data LiveReqs = LiveReqs
|
||||||
|
{ reqIdSuply :: [Word64]
|
||||||
|
, activeReqs :: Map Word64 (Ship, RespApi)
|
||||||
|
}
|
||||||
|
|
||||||
|
data ReqInfo = ReqInfo
|
||||||
|
{ riAdr :: Address
|
||||||
|
, riMet :: H.StdMethod
|
||||||
|
, riUrl :: ByteString
|
||||||
|
, riHdr :: [H.Header]
|
||||||
|
, riBod :: ByteString
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Live Requests Table -- All Requests Still Waiting for Responses -------------
|
||||||
|
|
||||||
|
emptyLiveReqs :: IO LiveReqs
|
||||||
|
emptyLiveReqs = io $ do
|
||||||
|
gen <- newStdGen
|
||||||
|
pure (LiveReqs (randoms gen) mempty)
|
||||||
|
|
||||||
|
routeRespAct :: Ship -> TVar LiveReqs -> Word64 -> RespAct -> STM Bool
|
||||||
|
routeRespAct who vLiv reqId act =
|
||||||
|
(lookup reqId . activeReqs <$> readTVar vLiv) >>= \case
|
||||||
|
Nothing -> pure False
|
||||||
|
Just (own, tv) -> do
|
||||||
|
if (who == own)
|
||||||
|
then raAct tv act
|
||||||
|
else pure False
|
||||||
|
|
||||||
|
rmLiveReq :: TVar LiveReqs -> Word64 -> STM ()
|
||||||
|
rmLiveReq var reqId = modifyTVar' var
|
||||||
|
$ \liv -> liv { activeReqs = deleteMap reqId (activeReqs liv) }
|
||||||
|
|
||||||
|
allocateReqId :: TVar LiveReqs -> STM Word64
|
||||||
|
allocateReqId var = do
|
||||||
|
LiveReqs supply tbl <- readTVar var
|
||||||
|
|
||||||
|
let loop :: [Word64] -> (Word64, [Word64])
|
||||||
|
loop [] = error "impossible"
|
||||||
|
loop (x:xs) | member x tbl = loop xs
|
||||||
|
loop (x:xs) | otherwise = (x, xs)
|
||||||
|
|
||||||
|
let (fresh, supply') = loop supply
|
||||||
|
writeTVar var (LiveReqs supply' tbl)
|
||||||
|
pure fresh
|
||||||
|
|
||||||
|
newLiveReq :: Ship -> TVar LiveReqs -> STM (Word64, STM RespAct)
|
||||||
|
newLiveReq who var = do
|
||||||
|
tmv <- newTQueue
|
||||||
|
kil <- newEmptyTMVar
|
||||||
|
nex <- allocateReqId var
|
||||||
|
|
||||||
|
LiveReqs sup tbl <- readTVar var
|
||||||
|
|
||||||
|
let waitAct = (<|>) (readTMVar kil $> RADone) (readTQueue tmv)
|
||||||
|
respApi = RespApi
|
||||||
|
{ raKil = putTMVar kil ()
|
||||||
|
, raAct = \act -> tryReadTMVar kil >>= \case
|
||||||
|
Nothing -> writeTQueue tmv act $> True
|
||||||
|
Just () -> pure False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
writeTVar var (LiveReqs sup (insertMap nex (who, respApi) tbl))
|
||||||
|
|
||||||
|
pure (nex, waitAct)
|
||||||
|
|
||||||
|
|
||||||
|
-- Random Helpers --------------------------------------------------------------
|
||||||
|
|
||||||
|
cookMeth :: W.Request -> Maybe Method
|
||||||
|
cookMeth = H.parseMethod . W.requestMethod >>> \case
|
||||||
|
Left _ -> Nothing
|
||||||
|
Right m -> Just m
|
||||||
|
|
||||||
|
reqAddr :: W.Request -> Address
|
||||||
|
reqAddr = W.remoteHost >>> \case
|
||||||
|
SockAddrInet _ a -> AIpv4 (Ipv4 a)
|
||||||
|
SockAddrInet6 _ _ a _ -> AIpv6 (mkIpv6 a)
|
||||||
|
_ -> error "invalid sock addr"
|
||||||
|
|
||||||
|
mkIpv6 :: (Word32, Word32, Word32, Word32) -> Ipv6
|
||||||
|
mkIpv6 (p, q, r, s) = Ipv6 (pBits .|. qBits .|. rBits .|. sBits)
|
||||||
|
where
|
||||||
|
pBits = shiftL (fromIntegral p) 0
|
||||||
|
qBits = shiftL (fromIntegral q) 32
|
||||||
|
rBits = shiftL (fromIntegral r) 64
|
||||||
|
sBits = shiftL (fromIntegral s) 96
|
||||||
|
|
||||||
|
reqUrl :: W.Request -> ByteString
|
||||||
|
reqUrl r = W.rawPathInfo r <> W.rawQueryString r
|
||||||
|
|
||||||
|
|
||||||
|
-- Responses -------------------------------------------------------------------
|
||||||
|
|
||||||
|
noHeader :: HasLogFunc e => RIO e a
|
||||||
|
noHeader = do
|
||||||
|
logError "Response block with no response header."
|
||||||
|
error "Bad HttpEvent: Response block with no response header."
|
||||||
|
|
||||||
|
dupHead :: HasLogFunc e => RIO e a
|
||||||
|
dupHead = do
|
||||||
|
logError "Multiple %head actions on one request"
|
||||||
|
error "Bad HttpEvent: Multiple header actions per on one request."
|
||||||
|
|
||||||
|
{-|
|
||||||
|
- Immediately yield all of the initial chunks
|
||||||
|
- Yield the data from %bloc action.
|
||||||
|
- Close the stream when we hit a %done action.
|
||||||
|
-}
|
||||||
|
streamBlocks
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> e
|
||||||
|
-> ByteString
|
||||||
|
-> STM RespAct
|
||||||
|
-> ConduitT () (Flush Builder) IO ()
|
||||||
|
streamBlocks env init getAct = send init >> loop
|
||||||
|
where
|
||||||
|
loop = atomically getAct >>= \case
|
||||||
|
RAHead _ _ _ -> runRIO env dupHead
|
||||||
|
RAFull _ _ _ -> runRIO env dupHead
|
||||||
|
RADone -> pure ()
|
||||||
|
RABloc c -> send c >> loop
|
||||||
|
|
||||||
|
send "" = pure ()
|
||||||
|
send c = do
|
||||||
|
runRIO env (logTrace (display ("sending chunk " <> tshow c)))
|
||||||
|
yield $ Chunk $ fromByteString c
|
||||||
|
yield Flush
|
||||||
|
|
||||||
|
sendResponse
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> (W.Response -> IO W.ResponseReceived)
|
||||||
|
-> STM RespAct
|
||||||
|
-> RIO e W.ResponseReceived
|
||||||
|
sendResponse cb waitAct = do
|
||||||
|
env <- ask
|
||||||
|
atomically waitAct >>= \case
|
||||||
|
RADone -> io $ cb $ W.responseLBS (H.mkStatus 444 "No Response") [] ""
|
||||||
|
RAFull s h b -> io $ cb $ W.responseLBS s h $ fromStrict b
|
||||||
|
RAHead s h b -> io $ cb $ W.responseSource s h $ streamBlocks env b waitAct
|
||||||
|
RABloc _ -> noHeader
|
||||||
|
|
||||||
|
liveReq :: Ship -> TVar LiveReqs -> RAcquire e (Word64, STM RespAct)
|
||||||
|
liveReq who vLiv = mkRAcquire ins del
|
||||||
|
where
|
||||||
|
ins = atomically (newLiveReq who vLiv)
|
||||||
|
del = atomically . rmLiveReq vLiv . fst
|
||||||
|
|
||||||
|
app
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> e
|
||||||
|
-> Ship
|
||||||
|
-> TVar LiveReqs
|
||||||
|
-> (Word64 -> ReqInfo -> STM ())
|
||||||
|
-> (Word64 -> STM ())
|
||||||
|
-> W.Application
|
||||||
|
app env who liv inform cancel req respond =
|
||||||
|
runRIO env $ rwith (liveReq who liv) $ \(reqId, respApi) -> do
|
||||||
|
bod <- io (toStrict <$> W.strictRequestBody req)
|
||||||
|
met <- maybe (error "bad method") pure (cookMeth req)
|
||||||
|
|
||||||
|
let adr = reqAddr req
|
||||||
|
hdr = W.requestHeaders req
|
||||||
|
url = reqUrl req
|
||||||
|
|
||||||
|
atomically $ inform reqId $ ReqInfo adr met url hdr bod
|
||||||
|
|
||||||
|
try (sendResponse respond respApi) >>= \case
|
||||||
|
Right rr -> pure rr
|
||||||
|
Left exn -> do
|
||||||
|
atomically (cancel reqId)
|
||||||
|
logError $ display ("Exception during request" <> tshow exn)
|
||||||
|
throwIO (exn :: SomeException)
|
@ -7,19 +7,22 @@
|
|||||||
|
|
||||||
module Urbit.Vere.Http.Client where
|
module Urbit.Vere.Http.Client where
|
||||||
|
|
||||||
import Urbit.Arvo (BlipEv(..), Ev(..), HttpClientEf(..),
|
import Urbit.Prelude hiding (Builder)
|
||||||
HttpClientEv(..), HttpClientReq(..),
|
|
||||||
HttpEvent(..), KingId, ResponseHeader(..))
|
|
||||||
import Urbit.Prelude hiding (Builder)
|
|
||||||
import Urbit.Vere.Pier.Types
|
|
||||||
|
|
||||||
import Urbit.Vere.Http
|
import Urbit.Vere.Http
|
||||||
|
import Urbit.Vere.Pier.Types
|
||||||
|
import Urbit.King.App
|
||||||
|
|
||||||
|
import Urbit.Arvo (BlipEv(..), Ev(..), HttpClientEf(..), HttpClientEv(..),
|
||||||
|
HttpClientReq(..), HttpEvent(..), KingId, ResponseHeader(..))
|
||||||
|
|
||||||
|
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import qualified Network.HTTP.Client as H
|
import qualified Network.HTTP.Client as H
|
||||||
import qualified Network.HTTP.Client.TLS as TLS
|
import qualified Network.HTTP.Client.TLS as TLS
|
||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
type ReqId = Word
|
type ReqId = Word
|
||||||
@ -54,14 +57,54 @@ bornEv king =
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
client :: forall e. HasLogFunc e
|
_bornFailed :: e -> WorkError -> IO ()
|
||||||
=> KingId -> QueueEv -> ([Ev], RAcquire e (EffCb e HttpClientEf))
|
_bornFailed env _ = runRIO env $ do
|
||||||
client kingId enqueueEv = (initialEvents, runHttpClient)
|
pure () -- TODO What to do in this case?
|
||||||
|
|
||||||
|
client'
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> RIO e ([Ev], RAcquire e (DriverApi HttpClientEf))
|
||||||
|
client' = do
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
env <- ask
|
||||||
|
|
||||||
|
let (bornEvs, startDriver) = client env (writeTQueue ventQ)
|
||||||
|
|
||||||
|
let runDriver = do
|
||||||
|
diOnEffect <- startDriver
|
||||||
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
|
pure (bornEvs, runDriver)
|
||||||
|
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Iris -- HTTP Client Driver
|
||||||
|
|
||||||
|
Until born events succeeds, ignore effects.
|
||||||
|
Wait until born event callbacks invoked.
|
||||||
|
If success, signal success.
|
||||||
|
If failure, try again several times.
|
||||||
|
If still failure, bring down ship.
|
||||||
|
Once born event succeeds, hold on to effects.
|
||||||
|
Once all other drivers have booted:
|
||||||
|
- Execute stashed effects.
|
||||||
|
- Begin normal operation (start accepting requests)
|
||||||
|
-}
|
||||||
|
client
|
||||||
|
:: forall e
|
||||||
|
. (HasLogFunc e, HasKingId e)
|
||||||
|
=> e
|
||||||
|
-> (EvErr -> STM ())
|
||||||
|
-> ([Ev], RAcquire e (HttpClientEf -> IO ()))
|
||||||
|
client env plan = (initialEvents, runHttpClient)
|
||||||
where
|
where
|
||||||
|
kingId = view (kingIdL . to fromIntegral) env
|
||||||
|
|
||||||
initialEvents :: [Ev]
|
initialEvents :: [Ev]
|
||||||
initialEvents = [bornEv kingId]
|
initialEvents = [bornEv kingId]
|
||||||
|
|
||||||
runHttpClient :: RAcquire e (EffCb e HttpClientEf)
|
runHttpClient :: RAcquire e (HttpClientEf -> IO ())
|
||||||
runHttpClient = handleEffect <$> mkRAcquire start stop
|
runHttpClient = handleEffect <$> mkRAcquire start stop
|
||||||
|
|
||||||
start :: RIO e (HttpClientDrv)
|
start :: RIO e (HttpClientDrv)
|
||||||
@ -75,10 +118,10 @@ client kingId enqueueEv = (initialEvents, runHttpClient)
|
|||||||
liveThreads <- atomically $ readTVar hcdLive
|
liveThreads <- atomically $ readTVar hcdLive
|
||||||
mapM_ cancel liveThreads
|
mapM_ cancel liveThreads
|
||||||
|
|
||||||
handleEffect :: HttpClientDrv -> HttpClientEf -> RIO e ()
|
handleEffect :: HttpClientDrv -> HttpClientEf -> IO ()
|
||||||
handleEffect drv = \case
|
handleEffect drv = \case
|
||||||
HCERequest _ id req -> newReq drv id req
|
HCERequest _ id req -> runRIO env (newReq drv id req)
|
||||||
HCECancelRequest _ id -> cancelReq drv id
|
HCECancelRequest _ id -> runRIO env (cancelReq drv id)
|
||||||
|
|
||||||
newReq :: HttpClientDrv -> ReqId -> HttpClientReq -> RIO e ()
|
newReq :: HttpClientDrv -> ReqId -> HttpClientReq -> RIO e ()
|
||||||
newReq drv id req = do
|
newReq drv id req = do
|
||||||
@ -124,8 +167,14 @@ client kingId enqueueEv = (initialEvents, runHttpClient)
|
|||||||
planEvent :: ReqId -> HttpEvent -> RIO e ()
|
planEvent :: ReqId -> HttpEvent -> RIO e ()
|
||||||
planEvent id ev = do
|
planEvent id ev = do
|
||||||
logDebug $ displayShow ("(http client response)", id, (describe ev))
|
logDebug $ displayShow ("(http client response)", id, (describe ev))
|
||||||
atomically $ enqueueEv $ EvBlip $ BlipEvHttpClient $
|
|
||||||
HttpClientEvReceive (kingId, ()) (fromIntegral id) ev
|
let recvEv = EvBlip
|
||||||
|
$ BlipEvHttpClient
|
||||||
|
$ HttpClientEvReceive (kingId, ()) (fromIntegral id) ev
|
||||||
|
|
||||||
|
let recvFailed _ = pure ()
|
||||||
|
|
||||||
|
atomically $ plan (EvErr recvEv recvFailed)
|
||||||
|
|
||||||
-- show an HttpEvent with byte count instead of raw data
|
-- show an HttpEvent with byte count instead of raw data
|
||||||
describe :: HttpEvent -> String
|
describe :: HttpEvent -> String
|
||||||
|
@ -1,635 +0,0 @@
|
|||||||
{-|
|
|
||||||
Http Server Driver
|
|
||||||
|
|
||||||
TODO Make sure that HTTP sockets get closed on shutdown.
|
|
||||||
|
|
||||||
TODO What is this about?
|
|
||||||
|
|
||||||
// if we don't explicitly set this field, h2o will send with
|
|
||||||
// transfer-encoding: chunked
|
|
||||||
//
|
|
||||||
if ( 1 == has_len_i ) {
|
|
||||||
rec_u->res.content_length = ( 0 == gen_u->bod_u ) ?
|
|
||||||
0 : gen_u->bod_u->len_w;
|
|
||||||
}
|
|
||||||
|
|
||||||
TODO Does this matter, is is using WAI's default behavior ok?
|
|
||||||
|
|
||||||
rec_u->res.reason = (status < 200) ? "weird" :
|
|
||||||
(status < 300) ? "ok" :
|
|
||||||
(status < 400) ? "moved" :
|
|
||||||
(status < 500) ? "missing" :
|
|
||||||
"hosed";
|
|
||||||
-}
|
|
||||||
|
|
||||||
module Urbit.Vere.Http.Server where
|
|
||||||
|
|
||||||
import Data.Conduit
|
|
||||||
import Urbit.Arvo hiding (ServerId, reqBody, reqUrl, secure)
|
|
||||||
import Urbit.King.Config
|
|
||||||
import Urbit.Noun
|
|
||||||
import Urbit.Prelude hiding (Builder)
|
|
||||||
import Urbit.Vere.Pier.Types
|
|
||||||
|
|
||||||
import Data.Binary.Builder (Builder, fromByteString)
|
|
||||||
import Data.Bits (shiftL, (.|.))
|
|
||||||
import Data.PEM (pemParseBS, pemWriteBS)
|
|
||||||
import Network.Socket (SockAddr(..))
|
|
||||||
import System.Directory (doesFileExist, removeFile)
|
|
||||||
import System.Random (randomIO)
|
|
||||||
import Urbit.Vere.Http (convertHeaders, unconvertHeaders)
|
|
||||||
|
|
||||||
import qualified Network.HTTP.Types as H
|
|
||||||
import qualified Network.Socket as Net
|
|
||||||
import qualified Network.Wai as W
|
|
||||||
import qualified Network.Wai.Conduit as W
|
|
||||||
import qualified Network.Wai.Handler.Warp as W
|
|
||||||
import qualified Network.Wai.Handler.WarpTLS as W
|
|
||||||
|
|
||||||
|
|
||||||
-- Internal Types --------------------------------------------------------------
|
|
||||||
|
|
||||||
type HasShipEnv e = (HasLogFunc e, HasNetworkConfig e, HasPierConfig e)
|
|
||||||
|
|
||||||
type ReqId = UD
|
|
||||||
type SeqId = UD -- Unused, always 1
|
|
||||||
|
|
||||||
{-|
|
|
||||||
The sequence of actions on a given request *should* be:
|
|
||||||
|
|
||||||
[%head .] [%bloc .]* %done
|
|
||||||
|
|
||||||
But we will actually accept anything, and mostly do the right
|
|
||||||
thing. There are two situations where we ignore ignore the data from
|
|
||||||
some actions.
|
|
||||||
|
|
||||||
- If you send something *after* a %done action, it will be ignored.
|
|
||||||
- If you send a %done before a %head, we will produce "444 No
|
|
||||||
Response" with an empty response body.
|
|
||||||
-}
|
|
||||||
data RespAction
|
|
||||||
= RAHead ResponseHeader File
|
|
||||||
| RAFull ResponseHeader File
|
|
||||||
| RABloc File
|
|
||||||
| RADone
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
data LiveReqs = LiveReqs
|
|
||||||
{ nextReqId :: ReqId
|
|
||||||
, activeReqs :: Map ReqId (TQueue RespAction)
|
|
||||||
}
|
|
||||||
|
|
||||||
data Ports = Ports
|
|
||||||
{ pHttps :: Maybe Port
|
|
||||||
, pHttp :: Port
|
|
||||||
, pLoop :: Port
|
|
||||||
}
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
newtype Drv = Drv { unDrv :: MVar (Maybe Serv) }
|
|
||||||
|
|
||||||
data Serv = Serv
|
|
||||||
{ sServId :: ServId
|
|
||||||
, sConfig :: HttpServerConf
|
|
||||||
, sLoopTid :: Async ()
|
|
||||||
, sHttpTid :: Async ()
|
|
||||||
, sHttpsTid :: Maybe (Async ())
|
|
||||||
, sLoopSock :: Net.Socket
|
|
||||||
, sHttpSock :: Net.Socket
|
|
||||||
, sHttpsSock :: Net.Socket
|
|
||||||
, sPorts :: Ports
|
|
||||||
, sPortsFile :: FilePath
|
|
||||||
, sLiveReqs :: TVar LiveReqs
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-- RespAction -- Reorganized HttpEvent for Cleaner Processing ------------------
|
|
||||||
|
|
||||||
reorgHttpEvent :: HttpEvent -> [RespAction]
|
|
||||||
reorgHttpEvent = \case
|
|
||||||
Start head mBlk True -> [RAFull head (fromMaybe "" mBlk)]
|
|
||||||
Start head mBlk False -> [RAHead head (fromMaybe "" mBlk)]
|
|
||||||
Cancel () -> [RADone]
|
|
||||||
Continue mBlk isDone -> toList (RABloc <$> mBlk)
|
|
||||||
<> if isDone then [RADone] else []
|
|
||||||
|
|
||||||
|
|
||||||
-- Generic Service Stop/Restart -- Using an MVar for Atomicity -----------------
|
|
||||||
|
|
||||||
{-|
|
|
||||||
Restart a running service.
|
|
||||||
|
|
||||||
This can probably be made simpler, but it
|
|
||||||
|
|
||||||
- Sets the MVar to Nothing if there was an exception whil starting
|
|
||||||
or stopping the service.
|
|
||||||
|
|
||||||
- Keeps the MVar lock until the restart process finishes.
|
|
||||||
-}
|
|
||||||
restartService :: ∀e s. HasLogFunc e
|
|
||||||
=> MVar (Maybe s)
|
|
||||||
-> RIO e s
|
|
||||||
-> (s -> RIO e ())
|
|
||||||
-> RIO e (Either SomeException s)
|
|
||||||
restartService vServ sstart kkill = do
|
|
||||||
logDebug "restartService"
|
|
||||||
modifyMVar vServ $ \case
|
|
||||||
Nothing -> doStart
|
|
||||||
Just sv -> doRestart sv
|
|
||||||
where
|
|
||||||
doRestart :: s -> RIO e (Maybe s, Either SomeException s)
|
|
||||||
doRestart serv = do
|
|
||||||
logDebug "doStart"
|
|
||||||
try (kkill serv) >>= \case
|
|
||||||
Left exn -> pure (Nothing, Left exn)
|
|
||||||
Right () -> doStart
|
|
||||||
|
|
||||||
doStart :: RIO e (Maybe s, Either SomeException s)
|
|
||||||
doStart = do
|
|
||||||
logDebug "doStart"
|
|
||||||
try sstart <&> \case
|
|
||||||
Right s -> (Just s, Right s)
|
|
||||||
Left exn -> (Nothing, Left exn)
|
|
||||||
|
|
||||||
stopService :: HasLogFunc e
|
|
||||||
=> MVar (Maybe s)
|
|
||||||
-> (s -> RIO e ())
|
|
||||||
-> RIO e (Either SomeException ())
|
|
||||||
stopService vServ kkill = do
|
|
||||||
logDebug "stopService"
|
|
||||||
modifyMVar vServ $ \case
|
|
||||||
Nothing -> pure (Nothing, Right ())
|
|
||||||
Just sv -> do res <- try (kkill sv)
|
|
||||||
pure (Nothing, res)
|
|
||||||
|
|
||||||
|
|
||||||
-- Live Requests Table -- All Requests Still Waiting for Responses -------------
|
|
||||||
|
|
||||||
emptyLiveReqs :: LiveReqs
|
|
||||||
emptyLiveReqs = LiveReqs 1 mempty
|
|
||||||
|
|
||||||
respondToLiveReq :: TVar LiveReqs -> ReqId -> RespAction -> STM ()
|
|
||||||
respondToLiveReq var req ev = do
|
|
||||||
mVar <- lookup req . activeReqs <$> readTVar var
|
|
||||||
case mVar of
|
|
||||||
Nothing -> pure ()
|
|
||||||
Just tv -> writeTQueue tv ev
|
|
||||||
|
|
||||||
rmLiveReq :: TVar LiveReqs -> ReqId -> STM ()
|
|
||||||
rmLiveReq var reqId = do
|
|
||||||
liv <- readTVar var
|
|
||||||
writeTVar var (liv { activeReqs = deleteMap reqId (activeReqs liv) })
|
|
||||||
|
|
||||||
newLiveReq :: TVar LiveReqs -> STM (ReqId, TQueue RespAction)
|
|
||||||
newLiveReq var = do
|
|
||||||
liv <- readTVar var
|
|
||||||
tmv <- newTQueue
|
|
||||||
|
|
||||||
let (nex, act) = (nextReqId liv, activeReqs liv)
|
|
||||||
|
|
||||||
writeTVar var (LiveReqs (nex+1) (insertMap nex tmv act))
|
|
||||||
|
|
||||||
pure (nex, tmv)
|
|
||||||
|
|
||||||
|
|
||||||
-- Ports File ------------------------------------------------------------------
|
|
||||||
|
|
||||||
removePortsFile :: FilePath -> RIO e ()
|
|
||||||
removePortsFile pax =
|
|
||||||
io (doesFileExist pax) >>= \case
|
|
||||||
True -> io $ removeFile pax
|
|
||||||
False -> pure ()
|
|
||||||
|
|
||||||
portsFileText :: Ports -> Text
|
|
||||||
portsFileText Ports{..} =
|
|
||||||
unlines $ catMaybes
|
|
||||||
[ pHttps <&> \p -> (tshow p <> " secure public")
|
|
||||||
, Just (tshow (unPort pHttp) <> " insecure public")
|
|
||||||
, Just (tshow (unPort pLoop) <> " insecure loopback")
|
|
||||||
]
|
|
||||||
|
|
||||||
writePortsFile :: FilePath -> Ports -> RIO e ()
|
|
||||||
writePortsFile f = writeFile f . encodeUtf8 . portsFileText
|
|
||||||
|
|
||||||
|
|
||||||
-- Random Helpers --------------------------------------------------------------
|
|
||||||
|
|
||||||
cordBytes :: Cord -> ByteString
|
|
||||||
cordBytes = encodeUtf8 . unCord
|
|
||||||
|
|
||||||
wainBytes :: Wain -> ByteString
|
|
||||||
wainBytes = encodeUtf8 . unWain
|
|
||||||
|
|
||||||
pass :: Monad m => m ()
|
|
||||||
pass = pure ()
|
|
||||||
|
|
||||||
whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
|
|
||||||
whenJust Nothing act = pure ()
|
|
||||||
whenJust (Just a) act = act a
|
|
||||||
|
|
||||||
cookMeth :: W.Request -> Maybe Method
|
|
||||||
cookMeth = H.parseMethod . W.requestMethod >>> \case
|
|
||||||
Left _ -> Nothing
|
|
||||||
Right m -> Just m
|
|
||||||
|
|
||||||
reqIdCord :: ReqId -> Cord
|
|
||||||
reqIdCord = Cord . tshow
|
|
||||||
|
|
||||||
reqBody :: W.Request -> RIO e (Maybe File)
|
|
||||||
reqBody req = do
|
|
||||||
bodyLbs <- io $ W.strictRequestBody req
|
|
||||||
pure $ if length bodyLbs == 0
|
|
||||||
then Nothing
|
|
||||||
else Just $ File $ Octs (toStrict bodyLbs)
|
|
||||||
|
|
||||||
reqAddr :: W.Request -> Address
|
|
||||||
reqAddr = W.remoteHost >>> \case
|
|
||||||
SockAddrInet _ a -> AIpv4 (Ipv4 a)
|
|
||||||
SockAddrInet6 _ _ a _ -> AIpv6 (mkIpv6 a)
|
|
||||||
_ -> error "invalid sock addr"
|
|
||||||
|
|
||||||
mkIpv6 :: (Word32, Word32, Word32, Word32) -> Ipv6
|
|
||||||
mkIpv6 (p, q, r, s) = Ipv6 (pBits .|. qBits .|. rBits .|. sBits)
|
|
||||||
where
|
|
||||||
pBits = shiftL (fromIntegral p) 0
|
|
||||||
qBits = shiftL (fromIntegral q) 32
|
|
||||||
rBits = shiftL (fromIntegral r) 64
|
|
||||||
sBits = shiftL (fromIntegral s) 96
|
|
||||||
|
|
||||||
reqUrl :: W.Request -> Cord
|
|
||||||
reqUrl r = Cord $ decodeUtf8 $ W.rawPathInfo r <> W.rawQueryString r
|
|
||||||
|
|
||||||
|
|
||||||
-- Utilities for Constructing Events -------------------------------------------
|
|
||||||
|
|
||||||
data WhichServer = Secure | Insecure | Loopback
|
|
||||||
deriving (Eq)
|
|
||||||
|
|
||||||
servEv :: HttpServerEv -> Ev
|
|
||||||
servEv = EvBlip . BlipEvHttpServer
|
|
||||||
|
|
||||||
bornEv :: KingId -> Ev
|
|
||||||
bornEv king =
|
|
||||||
servEv $ HttpServerEvBorn (king, ()) ()
|
|
||||||
|
|
||||||
liveEv :: ServId -> Ports -> Ev
|
|
||||||
liveEv sId Ports{..} =
|
|
||||||
servEv $ HttpServerEvLive (sId, ()) pHttp pHttps
|
|
||||||
|
|
||||||
cancelEv :: ServId -> ReqId -> Ev
|
|
||||||
cancelEv sId reqId =
|
|
||||||
servEv $ HttpServerEvCancelRequest (sId, reqId, 1, ()) ()
|
|
||||||
|
|
||||||
reqEv :: ServId -> ReqId -> WhichServer -> Address -> HttpRequest -> Ev
|
|
||||||
reqEv sId reqId which addr req =
|
|
||||||
case which of
|
|
||||||
Loopback ->
|
|
||||||
servEv $ HttpServerEvRequestLocal (sId, reqId, 1, ())
|
|
||||||
$ HttpServerReq False addr req
|
|
||||||
_ ->
|
|
||||||
servEv $ HttpServerEvRequest (sId, reqId, 1, ())
|
|
||||||
$ HttpServerReq (which == Secure) addr req
|
|
||||||
|
|
||||||
|
|
||||||
-- Http Server Flows -----------------------------------------------------------
|
|
||||||
|
|
||||||
data Resp
|
|
||||||
= RHead ResponseHeader [File]
|
|
||||||
| RFull ResponseHeader [File]
|
|
||||||
| RNone
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
{-|
|
|
||||||
This accepts all action orderings so that there are no edge-cases
|
|
||||||
to be handled:
|
|
||||||
|
|
||||||
- If %bloc before %head, collect it and wait for %head.
|
|
||||||
- If %done before %head, ignore all chunks and produce Nothing.
|
|
||||||
|
|
||||||
TODO Be strict about this instead. Ignore invalid request streams.
|
|
||||||
-}
|
|
||||||
getResp :: TQueue RespAction -> RIO e Resp
|
|
||||||
getResp tmv = go []
|
|
||||||
where
|
|
||||||
go çunks = atomically (readTQueue tmv) >>= \case
|
|
||||||
RAHead head ç -> pure $ RHead head $ reverse (ç : çunks)
|
|
||||||
RAFull head ç -> pure $ RFull head $ reverse (ç : çunks)
|
|
||||||
RABloc ç -> go (ç : çunks)
|
|
||||||
RADone -> pure RNone
|
|
||||||
|
|
||||||
{-|
|
|
||||||
- Immediatly yield all of the initial chunks
|
|
||||||
- Yield the data from %bloc action.
|
|
||||||
- Close the stream when we hit a %done action.
|
|
||||||
-}
|
|
||||||
streamBlocks :: HasLogFunc e
|
|
||||||
=> e -> [File] -> TQueue RespAction
|
|
||||||
-> ConduitT () (Flush Builder) IO ()
|
|
||||||
streamBlocks env init tmv =
|
|
||||||
for_ init yieldÇunk >> go
|
|
||||||
where
|
|
||||||
yieldFlush = \x -> yield (Chunk x) >> yield Flush
|
|
||||||
logDupHead = runRIO env (logError "Multiple %head actions on one request")
|
|
||||||
|
|
||||||
yieldÇunk = \case
|
|
||||||
"" -> runRIO env (logTrace "sending empty chunk")
|
|
||||||
c -> do runRIO env (logTrace (display ("sending chunk " <> tshow c)))
|
|
||||||
(yieldFlush . fromByteString . unOcts . unFile) c
|
|
||||||
|
|
||||||
go = atomically (readTQueue tmv) >>= \case
|
|
||||||
RAHead head c -> logDupHead >> yieldÇunk c >> go
|
|
||||||
RAFull head c -> logDupHead >> yieldÇunk c >> go
|
|
||||||
RABloc c -> yieldÇunk c >> go
|
|
||||||
RADone -> pure ()
|
|
||||||
|
|
||||||
sendResponse :: HasLogFunc e
|
|
||||||
=> (W.Response -> IO W.ResponseReceived)
|
|
||||||
-> TQueue RespAction
|
|
||||||
-> RIO e W.ResponseReceived
|
|
||||||
sendResponse cb tmv = do
|
|
||||||
env <- ask
|
|
||||||
getResp tmv >>= \case
|
|
||||||
RNone -> io $ cb $ W.responseLBS (H.mkStatus 444 "No Response") []
|
|
||||||
$ ""
|
|
||||||
RFull h f -> io $ cb $ W.responseLBS (hdrStatus h) (hdrHeaders h)
|
|
||||||
$ fromStrict $ concat $ unOcts . unFile <$> f
|
|
||||||
RHead h i -> io $ cb $ W.responseSource (hdrStatus h) (hdrHeaders h)
|
|
||||||
$ streamBlocks env i tmv
|
|
||||||
where
|
|
||||||
hdrHeaders :: ResponseHeader -> [H.Header]
|
|
||||||
hdrHeaders = unconvertHeaders . headers
|
|
||||||
|
|
||||||
hdrStatus :: ResponseHeader -> H.Status
|
|
||||||
hdrStatus = toEnum . fromIntegral . statusCode
|
|
||||||
|
|
||||||
liveReq :: TVar LiveReqs -> RAcquire e (ReqId, TQueue RespAction)
|
|
||||||
liveReq vLiv = mkRAcquire ins del
|
|
||||||
where
|
|
||||||
ins = atomically (newLiveReq vLiv)
|
|
||||||
del = atomically . rmLiveReq vLiv . fst
|
|
||||||
|
|
||||||
app :: HasLogFunc e
|
|
||||||
=> e -> ServId -> TVar LiveReqs -> (Ev -> STM ()) -> WhichServer
|
|
||||||
-> W.Application
|
|
||||||
app env sId liv plan which req respond =
|
|
||||||
runRIO env $
|
|
||||||
rwith (liveReq liv) $ \(reqId, respVar) -> do
|
|
||||||
body <- reqBody req
|
|
||||||
meth <- maybe (error "bad method") pure (cookMeth req)
|
|
||||||
|
|
||||||
let addr = reqAddr req
|
|
||||||
hdrs = convertHeaders $ W.requestHeaders req
|
|
||||||
evReq = HttpRequest meth (reqUrl req) hdrs body
|
|
||||||
|
|
||||||
atomically $ plan (reqEv sId reqId which addr evReq)
|
|
||||||
|
|
||||||
try (sendResponse respond respVar) >>= \case
|
|
||||||
Right rr -> pure rr
|
|
||||||
Left exn -> do
|
|
||||||
io $ atomically $ plan (cancelEv sId reqId)
|
|
||||||
logError $ display ("Exception during request" <> tshow exn)
|
|
||||||
throwIO (exn :: SomeException)
|
|
||||||
|
|
||||||
|
|
||||||
-- Top-Level Driver Interface --------------------------------------------------
|
|
||||||
|
|
||||||
data CantOpenPort = CantOpenPort W.Port
|
|
||||||
deriving (Eq, Ord, Show, Exception)
|
|
||||||
|
|
||||||
data WhichPort
|
|
||||||
= WPSpecific W.Port
|
|
||||||
| WPChoices [W.Port]
|
|
||||||
|
|
||||||
data SockOpts = SockOpts
|
|
||||||
{ soLocalhost :: Bool
|
|
||||||
, soWhich :: WhichPort
|
|
||||||
}
|
|
||||||
|
|
||||||
data PortsToTry = PortsToTry
|
|
||||||
{ pttSec :: SockOpts
|
|
||||||
, pttIns :: SockOpts
|
|
||||||
, pttLop :: SockOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
{-|
|
|
||||||
Opens a socket on some port, accepting connections from `127.0.0.1`
|
|
||||||
if fake and `0.0.0.0` if real.
|
|
||||||
|
|
||||||
It will attempt to open a socket on each of the supplied ports in
|
|
||||||
order. If they all fail, it will ask the operating system to give
|
|
||||||
us an open socket on *any* open port. If that fails, it will throw
|
|
||||||
an exception.
|
|
||||||
-}
|
|
||||||
openPort :: forall e . HasLogFunc e => SockOpts -> RIO e (W.Port, Net.Socket)
|
|
||||||
openPort SockOpts {..} = case soWhich of
|
|
||||||
WPSpecific x -> insist (fromIntegral x)
|
|
||||||
WPChoices xs -> loop (fromIntegral <$> xs)
|
|
||||||
|
|
||||||
where
|
|
||||||
loop :: [W.Port] -> RIO e (W.Port, Net.Socket)
|
|
||||||
loop = \case
|
|
||||||
[] -> do
|
|
||||||
logTrace "Fallback: asking the OS to give us some free port."
|
|
||||||
ps <- io W.openFreePort
|
|
||||||
logTrace (display ("Opened port " <> tshow (fst ps)))
|
|
||||||
pure ps
|
|
||||||
x : xs -> do
|
|
||||||
logTrace (display ("Trying to open port " <> tshow x))
|
|
||||||
io (tryOpen x) >>= \case
|
|
||||||
Left (err :: IOError) -> do
|
|
||||||
logWarn (display ("Failed to open port " <> tshow x))
|
|
||||||
logWarn (display (tshow err))
|
|
||||||
loop xs
|
|
||||||
Right ps -> do
|
|
||||||
logTrace (display ("Opened port " <> tshow (fst ps)))
|
|
||||||
pure ps
|
|
||||||
|
|
||||||
insist :: W.Port -> RIO e (W.Port, Net.Socket)
|
|
||||||
insist p = do
|
|
||||||
logTrace (display ("Opening configured port " <> tshow p))
|
|
||||||
io (tryOpen p) >>= \case
|
|
||||||
Left (err :: IOError) -> do
|
|
||||||
logWarn (display ("Failed to open port " <> tshow p))
|
|
||||||
logWarn (display (tshow err))
|
|
||||||
throwIO (CantOpenPort p)
|
|
||||||
Right ps -> do
|
|
||||||
logTrace (display ("Opened port " <> tshow (fst ps)))
|
|
||||||
pure ps
|
|
||||||
|
|
||||||
bindTo = if soLocalhost then "127.0.0.1" else "0.0.0.0"
|
|
||||||
|
|
||||||
getBindAddr :: W.Port -> IO SockAddr
|
|
||||||
getBindAddr por =
|
|
||||||
Net.getAddrInfo Nothing (Just bindTo) (Just (show por)) >>= \case
|
|
||||||
[] -> error "this should never happen."
|
|
||||||
x : _ -> pure (Net.addrAddress x)
|
|
||||||
|
|
||||||
bindListenPort :: W.Port -> Net.Socket -> IO Net.PortNumber
|
|
||||||
bindListenPort por sok = do
|
|
||||||
Net.bind sok =<< getBindAddr por
|
|
||||||
Net.listen sok 1
|
|
||||||
Net.socketPort sok
|
|
||||||
|
|
||||||
-- `inet_addr`, `bind`, and `listen` all throw `IOError` if they fail.
|
|
||||||
tryOpen :: W.Port -> IO (Either IOError (W.Port, Net.Socket))
|
|
||||||
tryOpen por = do
|
|
||||||
sok <- Net.socket Net.AF_INET Net.Stream Net.defaultProtocol
|
|
||||||
try (bindListenPort por sok) >>= \case
|
|
||||||
Left exn -> Net.close sok $> Left exn
|
|
||||||
Right por -> pure (Right (fromIntegral por, sok))
|
|
||||||
|
|
||||||
httpServerPorts :: HasShipEnv e => Bool -> RIO e PortsToTry
|
|
||||||
httpServerPorts fak = do
|
|
||||||
ins <- view (networkConfigL . ncHttpPort . to (fmap fromIntegral))
|
|
||||||
sec <- view (networkConfigL . ncHttpsPort . to (fmap fromIntegral))
|
|
||||||
lop <- view (networkConfigL . ncLocalPort . to (fmap fromIntegral))
|
|
||||||
localMode <- view (networkConfigL . ncNetMode . to (== NMLocalhost))
|
|
||||||
|
|
||||||
let local = localMode || fak
|
|
||||||
|
|
||||||
let pttSec = case (sec, fak) of
|
|
||||||
(Just p , _ ) -> SockOpts local (WPSpecific p)
|
|
||||||
(Nothing, False) -> SockOpts local (WPChoices (443 : [8443 .. 8448]))
|
|
||||||
(Nothing, True ) -> SockOpts local (WPChoices ([8443 .. 8448]))
|
|
||||||
|
|
||||||
let pttIns = case (ins, fak) of
|
|
||||||
(Just p , _ ) -> SockOpts local (WPSpecific p)
|
|
||||||
(Nothing, False) -> SockOpts local (WPChoices (80 : [8080 .. 8085]))
|
|
||||||
(Nothing, True ) -> SockOpts local (WPChoices [8080 .. 8085])
|
|
||||||
|
|
||||||
let pttLop = case (lop, fak) of
|
|
||||||
(Just p , _) -> SockOpts local (WPSpecific p)
|
|
||||||
(Nothing, _) -> SockOpts local (WPChoices [12321 .. 12326])
|
|
||||||
|
|
||||||
pure (PortsToTry { .. })
|
|
||||||
|
|
||||||
parseCerts :: ByteString -> Maybe (ByteString, [ByteString])
|
|
||||||
parseCerts bs = do
|
|
||||||
pems <- pemParseBS bs & either (const Nothing) Just
|
|
||||||
case pems of
|
|
||||||
[] -> Nothing
|
|
||||||
p:ps -> pure (pemWriteBS p, pemWriteBS <$> ps)
|
|
||||||
|
|
||||||
startServ :: (HasPierConfig e, HasLogFunc e, HasNetworkConfig e)
|
|
||||||
=> Bool -> HttpServerConf -> (Ev -> STM ())
|
|
||||||
-> RIO e Serv
|
|
||||||
startServ isFake conf plan = do
|
|
||||||
logDebug "startServ"
|
|
||||||
|
|
||||||
let tls = do (PEM key, PEM certs) <- hscSecure conf
|
|
||||||
(cert, chain) <- parseCerts (wainBytes certs)
|
|
||||||
pure $ W.tlsSettingsChainMemory cert chain $ wainBytes key
|
|
||||||
|
|
||||||
sId <- io $ ServId . UV . fromIntegral <$> (randomIO :: IO Word32)
|
|
||||||
liv <- newTVarIO emptyLiveReqs
|
|
||||||
|
|
||||||
ptt <- httpServerPorts isFake
|
|
||||||
|
|
||||||
(httpPortInt, httpSock) <- openPort (pttIns ptt)
|
|
||||||
(httpsPortInt, httpsSock) <- openPort (pttSec ptt)
|
|
||||||
(loopPortInt, loopSock) <- openPort (pttLop ptt)
|
|
||||||
|
|
||||||
let httpPort = Port (fromIntegral httpPortInt)
|
|
||||||
httpsPort = Port (fromIntegral httpsPortInt)
|
|
||||||
loopPort = Port (fromIntegral loopPortInt)
|
|
||||||
|
|
||||||
let loopOpts = W.defaultSettings & W.setPort (fromIntegral loopPort)
|
|
||||||
& W.setHost "127.0.0.1"
|
|
||||||
& W.setTimeout (5 * 60)
|
|
||||||
httpOpts = W.defaultSettings & W.setHost "*"
|
|
||||||
& W.setPort (fromIntegral httpPort)
|
|
||||||
httpsOpts = W.defaultSettings & W.setHost "*"
|
|
||||||
& W.setPort (fromIntegral httpsPort)
|
|
||||||
|
|
||||||
env <- ask
|
|
||||||
|
|
||||||
logDebug "Starting loopback server"
|
|
||||||
loopTid <- async $ io
|
|
||||||
$ W.runSettingsSocket loopOpts loopSock
|
|
||||||
$ app env sId liv plan Loopback
|
|
||||||
|
|
||||||
logDebug "Starting HTTP server"
|
|
||||||
httpTid <- async $ io
|
|
||||||
$ W.runSettingsSocket httpOpts httpSock
|
|
||||||
$ app env sId liv plan Insecure
|
|
||||||
|
|
||||||
logDebug "Starting HTTPS server"
|
|
||||||
httpsTid <- for tls $ \tlsOpts ->
|
|
||||||
async $ io
|
|
||||||
$ W.runTLSSocket tlsOpts httpsOpts httpsSock
|
|
||||||
$ app env sId liv plan Secure
|
|
||||||
|
|
||||||
pierPath <- view pierPathL
|
|
||||||
let por = Ports (tls <&> const httpsPort) httpPort loopPort
|
|
||||||
fil = pierPath <> "/.http.ports"
|
|
||||||
|
|
||||||
logDebug $ displayShow (sId, por, fil)
|
|
||||||
|
|
||||||
logDebug "Finished started HTTP Servers"
|
|
||||||
|
|
||||||
pure $ Serv sId conf
|
|
||||||
loopTid httpTid httpsTid
|
|
||||||
httpSock httpsSock loopSock
|
|
||||||
por fil liv
|
|
||||||
|
|
||||||
killServ :: HasLogFunc e => Serv -> RIO e ()
|
|
||||||
killServ Serv{..} = do
|
|
||||||
cancel sLoopTid
|
|
||||||
cancel sHttpTid
|
|
||||||
traverse_ cancel sHttpsTid
|
|
||||||
io $ Net.close sHttpSock
|
|
||||||
io $ Net.close sHttpsSock
|
|
||||||
io $ Net.close sLoopSock
|
|
||||||
removePortsFile sPortsFile
|
|
||||||
(void . waitCatch) sLoopTid
|
|
||||||
(void . waitCatch) sHttpTid
|
|
||||||
traverse_ (void . waitCatch) sHttpsTid
|
|
||||||
|
|
||||||
kill :: HasLogFunc e => Drv -> RIO e ()
|
|
||||||
kill (Drv v) = stopService v killServ >>= fromEither
|
|
||||||
|
|
||||||
respond :: HasLogFunc e
|
|
||||||
=> Drv -> ReqId -> HttpEvent -> RIO e ()
|
|
||||||
respond (Drv v) reqId ev = do
|
|
||||||
readMVar v >>= \case
|
|
||||||
Nothing -> logWarn "Got a response to a request that does not exist."
|
|
||||||
Just sv -> do logDebug $ displayShow $ reorgHttpEvent ev
|
|
||||||
for_ (reorgHttpEvent ev) $
|
|
||||||
atomically . respondToLiveReq (sLiveReqs sv) reqId
|
|
||||||
|
|
||||||
serv :: ∀e. HasShipEnv e
|
|
||||||
=> KingId -> QueueEv -> Bool
|
|
||||||
-> ([Ev], RAcquire e (EffCb e HttpServerEf))
|
|
||||||
serv king plan isFake =
|
|
||||||
(initialEvents, runHttpServer)
|
|
||||||
where
|
|
||||||
initialEvents :: [Ev]
|
|
||||||
initialEvents = [bornEv king]
|
|
||||||
|
|
||||||
runHttpServer :: RAcquire e (EffCb e HttpServerEf)
|
|
||||||
runHttpServer = handleEf <$> mkRAcquire (Drv <$> newMVar Nothing) kill
|
|
||||||
|
|
||||||
restart :: Drv -> HttpServerConf -> RIO e Serv
|
|
||||||
restart (Drv var) conf = do
|
|
||||||
logDebug "Restarting http server"
|
|
||||||
res <- fromEither =<<
|
|
||||||
restartService var (startServ isFake conf plan) killServ
|
|
||||||
logDebug "Done restating http server"
|
|
||||||
pure res
|
|
||||||
|
|
||||||
handleEf :: Drv -> HttpServerEf -> RIO e ()
|
|
||||||
handleEf drv = \case
|
|
||||||
HSESetConfig (i, ()) conf -> do
|
|
||||||
-- print (i, king)
|
|
||||||
-- when (i == fromIntegral king) $ do
|
|
||||||
logDebug "restarting"
|
|
||||||
Serv{..} <- restart drv conf
|
|
||||||
logDebug "Enqueue %live"
|
|
||||||
atomically $ plan (liveEv sServId sPorts)
|
|
||||||
logDebug "Write ports file"
|
|
||||||
writePortsFile sPortsFile sPorts
|
|
||||||
HSEResponse (i, req, _seq, ()) ev -> do
|
|
||||||
-- print (i, king)
|
|
||||||
-- when (i == fromIntegral king) $ do
|
|
||||||
logDebug "respond"
|
|
||||||
respond drv (fromIntegral req) ev
|
|
@ -55,24 +55,24 @@ wsConn :: (FromNoun i, ToNoun o, Show i, Show o, HasLogFunc e)
|
|||||||
-> WS.Connection
|
-> WS.Connection
|
||||||
-> RIO e ()
|
-> RIO e ()
|
||||||
wsConn pre inp out wsc = do
|
wsConn pre inp out wsc = do
|
||||||
logWarn (pre <> "(wcConn) Connected!")
|
logDebug (pre <> "(wcConn) Connected!")
|
||||||
|
|
||||||
writer <- withRIOThread $ forever $ do
|
writer <- withRIOThread $ forever $ do
|
||||||
logWarn (pre <> "(wsConn) Waiting for data.")
|
logDebug (pre <> "(wsConn) Waiting for data.")
|
||||||
byt <- io $ toStrict <$> WS.receiveData wsc
|
byt <- io $ toStrict <$> WS.receiveData wsc
|
||||||
logWarn (pre <> "Got data")
|
logDebug (pre <> "Got data")
|
||||||
dat <- cueBSExn byt >>= fromNounExn
|
dat <- cueBSExn byt >>= fromNounExn
|
||||||
logWarn (pre <> "(wsConn) Decoded data, writing to chan")
|
logDebug (pre <> "(wsConn) Decoded data, writing to chan")
|
||||||
atomically $ writeTBMChan inp dat
|
atomically $ writeTBMChan inp dat
|
||||||
|
|
||||||
reader <- withRIOThread $ forever $ do
|
reader <- withRIOThread $ forever $ do
|
||||||
logWarn (pre <> "Waiting for data from chan")
|
logDebug (pre <> "Waiting for data from chan")
|
||||||
atomically (readTBMChan out) >>= \case
|
atomically (readTBMChan out) >>= \case
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
logWarn (pre <> "(wsConn) Connection closed")
|
logDebug (pre <> "(wsConn) Connection closed")
|
||||||
error "dead-conn"
|
error "dead-conn"
|
||||||
Just msg -> do
|
Just msg -> do
|
||||||
logWarn (pre <> "(wsConn) Got message! " <> displayShow msg)
|
logDebug (pre <> "(wsConn) Got message! " <> displayShow msg)
|
||||||
io $ WS.sendBinaryData wsc $ fromStrict $ jamBS $ toNoun msg
|
io $ WS.sendBinaryData wsc $ fromStrict $ jamBS $ toNoun msg
|
||||||
|
|
||||||
let cleanup = do
|
let cleanup = do
|
||||||
@ -82,7 +82,7 @@ wsConn pre inp out wsc = do
|
|||||||
|
|
||||||
flip finally cleanup $ do
|
flip finally cleanup $ do
|
||||||
res <- atomically (waitCatchSTM writer <|> waitCatchSTM reader)
|
res <- atomically (waitCatchSTM writer <|> waitCatchSTM reader)
|
||||||
logWarn $ displayShow (res :: Either SomeException ())
|
logDebug $ displayShow (res :: Either SomeException ())
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@ -111,7 +111,7 @@ wsServApp :: (HasLogFunc e, ToNoun o, FromNoun i, Show i, Show o)
|
|||||||
-> WS.PendingConnection
|
-> WS.PendingConnection
|
||||||
-> RIO e ()
|
-> RIO e ()
|
||||||
wsServApp cb pen = do
|
wsServApp cb pen = do
|
||||||
logError "NOUNSERV (wsServer) Got connection!"
|
logDebug "NOUNSERV (wsServer) Got connection!"
|
||||||
wsc <- io $ WS.acceptRequest pen
|
wsc <- io $ WS.acceptRequest pen
|
||||||
inp <- io $ newTBMChanIO 5
|
inp <- io $ newTBMChanIO 5
|
||||||
out <- io $ newTBMChanIO 5
|
out <- io $ newTBMChanIO 5
|
||||||
@ -125,10 +125,10 @@ wsServer = do
|
|||||||
|
|
||||||
tid <- async $ do
|
tid <- async $ do
|
||||||
env <- ask
|
env <- ask
|
||||||
logError "NOUNSERV (wsServer) Starting server"
|
logDebug "NOUNSERV (wsServer) Starting server"
|
||||||
io $ WS.runServer "127.0.0.1" 9999
|
io $ WS.runServer "127.0.0.1" 9999
|
||||||
$ runRIO env . wsServApp (writeTBMChan con)
|
$ runRIO env . wsServApp (writeTBMChan con)
|
||||||
logError "NOUNSERV (wsServer) Server died"
|
logDebug "NOUNSERV (wsServer) Server died"
|
||||||
atomically $ closeTBMChan con
|
atomically $ closeTBMChan con
|
||||||
|
|
||||||
pure $ Server (readTBMChan con) tid 9999
|
pure $ Server (readTBMChan con) tid 9999
|
||||||
@ -147,34 +147,34 @@ example = Just (99, (), 44)
|
|||||||
|
|
||||||
testIt :: HasLogFunc e => RIO e ()
|
testIt :: HasLogFunc e => RIO e ()
|
||||||
testIt = do
|
testIt = do
|
||||||
logTrace "(testIt) Starting Server"
|
logDebug "(testIt) Starting Server"
|
||||||
Server{..} <- wsServer @Example @Example
|
Server{..} <- wsServer @Example @Example
|
||||||
logTrace "(testIt) Connecting"
|
logDebug "(testIt) Connecting"
|
||||||
Client{..} <- wsClient @Example @Example "/" sData
|
Client{..} <- wsClient @Example @Example "/" sData
|
||||||
|
|
||||||
logTrace "(testIt) Accepting connection"
|
logDebug "(testIt) Accepting connection"
|
||||||
sConn <- fromJust "accept" =<< atomically sAccept
|
sConn <- fromJust "accept" =<< atomically sAccept
|
||||||
|
|
||||||
let
|
let
|
||||||
clientSend = do
|
clientSend = do
|
||||||
logTrace "(testIt) Sending from client"
|
logDebug "(testIt) Sending from client"
|
||||||
atomically (cSend cConn example)
|
atomically (cSend cConn example)
|
||||||
logTrace "(testIt) Waiting for response"
|
logDebug "(testIt) Waiting for response"
|
||||||
res <- atomically (cRecv sConn)
|
res <- atomically (cRecv sConn)
|
||||||
print ("clientSend", res, example)
|
print ("clientSend", res, example)
|
||||||
unless (res == Just example) $ do
|
unless (res == Just example) $ do
|
||||||
error "Bad data"
|
error "Bad data"
|
||||||
logInfo "(testIt) Success"
|
logDebug "(testIt) Success"
|
||||||
|
|
||||||
serverSend = do
|
serverSend = do
|
||||||
logTrace "(testIt) Sending from server"
|
logDebug "(testIt) Sending from server"
|
||||||
atomically (cSend sConn example)
|
atomically (cSend sConn example)
|
||||||
logTrace "(testIt) Waiting for response"
|
logDebug "(testIt) Waiting for response"
|
||||||
res <- atomically (cRecv cConn)
|
res <- atomically (cRecv cConn)
|
||||||
print ("serverSend", res, example)
|
print ("serverSend", res, example)
|
||||||
unless (res == Just example) $ do
|
unless (res == Just example) $ do
|
||||||
error "Bad data"
|
error "Bad data"
|
||||||
logInfo "(testIt) Success"
|
logDebug "(testIt) Success"
|
||||||
|
|
||||||
clientSend
|
clientSend
|
||||||
clientSend
|
clientSend
|
||||||
|
@ -1,413 +1,553 @@
|
|||||||
{-|
|
{-|
|
||||||
Top-Level Pier Management
|
Top-Level Pier Management
|
||||||
|
|
||||||
This is the code that starts the IO drivers and deals with
|
This is the code that starts the IO drivers and deals with communication
|
||||||
communication between the serf, the log, and the IO drivers.
|
between the serf, the event log, and the IO drivers.
|
||||||
-}
|
-}
|
||||||
module Urbit.Vere.Pier
|
module Urbit.Vere.Pier
|
||||||
( booted, resumed, getSnapshot, pier, runPersist, runCompute, generateBootSeq
|
( booted
|
||||||
) where
|
, runSerf
|
||||||
|
, resumed
|
||||||
|
, getSnapshot
|
||||||
|
, pier
|
||||||
|
, runPersist
|
||||||
|
, runCompute
|
||||||
|
, genBootSeq
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
|
||||||
import RIO.Directory
|
|
||||||
import System.Random
|
|
||||||
import Urbit.Arvo
|
|
||||||
import Urbit.King.Config
|
|
||||||
import Urbit.Vere.Pier.Types
|
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
|
import RIO.Directory
|
||||||
|
import Urbit.Arvo
|
||||||
|
import Urbit.King.App
|
||||||
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Data.Text (append)
|
import Control.Monad.STM (retry)
|
||||||
import System.Posix.Files (ownerModes, setFileMode)
|
import System.Posix.Files (ownerModes, setFileMode)
|
||||||
import Urbit.King.App (HasConfigDir(..), HasStderrLogFunc(..))
|
import Urbit.EventLog.LMDB (EventLog)
|
||||||
import Urbit.Vere.Ames (ames)
|
import Urbit.King.API (TermConn)
|
||||||
import Urbit.Vere.Behn (behn)
|
import Urbit.Noun.Time (Wen)
|
||||||
import Urbit.Vere.Clay (clay)
|
import Urbit.TermSize (TermSize(..))
|
||||||
import Urbit.Vere.Http.Client (client)
|
import Urbit.Vere.Eyre.Multi (MultiEyreApi)
|
||||||
import Urbit.Vere.Http.Server (serv)
|
import Urbit.Vere.Serf (Serf)
|
||||||
import Urbit.Vere.Log (EventLog)
|
|
||||||
import Urbit.Vere.Serf (Serf, SerfState(..), doJob, sStderr)
|
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
import qualified System.Entropy as Ent
|
import qualified System.Entropy as Ent
|
||||||
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
import qualified Urbit.King.API as King
|
import qualified Urbit.King.API as King
|
||||||
import qualified Urbit.Time as Time
|
import qualified Urbit.Noun.Time as Time
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.Vere.Ames as Ames
|
||||||
|
import qualified Urbit.Vere.Behn as Behn
|
||||||
|
import qualified Urbit.Vere.Clay as Clay
|
||||||
|
import qualified Urbit.Vere.Eyre as Eyre
|
||||||
|
import qualified Urbit.Vere.Http.Client as Iris
|
||||||
import qualified Urbit.Vere.Serf as Serf
|
import qualified Urbit.Vere.Serf as Serf
|
||||||
import qualified Urbit.Vere.Term as Term
|
import qualified Urbit.Vere.Term as Term
|
||||||
import qualified Urbit.Vere.Term.API as Term
|
import qualified Urbit.Vere.Term.API as Term
|
||||||
import qualified Urbit.Vere.Term.Demux as Term
|
import qualified Urbit.Vere.Term.Demux as Term
|
||||||
import qualified Urbit.Vere.Term.Render as Term
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
-- Initialize pier directory. --------------------------------------------------
|
||||||
|
|
||||||
_ioDrivers = [] :: [IODriver]
|
data PierDirectoryAlreadyExists = PierDirectoryAlreadyExists
|
||||||
|
deriving (Show, Exception)
|
||||||
|
|
||||||
setupPierDirectory :: FilePath -> RIO e ()
|
setupPierDirectory :: FilePath -> RIO e ()
|
||||||
setupPierDirectory shipPath = do
|
setupPierDirectory shipPath = do
|
||||||
for_ ["put", "get", "log", "chk"] $ \seg -> do
|
-- shipPath will already exist because we put a lock file there.
|
||||||
let pax = shipPath <> "/.urb/" <> seg
|
alreadyExists <- doesPathExist (shipPath </> ".urb")
|
||||||
createDirectoryIfMissing True pax
|
when alreadyExists $ do
|
||||||
io $ setFileMode pax ownerModes
|
throwIO PierDirectoryAlreadyExists
|
||||||
|
for_ ["put", "get", "log", "chk"] $ \seg -> do
|
||||||
|
let pax = shipPath </> ".urb" </> seg
|
||||||
|
createDirectoryIfMissing True pax
|
||||||
|
io $ setFileMode pax ownerModes
|
||||||
|
|
||||||
|
|
||||||
-- Load pill into boot sequence. -----------------------------------------------
|
-- Load pill into boot sequence. -----------------------------------------------
|
||||||
|
|
||||||
genEntropy :: RIO e Word512
|
genEntropy :: MonadIO m => m Entropy
|
||||||
genEntropy = fromIntegral . bytesAtom <$> io (Ent.getEntropy 64)
|
genEntropy = Entropy . fromIntegral . bytesAtom <$> io (Ent.getEntropy 64)
|
||||||
|
|
||||||
generateBootSeq :: Ship -> Pill -> Bool -> LegacyBootEvent -> RIO e BootSeq
|
genBootSeq :: MonadIO m => Ship -> Pill -> Bool -> LegacyBootEvent -> m BootSeq
|
||||||
generateBootSeq ship Pill{..} lite boot = do
|
genBootSeq ship Pill {..} lite boot = io $ do
|
||||||
ent <- genEntropy
|
ent <- genEntropy
|
||||||
let ovums = preKern ent <> pKernelOvums <> postKern <> pUserspaceOvums
|
let ovums = preKern ent <> pKernelOvums <> postKern <> pUserspaceOvums
|
||||||
pure $ BootSeq ident pBootFormulas ovums
|
pure $ BootSeq ident pBootFormulas ovums
|
||||||
where
|
where
|
||||||
ident = LogIdentity ship isFake (fromIntegral $ length pBootFormulas)
|
ident = LogIdentity ship isFake (fromIntegral $ length pBootFormulas)
|
||||||
preKern ent = [ EvBlip $ BlipEvArvo $ ArvoEvWhom () ship
|
preKern ent =
|
||||||
, EvBlip $ BlipEvArvo $ ArvoEvWack () ent
|
[ EvBlip $ BlipEvArvo $ ArvoEvWhom () ship
|
||||||
]
|
, EvBlip $ BlipEvArvo $ ArvoEvWack () ent
|
||||||
postKern = [ EvBlip $ BlipEvTerm $ TermEvBoot (1,()) lite boot ]
|
]
|
||||||
isFake = case boot of
|
postKern = [EvBlip $ BlipEvTerm $ TermEvBoot (1, ()) lite boot]
|
||||||
Fake _ -> True
|
isFake = case boot of
|
||||||
_ -> False
|
Fake _ -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
|
||||||
-- Write a batch of jobs into the event log ------------------------------------
|
-- Write to the log. -----------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Write a batch of jobs to the event log.
|
||||||
writeJobs :: EventLog -> Vector Job -> RIO e ()
|
writeJobs :: EventLog -> Vector Job -> RIO e ()
|
||||||
writeJobs log !jobs = do
|
writeJobs log !jobs = do
|
||||||
expect <- Log.nextEv log
|
expect <- atomically (Log.nextEv log)
|
||||||
events <- fmap fromList $ traverse fromJob (zip [expect..] $ toList jobs)
|
events <- fmap fromList $ traverse fromJob (zip [expect ..] $ toList jobs)
|
||||||
Log.appendEvents log events
|
Log.appendEvents log events
|
||||||
where
|
where
|
||||||
fromJob :: (EventId, Job) -> RIO e ByteString
|
fromJob :: (EventId, Job) -> RIO e ByteString
|
||||||
fromJob (expectedId, job) = do
|
fromJob (expectedId, job) = do
|
||||||
unless (expectedId == jobId job) $
|
unless (expectedId == jobId job) $ error $ show
|
||||||
error $ show ("bad job id!", expectedId, jobId job)
|
("bad job id!", expectedId, jobId job)
|
||||||
pure $ jamBS $ jobPayload job
|
pure $ jamBS $ jobPayload job
|
||||||
|
|
||||||
jobPayload :: Job -> Noun
|
jobPayload :: Job -> Noun
|
||||||
jobPayload (RunNok (LifeCyc _ m n)) = toNoun (m, n)
|
jobPayload (RunNok (LifeCyc _ m n)) = toNoun (m, n)
|
||||||
jobPayload (DoWork (Work _ m d o)) = toNoun (m, d, o)
|
jobPayload (DoWork (Work _ m d o )) = toNoun (m, d, o)
|
||||||
|
|
||||||
|
|
||||||
|
-- Acquire a running serf. -----------------------------------------------------
|
||||||
|
|
||||||
|
printTank :: (Text -> IO ()) -> Atom -> Tank -> IO ()
|
||||||
|
printTank f _priority = f . unlines . fmap unTape . wash (WashCfg 0 80) . tankTree
|
||||||
|
where
|
||||||
|
tankTree (Tank t) = t
|
||||||
|
|
||||||
|
runSerf
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> TVar (Text -> IO ())
|
||||||
|
-> FilePath
|
||||||
|
-> RAcquire e Serf
|
||||||
|
runSerf vSlog pax = do
|
||||||
|
env <- ask
|
||||||
|
Serf.withSerf (config env)
|
||||||
|
where
|
||||||
|
slog txt = atomically (readTVar vSlog) >>= (\f -> f txt)
|
||||||
|
config env = Serf.Config
|
||||||
|
{ scSerf = env ^. pierConfigL . pcSerfExe . to unpack
|
||||||
|
, scPier = pax
|
||||||
|
, scFlag = env ^. pierConfigL . pcSerfFlags
|
||||||
|
, scSlog = \(pri, tank) -> printTank slog pri tank
|
||||||
|
, scStdr = \txt -> slog (txt <> "\r\n")
|
||||||
|
, scDead = pure () -- TODO: What can be done?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
-- Boot a new ship. ------------------------------------------------------------
|
-- Boot a new ship. ------------------------------------------------------------
|
||||||
|
|
||||||
booted :: (HasPierConfig e, HasStderrLogFunc e, HasLogFunc e)
|
booted
|
||||||
=> Pill -> Bool -> Serf.Flags -> Ship -> LegacyBootEvent
|
:: TVar (Text -> IO ())
|
||||||
-> RAcquire e (Serf e, EventLog, SerfState)
|
-> Pill
|
||||||
booted pill lite flags ship boot = do
|
-> Bool
|
||||||
seq@(BootSeq ident x y) <- rio $ generateBootSeq ship pill lite boot
|
-> Ship
|
||||||
|
-> LegacyBootEvent
|
||||||
|
-> RAcquire PierEnv (Serf, EventLog)
|
||||||
|
booted vSlog pill lite ship boot = do
|
||||||
|
rio $ bootNewShip pill lite ship boot
|
||||||
|
resumed vSlog Nothing
|
||||||
|
|
||||||
rio $ logTrace "BootSeq Computed"
|
bootSeqJobs :: Time.Wen -> BootSeq -> [Job]
|
||||||
|
bootSeqJobs now (BootSeq ident nocks ovums) = zipWith ($) bootSeqFns [1 ..]
|
||||||
|
where
|
||||||
|
wen :: EventId -> Time.Wen
|
||||||
|
wen off = Time.addGap now ((fromIntegral off - 1) ^. from Time.microSecs)
|
||||||
|
|
||||||
|
bootSeqFns :: [EventId -> Job]
|
||||||
|
bootSeqFns = fmap nockJob nocks <> fmap ovumJob ovums
|
||||||
|
where
|
||||||
|
nockJob nok eId = RunNok $ LifeCyc eId 0 nok
|
||||||
|
ovumJob ov eId = DoWork $ Work eId 0 (wen eId) ov
|
||||||
|
|
||||||
|
bootNewShip
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> Pill
|
||||||
|
-> Bool
|
||||||
|
-> Ship
|
||||||
|
-> LegacyBootEvent
|
||||||
|
-> RIO e ()
|
||||||
|
bootNewShip pill lite ship bootEv = do
|
||||||
|
seq@(BootSeq ident x y) <- genBootSeq ship pill lite bootEv
|
||||||
|
logDebug "BootSeq Computed"
|
||||||
|
|
||||||
pierPath <- view pierPathL
|
pierPath <- view pierPathL
|
||||||
|
|
||||||
liftRIO (setupPierDirectory pierPath)
|
rio (setupPierDirectory pierPath)
|
||||||
|
logDebug "Directory setup."
|
||||||
|
|
||||||
rio $ logTrace "Directory Setup"
|
let logPath = (pierPath </> ".urb/log")
|
||||||
|
|
||||||
log <- Log.new (pierPath <> "/.urb/log") ident
|
rwith (Log.new logPath ident) $ \log -> do
|
||||||
|
logDebug "Event log onitialized."
|
||||||
|
jobs <- (\now -> bootSeqJobs now seq) <$> io Time.now
|
||||||
|
writeJobs log (fromList jobs)
|
||||||
|
|
||||||
rio $ logTrace "Event Log Initialized"
|
logDebug "Finsihed populating event log with boot sequence"
|
||||||
|
|
||||||
serf <- Serf.run (Serf.Config pierPath flags)
|
|
||||||
|
|
||||||
rio $ logTrace "Serf Started"
|
|
||||||
|
|
||||||
rio $ do
|
|
||||||
(events, serfSt) <- Serf.bootFromSeq serf seq
|
|
||||||
logTrace "Boot Sequence completed"
|
|
||||||
Serf.snapshot serf serfSt
|
|
||||||
logTrace "Snapshot taken"
|
|
||||||
writeJobs log (fromList events)
|
|
||||||
logTrace "Events written"
|
|
||||||
pure (serf, log, serfSt)
|
|
||||||
|
|
||||||
|
|
||||||
-- Resume an existing ship. ----------------------------------------------------
|
-- Resume an existing ship. ----------------------------------------------------
|
||||||
|
|
||||||
resumed :: (HasStderrLogFunc e, HasPierConfig e, HasLogFunc e)
|
resumed
|
||||||
=> Maybe Word64 -> Serf.Flags
|
:: TVar (Text -> IO ())
|
||||||
-> RAcquire e (Serf e, EventLog, SerfState)
|
-> Maybe Word64
|
||||||
resumed event flags = do
|
-> RAcquire PierEnv (Serf, EventLog)
|
||||||
rio $ logTrace "Resuming ship"
|
resumed vSlog replayUntil = do
|
||||||
top <- view pierPathL
|
rio $ logTrace "Resuming ship"
|
||||||
tap <- fmap (fromMaybe top) $ rio $ runMaybeT $ do
|
top <- view pierPathL
|
||||||
ev <- MaybeT (pure event)
|
tap <- fmap (fromMaybe top) $ rio $ runMaybeT $ do
|
||||||
MaybeT (getSnapshot top ev)
|
ev <- MaybeT (pure replayUntil)
|
||||||
|
MaybeT (getSnapshot top ev)
|
||||||
|
|
||||||
rio $ logTrace $ display @Text ("pier: " <> pack top)
|
rio $ do
|
||||||
rio $ logTrace $ display @Text ("running serf in: " <> pack tap)
|
logTrace $ display @Text ("pier: " <> pack top)
|
||||||
|
logTrace $ display @Text ("running serf in: " <> pack tap)
|
||||||
|
|
||||||
log <- Log.existing (top <> "/.urb/log")
|
log <- Log.existing (top </> ".urb/log")
|
||||||
|
serf <- runSerf vSlog tap
|
||||||
|
|
||||||
serf <- Serf.run (Serf.Config tap flags)
|
rio $ do
|
||||||
|
logDebug "Replaying events"
|
||||||
|
Serf.execReplay serf log replayUntil >>= \case
|
||||||
|
Left err -> error (show err)
|
||||||
|
Right 0 -> do
|
||||||
|
logDebug "No work during replay so no snapshot"
|
||||||
|
pure ()
|
||||||
|
Right _ -> do
|
||||||
|
logDebug "Taking snapshot"
|
||||||
|
io (Serf.snapshot serf)
|
||||||
|
logDebug "SNAPSHOT TAKEN"
|
||||||
|
|
||||||
serfSt <- rio $ Serf.replay serf log event
|
pure (serf, log)
|
||||||
|
|
||||||
rio $ Serf.snapshot serf serfSt
|
-- | Get a fake pier directory for partial snapshots.
|
||||||
|
getSnapshot :: forall e . FilePath -> Word64 -> RIO e (Maybe FilePath)
|
||||||
pure (serf, log, serfSt)
|
|
||||||
|
|
||||||
getSnapshot :: forall e. FilePath -> Word64 -> RIO e (Maybe FilePath)
|
|
||||||
getSnapshot top last = do
|
getSnapshot top last = do
|
||||||
lastSnapshot <- lastMay <$> listReplays
|
lastSnapshot <- lastMay <$> listReplays
|
||||||
pure (replayToPath <$> lastSnapshot)
|
pure (replayToPath <$> lastSnapshot)
|
||||||
where
|
where
|
||||||
replayDir = top </> ".partial-replay"
|
replayDir = top </> ".partial-replay"
|
||||||
replayToPath eId = replayDir </> show eId
|
replayToPath eId = replayDir </> show eId
|
||||||
|
|
||||||
|
listReplays :: RIO e [Word64]
|
||||||
|
listReplays = do
|
||||||
|
createDirectoryIfMissing True replayDir
|
||||||
|
snapshotNums <- mapMaybe readMay <$> listDirectory replayDir
|
||||||
|
pure $ sort (filter (<= fromIntegral last) snapshotNums)
|
||||||
|
|
||||||
|
|
||||||
|
-- Utils for Spawning Worker Threads -------------------------------------------
|
||||||
|
|
||||||
|
acquireWorker :: HasLogFunc e => Text -> RIO e () -> RAcquire e (Async ())
|
||||||
|
acquireWorker nam act = mkRAcquire (async act) kill
|
||||||
|
where
|
||||||
|
kill tid = do
|
||||||
|
logDebug ("Killing worker thread: " <> display nam)
|
||||||
|
cancel tid
|
||||||
|
|
||||||
|
acquireWorkerBound :: HasLogFunc e => Text -> RIO e () -> RAcquire e (Async ())
|
||||||
|
acquireWorkerBound nam act = mkRAcquire (asyncBound act) kill
|
||||||
|
where
|
||||||
|
kill tid = do
|
||||||
|
logDebug ("Killing worker thread: " <> display nam)
|
||||||
|
cancel tid
|
||||||
|
|
||||||
listReplays :: RIO e [Word64]
|
|
||||||
listReplays = do
|
|
||||||
createDirectoryIfMissing True replayDir
|
|
||||||
snapshotNums <- mapMaybe readMay <$> listDirectory replayDir
|
|
||||||
pure $ sort (filter (<= fromIntegral last) snapshotNums)
|
|
||||||
|
|
||||||
|
|
||||||
-- Run Pier --------------------------------------------------------------------
|
-- Run Pier --------------------------------------------------------------------
|
||||||
|
|
||||||
acquireWorker :: RIO e () -> RAcquire e (Async ())
|
pier
|
||||||
acquireWorker act = mkRAcquire (async act) cancel
|
:: (Serf, EventLog)
|
||||||
|
-> TVar (Text -> IO ())
|
||||||
|
-> MVar ()
|
||||||
|
-> MultiEyreApi
|
||||||
|
-> RAcquire PierEnv ()
|
||||||
|
pier (serf, log) vSlog startedSig multi = do
|
||||||
|
let logId = Log.identity log :: LogIdentity
|
||||||
|
let ship = who logId :: Ship
|
||||||
|
|
||||||
pier :: ∀e. (HasConfigDir e, HasLogFunc e, HasNetworkConfig e, HasPierConfig e)
|
-- TODO Instead of using a TMVar, pull directly from the IO driver
|
||||||
=> (Serf e, EventLog, SerfState)
|
-- event sources.
|
||||||
-> MVar ()
|
computeQ :: TMVar RunReq <- newEmptyTMVarIO
|
||||||
-> RAcquire e ()
|
|
||||||
pier (serf, log, ss) mStart = do
|
|
||||||
computeQ <- newTQueueIO
|
|
||||||
persistQ <- newTQueueIO
|
|
||||||
executeQ <- newTQueueIO
|
|
||||||
saveM <- newEmptyTMVarIO
|
|
||||||
shutdownM <- newEmptyTMVarIO
|
|
||||||
|
|
||||||
kapi ← King.kingAPI
|
persistQ :: TQueue (Fact, FX) <- newTQueueIO
|
||||||
|
executeQ :: TQueue FX <- newTQueueIO
|
||||||
|
saveSig :: TMVar () <- newEmptyTMVarIO
|
||||||
|
kingApi :: King.King <- King.kingAPI
|
||||||
|
|
||||||
termApiQ <- atomically $ do
|
termApiQ :: TQueue TermConn <- atomically $ do
|
||||||
q <- newTQueue
|
q <- newTQueue
|
||||||
writeTVar (King.kTermConn kapi) (Just $ writeTQueue q)
|
writeTVar (King.kTermConn kingApi) (Just $ writeTQueue q)
|
||||||
pure q
|
pure q
|
||||||
|
|
||||||
let shutdownEvent = putTMVar shutdownM ()
|
(demux :: Term.Demux, muxed :: Term.Client) <- atomically $ do
|
||||||
|
res <- Term.mkDemux
|
||||||
|
pure (res, Term.useDemux res)
|
||||||
|
|
||||||
inst <- io (KingId . UV . fromIntegral <$> randomIO @Word16)
|
void $ acquireWorker "TERMSERV Listener" $ forever $ do
|
||||||
|
logDebug "TERMSERV Waiting for external terminal."
|
||||||
|
atomically $ do
|
||||||
|
ext <- Term.connClient <$> readTQueue termApiQ
|
||||||
|
Term.addDemux ext demux
|
||||||
|
logDebug "TERMSERV External terminal connected."
|
||||||
|
|
||||||
-- (sz, local) <- Term.localClient
|
-- Slogs go to both stderr and to the terminal.
|
||||||
|
env <- ask
|
||||||
|
atomically $ writeTVar vSlog $ \txt -> runRIO env $ do
|
||||||
|
atomically $ Term.trace muxed txt
|
||||||
|
logOther "serf" (display $ T.strip txt)
|
||||||
|
|
||||||
-- (waitExternalTerm, termServPort) <- Term.termServer
|
-- Our call above to set the logging function which echos errors from the
|
||||||
|
-- Serf doesn't have the appended \r\n because those \r\n s are added in
|
||||||
|
-- the c serf code. Logging output from our haskell process must manually
|
||||||
|
-- add them.
|
||||||
|
let compute = putTMVar computeQ
|
||||||
|
let execute = writeTQueue executeQ
|
||||||
|
let persist = writeTQueue persistQ
|
||||||
|
let sigint = Serf.sendSIGINT serf
|
||||||
|
|
||||||
(demux, muxed) <- atomically $ do
|
(bootEvents, startDrivers) <- do
|
||||||
res <- Term.mkDemux
|
env <- ask
|
||||||
-- Term.addDemux local res
|
let err = atomically . Term.trace muxed . (<> "\r\n")
|
||||||
pure (res, Term.useDemux res)
|
let siz = TermSize { tsWide = 80, tsTall = 24 }
|
||||||
|
let fak = isFake logId
|
||||||
|
drivers env multi ship fak compute (siz, muxed) err sigint
|
||||||
|
|
||||||
-- rio $ logInfo $ display $
|
scrySig <- newEmptyTMVarIO
|
||||||
-- "TERMSERV Terminal Server running on port: " <> tshow termServPort
|
onKill <- view onKillPierSigL
|
||||||
|
|
||||||
acquireWorker $ forever $ do
|
let computeConfig = ComputeConfig { ccOnWork = takeTMVar computeQ
|
||||||
logTrace "TERMSERV Waiting for external terminal."
|
, ccOnKill = onKill
|
||||||
atomically $ do
|
, ccOnSave = takeTMVar saveSig
|
||||||
ext <- Term.connClient <$> readTQueue termApiQ
|
, ccOnScry = takeTMVar scrySig
|
||||||
Term.addDemux ext demux
|
, ccPutResult = persist
|
||||||
logTrace "TERMSERV External terminal connected."
|
, ccShowSpinner = Term.spin muxed
|
||||||
|
, ccHideSpinner = Term.stopSpin muxed
|
||||||
|
, ccLastEvInLog = Log.lastEv log
|
||||||
|
}
|
||||||
|
|
||||||
swapMVar (sStderr serf) (atomically . Term.trace muxed)
|
tSerf <- acquireWorker "Serf" (runCompute serf computeConfig)
|
||||||
|
|
||||||
let logId = Log.identity log
|
-- Run all born events and retry them until they succeed.
|
||||||
let ship = who logId
|
wackEv <- EvBlip . BlipEvArvo . ArvoEvWack () <$> genEntropy
|
||||||
|
rio $ for_ (wackEv : bootEvents) $ \ev -> do
|
||||||
|
okaySig <- newEmptyMVar
|
||||||
|
|
||||||
-- Our call above to set the logging function which echos errors from the
|
let inject n = atomically $ compute $ RRWork $ EvErr ev $ cb n
|
||||||
-- Serf doesn't have the appended \r\n because those \r\n s are added in
|
|
||||||
-- the c serf code. Logging output from our haskell process must manually
|
|
||||||
-- add them.
|
|
||||||
let showErr = atomically . Term.trace muxed . (flip append "\r\n")
|
|
||||||
let (bootEvents, startDrivers) =
|
|
||||||
drivers inst ship (isFake logId)
|
|
||||||
(writeTQueue computeQ)
|
|
||||||
shutdownEvent
|
|
||||||
(Term.TSize{tsWide=80, tsTall=24}, muxed)
|
|
||||||
showErr
|
|
||||||
|
|
||||||
io $ atomically $ for_ bootEvents (writeTQueue computeQ)
|
-- TODO Make sure this dies cleanly.
|
||||||
|
cb :: Int -> WorkError -> IO ()
|
||||||
|
cb n | n >= 3 = error ("boot event failed: " <> show ev)
|
||||||
|
cb n = \case
|
||||||
|
RunOkay _ -> putMVar okaySig ()
|
||||||
|
RunSwap _ _ _ _ _ -> putMVar okaySig ()
|
||||||
|
RunBail _ -> inject (n + 1)
|
||||||
|
|
||||||
tExe <- startDrivers >>= router (readTQueue executeQ)
|
-- logTrace ("[BOOT EVENT]: " <> display (summarizeEvent ev))
|
||||||
tDisk <- runPersist log persistQ (writeTQueue executeQ)
|
io (inject 0)
|
||||||
tCpu <- runCompute serf ss
|
|
||||||
(readTQueue computeQ)
|
|
||||||
(takeTMVar saveM)
|
|
||||||
(takeTMVar shutdownM)
|
|
||||||
(Term.spin muxed)
|
|
||||||
(Term.stopSpin muxed)
|
|
||||||
(writeTQueue persistQ)
|
|
||||||
|
|
||||||
tSaveSignal <- saveSignalThread saveM
|
let slog :: Text -> IO ()
|
||||||
|
slog txt = do
|
||||||
|
fn <- atomically (readTVar vSlog)
|
||||||
|
fn txt
|
||||||
|
|
||||||
putMVar mStart ()
|
drivz <- startDrivers
|
||||||
|
tExec <- acquireWorker "Effects" (router slog (readTQueue executeQ) drivz)
|
||||||
|
tDisk <- acquireWorkerBound "Persist" (runPersist log persistQ execute)
|
||||||
|
|
||||||
-- Wait for something to die.
|
let snapshotEverySecs = 120
|
||||||
|
|
||||||
let ded = asum [ death "effect thread" tExe
|
void $ acquireWorker "Save" $ forever $ do
|
||||||
, death "persist thread" tDisk
|
threadDelay (snapshotEverySecs * 1_000_000)
|
||||||
, death "compute thread" tCpu
|
void $ atomically $ tryPutTMVar saveSig ()
|
||||||
]
|
|
||||||
|
|
||||||
atomically ded >>= \case
|
-- TODO bullshit scry tester
|
||||||
Left (txt, exn) -> logError $ displayShow ("Somthing died", txt, exn)
|
when False $ do
|
||||||
Right tag -> logError $ displayShow ("something simply exited", tag)
|
void $ acquireWorker "bullshit scry tester" $ do
|
||||||
|
env <- ask
|
||||||
|
forever $ do
|
||||||
|
threadDelay 15_000_000
|
||||||
|
wen <- io Time.now
|
||||||
|
let kal = \mTermNoun -> runRIO env $ do
|
||||||
|
logDebug $ displayShow ("scry result: ", mTermNoun)
|
||||||
|
let nkt = MkKnot $ tshow $ Time.MkDate wen
|
||||||
|
let pax = Path ["j", "~zod", "life", nkt, "~zod"]
|
||||||
|
atomically $ putTMVar scrySig (wen, Nothing, pax, kal)
|
||||||
|
|
||||||
atomically $ (Term.spin muxed) (Just "shutdown")
|
putMVar startedSig ()
|
||||||
|
|
||||||
|
-- Wait for something to die.
|
||||||
|
|
||||||
|
let ded = asum
|
||||||
|
[ death "effects thread" tExec
|
||||||
|
, death "persist thread" tDisk
|
||||||
|
, death "compute thread" tSerf
|
||||||
|
]
|
||||||
|
|
||||||
|
atomically ded >>= \case
|
||||||
|
Left (tag, exn) -> logError $ displayShow (tag, "crashed", exn)
|
||||||
|
Right "compute thread" -> pure ()
|
||||||
|
Right tag -> logError $ displayShow (tag, "exited unexpectly")
|
||||||
|
|
||||||
|
atomically $ (Term.spin muxed) (Just "shutdown")
|
||||||
|
|
||||||
death :: Text -> Async () -> STM (Either (Text, SomeException) Text)
|
death :: Text -> Async () -> STM (Either (Text, SomeException) Text)
|
||||||
death tag tid = do
|
death tag tid = do
|
||||||
waitCatchSTM tid <&> \case
|
waitCatchSTM tid <&> \case
|
||||||
Left exn -> Left (tag, exn)
|
Left exn -> Left (tag, exn)
|
||||||
Right () -> Right tag
|
Right () -> Right tag
|
||||||
|
|
||||||
saveSignalThread :: TMVar () -> RAcquire e (Async ())
|
|
||||||
saveSignalThread tm = mkRAcquire start cancel
|
|
||||||
where
|
|
||||||
start = async $ forever $ do
|
|
||||||
threadDelay (120 * 1000000) -- 120 seconds
|
|
||||||
atomically $ putTMVar tm ()
|
|
||||||
|
|
||||||
-- Start All Drivers -----------------------------------------------------------
|
-- Start All Drivers -----------------------------------------------------------
|
||||||
|
|
||||||
data Drivers e = Drivers
|
data Drivers = Drivers
|
||||||
{ dAmes :: EffCb e AmesEf
|
{ dBehn :: BehnEf -> IO ()
|
||||||
, dBehn :: EffCb e BehnEf
|
, dIris :: HttpClientEf -> IO ()
|
||||||
, dHttpClient :: EffCb e HttpClientEf
|
, dEyre :: HttpServerEf -> IO ()
|
||||||
, dHttpServer :: EffCb e HttpServerEf
|
, dNewt :: NewtEf -> IO ()
|
||||||
, dNewt :: EffCb e NewtEf
|
, dSync :: SyncEf -> IO ()
|
||||||
, dSync :: EffCb e SyncEf
|
, dTerm :: TermEf -> IO ()
|
||||||
, dTerm :: EffCb e TermEf
|
}
|
||||||
}
|
|
||||||
|
|
||||||
drivers :: (HasLogFunc e, HasNetworkConfig e, HasPierConfig e)
|
drivers
|
||||||
=> KingId -> Ship -> Bool -> (Ev -> STM ())
|
:: HasPierEnv e
|
||||||
-> STM()
|
=> e
|
||||||
-> (Term.TSize, Term.Client)
|
-> MultiEyreApi
|
||||||
-> (Text -> RIO e ())
|
-> Ship
|
||||||
-> ([Ev], RAcquire e (Drivers e))
|
-> Bool
|
||||||
drivers inst who isFake plan shutdownSTM termSys stderr =
|
-> (RunReq -> STM ())
|
||||||
(initialEvents, runDrivers)
|
-> (TermSize, Term.Client)
|
||||||
where
|
-> (Text -> RIO e ())
|
||||||
(behnBorn, runBehn) = behn inst plan
|
-> IO ()
|
||||||
(amesBorn, runAmes) = ames inst who isFake plan stderr
|
-> RAcquire e ([Ev], RAcquire e Drivers)
|
||||||
(httpBorn, runHttp) = serv inst plan isFake
|
drivers env multi who isFake plan termSys stderr serfSIGINT = do
|
||||||
(clayBorn, runClay) = clay inst plan
|
(behnBorn, runBehn) <- rio Behn.behn'
|
||||||
(irisBorn, runIris) = client inst plan
|
(termBorn, runTerm) <- rio (Term.term' termSys serfSIGINT)
|
||||||
(termBorn, runTerm) = Term.term termSys shutdownSTM inst plan
|
(amesBorn, runAmes) <- rio (Ames.ames' who isFake stderr)
|
||||||
initialEvents = mconcat [behnBorn, clayBorn, amesBorn, httpBorn,
|
(httpBorn, runEyre) <- rio (Eyre.eyre' multi who isFake)
|
||||||
termBorn, irisBorn]
|
(clayBorn, runClay) <- rio Clay.clay'
|
||||||
runDrivers = do
|
(irisBorn, runIris) <- rio Iris.client'
|
||||||
dNewt <- runAmes
|
|
||||||
dBehn <- liftAcquire $ runBehn
|
let initialEvents = mconcat [behnBorn,clayBorn,amesBorn,httpBorn,irisBorn,termBorn]
|
||||||
dAmes <- pure $ const $ pure ()
|
|
||||||
dHttpClient <- runIris
|
let runDrivers = do
|
||||||
dHttpServer <- runHttp
|
behn <- runBehn
|
||||||
dSync <- runClay
|
term <- runTerm
|
||||||
dTerm <- runTerm
|
ames <- runAmes
|
||||||
pure (Drivers{..})
|
iris <- runIris
|
||||||
|
eyre <- runEyre
|
||||||
|
clay <- runClay
|
||||||
|
|
||||||
|
-- Sources lower in the list are starved until sources above them
|
||||||
|
-- have no events to offer.
|
||||||
|
acquireWorker "Event Prioritization" $ forever $ atomically $ do
|
||||||
|
let x = diEventSource
|
||||||
|
let eventSources = [x term, x clay, x behn, x iris, x eyre, x ames]
|
||||||
|
pullEvent eventSources >>= \case
|
||||||
|
Nothing -> retry
|
||||||
|
Just rr -> plan rr
|
||||||
|
|
||||||
|
pure $ Drivers
|
||||||
|
{ dTerm = diOnEffect term
|
||||||
|
, dBehn = diOnEffect behn
|
||||||
|
, dNewt = diOnEffect ames
|
||||||
|
, dIris = diOnEffect iris
|
||||||
|
, dEyre = diOnEffect eyre
|
||||||
|
, dSync = diOnEffect clay
|
||||||
|
}
|
||||||
|
|
||||||
|
pure (initialEvents, runDrivers)
|
||||||
|
where
|
||||||
|
pullEvent :: [STM (Maybe a)] -> STM (Maybe a)
|
||||||
|
pullEvent [] = pure Nothing
|
||||||
|
pullEvent (d:ds) = d >>= \case
|
||||||
|
Just r -> pure (Just r)
|
||||||
|
Nothing -> pullEvent ds
|
||||||
|
|
||||||
|
|
||||||
-- Route Effects to Drivers ----------------------------------------------------
|
-- Route Effects to Drivers ----------------------------------------------------
|
||||||
|
|
||||||
router :: HasLogFunc e => STM FX -> Drivers e -> RAcquire e (Async ())
|
router :: HasPierEnv e => (Text -> IO ()) -> STM FX -> Drivers -> RIO e ()
|
||||||
router waitFx Drivers{..} =
|
router slog waitFx Drivers {..} = do
|
||||||
mkRAcquire start cancel
|
kill <- view killPierActionL
|
||||||
where
|
let exit = io (slog "<<<shutdown>>>\r\n") >> atomically kill
|
||||||
start = async $ forever $ do
|
let vega = io (slog "<<<reset>>>\r\n")
|
||||||
fx <- atomically waitFx
|
forever $ do
|
||||||
for_ fx $ \ef -> do
|
fx <- atomically waitFx
|
||||||
logEffect ef
|
for_ fx $ \ef -> do
|
||||||
case ef of
|
logEffect ef
|
||||||
GoodParse (EfVega _ _) -> error "TODO"
|
case ef of
|
||||||
GoodParse (EfExit _ _) -> error "TODO"
|
GoodParse (EfVega _ _ ) -> vega
|
||||||
GoodParse (EfVane (VEAmes ef)) -> dAmes ef
|
GoodParse (EfExit _ _ ) -> exit
|
||||||
GoodParse (EfVane (VEBehn ef)) -> dBehn ef
|
GoodParse (EfVane (VEBehn ef)) -> io (dBehn ef)
|
||||||
GoodParse (EfVane (VEBoat ef)) -> dSync ef
|
GoodParse (EfVane (VEBoat ef)) -> io (dSync ef)
|
||||||
GoodParse (EfVane (VEClay ef)) -> dSync ef
|
GoodParse (EfVane (VEClay ef)) -> io (dSync ef)
|
||||||
GoodParse (EfVane (VEHttpClient ef)) -> dHttpClient ef
|
GoodParse (EfVane (VEHttpClient ef)) -> io (dIris ef)
|
||||||
GoodParse (EfVane (VEHttpServer ef)) -> dHttpServer ef
|
GoodParse (EfVane (VEHttpServer ef)) -> io (dEyre ef)
|
||||||
GoodParse (EfVane (VENewt ef)) -> dNewt ef
|
GoodParse (EfVane (VENewt ef)) -> io (dNewt ef)
|
||||||
GoodParse (EfVane (VESync ef)) -> dSync ef
|
GoodParse (EfVane (VESync ef)) -> io (dSync ef)
|
||||||
GoodParse (EfVane (VETerm ef)) -> dTerm ef
|
GoodParse (EfVane (VETerm ef)) -> io (dTerm ef)
|
||||||
FailParse n -> logError
|
FailParse n -> logError $ display $ pack @Text (ppShow n)
|
||||||
$ display
|
|
||||||
$ pack @Text (ppShow n)
|
|
||||||
|
|
||||||
|
|
||||||
-- Compute Thread --------------------------------------------------------------
|
-- Compute (Serf) Thread -------------------------------------------------------
|
||||||
|
|
||||||
data ComputeRequest
|
|
||||||
= CREvent Ev
|
|
||||||
| CRSave ()
|
|
||||||
| CRShutdown ()
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
logEvent :: HasLogFunc e => Ev -> RIO e ()
|
logEvent :: HasLogFunc e => Ev -> RIO e ()
|
||||||
logEvent ev =
|
logEvent ev = do
|
||||||
logDebug $ display $ "[EVENT]\n" <> pretty
|
logTrace $ "<- " <> display (summarizeEvent ev)
|
||||||
where
|
logDebug $ "[EVENT]\n" <> display pretty
|
||||||
pretty :: Text
|
where
|
||||||
pretty = pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow ev
|
pretty :: Text
|
||||||
|
pretty = pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow ev
|
||||||
|
|
||||||
logEffect :: HasLogFunc e => Lenient Ef -> RIO e ()
|
logEffect :: HasLogFunc e => Lenient Ef -> RIO e ()
|
||||||
logEffect ef =
|
logEffect ef = do
|
||||||
logDebug $ display $ "[EFFECT]\n" <> pretty ef
|
logTrace $ " -> " <> display (summarizeEffect ef)
|
||||||
where
|
logDebug $ display $ "[EFFECT]\n" <> pretty ef
|
||||||
pretty :: Lenient Ef -> Text
|
where
|
||||||
pretty = \case
|
pretty :: Lenient Ef -> Text
|
||||||
GoodParse e -> pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow e
|
pretty = \case
|
||||||
FailParse n -> pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow n
|
GoodParse e -> pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow e
|
||||||
|
FailParse n -> pack $ unlines $ fmap ("\t" <>) $ lines $ ppShow n
|
||||||
|
|
||||||
runCompute :: ∀e. HasLogFunc e
|
data ComputeConfig = ComputeConfig
|
||||||
=> Serf e
|
{ ccOnWork :: STM RunReq
|
||||||
-> SerfState
|
, ccOnKill :: STM ()
|
||||||
-> STM Ev
|
, ccOnSave :: STM ()
|
||||||
-> STM ()
|
, ccOnScry :: STM (Wen, Gang, Path, Maybe (Term, Noun) -> IO ())
|
||||||
-> STM ()
|
, ccPutResult :: (Fact, FX) -> STM ()
|
||||||
-> (Maybe Text -> STM ())
|
, ccShowSpinner :: Maybe Text -> STM ()
|
||||||
-> STM ()
|
, ccHideSpinner :: STM ()
|
||||||
-> ((Job, FX) -> STM ())
|
, ccLastEvInLog :: STM EventId
|
||||||
-> RAcquire e (Async ())
|
}
|
||||||
runCompute serf ss getEvent getSaveSignal getShutdownSignal
|
|
||||||
showSpinner hideSpinner putResult =
|
|
||||||
mkRAcquire (async (go ss)) cancel
|
|
||||||
where
|
|
||||||
go :: SerfState -> RIO e ()
|
|
||||||
go ss = do
|
|
||||||
cr <- atomically $
|
|
||||||
CRShutdown <$> getShutdownSignal <|>
|
|
||||||
CRSave <$> getSaveSignal <|>
|
|
||||||
CREvent <$> getEvent
|
|
||||||
case cr of
|
|
||||||
CREvent ev -> do
|
|
||||||
logEvent ev
|
|
||||||
wen <- io Time.now
|
|
||||||
eId <- pure (ssNextEv ss)
|
|
||||||
mug <- pure (ssLastMug ss)
|
|
||||||
|
|
||||||
atomically $ showSpinner (getSpinnerNameForEvent ev)
|
runCompute :: forall e . HasKingEnv e => Serf.Serf -> ComputeConfig -> RIO e ()
|
||||||
(job', ss', fx) <- doJob serf $ DoWork $ Work eId mug wen ev
|
runCompute serf ComputeConfig {..} = do
|
||||||
atomically $ hideSpinner
|
logDebug "runCompute"
|
||||||
atomically (putResult (job', fx))
|
|
||||||
go ss'
|
let onRR = asum [ ccOnKill <&> Serf.RRKill
|
||||||
CRSave () -> do
|
, ccOnSave <&> Serf.RRSave
|
||||||
logDebug $ "Taking periodic snapshot"
|
, ccOnWork
|
||||||
Serf.snapshot serf ss
|
, ccOnScry <&> \(w,g,p,k) -> Serf.RRScry w g p k
|
||||||
go ss
|
]
|
||||||
CRShutdown () -> do
|
|
||||||
-- When shutting down, we first request a snapshot, and then we
|
vEvProcessing :: TMVar Ev <- newEmptyTMVarIO
|
||||||
-- just exit this recursive processing, which will cause the serf
|
|
||||||
-- to exit from its RAcquire.
|
void $ async $ forever (atomically (takeTMVar vEvProcessing) >>= logEvent)
|
||||||
logDebug $ "Shutting down compute system..."
|
|
||||||
Serf.snapshot serf ss
|
let onSpin :: Maybe Ev -> STM ()
|
||||||
pure ()
|
onSpin = \case
|
||||||
|
Nothing -> ccHideSpinner
|
||||||
|
Just ev -> do
|
||||||
|
ccShowSpinner (getSpinnerNameForEvent ev)
|
||||||
|
putTMVar vEvProcessing ev
|
||||||
|
|
||||||
|
let maxBatchSize = 10
|
||||||
|
|
||||||
|
io (Serf.run serf maxBatchSize ccLastEvInLog onRR ccPutResult onSpin)
|
||||||
|
|
||||||
|
|
||||||
-- Persist Thread --------------------------------------------------------------
|
-- Event-Log Persistence Thread ------------------------------------------------
|
||||||
|
|
||||||
data PersistExn = BadEventId EventId EventId
|
data PersistExn = BadEventId EventId EventId
|
||||||
deriving Show
|
deriving Show
|
||||||
@ -418,43 +558,36 @@ instance Exception PersistExn where
|
|||||||
, "\tExpected " <> show expected <> " but got " <> show got
|
, "\tExpected " <> show expected <> " but got " <> show got
|
||||||
]
|
]
|
||||||
|
|
||||||
runPersist :: ∀e. (HasPierConfig e, HasLogFunc e)
|
runPersist
|
||||||
=> EventLog
|
:: forall e
|
||||||
-> TQueue (Job, FX)
|
. HasPierEnv e
|
||||||
-> (FX -> STM ())
|
=> EventLog
|
||||||
-> RAcquire e (Async ())
|
-> TQueue (Fact, FX)
|
||||||
runPersist log inpQ out =
|
-> (FX -> STM ())
|
||||||
mkRAcquire runThread cancel
|
-> RIO e ()
|
||||||
where
|
runPersist log inpQ out = do
|
||||||
runThread :: RIO e (Async ())
|
dryRun <- view dryRunL
|
||||||
runThread = asyncBound $ do
|
forever $ do
|
||||||
dryRun <- view dryRunL
|
writs <- atomically getBatchFromQueue
|
||||||
forever $ do
|
events <- validateFactsAndGetBytes (fst <$> toNullable writs)
|
||||||
writs <- atomically getBatchFromQueue
|
unless dryRun (Log.appendEvents log events)
|
||||||
unless dryRun $ do
|
atomically $ for_ writs $ \(_, fx) -> do
|
||||||
events <- validateJobsAndGetBytes (toNullable writs)
|
out fx
|
||||||
Log.appendEvents log events
|
|
||||||
atomically $ for_ writs $ \(_,fx) -> out fx
|
|
||||||
|
|
||||||
validateJobsAndGetBytes :: [(Job, FX)] -> RIO e (Vector ByteString)
|
where
|
||||||
validateJobsAndGetBytes writs = do
|
validateFactsAndGetBytes :: [Fact] -> RIO e (Vector ByteString)
|
||||||
expect <- Log.nextEv log
|
validateFactsAndGetBytes facts = do
|
||||||
fmap fromList
|
expect <- atomically (Log.nextEv log)
|
||||||
$ for (zip [expect..] writs)
|
lis <- for (zip [expect ..] facts) $ \(expectedId, Fact eve mug wen non) ->
|
||||||
$ \(expectedId, (j, fx)) -> do
|
do
|
||||||
unless (expectedId == jobId j) $
|
unless (expectedId == eve) $ do
|
||||||
throwIO (BadEventId expectedId (jobId j))
|
throwIO (BadEventId expectedId eve)
|
||||||
case j of
|
pure $ jamBS $ toNoun (mug, wen, non)
|
||||||
RunNok _ ->
|
pure (fromList lis)
|
||||||
error "This shouldn't happen here!"
|
|
||||||
DoWork (Work eId mug wen ev) ->
|
|
||||||
pure $ jamBS $ toNoun (mug, wen, ev)
|
|
||||||
|
|
||||||
getBatchFromQueue :: STM (NonNull [(Job, FX)])
|
getBatchFromQueue :: STM (NonNull [(Fact, FX)])
|
||||||
getBatchFromQueue =
|
getBatchFromQueue = readTQueue inpQ >>= go . singleton
|
||||||
readTQueue inpQ >>= go . singleton
|
where
|
||||||
where
|
go acc = tryReadTQueue inpQ >>= \case
|
||||||
go acc =
|
Nothing -> pure (reverse acc)
|
||||||
tryReadTQueue inpQ >>= \case
|
Just item -> go (item <| acc)
|
||||||
Nothing -> pure (reverse acc)
|
|
||||||
Just item -> go (item <| acc)
|
|
||||||
|
@ -3,12 +3,27 @@
|
|||||||
|
|
||||||
TODO Most of these could probably find better homes.
|
TODO Most of these could probably find better homes.
|
||||||
-}
|
-}
|
||||||
module Urbit.Vere.Pier.Types where
|
module Urbit.Vere.Pier.Types
|
||||||
|
( module Urbit.Vere.Serf.Types
|
||||||
|
, LogIdentity(..)
|
||||||
|
, Pill(..)
|
||||||
|
, Job(..)
|
||||||
|
, LifeCyc(..)
|
||||||
|
, BootSeq(..)
|
||||||
|
, Work(..)
|
||||||
|
, jobId
|
||||||
|
, jobMug
|
||||||
|
, DriverApi(..)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.Prelude hiding (Term)
|
import Urbit.Prelude hiding (Term)
|
||||||
|
|
||||||
import Urbit.Arvo
|
import Urbit.Arvo
|
||||||
import Urbit.Time
|
import Urbit.Noun.Time
|
||||||
|
import Urbit.Vere.Serf.Types
|
||||||
|
|
||||||
|
import Urbit.EventLog.LMDB (LogIdentity(..))
|
||||||
|
|
||||||
|
|
||||||
-- Avoid touching Nock values. -------------------------------------------------
|
-- Avoid touching Nock values. -------------------------------------------------
|
||||||
@ -29,25 +44,16 @@ instance Show Nock where
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
type EventId = Word64
|
|
||||||
|
|
||||||
data Pill = Pill
|
data Pill = Pill
|
||||||
{ pBootFormulas :: [Nock]
|
{ pBootFormulas :: [Nock]
|
||||||
, pKernelOvums :: [Ev]
|
, pKernelOvums :: [Ev]
|
||||||
, pUserspaceOvums :: [Ev]
|
, pUserspaceOvums :: [Ev]
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
data LogIdentity = LogIdentity
|
|
||||||
{ who :: Ship
|
|
||||||
, isFake :: Bool
|
|
||||||
, lifecycleLen :: Word
|
|
||||||
} deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
data BootSeq = BootSeq LogIdentity [Nock] [Ev]
|
data BootSeq = BootSeq LogIdentity [Nock] [Ev]
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
deriveNoun ''LogIdentity
|
|
||||||
deriveNoun ''Pill
|
deriveNoun ''Pill
|
||||||
|
|
||||||
|
|
||||||
@ -60,40 +66,25 @@ data LifeCyc = LifeCyc EventId Mug Nock
|
|||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
data Job
|
data Job
|
||||||
= DoWork Work
|
= DoWork Work
|
||||||
| RunNok LifeCyc
|
| RunNok LifeCyc
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
jobId :: Job -> EventId
|
jobId :: Job -> EventId
|
||||||
jobId (RunNok (LifeCyc eId _ _)) = eId
|
jobId (RunNok (LifeCyc eId _ _)) = eId
|
||||||
jobId (DoWork (Work eId _ _ _)) = eId
|
jobId (DoWork (Work eId _ _ _ )) = eId
|
||||||
|
|
||||||
jobMug :: Job -> Mug
|
jobMug :: Job -> Mug
|
||||||
jobMug (RunNok (LifeCyc _ mug _)) = mug
|
jobMug (RunNok (LifeCyc _ mug _)) = mug
|
||||||
jobMug (DoWork (Work _ mug _ _)) = mug
|
jobMug (DoWork (Work _ mug _ _ )) = mug
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
-- API To IO Drivers -----------------------------------------------------------
|
||||||
|
|
||||||
data Order
|
data DriverApi ef = DriverApi
|
||||||
= OBoot Word -- lifecycle length
|
{ diEventSource :: STM (Maybe RunReq)
|
||||||
| OExit Word8
|
, diOnEffect :: ef -> IO ()
|
||||||
| OSave EventId
|
}
|
||||||
| OWork Job
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
deriveToNoun ''Order
|
|
||||||
|
|
||||||
type QueueEv = Ev -> STM ()
|
|
||||||
|
|
||||||
type EffCb e a = a -> RIO e ()
|
|
||||||
|
|
||||||
type Perform = Ef -> IO ()
|
|
||||||
|
|
||||||
data IODriver = IODriver
|
|
||||||
{ bornEvent :: IO Ev
|
|
||||||
, startDriver :: (Ev -> STM ()) -> IO (Async (), Perform)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-- Instances -------------------------------------------------------------------
|
-- Instances -------------------------------------------------------------------
|
||||||
@ -102,17 +93,17 @@ instance ToNoun Work where
|
|||||||
toNoun (Work eid m d o) = toNoun (eid, Jammed (m, d, o))
|
toNoun (Work eid m d o) = toNoun (eid, Jammed (m, d, o))
|
||||||
|
|
||||||
instance FromNoun Work where
|
instance FromNoun Work where
|
||||||
parseNoun n = named "Work" $ do
|
parseNoun n = named "Work" $ do
|
||||||
(eid, Jammed (m, d, o)) <- parseNoun n
|
(eid, Jammed (m, d, o)) <- parseNoun n
|
||||||
pure (Work eid m d o)
|
pure (Work eid m d o)
|
||||||
|
|
||||||
instance ToNoun LifeCyc where
|
instance ToNoun LifeCyc where
|
||||||
toNoun (LifeCyc eid m n) = toNoun (eid, Jammed (m, n))
|
toNoun (LifeCyc eid m n) = toNoun (eid, Jammed (m, n))
|
||||||
|
|
||||||
instance FromNoun LifeCyc where
|
instance FromNoun LifeCyc where
|
||||||
parseNoun n = named "LifeCyc" $ do
|
parseNoun n = named "LifeCyc" $ do
|
||||||
(eid, Jammed (m, n)) <- parseNoun n
|
(eid, Jammed (m, n)) <- parseNoun n
|
||||||
pure (LifeCyc eid m n)
|
pure (LifeCyc eid m n)
|
||||||
|
|
||||||
-- | No FromNoun instance, because it depends on context (lifecycle length)
|
-- | No FromNoun instance, because it depends on context (lifecycle length)
|
||||||
instance ToNoun Job where
|
instance ToNoun Job where
|
||||||
|
@ -1,547 +1,162 @@
|
|||||||
{-|
|
{-|
|
||||||
Serf Interface
|
High-Level Serf Interface
|
||||||
|
|
||||||
TODO: `recvLen` is not big-endian safe.
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Serf ( Serf, sStderr, SerfState(..), doJob
|
module Urbit.Vere.Serf
|
||||||
, run, shutdown, kill
|
( withSerf
|
||||||
, replay, bootFromSeq, snapshot
|
, execReplay
|
||||||
, collectFX
|
, collectFX
|
||||||
, Config(..), Flags, Flag(..)
|
, module X
|
||||||
) where
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
|
||||||
import Data.Conduit
|
import Data.Conduit
|
||||||
import System.Process
|
|
||||||
import System.ProgressBar
|
|
||||||
import Urbit.Arvo
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
import Urbit.Vere.Serf.IPC
|
||||||
|
|
||||||
import Data.Bits (setBit)
|
import Control.Monad.Trans.Resource (runResourceT)
|
||||||
import Data.ByteString (hGet)
|
import Urbit.Arvo (FX)
|
||||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
import Urbit.King.App.Class (HasStderrLogFunc(..))
|
||||||
import Foreign.Marshal.Alloc (alloca)
|
|
||||||
import Foreign.Ptr (castPtr)
|
|
||||||
import Foreign.Storable (peek, poke)
|
|
||||||
import System.Exit (ExitCode)
|
|
||||||
import Urbit.King.App (HasStderrLogFunc(..))
|
|
||||||
|
|
||||||
import qualified Data.ByteString.Unsafe as BS
|
|
||||||
import qualified Data.Conduit.Combinators as CC
|
import qualified Data.Conduit.Combinators as CC
|
||||||
import qualified Data.Text as T
|
import qualified System.ProgressBar as PB
|
||||||
import qualified System.IO as IO
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
import qualified System.IO.Error as IO
|
|
||||||
import qualified Urbit.Ob as Ob
|
|
||||||
import qualified Urbit.Time as Time
|
|
||||||
import qualified Urbit.Vere.Log as Log
|
|
||||||
|
|
||||||
|
import qualified Urbit.Vere.Serf.IPC as X (Config (..), EvErr (..), Flag (..),
|
||||||
-- Serf Config -----------------------------------------------------------------
|
RunReq (..), Serf, WorkError (..),
|
||||||
|
run, sendSIGINT, snapshot, start,
|
||||||
type Flags = [Flag]
|
stop)
|
||||||
|
|
||||||
data Flag
|
|
||||||
= DebugRam
|
|
||||||
| DebugCpu
|
|
||||||
| CheckCorrupt
|
|
||||||
| CheckFatal
|
|
||||||
| Verbose
|
|
||||||
| DryRun
|
|
||||||
| Quiet
|
|
||||||
| Hashless
|
|
||||||
| Trace
|
|
||||||
deriving (Eq, Ord, Show, Enum, Bounded)
|
|
||||||
|
|
||||||
compileFlags :: [Flag] -> Word
|
|
||||||
compileFlags = foldl' (\acc flag -> setBit acc (fromEnum flag)) 0
|
|
||||||
|
|
||||||
data Config = Config FilePath [Flag]
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
serf :: HasLogFunc e => Text -> RIO e ()
|
|
||||||
serf msg = logInfo $ display ("SERF: " <> msg)
|
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
data SerfState = SerfState
|
|
||||||
{ ssNextEv :: EventId
|
|
||||||
, ssLastMug :: Mug
|
|
||||||
}
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
ssLastEv :: SerfState -> EventId
|
|
||||||
ssLastEv = pred . ssNextEv
|
|
||||||
|
|
||||||
data Serf e = Serf
|
|
||||||
{ sendHandle :: Handle
|
|
||||||
, recvHandle :: Handle
|
|
||||||
, process :: ProcessHandle
|
|
||||||
, sStderr :: MVar (Text -> RIO e ())
|
|
||||||
}
|
|
||||||
|
|
||||||
data ShipId = ShipId Ship Bool
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
|
|
||||||
data Plea
|
|
||||||
= PPlay EventId Mug
|
|
||||||
| PWork Work
|
|
||||||
| PDone EventId Mug FX
|
|
||||||
| PStdr EventId Cord
|
|
||||||
| PSlog EventId Word32 Tank
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
type ReplacementEv = Job
|
|
||||||
type WorkResult = (SerfState, FX)
|
|
||||||
type SerfResp = Either ReplacementEv WorkResult
|
|
||||||
|
|
||||||
data SerfExn
|
|
||||||
= BadComputeId EventId WorkResult
|
|
||||||
| BadReplacementId EventId ReplacementEv
|
|
||||||
| UnexpectedPlay EventId (EventId, Mug)
|
|
||||||
| BadPleaAtom Atom
|
|
||||||
| BadPleaNoun Noun [Text] Text
|
|
||||||
| ReplacedEventDuringReplay EventId ReplacementEv
|
|
||||||
| ReplacedEventDuringBoot EventId ReplacementEv
|
|
||||||
| EffectsDuringBoot EventId FX
|
|
||||||
| SerfConnectionClosed
|
|
||||||
| UnexpectedPleaOnNewShip Plea
|
|
||||||
| InvalidInitialPlea Plea
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
|
|
||||||
-- Instances -------------------------------------------------------------------
|
|
||||||
|
|
||||||
instance Exception SerfExn
|
|
||||||
|
|
||||||
deriveNoun ''ShipId
|
|
||||||
deriveNoun ''Plea
|
|
||||||
|
|
||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
printTank :: HasLogFunc e
|
|
||||||
=> MVar (Text -> RIO e ()) -> Word32 -> Tank
|
|
||||||
-> RIO e ()
|
|
||||||
printTank log _pri = printErr log . unlines . fmap unTape . wash (WashCfg 0 80)
|
|
||||||
|
|
||||||
guardExn :: (Exception e, MonadIO m) => Bool -> e -> m ()
|
|
||||||
guardExn ok = io . unless ok . throwIO
|
|
||||||
|
|
||||||
fromRightExn :: (Exception e, MonadIO m) => Either a b -> (a -> e) -> m b
|
|
||||||
fromRightExn (Left m) exn = throwIO (exn m)
|
|
||||||
fromRightExn (Right x) _ = pure x
|
|
||||||
|
|
||||||
printErr :: MVar (Text -> RIO e ()) -> Text -> RIO e ()
|
|
||||||
printErr m txt = do
|
|
||||||
f <- readMVar m
|
|
||||||
f txt
|
|
||||||
|
|
||||||
|
|
||||||
-- Process Management ----------------------------------------------------------
|
|
||||||
|
|
||||||
run :: HasLogFunc e => Config -> RAcquire e (Serf e)
|
|
||||||
run config = mkRAcquire (startUp config) tearDown
|
|
||||||
|
|
||||||
startUp :: HasLogFunc e => Config -> RIO e (Serf e)
|
|
||||||
startUp conf@(Config pierPath flags) = do
|
|
||||||
logTrace "STARTING SERF"
|
|
||||||
logTrace (displayShow conf)
|
|
||||||
|
|
||||||
(i, o, e, p) <- io $ do
|
|
||||||
(Just i, Just o, Just e, p) <- createProcess pSpec
|
|
||||||
pure (i, o, e, p)
|
|
||||||
|
|
||||||
stderr <- newMVar serf
|
|
||||||
async (readStdErr e stderr)
|
|
||||||
pure (Serf i o p stderr)
|
|
||||||
where
|
|
||||||
diskKey = ""
|
|
||||||
config = show (compileFlags flags)
|
|
||||||
args = [pierPath, diskKey, config]
|
|
||||||
pSpec = (proc "urbit-worker" args)
|
|
||||||
{ std_in = CreatePipe
|
|
||||||
, std_out = CreatePipe
|
|
||||||
, std_err = CreatePipe
|
|
||||||
}
|
|
||||||
|
|
||||||
readStdErr :: ∀e. HasLogFunc e => Handle -> MVar (Text -> RIO e ()) -> RIO e ()
|
|
||||||
readStdErr h print =
|
|
||||||
untilEOFExn $ do
|
|
||||||
raw <- io $ IO.hGetLine h
|
|
||||||
let ln = T.strip (pack raw)
|
|
||||||
printErr print ln
|
|
||||||
serf ("[stderr] " <> ln)
|
|
||||||
where
|
|
||||||
eofMsg = "[Serf.readStdErr] serf stderr closed"
|
|
||||||
|
|
||||||
untilEOFExn :: RIO e () -> RIO e ()
|
|
||||||
untilEOFExn act = loop
|
|
||||||
where
|
|
||||||
loop :: RIO e ()
|
|
||||||
loop = do
|
|
||||||
env <- ask
|
|
||||||
res <- io $ IO.tryIOError $ runRIO env act
|
|
||||||
case res of
|
|
||||||
Left exn | IO.isEOFError exn -> logDebug eofMsg
|
|
||||||
Left exn -> io (IO.ioError exn)
|
|
||||||
Right () -> loop
|
|
||||||
|
|
||||||
tearDown :: HasLogFunc e => Serf e -> RIO e ()
|
|
||||||
tearDown serf = do
|
|
||||||
io $ terminateProcess (process serf)
|
|
||||||
void $ waitForExit serf
|
|
||||||
|
|
||||||
-- race_ waitThenKill (shutdownAndWait serf 0)
|
|
||||||
where
|
|
||||||
-- killedMsg =
|
|
||||||
-- "[Serf.tearDown]: Serf didn't die when asked, killing it"
|
|
||||||
|
|
||||||
-- waitThenKill = do
|
|
||||||
-- threadDelay 1000000
|
|
||||||
-- debug killedMsg
|
|
||||||
-- terminateProcess (process serf)
|
|
||||||
|
|
||||||
waitForExit :: HasLogFunc e => Serf e -> RIO e ExitCode
|
|
||||||
waitForExit = io . waitForProcess . process
|
|
||||||
|
|
||||||
kill :: HasLogFunc e => Serf e -> RIO e ExitCode
|
|
||||||
kill serf = io (terminateProcess $ process serf) >> waitForExit serf
|
|
||||||
|
|
||||||
_shutdownAndWait :: HasLogFunc e => Serf e -> Word8 -> RIO e ExitCode
|
|
||||||
_shutdownAndWait serf code = do
|
|
||||||
shutdown serf code
|
|
||||||
waitForExit serf
|
|
||||||
|
|
||||||
|
|
||||||
-- Basic Send and Receive Operations -------------------------------------------
|
|
||||||
|
|
||||||
withWord64AsByteString :: Word64 -> (ByteString -> RIO e a) -> RIO e a
|
|
||||||
withWord64AsByteString w k = do
|
|
||||||
env <- ask
|
|
||||||
io $ alloca $ \wp -> do
|
|
||||||
poke wp w
|
|
||||||
bs <- BS.unsafePackCStringLen (castPtr wp, 8)
|
|
||||||
runRIO env (k bs)
|
|
||||||
|
|
||||||
sendLen :: HasLogFunc e => Serf e -> Int -> RIO e ()
|
|
||||||
sendLen s i = do
|
|
||||||
w <- evaluate (fromIntegral i :: Word64)
|
|
||||||
withWord64AsByteString (fromIntegral i) (hPut (sendHandle s))
|
|
||||||
|
|
||||||
sendOrder :: HasLogFunc e => Serf e -> Order -> RIO e ()
|
|
||||||
sendOrder w o = do
|
|
||||||
-- logDebug $ display ("(sendOrder) " <> tshow o)
|
|
||||||
sendBytes w $ jamBS $ toNoun o
|
|
||||||
-- logDebug "(sendOrder) Done"
|
|
||||||
|
|
||||||
sendBytes :: HasLogFunc e => Serf e -> ByteString -> RIO e ()
|
|
||||||
sendBytes s bs = handle ioErr $ do
|
|
||||||
sendLen s (length bs)
|
|
||||||
hPut (sendHandle s) bs
|
|
||||||
hFlush (sendHandle s)
|
|
||||||
|
|
||||||
where
|
|
||||||
ioErr :: IOError -> RIO e ()
|
|
||||||
ioErr _ = throwIO SerfConnectionClosed
|
|
||||||
|
|
||||||
recvLen :: (MonadIO m, HasLogFunc e) => Serf e -> m Word64
|
|
||||||
recvLen w = io $ do
|
|
||||||
bs <- hGet (recvHandle w) 8
|
|
||||||
case length bs of
|
|
||||||
8 -> unsafeUseAsCString bs (peek . castPtr)
|
|
||||||
_ -> throwIO SerfConnectionClosed
|
|
||||||
|
|
||||||
recvBytes :: HasLogFunc e => Serf e -> Word64 -> RIO e ByteString
|
|
||||||
recvBytes serf =
|
|
||||||
io . hGet (recvHandle serf) . fromIntegral
|
|
||||||
|
|
||||||
recvAtom :: HasLogFunc e => Serf e -> RIO e Atom
|
|
||||||
recvAtom w = do
|
|
||||||
len <- recvLen w
|
|
||||||
bytesAtom <$> recvBytes w len
|
|
||||||
|
|
||||||
cordText :: Cord -> Text
|
|
||||||
cordText = T.strip . unCord
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
snapshot :: HasLogFunc e => Serf e -> SerfState -> RIO e ()
|
parseLogRow :: MonadIO m => ByteString -> m (Mug, Noun)
|
||||||
snapshot serf ss = do
|
parseLogRow = cueBSExn >=> fromNounExn
|
||||||
logTrace $ display ("Taking snapshot at event " <> tshow (ssLastEv ss))
|
|
||||||
sendOrder serf $ OSave $ ssLastEv ss
|
|
||||||
|
|
||||||
shutdown :: HasLogFunc e => Serf e -> Word8 -> RIO e ()
|
withSerf :: HasLogFunc e => Config -> RAcquire e Serf
|
||||||
shutdown serf code = sendOrder serf (OExit code)
|
withSerf config = mkRAcquire startup kill
|
||||||
|
where
|
||||||
|
startup = do
|
||||||
|
(serf, st) <- io $ start config
|
||||||
|
logDebug (displayShow ("serf state", st))
|
||||||
|
pure serf
|
||||||
|
kill serf = do
|
||||||
|
void $ rio $ stop serf
|
||||||
|
|
||||||
{-|
|
execReplay
|
||||||
TODO Find a cleaner way to handle `PStdr` Pleas.
|
:: forall e
|
||||||
-}
|
. (HasLogFunc e, HasStderrLogFunc e)
|
||||||
recvPlea :: HasLogFunc e => Serf e -> RIO e Plea
|
=> Serf
|
||||||
recvPlea w = do
|
-> Log.EventLog
|
||||||
logDebug "(recvPlea) Waiting"
|
-> Maybe Word64
|
||||||
a <- recvAtom w
|
-> RIO e (Either PlayBail Word)
|
||||||
logDebug "(recvPlea) Got atom"
|
execReplay serf log last = do
|
||||||
n <- fromRightExn (cue a) (const $ BadPleaAtom a)
|
lastEventInSnap <- io (serfLastEventBlocking serf)
|
||||||
p <- fromRightExn (fromNounErr n) (\(p,m) -> BadPleaNoun n p m)
|
if lastEventInSnap == 0 then doBoot else doReplay
|
||||||
|
where
|
||||||
|
doBoot :: RIO e (Either PlayBail Word)
|
||||||
|
doBoot = do
|
||||||
|
logDebug "Beginning boot sequence"
|
||||||
|
|
||||||
case p of PStdr e msg -> do printErr (sStderr w) (cordText msg)
|
let bootSeqLen = lifecycleLen (Log.identity log)
|
||||||
recvPlea w
|
|
||||||
PSlog _ pri t -> do printTank (sStderr w) pri t
|
|
||||||
recvPlea w
|
|
||||||
_ -> do logTrace "recvPlea got something else"
|
|
||||||
pure p
|
|
||||||
|
|
||||||
{-|
|
evs <- runConduit $ Log.streamEvents log 1
|
||||||
Waits for initial plea, and then sends boot IPC if necessary.
|
.| CC.take (fromIntegral bootSeqLen)
|
||||||
-}
|
.| CC.mapM (fmap snd . parseLogRow)
|
||||||
handshake :: HasLogFunc e => Serf e -> LogIdentity -> RIO e SerfState
|
.| CC.sinkList
|
||||||
handshake serf ident = do
|
|
||||||
logTrace "Serf Handshake"
|
|
||||||
|
|
||||||
ss@SerfState{..} <- recvPlea serf >>= \case
|
let numEvs = fromIntegral (length evs)
|
||||||
PPlay e m -> pure $ SerfState e m
|
|
||||||
x -> throwIO (InvalidInitialPlea x)
|
|
||||||
|
|
||||||
logTrace $ display ("Handshake result: " <> tshow ss)
|
when (numEvs /= bootSeqLen) $ do
|
||||||
|
throwIO (MissingBootEventsInEventLog numEvs bootSeqLen)
|
||||||
|
|
||||||
when (ssNextEv == 1) $ do
|
logDebug $ display ("Sending " <> tshow numEvs <> " boot events to serf")
|
||||||
let ev = OBoot (lifecycleLen ident)
|
|
||||||
logTrace $ display ("No snapshot. Sending boot event: " <> tshow ev)
|
|
||||||
sendOrder serf ev
|
|
||||||
|
|
||||||
logTrace "Finished handshake"
|
io (boot serf evs) >>= \case
|
||||||
|
Just err -> do
|
||||||
|
logDebug "Error on replay, exiting"
|
||||||
|
pure (Left err)
|
||||||
|
Nothing -> do
|
||||||
|
logDebug "Finished boot events, moving on to more events from log."
|
||||||
|
doReplay <&> \case
|
||||||
|
Left err -> Left err
|
||||||
|
Right num -> Right (num + numEvs)
|
||||||
|
|
||||||
pure ss
|
doReplay :: RIO e (Either PlayBail Word)
|
||||||
|
doReplay = do
|
||||||
sendWork :: ∀e. HasLogFunc e => Serf e -> Job -> RIO e SerfResp
|
|
||||||
sendWork w job =
|
|
||||||
do
|
|
||||||
sendOrder w (OWork job)
|
|
||||||
res <- loop
|
|
||||||
logTrace ("[sendWork] Got response")
|
|
||||||
pure res
|
|
||||||
where
|
|
||||||
eId = jobId job
|
|
||||||
|
|
||||||
produce :: WorkResult -> RIO e SerfResp
|
|
||||||
produce (ss@SerfState{..}, o) = do
|
|
||||||
guardExn (ssNextEv == (1+eId)) (BadComputeId eId (ss, o))
|
|
||||||
pure $ Right (ss, o)
|
|
||||||
|
|
||||||
replace :: ReplacementEv -> RIO e SerfResp
|
|
||||||
replace job = do
|
|
||||||
guardExn (jobId job == eId) (BadReplacementId eId job)
|
|
||||||
pure (Left job)
|
|
||||||
|
|
||||||
loop :: RIO e SerfResp
|
|
||||||
loop = recvPlea w >>= \case
|
|
||||||
PPlay e m -> throwIO (UnexpectedPlay eId (e, m))
|
|
||||||
PDone i m o -> produce (SerfState (i+1) m, o)
|
|
||||||
PWork work -> replace (DoWork work)
|
|
||||||
PStdr _ cord -> printErr (sStderr w) (cordText cord) >> loop
|
|
||||||
PSlog _ pri t -> printTank (sStderr w) pri t >> loop
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
doJob :: HasLogFunc e => Serf e -> Job -> RIO e (Job, SerfState, FX)
|
|
||||||
doJob serf job = do
|
|
||||||
sendWork serf job >>= \case
|
|
||||||
Left replaced -> doJob serf replaced
|
|
||||||
Right (ss, fx) -> pure (job, ss, fx)
|
|
||||||
|
|
||||||
bootJob :: HasLogFunc e => Serf e -> Job -> RIO e (Job, SerfState)
|
|
||||||
bootJob serf job = do
|
|
||||||
doJob serf job >>= \case
|
|
||||||
(job, ss, _) -> pure (job, ss)
|
|
||||||
-- (job, ss, fx) -> throwIO (EffectsDuringBoot (jobId job) fx)
|
|
||||||
|
|
||||||
replayJob :: HasLogFunc e => Serf e -> Job -> RIO e SerfState
|
|
||||||
replayJob serf job = do
|
|
||||||
sendWork serf job >>= \case
|
|
||||||
Left replace -> throwIO (ReplacedEventDuringReplay (jobId job) replace)
|
|
||||||
Right (ss, _) -> pure ss
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
updateProgressBar :: HasLogFunc e
|
|
||||||
=> Int -> Text -> Maybe (ProgressBar ())
|
|
||||||
-> RIO e (Maybe (ProgressBar ()))
|
|
||||||
updateProgressBar count startMsg = \case
|
|
||||||
Nothing -> do
|
|
||||||
-- We only construct the progress bar on the first time that we
|
|
||||||
-- process an event so that we don't display an empty progress
|
|
||||||
-- bar when the snapshot is caught up to the log.
|
|
||||||
let style = defStyle { stylePrefix = msg (fromStrict startMsg) }
|
|
||||||
pb <- newProgressBar style 10 (Progress 0 count ())
|
|
||||||
pure (Just pb)
|
|
||||||
Just pb -> do
|
|
||||||
incProgress pb 1
|
|
||||||
pure (Just pb)
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type BootSeqFn = EventId -> Mug -> Time.Wen -> Job
|
|
||||||
|
|
||||||
data BootExn = ShipAlreadyBooted
|
|
||||||
deriving stock (Eq, Ord, Show)
|
|
||||||
deriving anyclass (Exception)
|
|
||||||
|
|
||||||
logStderr :: HasStderrLogFunc e => RIO LogFunc a -> RIO e a
|
|
||||||
logStderr action = do
|
|
||||||
logFunc <- view stderrLogFuncL
|
|
||||||
runRIO logFunc action
|
|
||||||
|
|
||||||
bootFromSeq :: ∀e. (HasStderrLogFunc e, HasLogFunc e)
|
|
||||||
=> Serf e -> BootSeq -> RIO e ([Job], SerfState)
|
|
||||||
bootFromSeq serf (BootSeq ident nocks ovums) = do
|
|
||||||
handshake serf ident >>= \case
|
|
||||||
ss@(SerfState 1 (Mug 0)) -> loop [] ss Nothing bootSeqFns
|
|
||||||
_ -> throwIO ShipAlreadyBooted
|
|
||||||
|
|
||||||
where
|
|
||||||
loop :: [Job] -> SerfState -> Maybe (ProgressBar ()) -> [BootSeqFn]
|
|
||||||
-> RIO e ([Job], SerfState)
|
|
||||||
loop acc ss pb = \case
|
|
||||||
[] -> do
|
|
||||||
pb <- logStderr (updateProgressBar 0 bootMsg pb)
|
|
||||||
pure (reverse acc, ss)
|
|
||||||
x:xs -> do
|
|
||||||
wen <- io Time.now
|
|
||||||
job <- pure $ x (ssNextEv ss) (ssLastMug ss) wen
|
|
||||||
pb <- logStderr (updateProgressBar (1 + length xs) bootMsg pb)
|
|
||||||
(job, ss) <- bootJob serf job
|
|
||||||
loop (job:acc) ss pb xs
|
|
||||||
|
|
||||||
bootSeqFns :: [BootSeqFn]
|
|
||||||
bootSeqFns = fmap muckNock nocks <> fmap muckOvum ovums
|
|
||||||
where
|
|
||||||
muckNock nok eId mug _ = RunNok $ LifeCyc eId mug nok
|
|
||||||
muckOvum ov eId mug wen = DoWork $ Work eId mug wen ov
|
|
||||||
|
|
||||||
bootMsg = "Booting " ++ (fakeStr (isFake ident)) ++
|
|
||||||
(Ob.renderPatp (Ob.patp (fromIntegral (who ident))))
|
|
||||||
fakeStr True = "fake "
|
|
||||||
fakeStr False = ""
|
|
||||||
|
|
||||||
{-|
|
|
||||||
The ship is booted, but it is behind. shove events to the worker
|
|
||||||
until it is caught up.
|
|
||||||
-}
|
|
||||||
replayJobs :: (HasStderrLogFunc e, HasLogFunc e)
|
|
||||||
=> Serf e -> Int -> SerfState -> ConduitT Job Void (RIO e) SerfState
|
|
||||||
replayJobs serf lastEv = go Nothing
|
|
||||||
where
|
|
||||||
go pb ss = do
|
|
||||||
await >>= \case
|
|
||||||
Nothing -> pure ss
|
|
||||||
Just job -> do
|
|
||||||
pb <- lift $ logStderr (updatePb ss pb)
|
|
||||||
played <- lift $ replayJob serf job
|
|
||||||
go pb played
|
|
||||||
|
|
||||||
updatePb ss = do
|
|
||||||
let start = lastEv - fromIntegral (ssNextEv ss)
|
|
||||||
let msg = pack ( "Replaying events #" ++ (show (ssNextEv ss))
|
|
||||||
<> " to #" ++ (show lastEv)
|
|
||||||
)
|
|
||||||
updateProgressBar start msg
|
|
||||||
|
|
||||||
|
|
||||||
replay :: (HasStderrLogFunc e, HasLogFunc e)
|
|
||||||
=> Serf e -> Log.EventLog -> Maybe Word64 -> RIO e SerfState
|
|
||||||
replay serf log last = do
|
|
||||||
logTrace "Beginning event log replay"
|
logTrace "Beginning event log replay"
|
||||||
|
|
||||||
|
lastEventInSnap <- io (serfLastEventBlocking serf)
|
||||||
|
|
||||||
last & \case
|
last & \case
|
||||||
Nothing -> pure ()
|
Nothing -> pure ()
|
||||||
Just lt -> logTrace $ display $
|
Just lt -> logTrace $ display $
|
||||||
"User requested to replay up to event #" <> tshow lt
|
"User requested to replay up to event #" <> tshow lt
|
||||||
|
|
||||||
ss <- handshake serf (Log.identity log)
|
logLastEv :: Word64 <- atomically $ fromIntegral <$> Log.lastEv log
|
||||||
|
|
||||||
logLastEv :: Word64 <- fromIntegral <$> Log.lastEv log
|
|
||||||
|
|
||||||
let serfNextEv = ssNextEv ss
|
|
||||||
lastEventInSnap = serfNextEv - 1
|
|
||||||
|
|
||||||
logTrace $ display $ "Last event in event log is #" <> tshow logLastEv
|
logTrace $ display $ "Last event in event log is #" <> tshow logLastEv
|
||||||
|
|
||||||
let replayUpTo = fromMaybe logLastEv last
|
let replayUpTo = min (fromMaybe logLastEv last) logLastEv
|
||||||
|
|
||||||
let numEvs :: Int = fromIntegral replayUpTo - fromIntegral lastEventInSnap
|
let numEvs :: Int = fromIntegral replayUpTo - fromIntegral lastEventInSnap
|
||||||
|
|
||||||
|
when (numEvs < 0) $ do
|
||||||
|
throwIO (SnapshotAheadOfLog logLastEv lastEventInSnap)
|
||||||
|
|
||||||
|
incProgress <- logStderr (trackProgress (fromIntegral numEvs))
|
||||||
|
|
||||||
logTrace $ display $ "Replaying up to event #" <> tshow replayUpTo
|
logTrace $ display $ "Replaying up to event #" <> tshow replayUpTo
|
||||||
logTrace $ display $ "Will replay " <> tshow numEvs <> " in total."
|
logTrace $ display $ "Will replay " <> tshow numEvs <> " in total."
|
||||||
|
|
||||||
runConduit $ Log.streamEvents log serfNextEv
|
env <- ask
|
||||||
.| CC.take (fromIntegral numEvs)
|
|
||||||
.| toJobs (Log.identity log) serfNextEv
|
|
||||||
.| replayJobs serf (fromIntegral replayUpTo) ss
|
|
||||||
|
|
||||||
toJobs :: HasLogFunc e
|
res <- runResourceT
|
||||||
=> LogIdentity -> EventId -> ConduitT ByteString Job (RIO e) ()
|
$ runConduit
|
||||||
toJobs ident eId =
|
$ Log.streamEvents log (lastEventInSnap + 1)
|
||||||
await >>= \case
|
.| CC.take (fromIntegral numEvs)
|
||||||
Nothing -> lift $ logTrace "[toJobs] no more jobs"
|
.| CC.mapM (fmap snd . parseLogRow)
|
||||||
Just at -> do yield =<< lift (fromAtom at)
|
.| replay 5 incProgress serf
|
||||||
lift $ logTrace $ display ("[toJobs] " <> tshow eId)
|
|
||||||
toJobs ident (eId+1)
|
|
||||||
where
|
|
||||||
isNock = eId <= fromIntegral (lifecycleLen ident)
|
|
||||||
|
|
||||||
fromAtom :: ByteString -> RIO e Job
|
res & \case
|
||||||
fromAtom bs | isNock = do
|
Nothing -> pure (Right $ fromIntegral numEvs)
|
||||||
noun <- cueBSExn bs
|
Just er -> pure (Left er)
|
||||||
(mug, nok) <- fromNounExn noun
|
|
||||||
pure $ RunNok (LifeCyc eId mug nok)
|
logStderr :: HasStderrLogFunc e => RIO LogFunc a -> RIO e a
|
||||||
fromAtom bs = do
|
logStderr action = do
|
||||||
noun <- cueBSExn bs
|
logFunc <- view stderrLogFuncL
|
||||||
(mug, wen, ovm) <- fromNounExn noun
|
runRIO logFunc action
|
||||||
pure $ DoWork (Work eId mug wen ovm)
|
|
||||||
|
trackProgress
|
||||||
|
:: HasLogFunc e
|
||||||
|
=> Word64
|
||||||
|
-> RIO e (Int -> IO ())
|
||||||
|
trackProgress = \case
|
||||||
|
0 -> pure $ const $ pure ()
|
||||||
|
num -> do
|
||||||
|
let style = PB.defStyle { PB.stylePostfix = PB.exact }
|
||||||
|
let refresh = 10
|
||||||
|
let init = PB.Progress 0 (fromIntegral num) ()
|
||||||
|
bar <- PB.newProgressBar style refresh init
|
||||||
|
env <- ask
|
||||||
|
let incr = PB.incProgress bar
|
||||||
|
pure (runRIO env . incr)
|
||||||
|
|
||||||
|
|
||||||
-- Collect Effects for Parsing -------------------------------------------------
|
-- Collect FX ------------------------------------------------------------------
|
||||||
|
|
||||||
collectFX :: HasLogFunc e => Serf e -> Log.EventLog -> RIO e ()
|
collectFX :: HasLogFunc e => Serf -> Log.EventLog -> RIO e ()
|
||||||
collectFX serf log = do
|
collectFX serf log = do
|
||||||
ss <- handshake serf (Log.identity log)
|
lastEv <- io (serfLastEventBlocking serf)
|
||||||
|
runResourceT
|
||||||
|
$ runConduit
|
||||||
|
$ Log.streamEvents log (lastEv + 1)
|
||||||
|
.| CC.mapM (parseLogRow >=> fromNounExn . snd)
|
||||||
|
.| swim serf
|
||||||
|
.| persistFX log
|
||||||
|
|
||||||
runConduit $ Log.streamEvents log (ssNextEv ss)
|
persistFX :: MonadIO m => Log.EventLog -> ConduitT (EventId, FX) Void m ()
|
||||||
.| toJobs (Log.identity log) (ssNextEv ss)
|
persistFX log = CC.mapM_ $ \(eId, fx) -> do
|
||||||
.| doCollectFX serf ss
|
Log.writeEffectsRow log eId $ jamBS $ toNoun fx
|
||||||
.| persistFX log
|
|
||||||
|
|
||||||
persistFX :: Log.EventLog -> ConduitT (EventId, FX) Void (RIO e) ()
|
|
||||||
persistFX log = loop
|
|
||||||
where
|
|
||||||
loop = await >>= \case
|
|
||||||
Nothing -> pure ()
|
|
||||||
Just (eId, fx) -> do
|
|
||||||
lift $ Log.writeEffectsRow log eId (jamBS $ toNoun fx)
|
|
||||||
loop
|
|
||||||
|
|
||||||
doCollectFX :: ∀e. HasLogFunc e
|
|
||||||
=> Serf e -> SerfState -> ConduitT Job (EventId, FX) (RIO e) ()
|
|
||||||
doCollectFX serf = go
|
|
||||||
where
|
|
||||||
go :: SerfState -> ConduitT Job (EventId, FX) (RIO e) ()
|
|
||||||
go ss = await >>= \case
|
|
||||||
Nothing -> pure ()
|
|
||||||
Just jb -> do
|
|
||||||
-- jb <- pure $ replaceMug jb (ssLastMug ss)
|
|
||||||
(_, ss, fx) <- lift $ doJob serf jb
|
|
||||||
when (0 == (jobId jb `mod` 10_000)) $ do
|
|
||||||
lift $ logTrace $ displayShow (jobId jb)
|
|
||||||
yield (jobId jb, fx)
|
|
||||||
go ss
|
|
||||||
|
|
||||||
_replaceMug :: Job -> Mug -> Job
|
|
||||||
_replaceMug jb mug =
|
|
||||||
case jb of
|
|
||||||
DoWork (Work eId _ w o) -> DoWork (Work eId mug w o)
|
|
||||||
RunNok (LifeCyc eId _ n) -> RunNok (LifeCyc eId mug n)
|
|
||||||
|
704
pkg/hs/urbit-king/lib/Urbit/Vere/Serf/IPC.hs
Normal file
704
pkg/hs/urbit-king/lib/Urbit/Vere/Serf/IPC.hs
Normal file
@ -0,0 +1,704 @@
|
|||||||
|
{-|
|
||||||
|
Low-Level IPC flows for interacting with the serf process.
|
||||||
|
|
||||||
|
- Serf process can be started and shutdown with `start` and `stop`.
|
||||||
|
- You can ask the serf what it's last event was with
|
||||||
|
`serfLastEventBlocking`.
|
||||||
|
- A running serf can be asked to compact it's heap or take a snapshot.
|
||||||
|
- You can scry into a running serf.
|
||||||
|
- A running serf can be asked to execute a boot sequence, replay from
|
||||||
|
existing events, and run a ship with `boot`, `replay`, and `run`.
|
||||||
|
|
||||||
|
The `run` and `replay` flows will do batching of events to keep the
|
||||||
|
IPC pipe full.
|
||||||
|
|
||||||
|
```
|
||||||
|
|%
|
||||||
|
:: +writ: from king to serf
|
||||||
|
::
|
||||||
|
+$ gang (unit (set ship))
|
||||||
|
+$ writ
|
||||||
|
$% $: %live
|
||||||
|
$% [%cram eve=@]
|
||||||
|
[%exit cod=@]
|
||||||
|
[%save eve=@]
|
||||||
|
[%pack ~]
|
||||||
|
== ==
|
||||||
|
[%peek mil=@ now=@da lyc=gang pat=path]
|
||||||
|
[%play eve=@ lit=(list ?((pair @da ovum) *))]
|
||||||
|
[%work mil=@ job=(pair @da ovum)]
|
||||||
|
==
|
||||||
|
:: +plea: from serf to king
|
||||||
|
::
|
||||||
|
+$ plea
|
||||||
|
$% [%live ~]
|
||||||
|
[%ripe [pro=%1 hon=@ nok=@] eve=@ mug=@]
|
||||||
|
[%slog pri=@ ?(cord tank)]
|
||||||
|
$: %peek
|
||||||
|
$% [%done dat=(unit (cask))]
|
||||||
|
[%bail dud=goof]
|
||||||
|
== ==
|
||||||
|
$: %play
|
||||||
|
$% [%done mug=@]
|
||||||
|
[%bail eve=@ mug=@ dud=goof]
|
||||||
|
== ==
|
||||||
|
$: %work
|
||||||
|
$% [%done eve=@ mug=@ fec=(list ovum)]
|
||||||
|
[%swap eve=@ mug=@ job=(pair @da ovum) fec=(list ovum)]
|
||||||
|
[%bail lud=(list goof)]
|
||||||
|
== ==
|
||||||
|
==
|
||||||
|
```
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Urbit.Vere.Serf.IPC
|
||||||
|
( Serf
|
||||||
|
, start
|
||||||
|
, stop
|
||||||
|
, serfLastEventBlocking
|
||||||
|
, snapshot
|
||||||
|
, compact
|
||||||
|
, scry
|
||||||
|
, boot
|
||||||
|
, replay
|
||||||
|
, run
|
||||||
|
, swim
|
||||||
|
, sendSIGINT
|
||||||
|
, module Urbit.Vere.Serf.Types
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Urbit.Prelude hiding ((<|))
|
||||||
|
|
||||||
|
import Data.Bits
|
||||||
|
import Data.Conduit
|
||||||
|
import System.Process
|
||||||
|
import Urbit.Vere.Serf.Types
|
||||||
|
|
||||||
|
import Control.Monad.STM (retry)
|
||||||
|
import Control.Monad.Trans.Resource (MonadResource, allocate, runResourceT)
|
||||||
|
import Data.Sequence (Seq((:<|), (:|>)))
|
||||||
|
import Foreign.Marshal.Alloc (alloca)
|
||||||
|
import Foreign.Ptr (castPtr)
|
||||||
|
import Foreign.Storable (peek, poke)
|
||||||
|
import RIO.Prelude (decodeUtf8Lenient)
|
||||||
|
import System.Posix.Signals (sigINT, sigKILL, signalProcess)
|
||||||
|
import Urbit.Arvo (Ev, FX)
|
||||||
|
import Urbit.Noun.Time (Wen)
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified Data.ByteString.Unsafe as BS
|
||||||
|
import qualified System.IO.Error as IO
|
||||||
|
import qualified Urbit.Noun.Time as Time
|
||||||
|
|
||||||
|
|
||||||
|
-- Serf API --------------------------------------------------------------------
|
||||||
|
|
||||||
|
data Serf = Serf
|
||||||
|
{ serfSend :: Handle
|
||||||
|
, serfRecv :: Handle
|
||||||
|
, serfProc :: ProcessHandle
|
||||||
|
, serfSlog :: Slog -> IO ()
|
||||||
|
, serfLock :: MVar (Maybe SerfState)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Internal Protocol Types -----------------------------------------------------
|
||||||
|
|
||||||
|
data Live
|
||||||
|
= LExit Atom -- exit status code
|
||||||
|
| LSave EventId
|
||||||
|
| LCram EventId
|
||||||
|
| LPack ()
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Play
|
||||||
|
= PDone Mug
|
||||||
|
| PBail PlayBail
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Scry
|
||||||
|
= SDone (Maybe (Term, Noun))
|
||||||
|
| SBail Goof
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Work
|
||||||
|
= WDone EventId Mug FX
|
||||||
|
| WSwap EventId Mug (Wen, Noun) FX
|
||||||
|
| WBail [Goof]
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Writ
|
||||||
|
= WLive Live
|
||||||
|
| WPeek Atom Wen Gang Path
|
||||||
|
| WPlay EventId [Noun]
|
||||||
|
| WWork Atom Wen Ev
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Plea
|
||||||
|
= PLive ()
|
||||||
|
| PRipe SerfInfo
|
||||||
|
| PSlog Slog
|
||||||
|
| PPeek Scry
|
||||||
|
| PPlay Play
|
||||||
|
| PWork Work
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
deriveNoun ''Live
|
||||||
|
deriveNoun ''Play
|
||||||
|
deriveNoun ''Scry
|
||||||
|
deriveNoun ''Work
|
||||||
|
deriveNoun ''Writ
|
||||||
|
deriveNoun ''Plea
|
||||||
|
|
||||||
|
|
||||||
|
-- Access Current Serf State ---------------------------------------------------
|
||||||
|
|
||||||
|
serfLastEventBlocking :: Serf -> IO EventId
|
||||||
|
serfLastEventBlocking Serf{serfLock} = readMVar serfLock >>= \case
|
||||||
|
Nothing -> throwIO SerfNotRunning
|
||||||
|
Just ss -> pure (ssLast ss)
|
||||||
|
|
||||||
|
|
||||||
|
-- Low Level IPC Functions -----------------------------------------------------
|
||||||
|
|
||||||
|
fromRightExn :: (Exception e, MonadIO m) => Either a b -> (a -> e) -> m b
|
||||||
|
fromRightExn (Left m) exn = throwIO (exn m)
|
||||||
|
fromRightExn (Right x) _ = pure x
|
||||||
|
|
||||||
|
-- TODO Support Big Endian
|
||||||
|
sendLen :: Serf -> Int -> IO ()
|
||||||
|
sendLen s i = do
|
||||||
|
w <- evaluate (fromIntegral i :: Word64)
|
||||||
|
withWord64AsByteString w (hPut (serfSend s))
|
||||||
|
where
|
||||||
|
withWord64AsByteString :: Word64 -> (ByteString -> IO a) -> IO a
|
||||||
|
withWord64AsByteString w k = alloca $ \wp -> do
|
||||||
|
poke wp w
|
||||||
|
bs <- BS.unsafePackCStringLen (castPtr wp, 8)
|
||||||
|
k bs
|
||||||
|
|
||||||
|
sendBytes :: Serf -> ByteString -> IO ()
|
||||||
|
sendBytes s bs = handle onIOError $ do
|
||||||
|
sendLen s (length bs)
|
||||||
|
hPut (serfSend s) bs
|
||||||
|
hFlush (serfSend s)
|
||||||
|
where
|
||||||
|
onIOError :: IOError -> IO ()
|
||||||
|
onIOError = const (throwIO SerfConnectionClosed)
|
||||||
|
|
||||||
|
recvBytes :: Serf -> Word64 -> IO ByteString
|
||||||
|
recvBytes serf = BS.hGet (serfRecv serf) . fromIntegral
|
||||||
|
|
||||||
|
recvLen :: Serf -> IO Word64
|
||||||
|
recvLen w = do
|
||||||
|
bs <- BS.hGet (serfRecv w) 8
|
||||||
|
case length bs of
|
||||||
|
8 -> BS.unsafeUseAsCString bs (peek @Word64 . castPtr)
|
||||||
|
_ -> throwIO SerfConnectionClosed
|
||||||
|
|
||||||
|
recvResp :: Serf -> IO ByteString
|
||||||
|
recvResp serf = do
|
||||||
|
len <- recvLen serf
|
||||||
|
recvBytes serf len
|
||||||
|
|
||||||
|
|
||||||
|
-- Send Writ / Recv Plea -------------------------------------------------------
|
||||||
|
|
||||||
|
sendWrit :: Serf -> Writ -> IO ()
|
||||||
|
sendWrit s = sendBytes s . jamBS . toNoun
|
||||||
|
|
||||||
|
recvPlea :: Serf -> IO Plea
|
||||||
|
recvPlea w = do
|
||||||
|
b <- recvResp w
|
||||||
|
n <- fromRightExn (cueBS b) (const $ BadPleaAtom $ bytesAtom b)
|
||||||
|
p <- fromRightExn (fromNounErr @Plea n) (\(p, m) -> BadPleaNoun n p m)
|
||||||
|
pure p
|
||||||
|
|
||||||
|
recvPleaHandlingSlog :: Serf -> IO Plea
|
||||||
|
recvPleaHandlingSlog serf = loop
|
||||||
|
where
|
||||||
|
loop = recvPlea serf >>= \case
|
||||||
|
PSlog info -> serfSlog serf info >> loop
|
||||||
|
other -> pure other
|
||||||
|
|
||||||
|
|
||||||
|
-- Higher-Level IPC Functions --------------------------------------------------
|
||||||
|
|
||||||
|
recvRipe :: Serf -> IO SerfInfo
|
||||||
|
recvRipe serf = recvPleaHandlingSlog serf >>= \case
|
||||||
|
PRipe ripe -> pure ripe
|
||||||
|
plea -> throwIO (UnexpectedPlea (toNoun plea) "expecting %play")
|
||||||
|
|
||||||
|
recvPlay :: Serf -> IO Play
|
||||||
|
recvPlay serf = recvPleaHandlingSlog serf >>= \case
|
||||||
|
PPlay play -> pure play
|
||||||
|
plea -> throwIO (UnexpectedPlea (toNoun plea) "expecting %play")
|
||||||
|
|
||||||
|
recvLive :: Serf -> IO ()
|
||||||
|
recvLive serf = recvPleaHandlingSlog serf >>= \case
|
||||||
|
PLive () -> pure ()
|
||||||
|
plea -> throwIO (UnexpectedPlea (toNoun plea) "expecting %live")
|
||||||
|
|
||||||
|
recvWork :: Serf -> IO Work
|
||||||
|
recvWork serf = do
|
||||||
|
recvPleaHandlingSlog serf >>= \case
|
||||||
|
PWork work -> pure work
|
||||||
|
plea -> throwIO (UnexpectedPlea (toNoun plea) "expecting %work")
|
||||||
|
|
||||||
|
recvPeek :: Serf -> IO (Maybe (Term, Noun))
|
||||||
|
recvPeek serf = do
|
||||||
|
recvPleaHandlingSlog serf >>= \case
|
||||||
|
PPeek (SDone peek) -> pure peek
|
||||||
|
-- XX produce error
|
||||||
|
PPeek (SBail dud) -> throwIO (PeekBail dud)
|
||||||
|
plea -> throwIO (UnexpectedPlea (toNoun plea) "expecting %peek")
|
||||||
|
|
||||||
|
|
||||||
|
-- Request-Response Points -- These don't touch the lock -----------------------
|
||||||
|
|
||||||
|
sendSnapshotRequest :: Serf -> EventId -> IO ()
|
||||||
|
sendSnapshotRequest serf eve = do
|
||||||
|
sendWrit serf (WLive $ LSave eve)
|
||||||
|
recvLive serf
|
||||||
|
|
||||||
|
sendCompactionRequest :: Serf -> IO ()
|
||||||
|
sendCompactionRequest serf = do
|
||||||
|
sendWrit serf (WLive $ LPack ())
|
||||||
|
recvLive serf
|
||||||
|
|
||||||
|
sendScryRequest :: Serf -> Wen -> Gang -> Path -> IO (Maybe (Term, Noun))
|
||||||
|
sendScryRequest serf w g p = do
|
||||||
|
sendWrit serf (WPeek 0 w g p)
|
||||||
|
recvPeek serf
|
||||||
|
|
||||||
|
sendShutdownRequest :: Serf -> Atom -> IO ()
|
||||||
|
sendShutdownRequest serf exitCode = do
|
||||||
|
sendWrit serf (WLive $ LExit exitCode)
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
|
||||||
|
-- Starting the Serf -----------------------------------------------------------
|
||||||
|
|
||||||
|
compileFlags :: [Flag] -> Word
|
||||||
|
compileFlags = foldl' (\acc flag -> setBit acc (fromEnum flag)) 0
|
||||||
|
|
||||||
|
readStdErr :: Handle -> (Text -> IO ()) -> IO () -> IO ()
|
||||||
|
readStdErr h onLine onClose = loop
|
||||||
|
where
|
||||||
|
loop = do
|
||||||
|
IO.tryIOError (BS.hGetLine h >>= onLine . decodeUtf8Lenient) >>= \case
|
||||||
|
Left exn -> onClose
|
||||||
|
Right () -> loop
|
||||||
|
|
||||||
|
start :: Config -> IO (Serf, SerfInfo)
|
||||||
|
start (Config exePax pierPath flags onSlog onStdr onDead) = do
|
||||||
|
(Just i, Just o, Just e, p) <- createProcess pSpec
|
||||||
|
void $ async (readStdErr e onStdr onDead)
|
||||||
|
vLock <- newEmptyMVar
|
||||||
|
let serf = Serf i o p onSlog vLock
|
||||||
|
info <- recvRipe serf
|
||||||
|
putMVar vLock (Just $ siStat info)
|
||||||
|
pure (serf, info)
|
||||||
|
where
|
||||||
|
diskKey = ""
|
||||||
|
config = show (compileFlags flags)
|
||||||
|
rock = "0" -- XX support loading from rock
|
||||||
|
cache = "50000" -- XX support memo-cache size
|
||||||
|
args = ["serf", pierPath, diskKey, config, cache, rock]
|
||||||
|
pSpec = (proc exePax args) { std_in = CreatePipe
|
||||||
|
, std_out = CreatePipe
|
||||||
|
, std_err = CreatePipe
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Taking the SerfState Lock ---------------------------------------------------
|
||||||
|
|
||||||
|
takeLock :: MonadIO m => Serf -> m SerfState
|
||||||
|
takeLock serf = io $ do
|
||||||
|
takeMVar (serfLock serf) >>= \case
|
||||||
|
Nothing -> putMVar (serfLock serf) Nothing >> throwIO SerfNotRunning
|
||||||
|
Just ss -> pure ss
|
||||||
|
|
||||||
|
serfLockTaken
|
||||||
|
:: MonadResource m => Serf -> m (IORef (Maybe SerfState), SerfState)
|
||||||
|
serfLockTaken serf = snd <$> allocate take release
|
||||||
|
where
|
||||||
|
take = (,) <$> newIORef Nothing <*> takeLock serf
|
||||||
|
release (rv, _) = do
|
||||||
|
mRes <- readIORef rv
|
||||||
|
when (mRes == Nothing) (forcefullyKillSerf serf)
|
||||||
|
putMVar (serfLock serf) mRes
|
||||||
|
|
||||||
|
withSerfLock
|
||||||
|
:: MonadResource m => Serf -> (SerfState -> m (SerfState, a)) -> m a
|
||||||
|
withSerfLock serf act = do
|
||||||
|
(vState , initialState) <- serfLockTaken serf
|
||||||
|
(newState, result ) <- act initialState
|
||||||
|
writeIORef vState (Just newState)
|
||||||
|
pure result
|
||||||
|
|
||||||
|
withSerfLockIO :: Serf -> (SerfState -> IO (SerfState, a)) -> IO a
|
||||||
|
withSerfLockIO s a = runResourceT (withSerfLock s (io . a))
|
||||||
|
|
||||||
|
|
||||||
|
-- SIGINT ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sendSIGINT :: Serf -> IO ()
|
||||||
|
sendSIGINT serf = do
|
||||||
|
getPid (serfProc serf) >>= \case
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just pid -> do
|
||||||
|
io $ signalProcess sigINT pid
|
||||||
|
|
||||||
|
|
||||||
|
-- Killing the Serf ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Ask the serf to shutdown. If it takes more than 2s, kill it with
|
||||||
|
SIGKILL.
|
||||||
|
-}
|
||||||
|
stop :: HasLogFunc e => Serf -> RIO e ()
|
||||||
|
stop serf = do
|
||||||
|
race_ niceKill (wait2sec >> forceKill)
|
||||||
|
where
|
||||||
|
wait2sec = threadDelay 2_000_000
|
||||||
|
|
||||||
|
niceKill = do
|
||||||
|
logTrace "Asking serf to shut down"
|
||||||
|
io (gracefullyKillSerf serf)
|
||||||
|
logTrace "Serf went down when asked."
|
||||||
|
|
||||||
|
forceKill = do
|
||||||
|
logTrace "Serf taking too long to go down, kill with fire (SIGTERM)."
|
||||||
|
io (forcefullyKillSerf serf)
|
||||||
|
logTrace "Serf process killed with SIGTERM."
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Kill the serf by taking the lock, then asking for it to exit.
|
||||||
|
-}
|
||||||
|
gracefullyKillSerf :: Serf -> IO ()
|
||||||
|
gracefullyKillSerf serf@Serf{..} = do
|
||||||
|
finalState <- takeMVar serfLock
|
||||||
|
sendShutdownRequest serf 0
|
||||||
|
waitForProcess serfProc
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Kill the serf by sending it a SIGKILL.
|
||||||
|
-}
|
||||||
|
forcefullyKillSerf :: Serf -> IO ()
|
||||||
|
forcefullyKillSerf serf = do
|
||||||
|
getPid (serfProc serf) >>= \case
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just pid -> do
|
||||||
|
io $ signalProcess sigKILL pid
|
||||||
|
io $ void $ waitForProcess (serfProc serf)
|
||||||
|
|
||||||
|
|
||||||
|
-- Flows for Interacting with the Serf -----------------------------------------
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Ask the serf to write a snapshot to disk.
|
||||||
|
-}
|
||||||
|
snapshot :: Serf -> IO ()
|
||||||
|
snapshot serf = withSerfLockIO serf $ \ss -> do
|
||||||
|
sendSnapshotRequest serf (ssLast ss)
|
||||||
|
pure (ss, ())
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Ask the serf to de-duplicate and de-fragment it's heap.
|
||||||
|
-}
|
||||||
|
compact :: Serf -> IO ()
|
||||||
|
compact serf = withSerfLockIO serf $ \ss -> do
|
||||||
|
sendCompactionRequest serf
|
||||||
|
pure (ss, ())
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Peek into the serf state.
|
||||||
|
-}
|
||||||
|
scry :: Serf -> Wen -> Gang -> Path -> IO (Maybe (Term, Noun))
|
||||||
|
scry serf w g p = withSerfLockIO serf $ \ss -> do
|
||||||
|
(ss,) <$> sendScryRequest serf w g p
|
||||||
|
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Given a list of boot events, send them to to the serf in a single
|
||||||
|
%play message. They must all be sent in a single %play event so that
|
||||||
|
the serf can determine the length of the boot sequence.
|
||||||
|
-}
|
||||||
|
boot :: Serf -> [Noun] -> IO (Maybe PlayBail)
|
||||||
|
boot serf@Serf {..} seq = do
|
||||||
|
withSerfLockIO serf $ \ss -> do
|
||||||
|
sendWrit serf (WPlay 1 seq)
|
||||||
|
recvPlay serf >>= \case
|
||||||
|
PBail bail -> pure (ss, Just bail)
|
||||||
|
PDone mug -> pure (SerfState (fromIntegral $ length seq) mug, Nothing)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Given a stream of nouns (from the event log), feed them into the serf
|
||||||
|
in batches of size `batchSize`.
|
||||||
|
|
||||||
|
- On `%bail` response, return early.
|
||||||
|
- On IPC errors, kill the serf and rethrow.
|
||||||
|
- On success, return `Nothing`.
|
||||||
|
-}
|
||||||
|
replay
|
||||||
|
:: forall m
|
||||||
|
. (MonadResource m, MonadUnliftIO m, MonadIO m)
|
||||||
|
=> Int
|
||||||
|
-> (Int -> IO ())
|
||||||
|
-> Serf
|
||||||
|
-> ConduitT Noun Void m (Maybe PlayBail)
|
||||||
|
replay batchSize cb serf = do
|
||||||
|
withSerfLock serf $ \ss -> do
|
||||||
|
(r, ss') <- loop ss
|
||||||
|
pure (ss', r)
|
||||||
|
where
|
||||||
|
loop :: SerfState -> ConduitT Noun Void m (Maybe PlayBail, SerfState)
|
||||||
|
loop ss@(SerfState lastEve lastMug) = do
|
||||||
|
awaitBatch batchSize >>= \case
|
||||||
|
[] -> pure (Nothing, SerfState lastEve lastMug)
|
||||||
|
evs -> do
|
||||||
|
let nexEve = lastEve + 1
|
||||||
|
let newEve = lastEve + fromIntegral (length evs)
|
||||||
|
io $ sendWrit serf (WPlay nexEve evs)
|
||||||
|
io (recvPlay serf) >>= \case
|
||||||
|
PBail bail -> pure (Just bail, SerfState lastEve lastMug)
|
||||||
|
PDone newMug -> do
|
||||||
|
io (cb $ length evs)
|
||||||
|
loop (SerfState newEve newMug)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
TODO If this is slow, use a mutable vector instead of reversing a list.
|
||||||
|
-}
|
||||||
|
awaitBatch :: Monad m => Int -> ConduitT i o m [i]
|
||||||
|
awaitBatch = go []
|
||||||
|
where
|
||||||
|
go acc 0 = pure (reverse acc)
|
||||||
|
go acc n = await >>= \case
|
||||||
|
Nothing -> pure (reverse acc)
|
||||||
|
Just x -> go (x:acc) (n-1)
|
||||||
|
|
||||||
|
|
||||||
|
-- Special Replay for Collecting FX --------------------------------------------
|
||||||
|
|
||||||
|
{-|
|
||||||
|
This does event-log replay using the running IPC flow so that we
|
||||||
|
can collect effects.
|
||||||
|
|
||||||
|
We don't tolerate replacement events or bails since we are actually
|
||||||
|
replaying the log, so we just throw exceptions in those cases.
|
||||||
|
-}
|
||||||
|
swim
|
||||||
|
:: forall m
|
||||||
|
. (MonadIO m, MonadUnliftIO m, MonadResource m)
|
||||||
|
=> Serf
|
||||||
|
-> ConduitT (Wen, Ev) (EventId, FX) m ()
|
||||||
|
swim serf = do
|
||||||
|
withSerfLock serf $ \SerfState {..} -> do
|
||||||
|
(, ()) <$> loop ssHash ssLast
|
||||||
|
where
|
||||||
|
loop
|
||||||
|
:: Mug
|
||||||
|
-> EventId
|
||||||
|
-> ConduitT (Wen, Ev) (EventId, FX) m SerfState
|
||||||
|
loop mug eve = await >>= \case
|
||||||
|
Nothing -> do
|
||||||
|
pure (SerfState eve mug)
|
||||||
|
Just (wen, evn) -> do
|
||||||
|
io (sendWrit serf (WWork 0 wen evn))
|
||||||
|
io (recvWork serf) >>= \case
|
||||||
|
WBail goofs -> do
|
||||||
|
throwIO (BailDuringReplay eve goofs)
|
||||||
|
WSwap eid hash (wen, noun) fx -> do
|
||||||
|
throwIO (SwapDuringReplay eid hash (wen, noun) fx)
|
||||||
|
WDone eid hash fx -> do
|
||||||
|
yield (eid, fx)
|
||||||
|
loop hash eid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Running Ship Flow -----------------------------------------------------------
|
||||||
|
|
||||||
|
{-|
|
||||||
|
TODO Don't take snapshot until event log has processed current event.
|
||||||
|
-}
|
||||||
|
run
|
||||||
|
:: Serf
|
||||||
|
-> Int
|
||||||
|
-> STM EventId
|
||||||
|
-> STM RunReq
|
||||||
|
-> ((Fact, FX) -> STM ())
|
||||||
|
-> (Maybe Ev -> STM ())
|
||||||
|
-> IO ()
|
||||||
|
run serf maxBatchSize getLastEvInLog onInput sendOn spin = topLoop
|
||||||
|
where
|
||||||
|
topLoop :: IO ()
|
||||||
|
topLoop = atomically onInput >>= \case
|
||||||
|
RRWork workErr -> doWork workErr
|
||||||
|
RRSave () -> doSave
|
||||||
|
RRKill () -> doKill
|
||||||
|
RRPack () -> doPack
|
||||||
|
RRScry w g p k -> doScry w g p k
|
||||||
|
|
||||||
|
doPack :: IO ()
|
||||||
|
doPack = compact serf >> topLoop
|
||||||
|
|
||||||
|
waitForLog :: IO ()
|
||||||
|
waitForLog = do
|
||||||
|
serfLast <- serfLastEventBlocking serf
|
||||||
|
atomically $ do
|
||||||
|
logLast <- getLastEvInLog
|
||||||
|
when (logLast < serfLast) retry
|
||||||
|
|
||||||
|
doSave :: IO ()
|
||||||
|
doSave = waitForLog >> snapshot serf >> topLoop
|
||||||
|
|
||||||
|
doKill :: IO ()
|
||||||
|
doKill = waitForLog >> snapshot serf >> pure ()
|
||||||
|
|
||||||
|
doScry :: Wen -> Gang -> Path -> (Maybe (Term, Noun) -> IO ()) -> IO ()
|
||||||
|
doScry w g p k = (scry serf w g p >>= k) >> topLoop
|
||||||
|
|
||||||
|
doWork :: EvErr -> IO ()
|
||||||
|
doWork firstWorkErr = do
|
||||||
|
que <- newTBMQueueIO 1
|
||||||
|
() <- atomically (writeTBMQueue que firstWorkErr)
|
||||||
|
tWork <- async (processWork serf maxBatchSize que onWorkResp spin)
|
||||||
|
flip onException (cancel tWork) $ do
|
||||||
|
nexSt <- workLoop que
|
||||||
|
wait tWork
|
||||||
|
nexSt
|
||||||
|
|
||||||
|
workLoop :: TBMQueue EvErr -> IO (IO ())
|
||||||
|
workLoop que = atomically onInput >>= \case
|
||||||
|
RRKill () -> atomically (closeTBMQueue que) >> pure doKill
|
||||||
|
RRSave () -> atomically (closeTBMQueue que) >> pure doSave
|
||||||
|
RRPack () -> atomically (closeTBMQueue que) >> pure doPack
|
||||||
|
RRScry w g p k -> atomically (closeTBMQueue que) >> pure (doScry w g p k)
|
||||||
|
RRWork workErr -> atomically (writeTBMQueue que workErr) >> workLoop que
|
||||||
|
|
||||||
|
onWorkResp :: Wen -> EvErr -> Work -> IO ()
|
||||||
|
onWorkResp wen (EvErr evn err) = \case
|
||||||
|
WDone eid hash fx -> do
|
||||||
|
io $ err (RunOkay eid)
|
||||||
|
atomically $ sendOn ((Fact eid hash wen (toNoun evn)), fx)
|
||||||
|
WSwap eid hash (wen, noun) fx -> do
|
||||||
|
io $ err (RunSwap eid hash wen noun fx)
|
||||||
|
atomically $ sendOn (Fact eid hash wen noun, fx)
|
||||||
|
WBail goofs -> do
|
||||||
|
io $ err (RunBail goofs)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Given:
|
||||||
|
|
||||||
|
- A stream of incoming requests
|
||||||
|
- A sequence of in-flight requests that haven't been responded to
|
||||||
|
- A maximum number of in-flight requests.
|
||||||
|
|
||||||
|
Wait until the number of in-fligh requests is smaller than the maximum,
|
||||||
|
and then take the next item from the stream of requests.
|
||||||
|
-}
|
||||||
|
pullFromQueueBounded :: Int -> TVar (Seq a) -> TBMQueue b -> STM (Maybe b)
|
||||||
|
pullFromQueueBounded maxSize vInFlight queue = do
|
||||||
|
inFlight <- length <$> readTVar vInFlight
|
||||||
|
if inFlight >= maxSize
|
||||||
|
then retry
|
||||||
|
else readTBMQueue queue
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Given
|
||||||
|
|
||||||
|
- `maxSize`: The maximum number of jobs to send to the serf before
|
||||||
|
getting a response.
|
||||||
|
- `q`: A bounded queue (which can be closed)
|
||||||
|
- `onResp`: a callback to call for each response from the serf.
|
||||||
|
- `spin`: a callback to tell the terminal driver which event is
|
||||||
|
currently being processed.
|
||||||
|
|
||||||
|
Pull jobs from the queue and send them to the serf (eagerly, up to
|
||||||
|
`maxSize`) and call the callback with each response from the serf.
|
||||||
|
|
||||||
|
When the queue is closed, wait for the serf to respond to all pending
|
||||||
|
work, and then return.
|
||||||
|
|
||||||
|
Whenever the serf is idle, call `spin Nothing` and whenever the serf
|
||||||
|
is working on an event, call `spin (Just ev)`.
|
||||||
|
-}
|
||||||
|
processWork
|
||||||
|
:: Serf
|
||||||
|
-> Int
|
||||||
|
-> TBMQueue EvErr
|
||||||
|
-> (Wen -> EvErr -> Work -> IO ())
|
||||||
|
-> (Maybe Ev -> STM ())
|
||||||
|
-> IO ()
|
||||||
|
processWork serf maxSize q onResp spin = do
|
||||||
|
vDoneFlag <- newTVarIO False
|
||||||
|
vInFlightQueue <- newTVarIO empty
|
||||||
|
recvThread <- async (recvLoop serf vDoneFlag vInFlightQueue spin)
|
||||||
|
flip onException (print "KILLING: processWork" >> cancel recvThread) $ do
|
||||||
|
loop vInFlightQueue vDoneFlag
|
||||||
|
wait recvThread
|
||||||
|
where
|
||||||
|
loop :: TVar (Seq (Ev, Work -> IO ())) -> TVar Bool -> IO ()
|
||||||
|
loop vInFlight vDone = do
|
||||||
|
atomically (pullFromQueueBounded maxSize vInFlight q) >>= \case
|
||||||
|
Nothing -> do
|
||||||
|
atomically (writeTVar vDone True)
|
||||||
|
Just evErr@(EvErr ev _) -> do
|
||||||
|
now <- Time.now
|
||||||
|
let cb = onResp now evErr
|
||||||
|
atomically $ modifyTVar' vInFlight (:|> (ev, cb))
|
||||||
|
sendWrit serf (WWork 0 now ev)
|
||||||
|
loop vInFlight vDone
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Given:
|
||||||
|
|
||||||
|
- `vDone`: A flag that no more work will be sent to the serf.
|
||||||
|
|
||||||
|
- `vWork`: A list of work requests that have been sent to the serf,
|
||||||
|
haven't been responded to yet.
|
||||||
|
|
||||||
|
If the serf has responded to all work requests, and no more work is
|
||||||
|
going to be sent to the serf, then return.
|
||||||
|
|
||||||
|
If we are going to send more work to the serf, but the queue is empty,
|
||||||
|
then wait.
|
||||||
|
|
||||||
|
If work requests have been sent to the serf, take the first one,
|
||||||
|
wait for a response from the serf, call the associated callback,
|
||||||
|
and repeat the whole process.
|
||||||
|
-}
|
||||||
|
recvLoop
|
||||||
|
:: Serf
|
||||||
|
-> TVar Bool
|
||||||
|
-> TVar (Seq (Ev, Work -> IO ()))
|
||||||
|
-> (Maybe Ev -> STM ())
|
||||||
|
-> IO ()
|
||||||
|
recvLoop serf vDone vWork spin = do
|
||||||
|
withSerfLockIO serf \SerfState {..} -> do
|
||||||
|
loop ssLast ssHash
|
||||||
|
where
|
||||||
|
loop eve mug = do
|
||||||
|
atomically $ do
|
||||||
|
whenM (null <$> readTVar vWork) $ do
|
||||||
|
spin Nothing
|
||||||
|
atomically takeCallback >>= \case
|
||||||
|
Nothing -> pure (SerfState eve mug, ())
|
||||||
|
Just (curEve, cb) -> do
|
||||||
|
atomically (spin (Just curEve))
|
||||||
|
recvWork serf >>= \case
|
||||||
|
work@(WDone eid hash _) -> cb work >> loop eid hash
|
||||||
|
work@(WSwap eid hash _ _) -> cb work >> loop eid hash
|
||||||
|
work@(WBail _) -> cb work >> loop eve mug
|
||||||
|
|
||||||
|
takeCallback :: STM (Maybe (Ev, Work -> IO ()))
|
||||||
|
takeCallback = do
|
||||||
|
((,) <$> readTVar vDone <*> readTVar vWork) >>= \case
|
||||||
|
(False, Empty ) -> retry
|
||||||
|
(True , Empty ) -> pure Nothing
|
||||||
|
(_ , (e, x) :<| xs) -> writeTVar vWork xs $> Just (e, x)
|
||||||
|
(_ , _ ) -> error "impossible"
|
121
pkg/hs/urbit-king/lib/Urbit/Vere/Serf/Types.hs
Normal file
121
pkg/hs/urbit-king/lib/Urbit/Vere/Serf/Types.hs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
module Urbit.Vere.Serf.Types where
|
||||||
|
|
||||||
|
import Urbit.Prelude
|
||||||
|
|
||||||
|
import Urbit.Arvo (Ev, FX)
|
||||||
|
import Urbit.Noun.Time (Wen)
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type EventId = Word64
|
||||||
|
|
||||||
|
type PlayBail = (EventId, Mug, Goof)
|
||||||
|
|
||||||
|
type Slog = (Atom, Tank)
|
||||||
|
|
||||||
|
data SerfState = SerfState
|
||||||
|
{ ssLast :: EventId
|
||||||
|
, ssHash :: Mug
|
||||||
|
}
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
data RipeInfo = RipeInfo
|
||||||
|
{ riProt :: Atom
|
||||||
|
, riHoon :: Atom
|
||||||
|
, riNock :: Atom
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data SerfInfo = SerfInfo
|
||||||
|
{ siRipe :: RipeInfo
|
||||||
|
, siStat :: SerfState
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Fact = Fact
|
||||||
|
{ factEve :: EventId
|
||||||
|
, factMug :: Mug
|
||||||
|
, factWen :: Wen
|
||||||
|
, factNon :: Noun
|
||||||
|
}
|
||||||
|
|
||||||
|
data Flag
|
||||||
|
= DebugRam
|
||||||
|
| DebugCpu
|
||||||
|
| CheckCorrupt
|
||||||
|
| CheckFatal
|
||||||
|
| Verbose
|
||||||
|
| DryRun
|
||||||
|
| Quiet
|
||||||
|
| Hashless
|
||||||
|
| Trace
|
||||||
|
deriving (Eq, Ord, Show, Enum, Bounded)
|
||||||
|
|
||||||
|
data Config = Config
|
||||||
|
{ scSerf :: FilePath -- Where is the urbit-worker executable?
|
||||||
|
, scPier :: FilePath -- Where is the pier directory?
|
||||||
|
, scFlag :: [Flag] -- Serf execution flags.
|
||||||
|
, scSlog :: Slog -> IO () -- What to do with slogs?
|
||||||
|
, scStdr :: Text -> IO () -- What to do with lines from stderr?
|
||||||
|
, scDead :: IO () -- What to do when the serf process goes down?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Serf Commands ---------------------------------------------------------------
|
||||||
|
|
||||||
|
type Gang = Maybe (HoonSet Ship)
|
||||||
|
|
||||||
|
type Goof = (Term, [Tank])
|
||||||
|
|
||||||
|
data EvErr = EvErr Ev (WorkError -> IO ())
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Two types of serf failures.
|
||||||
|
|
||||||
|
- `RunSwap`: Event processing failed, but the serf replaced it with
|
||||||
|
another event which succeeded.
|
||||||
|
|
||||||
|
- `RunBail`: Event processing failed and all attempt to replace it
|
||||||
|
with a failure-notice event also caused crashes. We are really fucked.
|
||||||
|
-}
|
||||||
|
data WorkError -- TODO Rename type and constructors
|
||||||
|
= RunSwap EventId Mug Wen Noun FX -- TODO Maybe provide less info here?
|
||||||
|
| RunBail [Goof]
|
||||||
|
| RunOkay EventId
|
||||||
|
|
||||||
|
{-
|
||||||
|
- RRWork: Ask the serf to do work, will output (Fact, FX) if work
|
||||||
|
succeeded and call callback on failure.
|
||||||
|
- RRSave: Wait for the serf to finish all pending work
|
||||||
|
-}
|
||||||
|
data RunReq
|
||||||
|
= RRWork EvErr
|
||||||
|
| RRSave ()
|
||||||
|
| RRKill ()
|
||||||
|
| RRPack ()
|
||||||
|
| RRScry Wen Gang Path (Maybe (Term, Noun) -> IO ())
|
||||||
|
|
||||||
|
|
||||||
|
-- Exceptions ------------------------------------------------------------------
|
||||||
|
|
||||||
|
data SerfExn
|
||||||
|
= UnexpectedPlea Noun Text
|
||||||
|
| BadPleaAtom Atom
|
||||||
|
| BadPleaNoun Noun [Text] Text
|
||||||
|
| PeekBail Goof
|
||||||
|
| SerfConnectionClosed
|
||||||
|
| SerfHasShutdown
|
||||||
|
| BailDuringReplay EventId [Goof]
|
||||||
|
| SwapDuringReplay EventId Mug (Wen, Noun) FX
|
||||||
|
| SerfNotRunning
|
||||||
|
| MissingBootEventsInEventLog Word Word
|
||||||
|
| SnapshotAheadOfLog EventId EventId
|
||||||
|
deriving (Show, Exception)
|
||||||
|
|
||||||
|
|
||||||
|
-- Instances -------------------------------------------------------------------
|
||||||
|
|
||||||
|
deriveNoun ''RipeInfo
|
||||||
|
deriveNoun ''SerfInfo
|
||||||
|
deriveNoun ''SerfState
|
@ -8,6 +8,7 @@ module Urbit.Vere.Term
|
|||||||
, runTerminalClient
|
, runTerminalClient
|
||||||
, connClient
|
, connClient
|
||||||
, term
|
, term
|
||||||
|
, term'
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Char
|
import Data.Char
|
||||||
@ -18,19 +19,21 @@ import RIO.FilePath
|
|||||||
import System.Posix.IO
|
import System.Posix.IO
|
||||||
import System.Posix.Terminal
|
import System.Posix.Terminal
|
||||||
import Urbit.Arvo hiding (Term)
|
import Urbit.Arvo hiding (Term)
|
||||||
import Urbit.King.Config
|
import Urbit.King.App
|
||||||
|
import Urbit.Noun.Time
|
||||||
import Urbit.Prelude hiding (getCurrentTime)
|
import Urbit.Prelude hiding (getCurrentTime)
|
||||||
import Urbit.Time
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Data.List ((!!))
|
import Data.List ((!!))
|
||||||
import RIO.Directory (createDirectoryIfMissing)
|
import RIO.Directory (createDirectoryIfMissing)
|
||||||
import Urbit.King.API (readPortsFile)
|
import Urbit.King.API (readPortsFile)
|
||||||
import Urbit.King.App (HasConfigDir(..))
|
import Urbit.TermSize (TermSize(TermSize))
|
||||||
import Urbit.Vere.Term.API (Client(Client))
|
import Urbit.Vere.Term.API (Client(Client))
|
||||||
|
|
||||||
import qualified Data.ByteString.Internal as BS
|
import qualified Data.ByteString.Internal as BS
|
||||||
import qualified Data.ByteString.UTF8 as BS
|
import qualified Data.ByteString.UTF8 as BS
|
||||||
|
import qualified System.Console.ANSI as ANSI
|
||||||
|
import qualified Urbit.TermSize as T
|
||||||
import qualified Urbit.Vere.NounServ as Serv
|
import qualified Urbit.Vere.NounServ as Serv
|
||||||
import qualified Urbit.Vere.Term.API as Term
|
import qualified Urbit.Vere.Term.API as Term
|
||||||
import qualified Urbit.Vere.Term.Render as T
|
import qualified Urbit.Vere.Term.Render as T
|
||||||
@ -74,20 +77,6 @@ initialHail = EvBlip $ BlipEvTerm $ TermEvHail (UD 1, ()) ()
|
|||||||
-- Version one of this is punting on the ops_u.dem flag: whether we're running
|
-- Version one of this is punting on the ops_u.dem flag: whether we're running
|
||||||
-- in daemon mode.
|
-- in daemon mode.
|
||||||
|
|
||||||
spinners :: [Text]
|
|
||||||
spinners = ["|", "/", "-", "\\"]
|
|
||||||
|
|
||||||
leftBracket :: Text
|
|
||||||
leftBracket = "«"
|
|
||||||
|
|
||||||
rightBracket :: Text
|
|
||||||
rightBracket = "»"
|
|
||||||
|
|
||||||
_spin_cool_us = 500000
|
|
||||||
_spin_warm_us = 50000
|
|
||||||
_spin_rate_us = 250000
|
|
||||||
_spin_idle_us = 500000
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
rioAllocaBytes :: (MonadIO m, MonadUnliftIO m)
|
rioAllocaBytes :: (MonadIO m, MonadUnliftIO m)
|
||||||
@ -138,7 +127,7 @@ connectToRemote port local = mkRAcquire start stop
|
|||||||
|
|
||||||
data HackConfigDir = HCD { _hcdPax :: FilePath }
|
data HackConfigDir = HCD { _hcdPax :: FilePath }
|
||||||
makeLenses ''HackConfigDir
|
makeLenses ''HackConfigDir
|
||||||
instance HasConfigDir HackConfigDir where configDirL = hcdPax
|
instance HasPierPath HackConfigDir where pierPathL = hcdPax
|
||||||
|
|
||||||
runTerminalClient :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
runTerminalClient :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
||||||
runTerminalClient pier = runRAcquire $ do
|
runTerminalClient pier = runRAcquire $ do
|
||||||
@ -153,20 +142,46 @@ runTerminalClient pier = runRAcquire $ do
|
|||||||
runRAcquire :: RAcquire e () -> RIO e ()
|
runRAcquire :: RAcquire e () -> RIO e ()
|
||||||
runRAcquire act = rwith act $ const $ pure ()
|
runRAcquire act = rwith act $ const $ pure ()
|
||||||
|
|
||||||
|
|
||||||
|
-- Spinner ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Call an STM action after delay of `first` microseconds and then every
|
||||||
|
-- `rest` microseconds after that.
|
||||||
|
repeatedly :: Int -> Int -> STM () -> IO ()
|
||||||
|
repeatedly first rest action = do
|
||||||
|
threadDelay first
|
||||||
|
forever $ do
|
||||||
|
atomically action
|
||||||
|
threadDelay rest
|
||||||
|
|
||||||
|
spinners :: [Text]
|
||||||
|
spinners = ["|", "/", "-", "\\"]
|
||||||
|
|
||||||
|
leftBracket, rightBracket :: Text
|
||||||
|
leftBracket = "«"
|
||||||
|
rightBracket = "»"
|
||||||
|
|
||||||
|
_spin_cool_us = 500000
|
||||||
|
_spin_warm_us = 50000
|
||||||
|
_spin_rate_us = 250000
|
||||||
|
_spin_idle_us = 500000
|
||||||
|
|
||||||
|
|
||||||
|
-- Client ----------------------------------------------------------------------
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Initializes the generalized input/output parts of the terminal.
|
Initializes the generalized input/output parts of the terminal.
|
||||||
-}
|
-}
|
||||||
localClient :: ∀e. HasLogFunc e
|
localClient :: ∀e. HasLogFunc e
|
||||||
=> STM ()
|
=> STM ()
|
||||||
-> RAcquire e (T.TSize, Client)
|
-> RAcquire e (TermSize, Client)
|
||||||
localClient doneSignal = fst <$> mkRAcquire start stop
|
localClient doneSignal = fst <$> mkRAcquire start stop
|
||||||
where
|
where
|
||||||
start :: HasLogFunc e => RIO e ((T.TSize, Client), Private)
|
start :: HasLogFunc e => RIO e ((TermSize, Client), Private)
|
||||||
start = do
|
start = do
|
||||||
tsWriteQueue <- newTQueueIO
|
tsWriteQueue <- newTQueueIO :: RIO e (TQueue [Term.Ev])
|
||||||
spinnerMVar <- newEmptyTMVarIO
|
spinnerMVar <- newEmptyTMVarIO :: RIO e (TMVar ())
|
||||||
pWriterThread <-
|
pWriterThread <- asyncBound (writeTerminal tsWriteQueue spinnerMVar)
|
||||||
asyncBound (writeTerminal tsWriteQueue spinnerMVar)
|
|
||||||
|
|
||||||
pPreviousConfiguration <- io $ getTerminalAttributes stdInput
|
pPreviousConfiguration <- io $ getTerminalAttributes stdInput
|
||||||
|
|
||||||
@ -187,12 +202,12 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
, give = writeTQueue tsWriteQueue
|
, give = writeTQueue tsWriteQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
tsize <- io $ T.tsize
|
tsize <- io $ T.termSize
|
||||||
|
|
||||||
pure ((tsize, client), Private{..})
|
pure ((tsize, client), Private{..})
|
||||||
|
|
||||||
stop :: HasLogFunc e
|
stop :: HasLogFunc e
|
||||||
=> ((T.TSize, Client), Private) -> RIO e ()
|
=> ((TermSize, Client), Private) -> RIO e ()
|
||||||
stop ((_, Client{..}), Private{..}) = do
|
stop ((_, Client{..}), Private{..}) = do
|
||||||
-- Note that we don't `cancel pReaderThread` here. This is a deliberate
|
-- Note that we don't `cancel pReaderThread` here. This is a deliberate
|
||||||
-- decision because fdRead calls into a native function which the runtime
|
-- decision because fdRead calls into a native function which the runtime
|
||||||
@ -226,17 +241,6 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
, ProcessOutput
|
, ProcessOutput
|
||||||
]
|
]
|
||||||
|
|
||||||
-- An async which will put into an mvar after a delay. Used to spin the
|
|
||||||
-- spinner in writeTerminal.
|
|
||||||
spinnerHeartBeat :: Int -> Int -> TMVar () -> RIO e ()
|
|
||||||
spinnerHeartBeat first rest mvar = do
|
|
||||||
threadDelay first
|
|
||||||
loop
|
|
||||||
where
|
|
||||||
loop = do
|
|
||||||
atomically $ putTMVar mvar ()
|
|
||||||
threadDelay rest
|
|
||||||
loop
|
|
||||||
|
|
||||||
-- Writes data to the terminal. Both the terminal reading, normal logging,
|
-- Writes data to the terminal. Both the terminal reading, normal logging,
|
||||||
-- and effect handling can all emit bytes which go to the terminal.
|
-- and effect handling can all emit bytes which go to the terminal.
|
||||||
@ -246,9 +250,7 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
loop (LineState "" 0 Nothing Nothing True 0 currentTime)
|
loop (LineState "" 0 Nothing Nothing True 0 currentTime)
|
||||||
where
|
where
|
||||||
writeBlank :: LineState -> RIO e LineState
|
writeBlank :: LineState -> RIO e LineState
|
||||||
writeBlank ls = do
|
writeBlank ls = putStr "\r\n" $> ls
|
||||||
putStr "\r\n"
|
|
||||||
pure ls
|
|
||||||
|
|
||||||
writeTrace :: LineState -> Text -> RIO e LineState
|
writeTrace :: LineState -> Text -> RIO e LineState
|
||||||
writeTrace ls p = do
|
writeTrace ls p = do
|
||||||
@ -266,6 +268,8 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
-}
|
-}
|
||||||
doSpin :: LineState -> Maybe Text -> RIO e LineState
|
doSpin :: LineState -> Maybe Text -> RIO e LineState
|
||||||
doSpin ls@LineState{..} mTxt = do
|
doSpin ls@LineState{..} mTxt = do
|
||||||
|
maybe (pure ()) cancel lsSpinTimer
|
||||||
|
|
||||||
current <- io $ now
|
current <- io $ now
|
||||||
delay <- pure $ case mTxt of
|
delay <- pure $ case mTxt of
|
||||||
Nothing -> 0
|
Nothing -> 0
|
||||||
@ -274,7 +278,10 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
then _spin_warm_us
|
then _spin_warm_us
|
||||||
else _spin_cool_us
|
else _spin_cool_us
|
||||||
|
|
||||||
spinTimer <- async $ spinnerHeartBeat delay _spin_rate_us spinner
|
spinTimer <- io $ async
|
||||||
|
$ repeatedly delay _spin_rate_us
|
||||||
|
$ void
|
||||||
|
$ tryPutTMVar spinner ()
|
||||||
|
|
||||||
pure $ ls { lsSpinTimer = Just spinTimer
|
pure $ ls { lsSpinTimer = Just spinTimer
|
||||||
, lsSpinCause = mTxt
|
, lsSpinCause = mTxt
|
||||||
@ -291,7 +298,7 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
|
|
||||||
-- If we ever actually ran the spinner display callback, we need
|
-- If we ever actually ran the spinner display callback, we need
|
||||||
-- to force a redisplay of the command prompt.
|
-- to force a redisplay of the command prompt.
|
||||||
ls <- if not lsSpinFirstRender
|
ls <- if not lsSpinFirstRender || True
|
||||||
then termRefreshLine ls
|
then termRefreshLine ls
|
||||||
else pure ls
|
else pure ls
|
||||||
|
|
||||||
@ -306,16 +313,16 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
Term.Spinr (Just txt) -> doSpin ls (unCord <$> txt)
|
Term.Spinr (Just txt) -> doSpin ls (unCord <$> txt)
|
||||||
Term.Spinr Nothing -> unspin ls
|
Term.Spinr Nothing -> unspin ls
|
||||||
|
|
||||||
|
-- TODO What does this do?
|
||||||
spin :: LineState -> RIO e LineState
|
spin :: LineState -> RIO e LineState
|
||||||
spin ls@LineState{..} = do
|
spin ls@LineState{..} = do
|
||||||
let spinner = (spinners !! lsSpinFrame) ++ case lsSpinCause of
|
let spinner = (spinners !! lsSpinFrame) ++ case lsSpinCause of
|
||||||
Nothing -> ""
|
Nothing -> ""
|
||||||
Just str -> leftBracket ++ str ++ rightBracket
|
Just str -> leftBracket ++ str ++ rightBracket
|
||||||
|
|
||||||
putStr spinner
|
putStr (spinner <> pack (ANSI.cursorBackwardCode (length spinner)))
|
||||||
termSpinnerMoveLeft (length spinner)
|
|
||||||
|
|
||||||
let newFrame = (lsSpinFrame + 1) `mod` (length spinners)
|
let newFrame = (lsSpinFrame + 1) `mod` length spinners
|
||||||
|
|
||||||
pure $ ls { lsSpinFirstRender = False
|
pure $ ls { lsSpinFirstRender = False
|
||||||
, lsSpinFrame = newFrame
|
, lsSpinFrame = newFrame
|
||||||
@ -356,8 +363,8 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
|
|
||||||
-- Moves the cursor left without any mutation of the LineState. Used only
|
-- Moves the cursor left without any mutation of the LineState. Used only
|
||||||
-- in cursor spinning.
|
-- in cursor spinning.
|
||||||
termSpinnerMoveLeft :: Int → RIO e ()
|
_termSpinnerMoveLeft :: Int → RIO e ()
|
||||||
termSpinnerMoveLeft = T.cursorLeft
|
_termSpinnerMoveLeft = T.cursorLeft
|
||||||
|
|
||||||
-- Displays and sets the current line
|
-- Displays and sets the current line
|
||||||
termShowLine :: LineState -> Text -> RIO e LineState
|
termShowLine :: LineState -> Text -> RIO e LineState
|
||||||
@ -489,28 +496,55 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
-- logDebug $ displayShow ("terminalBelt", b)
|
-- logDebug $ displayShow ("terminalBelt", b)
|
||||||
atomically $ writeTQueue rq b
|
atomically $ writeTQueue rq b
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Terminal Driver
|
||||||
|
|
||||||
|
Until blew/hail events succeeds, ignore effects.
|
||||||
|
Wait until blew/hail event callbacks invoked.
|
||||||
|
If success, signal success.
|
||||||
|
If failure, try again several times.
|
||||||
|
If still failure, bring down ship.
|
||||||
|
Don't wait for other drivers to boot
|
||||||
|
Begin normal operation (start accepting requests)
|
||||||
|
-}
|
||||||
|
term'
|
||||||
|
:: HasPierEnv e
|
||||||
|
=> (TermSize, Client)
|
||||||
|
-> IO ()
|
||||||
|
-> RIO e ([Ev], RAcquire e (DriverApi TermEf))
|
||||||
|
term' (tsize, client) serfSIGINT = do
|
||||||
|
let TermSize wi hi = tsize
|
||||||
|
initEv = [initialBlew wi hi, initialHail]
|
||||||
|
|
||||||
|
pure (initEv, runDriver)
|
||||||
|
where
|
||||||
|
runDriver = do
|
||||||
|
env <- ask
|
||||||
|
ventQ :: TQueue EvErr <- newTQueueIO
|
||||||
|
diOnEffect <- term env (tsize, client) (writeTQueue ventQ) serfSIGINT
|
||||||
|
|
||||||
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
||||||
|
|
||||||
|
pure (DriverApi {..})
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Terminal Driver
|
Terminal Driver
|
||||||
-}
|
-}
|
||||||
term :: forall e. (HasPierConfig e, HasLogFunc e)
|
term :: forall e. (HasPierEnv e)
|
||||||
=> (T.TSize, Client)
|
=> e
|
||||||
-> (STM ())
|
-> (TermSize, Client)
|
||||||
-> KingId
|
-> (EvErr -> STM ())
|
||||||
-> QueueEv
|
-> IO ()
|
||||||
-> ([Ev], RAcquire e (EffCb e TermEf))
|
-> RAcquire e (TermEf -> IO ())
|
||||||
term (tsize, Client{..}) shutdownSTM king enqueueEv =
|
term env (tsize, Client{..}) plan serfSIGINT = runTerm
|
||||||
(initialEvents, runTerm)
|
|
||||||
where
|
where
|
||||||
T.TSize wi hi = tsize
|
runTerm :: RAcquire e (TermEf -> IO ())
|
||||||
|
|
||||||
initialEvents = [(initialBlew wi hi), initialHail]
|
|
||||||
|
|
||||||
runTerm :: RAcquire e (EffCb e TermEf)
|
|
||||||
runTerm = do
|
runTerm = do
|
||||||
tim <- mkRAcquire (async readLoop) cancel
|
tim <- mkRAcquire (async readLoop) cancel
|
||||||
pure handleEffect
|
pure (runRIO env . handleEffect)
|
||||||
|
|
||||||
{-
|
{-
|
||||||
Because our terminals are always `Demux`ed, we don't have to
|
Because our terminals are always `Demux`ed, we don't have to
|
||||||
@ -521,14 +555,17 @@ term (tsize, Client{..}) shutdownSTM king enqueueEv =
|
|||||||
atomically take >>= \case
|
atomically take >>= \case
|
||||||
Nothing -> pure ()
|
Nothing -> pure ()
|
||||||
Just b -> do
|
Just b -> do
|
||||||
let blip = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
when (b == Ctl (Cord "c")) $ do
|
||||||
atomically $ enqueueEv $ blip
|
io serfSIGINT
|
||||||
|
let beltEv = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
||||||
|
let beltFailed _ = pure ()
|
||||||
|
atomically $ plan (EvErr beltEv beltFailed)
|
||||||
|
|
||||||
handleEffect :: TermEf -> RIO e ()
|
handleEffect :: TermEf -> RIO e ()
|
||||||
handleEffect = \case
|
handleEffect = \case
|
||||||
TermEfInit _ _ -> pure ()
|
TermEfInit _ _ -> pure ()
|
||||||
TermEfMass _ _ -> pure ()
|
TermEfMass _ _ -> pure ()
|
||||||
TermEfLogo _ _ -> atomically shutdownSTM
|
TermEfLogo _ _ -> atomically =<< view killPierActionL
|
||||||
TermEfBlit _ blits -> do
|
TermEfBlit _ blits -> do
|
||||||
let (termBlits, fsWrites) = partition isTerminalBlit blits
|
let (termBlits, fsWrites) = partition isTerminalBlit blits
|
||||||
atomically $ give [Term.Blits termBlits]
|
atomically $ give [Term.Blits termBlits]
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
Terminal Driver
|
Terminal Driver
|
||||||
-}
|
-}
|
||||||
module Urbit.Vere.Term.Render
|
module Urbit.Vere.Term.Render
|
||||||
( TSize(..)
|
( clearScreen
|
||||||
, tsize
|
|
||||||
, clearScreen
|
|
||||||
, clearLine
|
, clearLine
|
||||||
, cursorRight
|
, cursorRight
|
||||||
, cursorLeft
|
, cursorLeft
|
||||||
@ -13,29 +11,11 @@ module Urbit.Vere.Term.Render
|
|||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
|
||||||
import qualified System.Console.Terminal.Size as TSize
|
import qualified System.Console.ANSI as ANSI
|
||||||
import qualified System.Console.ANSI as ANSI
|
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
data TSize = TSize
|
|
||||||
{ tsWide ∷ Word
|
|
||||||
, tsTall ∷ Word
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
{- |
|
|
||||||
Get terminal size. Produces 80x24 as a fallback if unable to figure
|
|
||||||
out terminal size.
|
|
||||||
-}
|
|
||||||
tsize ∷ IO TSize
|
|
||||||
tsize = do
|
|
||||||
TSize.Window wi hi <- TSize.size <&> fromMaybe (TSize.Window 80 24)
|
|
||||||
pure $ TSize { tsWide = wi, tsTall = hi }
|
|
||||||
|
|
||||||
clearScreen ∷ MonadIO m ⇒ m ()
|
clearScreen ∷ MonadIO m ⇒ m ()
|
||||||
clearScreen = liftIO $ ANSI.clearScreen
|
clearScreen = liftIO $ ANSI.clearScreen
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name: urbit-king
|
name: urbit-king
|
||||||
version: 0.10.4
|
version: 0.10.8
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
|
||||||
@ -72,6 +72,7 @@ dependencies:
|
|||||||
- primitive
|
- primitive
|
||||||
- process
|
- process
|
||||||
- QuickCheck
|
- QuickCheck
|
||||||
|
- racquire
|
||||||
- random
|
- random
|
||||||
- regex-tdfa
|
- regex-tdfa
|
||||||
- regex-tdfa-text
|
- regex-tdfa-text
|
||||||
@ -88,10 +89,10 @@ dependencies:
|
|||||||
- tasty-th
|
- tasty-th
|
||||||
- template-haskell
|
- template-haskell
|
||||||
- terminal-progress-bar
|
- terminal-progress-bar
|
||||||
- terminal-size
|
|
||||||
- text
|
- text
|
||||||
- these
|
- these
|
||||||
- time
|
- time
|
||||||
|
- tls
|
||||||
- transformers
|
- transformers
|
||||||
- unix
|
- unix
|
||||||
- unliftio
|
- unliftio
|
||||||
@ -99,7 +100,11 @@ dependencies:
|
|||||||
- unordered-containers
|
- unordered-containers
|
||||||
- urbit-atom
|
- urbit-atom
|
||||||
- urbit-azimuth
|
- urbit-azimuth
|
||||||
|
- urbit-eventlog-lmdb
|
||||||
- urbit-hob
|
- urbit-hob
|
||||||
|
- urbit-noun
|
||||||
|
- urbit-noun-core
|
||||||
|
- urbit-termsize
|
||||||
- utf8-string
|
- utf8-string
|
||||||
- vector
|
- vector
|
||||||
- wai
|
- wai
|
||||||
|
@ -8,22 +8,27 @@ import Test.Tasty
|
|||||||
import Test.Tasty.QuickCheck
|
import Test.Tasty.QuickCheck
|
||||||
import Test.Tasty.TH
|
import Test.Tasty.TH
|
||||||
import Urbit.Arvo
|
import Urbit.Arvo
|
||||||
|
import Urbit.EventLog.LMDB
|
||||||
import Urbit.King.Config
|
import Urbit.King.Config
|
||||||
import Urbit.Noun
|
import Urbit.Noun
|
||||||
|
import Urbit.Noun.Time
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Time
|
|
||||||
import Urbit.Vere.Ames
|
import Urbit.Vere.Ames
|
||||||
import Urbit.Vere.Log
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Concurrent (runInBoundThread)
|
import Control.Concurrent (runInBoundThread)
|
||||||
import Data.LargeWord (LargeKey(..))
|
import Data.LargeWord (LargeKey(..))
|
||||||
import GHC.Natural (Natural)
|
import GHC.Natural (Natural)
|
||||||
import Network.Socket (tupleToHostAddress)
|
import Network.Socket (tupleToHostAddress)
|
||||||
|
import Urbit.King.App (HasKingId(..))
|
||||||
|
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type HasAmes e = (HasLogFunc e, HasNetworkConfig e, HasKingId e)
|
||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
pid :: KingId
|
pid :: KingId
|
||||||
@ -38,6 +43,7 @@ sendEf g w bs = NewtEfSend (0, ()) (EachYes g) bs
|
|||||||
data NetworkTestApp = NetworkTestApp
|
data NetworkTestApp = NetworkTestApp
|
||||||
{ _ntaLogFunc :: !LogFunc
|
{ _ntaLogFunc :: !LogFunc
|
||||||
, _ntaNetworkConfig :: !NetworkConfig
|
, _ntaNetworkConfig :: !NetworkConfig
|
||||||
|
, _ntaKingId :: !Word16
|
||||||
}
|
}
|
||||||
|
|
||||||
makeLenses ''NetworkTestApp
|
makeLenses ''NetworkTestApp
|
||||||
@ -48,67 +54,81 @@ instance HasLogFunc NetworkTestApp where
|
|||||||
instance HasNetworkConfig NetworkTestApp where
|
instance HasNetworkConfig NetworkTestApp where
|
||||||
networkConfigL = ntaNetworkConfig
|
networkConfigL = ntaNetworkConfig
|
||||||
|
|
||||||
|
instance HasKingId NetworkTestApp where
|
||||||
|
kingIdL = ntaKingId
|
||||||
|
|
||||||
runNetworkApp :: RIO NetworkTestApp a -> IO a
|
runNetworkApp :: RIO NetworkTestApp a -> IO a
|
||||||
runNetworkApp = runRIO NetworkTestApp
|
runNetworkApp = runRIO NetworkTestApp
|
||||||
{ _ntaLogFunc = mkLogFunc l
|
{ _ntaLogFunc = mkLogFunc (\_ _ _ _ -> pure ())
|
||||||
, _ntaNetworkConfig = NetworkConfig NMNormal Nothing Nothing Nothing Nothing
|
, _ntaKingId = 34
|
||||||
|
, _ntaNetworkConfig = NetworkConfig { _ncNetMode = NMNormal
|
||||||
|
, _ncAmesPort = Nothing
|
||||||
|
, _ncNoAmes = False
|
||||||
|
, _ncNoHttp = False
|
||||||
|
, _ncNoHttps = False
|
||||||
|
, _ncHttpPort = Nothing
|
||||||
|
, _ncHttpsPort = Nothing
|
||||||
|
, _ncLocalPort = Nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
where
|
|
||||||
l _ _ _ _ = pure ()
|
|
||||||
|
|
||||||
runGala :: forall e. (HasLogFunc e, HasNetworkConfig e)
|
runGala
|
||||||
=> Word8 -> RAcquire e (TQueue Ev, EffCb e NewtEf)
|
:: forall e
|
||||||
runGala point =
|
. HasAmes e
|
||||||
do
|
=> Word8
|
||||||
q <- newTQueueIO
|
-> RAcquire e (TQueue EvErr, NewtEf -> IO ())
|
||||||
let (_, runAmes) =
|
runGala point = do
|
||||||
ames pid (fromIntegral point) True (writeTQueue q) noStderr
|
env <- ask
|
||||||
cb ← runAmes
|
que <- newTQueueIO
|
||||||
rio $ cb turfEf
|
let enqueue = \p -> writeTQueue que p $> Intake
|
||||||
pure (q, cb)
|
let (_, runAmes) = ames env (fromIntegral point) True enqueue noStderr
|
||||||
|
cb <- runAmes
|
||||||
|
io (cb turfEf)
|
||||||
|
pure (que, cb)
|
||||||
where
|
where
|
||||||
noStderr _ = pure ()
|
noStderr _ = pure ()
|
||||||
|
|
||||||
waitForPacket :: TQueue Ev -> Bytes -> IO Bool
|
waitForPacket :: TQueue EvErr -> Bytes -> IO Bool
|
||||||
waitForPacket q val = go
|
waitForPacket q val = go
|
||||||
where
|
where
|
||||||
go =
|
go = atomically (readTQueue q) >>= \case
|
||||||
atomically (readTQueue q) >>= \case
|
EvErr (EvBlip (BlipEvNewt (NewtEvBorn (_, ()) ()))) _ -> go
|
||||||
EvBlip (BlipEvNewt (NewtEvBorn (_, ()) ())) -> go
|
EvErr (EvBlip (BlipEvAmes (AmesEvHear () _ bs))) _ -> pure (bs == val)
|
||||||
EvBlip (BlipEvAmes (AmesEvHear () _ bs)) -> pure (bs == val)
|
_ -> pure False
|
||||||
_ -> pure False
|
|
||||||
|
|
||||||
runRAcquire :: RAcquire e a -> RIO e a
|
runRAcquire :: RAcquire e a -> RIO e a
|
||||||
runRAcquire acq = rwith acq pure
|
runRAcquire acq = rwith acq pure
|
||||||
|
|
||||||
sendThread :: EffCb e NewtEf -> (Galaxy, Bytes) -> RAcquire e ()
|
sendThread :: (NewtEf -> IO ()) -> (Galaxy, Bytes) -> RAcquire e ()
|
||||||
sendThread cb (to, val) = void $ mkRAcquire start cancel
|
sendThread cb (to, val) = void $ mkRAcquire start cancel
|
||||||
where
|
where
|
||||||
start = async $ forever $ do threadDelay 1_000
|
start = async $ forever $ do threadDelay 1_000
|
||||||
wen <- io $ now
|
wen <- io $ now
|
||||||
cb (sendEf to wen val)
|
io $ cb (sendEf to wen val)
|
||||||
threadDelay 10_000
|
threadDelay 10_000
|
||||||
|
|
||||||
zodSelfMsg :: Property
|
zodSelfMsg :: Property
|
||||||
zodSelfMsg = forAll arbitrary (ioProperty . runNetworkApp . runTest)
|
zodSelfMsg = forAll arbitrary (ioProperty . runNetworkApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: (HasLogFunc e, HasNetworkConfig e) => Bytes -> RIO e Bool
|
runTest
|
||||||
runTest val = runRAcquire $ do
|
:: (HasLogFunc e, HasNetworkConfig e, HasKingId e) => Bytes -> RIO e Bool
|
||||||
(zodQ, zod) <- runGala 0
|
runTest val = runRAcquire $ do
|
||||||
() <- sendThread zod (0, val)
|
env <- ask
|
||||||
liftIO (waitForPacket zodQ val)
|
(zodQ, zod) <- runGala 0
|
||||||
|
() <- sendThread zod (0, val)
|
||||||
|
liftIO (waitForPacket zodQ val)
|
||||||
|
|
||||||
twoTalk :: Property
|
twoTalk :: Property
|
||||||
twoTalk = forAll arbitrary (ioProperty . runNetworkApp . runTest)
|
twoTalk = forAll arbitrary (ioProperty . runNetworkApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: (HasLogFunc e, HasNetworkConfig e)
|
runTest :: (HasLogFunc e, HasNetworkConfig e, HasKingId e)
|
||||||
=> (Word8, Word8, Bytes) -> RIO e Bool
|
=> (Word8, Word8, Bytes) -> RIO e Bool
|
||||||
runTest (aliceShip, bobShip, val) =
|
runTest (aliceShip, bobShip, val) =
|
||||||
if aliceShip == bobShip
|
if aliceShip == bobShip
|
||||||
then pure True
|
then pure True
|
||||||
else go aliceShip bobShip val
|
else go aliceShip bobShip val
|
||||||
|
|
||||||
go :: (HasLogFunc e, HasNetworkConfig e)
|
go :: (HasLogFunc e, HasNetworkConfig e, HasKingId e)
|
||||||
=> Word8 -> Word8 -> Bytes -> RIO e Bool
|
=> Word8 -> Word8 -> Bytes -> RIO e Bool
|
||||||
go aliceShip bobShip val = runRAcquire $ do
|
go aliceShip bobShip val = runRAcquire $ do
|
||||||
(aliceQ, alice) <- runGala aliceShip
|
(aliceQ, alice) <- runGala aliceShip
|
||||||
|
@ -10,9 +10,9 @@ import Test.Tasty
|
|||||||
import Test.Tasty.QuickCheck
|
import Test.Tasty.QuickCheck
|
||||||
import Test.Tasty.TH
|
import Test.Tasty.TH
|
||||||
import Urbit.Arvo
|
import Urbit.Arvo
|
||||||
|
import Urbit.EventLog.LMDB
|
||||||
|
import Urbit.Noun.Time
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Time
|
|
||||||
import Urbit.Vere.Log
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Concurrent (runInBoundThread, threadDelay)
|
import Control.Concurrent (runInBoundThread, threadDelay)
|
||||||
@ -20,7 +20,7 @@ import Data.LargeWord (LargeKey(..))
|
|||||||
import GHC.Natural (Natural)
|
import GHC.Natural (Natural)
|
||||||
import Network.Socket (tupleToHostAddress)
|
import Network.Socket (tupleToHostAddress)
|
||||||
|
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
|
|
||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
@ -9,37 +9,36 @@ import Test.Tasty
|
|||||||
import Test.Tasty.QuickCheck
|
import Test.Tasty.QuickCheck
|
||||||
import Test.Tasty.TH
|
import Test.Tasty.TH
|
||||||
import Urbit.Arvo
|
import Urbit.Arvo
|
||||||
|
import Urbit.EventLog.LMDB
|
||||||
import Urbit.Noun
|
import Urbit.Noun
|
||||||
|
import Urbit.Noun.Time
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Time
|
|
||||||
import Urbit.Vere.Behn
|
import Urbit.Vere.Behn
|
||||||
import Urbit.Vere.Log
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Concurrent (runInBoundThread, threadDelay)
|
import Control.Concurrent (runInBoundThread, threadDelay)
|
||||||
import Data.LargeWord (LargeKey(..))
|
import Data.LargeWord (LargeKey(..))
|
||||||
import GHC.Natural (Natural)
|
import GHC.Natural (Natural)
|
||||||
import Network.Socket (tupleToHostAddress)
|
import Network.Socket (tupleToHostAddress)
|
||||||
import Urbit.King.App (runApp)
|
import Urbit.King.App (runKingEnvNoLog, HasKingId(..))
|
||||||
|
|
||||||
import qualified Urbit.Time as Time
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.Noun.Time as Time
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
king :: KingId
|
|
||||||
king = KingId 0
|
|
||||||
|
|
||||||
-- TODO Timers always fire immediatly. Something is wrong!
|
-- TODO Timers always fire immediatly. Something is wrong!
|
||||||
timerFires :: Property
|
timerFires :: Property
|
||||||
timerFires = forAll arbitrary (ioProperty . runApp . runTest)
|
timerFires = forAll arbitrary (ioProperty . runKingEnvNoLog . runTest)
|
||||||
where
|
where
|
||||||
runTest :: () -> RIO e Bool
|
runTest :: HasKingId e => () -> RIO e Bool
|
||||||
runTest () = do
|
runTest () = do
|
||||||
|
envr <- ask
|
||||||
|
king <- fromIntegral <$> view kingIdL
|
||||||
q <- newTQueueIO
|
q <- newTQueueIO
|
||||||
rwith (liftAcquire $ snd $ behn king (writeTQueue q)) $ \cb -> do
|
rwith (liftAcquire $ behn envr (writeTQueue q)) $ \cb -> do
|
||||||
cb (BehnEfDoze (king, ()) (Just (2^20)))
|
io $ cb (BehnEfDoze (king, ()) (Just (2^20)))
|
||||||
t <- atomically $ readTQueue q
|
t <- atomically $ readTQueue q
|
||||||
pure True
|
pure True
|
||||||
|
|
||||||
|
@ -7,15 +7,15 @@ import Test.QuickCheck hiding ((.&.))
|
|||||||
import Test.Tasty
|
import Test.Tasty
|
||||||
import Test.Tasty.QuickCheck
|
import Test.Tasty.QuickCheck
|
||||||
import Test.Tasty.TH
|
import Test.Tasty.TH
|
||||||
|
import Urbit.EventLog.LMDB
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Vere.Log
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Concurrent (runInBoundThread, threadDelay)
|
import Control.Concurrent (runInBoundThread, threadDelay)
|
||||||
import Data.LargeWord (LargeKey(..))
|
import Data.LargeWord (LargeKey(..))
|
||||||
import GHC.Natural (Natural)
|
import GHC.Natural (Natural)
|
||||||
|
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
|
|
||||||
|
|
||||||
-- Sum Types -------------------------------------------------------------------
|
-- Sum Types -------------------------------------------------------------------
|
||||||
|
@ -7,16 +7,16 @@ import Test.QuickCheck hiding ((.&.))
|
|||||||
import Test.Tasty
|
import Test.Tasty
|
||||||
import Test.Tasty.QuickCheck
|
import Test.Tasty.QuickCheck
|
||||||
import Test.Tasty.TH
|
import Test.Tasty.TH
|
||||||
|
import Urbit.EventLog.LMDB
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
import Urbit.Vere.Log
|
|
||||||
import Urbit.Vere.Pier.Types
|
import Urbit.Vere.Pier.Types
|
||||||
|
|
||||||
import Control.Concurrent (runInBoundThread, threadDelay)
|
import Control.Concurrent (runInBoundThread, threadDelay)
|
||||||
import Data.LargeWord (LargeKey(..))
|
import Data.LargeWord (LargeKey(..))
|
||||||
import GHC.Natural (Natural)
|
import GHC.Natural (Natural)
|
||||||
import Urbit.King.App (App, runApp)
|
import Urbit.King.App (KingEnv, runKingEnvNoLog)
|
||||||
|
|
||||||
import qualified Urbit.Vere.Log as Log
|
import qualified Urbit.EventLog.LMDB as Log
|
||||||
|
|
||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
-- Utils -----------------------------------------------------------------------
|
||||||
@ -42,13 +42,13 @@ data Db = Db LogIdentity [ByteString] (Map Word64 ByteString)
|
|||||||
addEvents :: Db -> [ByteString] -> Db
|
addEvents :: Db -> [ByteString] -> Db
|
||||||
addEvents (Db id evs efs) new = Db id (evs <> new) efs
|
addEvents (Db id evs efs) new = Db id (evs <> new) efs
|
||||||
|
|
||||||
readDb :: EventLog -> RIO App Db
|
readDb :: EventLog -> RIO KingEnv Db
|
||||||
readDb log = do
|
readDb log = do
|
||||||
events <- runConduit (streamEvents log 1 .| consume)
|
events <- runConduit (streamEvents log 1 .| consume)
|
||||||
effects <- runConduit (streamEffectsRows log 0 .| consume)
|
effects <- runConduit (streamEffectsRows log 0 .| consume)
|
||||||
pure $ Db (Log.identity log) events (mapFromList effects)
|
pure $ Db (Log.identity log) events (mapFromList effects)
|
||||||
|
|
||||||
withDb :: FilePath -> Db -> (EventLog -> RIO App a) -> RIO App a
|
withDb :: FilePath -> Db -> (EventLog -> RIO KingEnv a) -> RIO KingEnv a
|
||||||
withDb dir (Db dId dEvs dFx) act = do
|
withDb dir (Db dId dEvs dFx) act = do
|
||||||
rwith (Log.new dir dId) $ \log -> do
|
rwith (Log.new dir dId) $ \log -> do
|
||||||
Log.appendEvents log (fromList dEvs)
|
Log.appendEvents log (fromList dEvs)
|
||||||
@ -58,10 +58,13 @@ withDb dir (Db dId dEvs dFx) act = do
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
runApp :: RIO KingEnv a -> IO a
|
||||||
|
runApp = runKingEnvNoLog
|
||||||
|
|
||||||
tryReadIdentity :: Property
|
tryReadIdentity :: Property
|
||||||
tryReadIdentity = forAll arbitrary (ioProperty . runApp . runTest)
|
tryReadIdentity = forAll arbitrary (ioProperty . runApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: LogIdentity -> RIO App Bool
|
runTest :: LogIdentity -> RIO KingEnv Bool
|
||||||
runTest ident = do
|
runTest ident = do
|
||||||
env <- ask
|
env <- ask
|
||||||
io $ runInBoundThread $ runRIO env $
|
io $ runInBoundThread $ runRIO env $
|
||||||
@ -77,7 +80,7 @@ tryReadIdentity = forAll arbitrary (ioProperty . runApp . runTest)
|
|||||||
tryReadDatabase :: Property
|
tryReadDatabase :: Property
|
||||||
tryReadDatabase = forAll arbitrary (ioProperty . runApp . runTest)
|
tryReadDatabase = forAll arbitrary (ioProperty . runApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: Db -> RIO App Bool
|
runTest :: Db -> RIO KingEnv Bool
|
||||||
runTest db = do
|
runTest db = do
|
||||||
env <- ask
|
env <- ask
|
||||||
io $ runInBoundThread $ runRIO env $
|
io $ runInBoundThread $ runRIO env $
|
||||||
@ -89,7 +92,7 @@ tryReadDatabase = forAll arbitrary (ioProperty . runApp . runTest)
|
|||||||
tryReadDatabaseFuzz :: Property
|
tryReadDatabaseFuzz :: Property
|
||||||
tryReadDatabaseFuzz = forAll arbitrary (ioProperty . runApp . runTest)
|
tryReadDatabaseFuzz = forAll arbitrary (ioProperty . runApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: Db -> RIO App Bool
|
runTest :: Db -> RIO KingEnv Bool
|
||||||
runTest db = do
|
runTest db = do
|
||||||
env <- ask
|
env <- ask
|
||||||
io $ runInBoundThread $ runRIO env $
|
io $ runInBoundThread $ runRIO env $
|
||||||
@ -106,7 +109,7 @@ tryReadDatabaseFuzz = forAll arbitrary (ioProperty . runApp . runTest)
|
|||||||
tryAppend :: Property
|
tryAppend :: Property
|
||||||
tryAppend = forAll arbitrary (ioProperty . runApp . runTest)
|
tryAppend = forAll arbitrary (ioProperty . runApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: ([ByteString], Db) -> RIO App Bool
|
runTest :: ([ByteString], Db) -> RIO KingEnv Bool
|
||||||
runTest (extra, db) = do
|
runTest (extra, db) = do
|
||||||
env <- ask
|
env <- ask
|
||||||
io $ runInBoundThread $ runRIO env $
|
io $ runInBoundThread $ runRIO env $
|
||||||
@ -123,7 +126,7 @@ tryAppend = forAll arbitrary (ioProperty . runApp . runTest)
|
|||||||
tryAppendHuge :: Property
|
tryAppendHuge :: Property
|
||||||
tryAppendHuge = forAll arbitrary (ioProperty . runApp . runTest)
|
tryAppendHuge = forAll arbitrary (ioProperty . runApp . runTest)
|
||||||
where
|
where
|
||||||
runTest :: ([ByteString], Db) -> RIO App Bool
|
runTest :: ([ByteString], Db) -> RIO KingEnv Bool
|
||||||
runTest (extra, db) = do
|
runTest (extra, db) = do
|
||||||
env <- ask
|
env <- ask
|
||||||
io $ runInBoundThread $ runRIO env $ do
|
io $ runInBoundThread $ runRIO env $ do
|
||||||
|
3
pkg/hs/urbit-noun-core/.gitignore
vendored
Normal file
3
pkg/hs/urbit-noun-core/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.stack-work
|
||||||
|
*.cabal
|
||||||
|
test/gold/*.writ
|
21
pkg/hs/urbit-noun-core/LICENSE
Normal file
21
pkg/hs/urbit-noun-core/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 urbit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
71
pkg/hs/urbit-noun-core/package.yaml
Normal file
71
pkg/hs/urbit-noun-core/package.yaml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
name: urbit-noun-core
|
||||||
|
version: 0.10.4
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: lib
|
||||||
|
ghc-options:
|
||||||
|
- -fwarn-incomplete-patterns
|
||||||
|
- -fwarn-unused-binds
|
||||||
|
- -fwarn-unused-imports
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- QuickCheck
|
||||||
|
- ghc-prim
|
||||||
|
- hashable
|
||||||
|
- urbit-atom
|
||||||
|
- classy-prelude
|
||||||
|
- bytestring
|
||||||
|
- hashtables
|
||||||
|
- vector
|
||||||
|
- integer-gmp
|
||||||
|
- template-haskell
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
- ApplicativeDo
|
||||||
|
- BangPatterns
|
||||||
|
- BlockArguments
|
||||||
|
- ConstraintKinds
|
||||||
|
- DataKinds
|
||||||
|
- DefaultSignatures
|
||||||
|
- DeriveAnyClass
|
||||||
|
- DeriveDataTypeable
|
||||||
|
- DeriveFoldable
|
||||||
|
- DeriveGeneric
|
||||||
|
- DeriveTraversable
|
||||||
|
- DerivingStrategies
|
||||||
|
- EmptyCase
|
||||||
|
- EmptyDataDecls
|
||||||
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
|
- FunctionalDependencies
|
||||||
|
- GADTs
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
|
- LambdaCase
|
||||||
|
- MagicHash
|
||||||
|
- MultiParamTypeClasses
|
||||||
|
- NamedFieldPuns
|
||||||
|
- NoImplicitPrelude
|
||||||
|
- NumericUnderscores
|
||||||
|
- OverloadedStrings
|
||||||
|
- PackageImports
|
||||||
|
- PartialTypeSignatures
|
||||||
|
- PatternSynonyms
|
||||||
|
- QuasiQuotes
|
||||||
|
- Rank2Types
|
||||||
|
- RankNTypes
|
||||||
|
- RecordWildCards
|
||||||
|
- ScopedTypeVariables
|
||||||
|
- StandaloneDeriving
|
||||||
|
- TemplateHaskell
|
||||||
|
- TupleSections
|
||||||
|
- TypeApplications
|
||||||
|
- TypeFamilies
|
||||||
|
- TypeOperators
|
||||||
|
- UnboxedTuples
|
||||||
|
- UnicodeSyntax
|
||||||
|
- ViewPatterns
|
3
pkg/hs/urbit-noun/.gitignore
vendored
Normal file
3
pkg/hs/urbit-noun/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.stack-work
|
||||||
|
*.cabal
|
||||||
|
test/gold/*.writ
|
21
pkg/hs/urbit-noun/LICENSE
Normal file
21
pkg/hs/urbit-noun/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 urbit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -713,6 +713,7 @@ instance (FromNoun a, FromNoun b) => FromNoun (Each a b) where
|
|||||||
1 -> named "|" (EachNo <$> parseNoun v)
|
1 -> named "|" (EachNo <$> parseNoun v)
|
||||||
n -> fail ("Each has invalid head-atom: " <> show n)
|
n -> fail ("Each has invalid head-atom: " <> show n)
|
||||||
|
|
||||||
|
|
||||||
-- Tuple Conversions -----------------------------------------------------------
|
-- Tuple Conversions -----------------------------------------------------------
|
||||||
|
|
||||||
instance ToNoun () where
|
instance ToNoun () where
|
@ -7,18 +7,32 @@ module Urbit.Noun.Tank where
|
|||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Urbit.Noun.Conversions
|
import Urbit.Noun.Conversions
|
||||||
import Urbit.Noun.TH
|
import Urbit.Noun.TH
|
||||||
|
import Urbit.Noun.Convert
|
||||||
|
import Urbit.Noun.Core
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Tang = [Tank]
|
type Tang = [Tank]
|
||||||
|
|
||||||
data Tank
|
data TankTree
|
||||||
= Leaf Tape
|
= Leaf Tape
|
||||||
| Plum Plum
|
| Plum Plum
|
||||||
| Palm (Tape, Tape, Tape, Tape) [Tank]
|
| Palm (Tape, Tape, Tape, Tape) [TankTree]
|
||||||
| Rose (Tape, Tape, Tape) [Tank]
|
| Rose (Tape, Tape, Tape) [TankTree]
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
newtype Tank = Tank { tankTree :: TankTree }
|
||||||
|
deriving newtype (Eq, Ord, Show)
|
||||||
|
|
||||||
|
instance ToNoun Tank where
|
||||||
|
toNoun (Tank t) = toNoun t
|
||||||
|
|
||||||
|
instance FromNoun Tank where
|
||||||
|
parseNoun n@(Atom _) = do
|
||||||
|
Cord txt <- parseNoun n
|
||||||
|
pure $ Tank $ Leaf $ Tape txt
|
||||||
|
parseNoun n = Tank <$> parseNoun n
|
||||||
|
|
||||||
data WideFmt = WideFmt { delimit :: Cord, enclose :: Maybe (Cord, Cord) }
|
data WideFmt = WideFmt { delimit :: Cord, enclose :: Maybe (Cord, Cord) }
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
@ -39,7 +53,7 @@ data PlumTree
|
|||||||
deriveNoun ''WideFmt
|
deriveNoun ''WideFmt
|
||||||
deriveNoun ''TallFmt
|
deriveNoun ''TallFmt
|
||||||
deriveNoun ''PlumFmt
|
deriveNoun ''PlumFmt
|
||||||
deriveNoun ''Tank
|
deriveNoun ''TankTree
|
||||||
deriveNoun ''PlumTree
|
deriveNoun ''PlumTree
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@ -51,7 +65,7 @@ data WashCfg = WashCfg
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
wash :: WashCfg -> Tank -> Wall
|
wash :: WashCfg -> TankTree -> Wall
|
||||||
wash _cfg t = [ram t]
|
wash _cfg t = [ram t]
|
||||||
|
|
||||||
-- win :: WashCfg -> Tank -> Wall
|
-- win :: WashCfg -> Tank -> Wall
|
||||||
@ -60,7 +74,7 @@ wash _cfg t = [ram t]
|
|||||||
flat :: Plum -> Tape
|
flat :: Plum -> Tape
|
||||||
flat = Tape . tshow
|
flat = Tape . tshow
|
||||||
|
|
||||||
ram :: Tank -> Tape
|
ram :: TankTree -> Tape
|
||||||
ram = \case
|
ram = \case
|
||||||
Leaf tape -> tape
|
Leaf tape -> tape
|
||||||
Plum plum -> flat plum
|
Plum plum -> flat plum
|
@ -2,16 +2,21 @@
|
|||||||
TODO This is slow.
|
TODO This is slow.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Time where
|
module Urbit.Noun.Time where
|
||||||
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
import Data.Bits (shiftL, shiftR)
|
import Data.Bits (shiftL, shiftR, (.&.))
|
||||||
import Data.Time.Clock (DiffTime, UTCTime)
|
import Data.List (intercalate)
|
||||||
|
import Data.Time.Calendar (toGregorian)
|
||||||
|
import Data.Time.Clock (DiffTime, UTCTime(..))
|
||||||
import Data.Time.Clock (diffTimeToPicoseconds, picosecondsToDiffTime)
|
import Data.Time.Clock (diffTimeToPicoseconds, picosecondsToDiffTime)
|
||||||
import Data.Time.Clock.System (SystemTime(..), getSystemTime)
|
import Data.Time.Clock.System (SystemTime(..), getSystemTime)
|
||||||
import Data.Time.Clock.System (systemToUTCTime, utcToSystemTime)
|
import Data.Time.Clock.System (systemToUTCTime, utcToSystemTime)
|
||||||
|
import Data.Time.LocalTime (TimeOfDay(..), timeToTimeOfDay)
|
||||||
|
import Data.Word (Word64)
|
||||||
|
import Text.Printf (printf)
|
||||||
import Urbit.Noun (FromNoun, ToNoun)
|
import Urbit.Noun (FromNoun, ToNoun)
|
||||||
|
|
||||||
|
|
||||||
@ -26,12 +31,47 @@ newtype Unix = Unix { _sinceUnixEpoch :: Gap }
|
|||||||
newtype Wen = Wen { _sinceUrbitEpoch :: Gap }
|
newtype Wen = Wen { _sinceUrbitEpoch :: Gap }
|
||||||
deriving newtype (Eq, Ord, Show, Num, ToNoun, FromNoun)
|
deriving newtype (Eq, Ord, Show, Num, ToNoun, FromNoun)
|
||||||
|
|
||||||
|
newtype Date = MkDate { _dateWen :: Wen }
|
||||||
|
deriving newtype (Eq, Ord, Num, ToNoun, FromNoun)
|
||||||
|
|
||||||
-- Lenses ----------------------------------------------------------------------
|
|
||||||
|
-- Record Lenses ---------------------------------------------------------------
|
||||||
|
|
||||||
makeLenses ''Gap
|
makeLenses ''Gap
|
||||||
makeLenses ''Unix
|
makeLenses ''Unix
|
||||||
makeLenses ''Wen
|
makeLenses ''Wen
|
||||||
|
makeLenses ''Date
|
||||||
|
|
||||||
|
|
||||||
|
-- Instances -------------------------------------------------------------------
|
||||||
|
|
||||||
|
instance Show Date where
|
||||||
|
show (MkDate wen) = if fs == 0
|
||||||
|
then printf "~%i.%u.%u..%02u.%02u.%02u" y m d h min s
|
||||||
|
else printf "~%i.%u.%u..%02u.%02u.%02u..%s" y m d h min s (showGap fs)
|
||||||
|
where
|
||||||
|
utc = wen ^. systemTime . to systemToUTCTime
|
||||||
|
(y, m, d) = toGregorian (utctDay utc)
|
||||||
|
TimeOfDay h min (floor -> s::Int) = timeToTimeOfDay (utctDayTime utc)
|
||||||
|
fs = (wen ^. wenFracto . to (fromIntegral @Integer @Word64))
|
||||||
|
|
||||||
|
wenFracto :: Lens' Wen Integer
|
||||||
|
wenFracto = sinceUrbitEpoch . fractoSecs
|
||||||
|
|
||||||
|
showGap :: Word64 -> String
|
||||||
|
showGap gap = intercalate "." (printf "%04x" <$> bs)
|
||||||
|
where
|
||||||
|
bs = reverse $ dropWhile (== 0) [b4, b3, b2, b1]
|
||||||
|
b4 = takeBits 16 gap
|
||||||
|
b3 = takeBits 16 (shiftR gap 16)
|
||||||
|
b2 = takeBits 16 (shiftR gap 32)
|
||||||
|
b1 = takeBits 16 (shiftR gap 48)
|
||||||
|
|
||||||
|
takeBits :: Int -> Word64 -> Word64
|
||||||
|
takeBits wid wor = wor .&. (shiftL 1 wid - 1)
|
||||||
|
|
||||||
|
|
||||||
|
-- Conversion Lenses -----------------------------------------------------------
|
||||||
|
|
||||||
diffTime :: Iso' Gap DiffTime
|
diffTime :: Iso' Gap DiffTime
|
||||||
diffTime = iso fromGap toGap
|
diffTime = iso fromGap toGap
|
73
pkg/hs/urbit-noun/package.yaml
Normal file
73
pkg/hs/urbit-noun/package.yaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
name: urbit-noun
|
||||||
|
version: 0.10.4
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: lib
|
||||||
|
ghc-options:
|
||||||
|
- -fwarn-incomplete-patterns
|
||||||
|
- -fwarn-unused-binds
|
||||||
|
- -fwarn-unused-imports
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- classy-prelude
|
||||||
|
- ghc-prim
|
||||||
|
- largeword
|
||||||
|
- lens
|
||||||
|
- murmur3
|
||||||
|
- regex-tdfa
|
||||||
|
- regex-tdfa-text
|
||||||
|
- rio
|
||||||
|
- text
|
||||||
|
- time
|
||||||
|
- urbit-atom
|
||||||
|
- urbit-noun-core
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
- ApplicativeDo
|
||||||
|
- BangPatterns
|
||||||
|
- BlockArguments
|
||||||
|
- ConstraintKinds
|
||||||
|
- DataKinds
|
||||||
|
- DefaultSignatures
|
||||||
|
- DeriveAnyClass
|
||||||
|
- DeriveDataTypeable
|
||||||
|
- DeriveFoldable
|
||||||
|
- DeriveGeneric
|
||||||
|
- DeriveTraversable
|
||||||
|
- DerivingStrategies
|
||||||
|
- EmptyCase
|
||||||
|
- EmptyDataDecls
|
||||||
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
|
- FunctionalDependencies
|
||||||
|
- GADTs
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
|
- LambdaCase
|
||||||
|
- MagicHash
|
||||||
|
- MultiParamTypeClasses
|
||||||
|
- NamedFieldPuns
|
||||||
|
- NoImplicitPrelude
|
||||||
|
- NumericUnderscores
|
||||||
|
- OverloadedStrings
|
||||||
|
- PackageImports
|
||||||
|
- PartialTypeSignatures
|
||||||
|
- PatternSynonyms
|
||||||
|
- QuasiQuotes
|
||||||
|
- Rank2Types
|
||||||
|
- RankNTypes
|
||||||
|
- RecordWildCards
|
||||||
|
- ScopedTypeVariables
|
||||||
|
- StandaloneDeriving
|
||||||
|
- TemplateHaskell
|
||||||
|
- TupleSections
|
||||||
|
- TypeApplications
|
||||||
|
- TypeFamilies
|
||||||
|
- TypeOperators
|
||||||
|
- UnboxedTuples
|
||||||
|
- UnicodeSyntax
|
||||||
|
- ViewPatterns
|
3
pkg/hs/urbit-termsize/.gitignore
vendored
Normal file
3
pkg/hs/urbit-termsize/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.stack-work/
|
||||||
|
urbit-termsize.cabal
|
||||||
|
*~
|
21
pkg/hs/urbit-termsize/LICENSE
Normal file
21
pkg/hs/urbit-termsize/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 urbit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
13
pkg/hs/urbit-termsize/app/Main.hs
Normal file
13
pkg/hs/urbit-termsize/app/Main.hs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Urbit.TermSize (liveTermSize)
|
||||||
|
import System.IO (getLine)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
init <- liveTermSize (putStrLn . ("New Size: " <>) . show)
|
||||||
|
putStrLn ("Initial Size: " <> show init)
|
||||||
|
_ <- getLine
|
||||||
|
pure ()
|
40
pkg/hs/urbit-termsize/lib/Urbit/TermSize.hs
Normal file
40
pkg/hs/urbit-termsize/lib/Urbit/TermSize.hs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
|
module Urbit.TermSize
|
||||||
|
( TermSize(..)
|
||||||
|
, termSize
|
||||||
|
, liveTermSize
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Functor ((<&>))
|
||||||
|
import System.Console.Terminal.Size (Window(..), size)
|
||||||
|
|
||||||
|
import qualified System.Posix.Signals as Sys
|
||||||
|
import qualified System.Posix.Signals.Exts as Sys
|
||||||
|
|
||||||
|
|
||||||
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
data TermSize = TermSize
|
||||||
|
{ tsWide :: !Word
|
||||||
|
, tsTall :: !Word
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
|
||||||
|
-- Utilities -------------------------------------------------------------------
|
||||||
|
|
||||||
|
termSize :: IO TermSize
|
||||||
|
termSize = size <&> \case
|
||||||
|
Nothing -> TermSize 80 24
|
||||||
|
Just (Window {..}) -> TermSize width height
|
||||||
|
|
||||||
|
liveTermSize :: (TermSize -> IO ()) -> IO TermSize
|
||||||
|
liveTermSize cb = do
|
||||||
|
Sys.installHandler Sys.sigWINCH (Sys.Catch (termSize >>= cb)) Nothing
|
||||||
|
termSize
|
25
pkg/hs/urbit-termsize/package.yaml
Normal file
25
pkg/hs/urbit-termsize/package.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: urbit-termsize
|
||||||
|
version: 0.1.0
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- terminal-size
|
||||||
|
- unix
|
||||||
|
|
||||||
|
ghc-options:
|
||||||
|
- -fwarn-incomplete-patterns
|
||||||
|
- -fwarn-unused-binds
|
||||||
|
- -fwarn-unused-imports
|
||||||
|
- -O2
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: lib
|
||||||
|
|
||||||
|
executables:
|
||||||
|
urbit-test-termsize-updates:
|
||||||
|
main: Main.hs
|
||||||
|
source-dirs: app
|
||||||
|
dependencies:
|
||||||
|
- urbit-termsize
|
@ -2,7 +2,7 @@ include config.mk
|
|||||||
|
|
||||||
jets = jets/tree.c $(wildcard jets/*/*.c)
|
jets = jets/tree.c $(wildcard jets/*/*.c)
|
||||||
noun = $(wildcard noun/*.c)
|
noun = $(wildcard noun/*.c)
|
||||||
vere = $(wildcard vere/*.c)
|
vere = $(wildcard vere/*.c) $(wildcard vere/*/*.c)
|
||||||
daemon = $(wildcard daemon/*.c)
|
daemon = $(wildcard daemon/*.c)
|
||||||
worker = $(wildcard worker/*.c)
|
worker = $(wildcard worker/*.c)
|
||||||
tests = $(wildcard tests/*.c)
|
tests = $(wildcard tests/*.c)
|
||||||
|
8
pkg/urbit/configure
vendored
8
pkg/urbit/configure
vendored
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
URBIT_VERSION="0.10.7"
|
URBIT_VERSION="0.10.8"
|
||||||
|
|
||||||
deps=" \
|
deps=" \
|
||||||
curl gmp sigsegv argon2 ed25519 ent h2o scrypt sni uv murmur3 secp256k1 \
|
curl gmp sigsegv argon2 ed25519 ent h2o scrypt uv murmur3 secp256k1 \
|
||||||
softfloat3 ncurses ssl crypto z lmdb ge-additions aes_siv \
|
softfloat3 ssl crypto z lmdb ge-additions aes_siv pthread \
|
||||||
"
|
"
|
||||||
|
|
||||||
headers=" \
|
headers=" \
|
||||||
|
@ -9,9 +9,7 @@
|
|||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
#include <sigsegv.h>
|
#include <sigsegv.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <ncurses/curses.h>
|
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <ncurses/term.h>
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
#include <openssl/engine.h>
|
#include <openssl/engine.h>
|
||||||
@ -20,6 +18,7 @@
|
|||||||
#include <h2o.h>
|
#include <h2o.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <argon2.h>
|
#include <argon2.h>
|
||||||
|
#include <lmdb.h>
|
||||||
|
|
||||||
#define U3_GLOBAL
|
#define U3_GLOBAL
|
||||||
#define C3_GLOBAL
|
#define C3_GLOBAL
|
||||||
@ -97,9 +96,17 @@ _main_getopt(c3_i argc, c3_c** argv)
|
|||||||
u3_Host.ops_u.kno_w = DefaultKernel;
|
u3_Host.ops_u.kno_w = DefaultKernel;
|
||||||
|
|
||||||
while ( -1 != (ch_i=getopt(argc, argv,
|
while ( -1 != (ch_i=getopt(argc, argv,
|
||||||
"G:J:B:K:A:H:I:C:w:u:e:F:k:p:LljacdgqstvxPDRS")) )
|
"X:Y:G:J:B:K:A:H:I:C:w:u:e:F:k:n:p:r:LljacdgqstvxPDRS")) )
|
||||||
{
|
{
|
||||||
switch ( ch_i ) {
|
switch ( ch_i ) {
|
||||||
|
case 'X': {
|
||||||
|
u3_Host.ops_u.pek_c = strdup(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'Y': {
|
||||||
|
u3_Host.ops_u.puk_c = strdup(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'J': {
|
case 'J': {
|
||||||
u3_Host.ops_u.lit_c = strdup(optarg);
|
u3_Host.ops_u.lit_c = strdup(optarg);
|
||||||
break;
|
break;
|
||||||
@ -162,6 +169,10 @@ _main_getopt(c3_i argc, c3_c** argv)
|
|||||||
u3_Host.ops_u.key_c = strdup(optarg);
|
u3_Host.ops_u.key_c = strdup(optarg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'n': {
|
||||||
|
u3_Host.ops_u.til_c = strdup(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'p': {
|
case 'p': {
|
||||||
if ( c3n == _main_readw(optarg, 65536, &arg_w) ) {
|
if ( c3n == _main_readw(optarg, 65536, &arg_w) ) {
|
||||||
return c3n;
|
return c3n;
|
||||||
@ -172,6 +183,10 @@ _main_getopt(c3_i argc, c3_c** argv)
|
|||||||
u3_Host.ops_u.rep = c3y;
|
u3_Host.ops_u.rep = c3y;
|
||||||
return c3y;
|
return c3y;
|
||||||
}
|
}
|
||||||
|
case 'r': {
|
||||||
|
u3_Host.ops_u.roc_c = strdup(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'L': { u3_Host.ops_u.net = c3n; break; }
|
case 'L': { u3_Host.ops_u.net = c3n; break; }
|
||||||
case 'l': { u3_Host.ops_u.lit = c3y; break; }
|
case 'l': { u3_Host.ops_u.lit = c3y; break; }
|
||||||
case 'j': { u3_Host.ops_u.tra = c3y; break; }
|
case 'j': { u3_Host.ops_u.tra = c3y; break; }
|
||||||
@ -398,7 +413,9 @@ u3_ve_usage(c3_i argc, c3_c** argv)
|
|||||||
"-u url URL from which to download pill\n",
|
"-u url URL from which to download pill\n",
|
||||||
"-v Verbose\n",
|
"-v Verbose\n",
|
||||||
"-w name Boot as ~name\n",
|
"-w name Boot as ~name\n",
|
||||||
|
"-X path Scry, jam to file, then exit\n"
|
||||||
"-x Exit immediately\n",
|
"-x Exit immediately\n",
|
||||||
|
"-Y file Optional name of jamfile (for -X)\n"
|
||||||
"\n",
|
"\n",
|
||||||
"Development Usage:\n",
|
"Development Usage:\n",
|
||||||
" To create a development ship, use a fakezod:\n",
|
" To create a development ship, use a fakezod:\n",
|
||||||
@ -449,7 +466,6 @@ report(void)
|
|||||||
(libsigsegv_version >> 8) & 0xff,
|
(libsigsegv_version >> 8) & 0xff,
|
||||||
libsigsegv_version & 0xff);
|
libsigsegv_version & 0xff);
|
||||||
printf("openssl: %s\n", SSLeay_version(SSLEAY_VERSION));
|
printf("openssl: %s\n", SSLeay_version(SSLEAY_VERSION));
|
||||||
printf("curses: %s\n", curses_version());
|
|
||||||
printf("libuv: %s\n", uv_version_string());
|
printf("libuv: %s\n", uv_version_string());
|
||||||
printf("libh2o: %d.%d.%d\n",
|
printf("libh2o: %d.%d.%d\n",
|
||||||
H2O_LIBRARY_VERSION_MAJOR,
|
H2O_LIBRARY_VERSION_MAJOR,
|
||||||
@ -474,19 +490,7 @@ _stop_exit(c3_i int_i)
|
|||||||
// explicit fprintf to avoid allocation in u3l_log
|
// explicit fprintf to avoid allocation in u3l_log
|
||||||
//
|
//
|
||||||
fprintf(stderr, "\r\n[received keyboard stop signal, exiting]\r\n");
|
fprintf(stderr, "\r\n[received keyboard stop signal, exiting]\r\n");
|
||||||
u3_daemon_bail();
|
u3_king_bail();
|
||||||
}
|
|
||||||
|
|
||||||
/* _stop_signal(): handle termination signal.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
_stop_signal(c3_i int_i)
|
|
||||||
{
|
|
||||||
// if we have a pier, unmap the event log before dumping core
|
|
||||||
//
|
|
||||||
if ( 0 != u3K.len_w ) {
|
|
||||||
u3_pier_db_shutdown(u3_pier_stub());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -584,7 +588,7 @@ _fork_into_background_process()
|
|||||||
static void
|
static void
|
||||||
_stop_on_boot_completed_cb()
|
_stop_on_boot_completed_cb()
|
||||||
{
|
{
|
||||||
u3_pier_exit(u3_pier_stub());
|
u3_king_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
c3_i
|
c3_i
|
||||||
@ -603,12 +607,6 @@ main(c3_i argc,
|
|||||||
u3_Host.wrk_c = c3_malloc(worker_exe_len);
|
u3_Host.wrk_c = c3_malloc(worker_exe_len);
|
||||||
snprintf(u3_Host.wrk_c, worker_exe_len, "%s-worker", argv[0]);
|
snprintf(u3_Host.wrk_c, worker_exe_len, "%s-worker", argv[0]);
|
||||||
|
|
||||||
// Set TERMINFO_DIRS environment variable
|
|
||||||
c3_i terminfo_len = 1 + strlen(argv[0]) + strlen("-terminfo");
|
|
||||||
c3_c terminfo_dir[terminfo_len];
|
|
||||||
snprintf(terminfo_dir, terminfo_len, "%s-terminfo", argv[0]);
|
|
||||||
setenv("TERMINFO_DIRS", terminfo_dir, 1);
|
|
||||||
|
|
||||||
if ( c3y == u3_Host.ops_u.dem ) {
|
if ( c3y == u3_Host.ops_u.dem ) {
|
||||||
_fork_into_background_process();
|
_fork_into_background_process();
|
||||||
}
|
}
|
||||||
@ -651,10 +649,6 @@ main(c3_i argc,
|
|||||||
//
|
//
|
||||||
signal(SIGTSTP, _stop_exit);
|
signal(SIGTSTP, _stop_exit);
|
||||||
|
|
||||||
// Cleanup on SIGABRT.
|
|
||||||
//
|
|
||||||
signal(SIGABRT, _stop_signal);
|
|
||||||
|
|
||||||
printf("~\n");
|
printf("~\n");
|
||||||
// printf("welcome.\n");
|
// printf("welcome.\n");
|
||||||
printf("urbit %s\n", URBIT_VERSION);
|
printf("urbit %s\n", URBIT_VERSION);
|
||||||
@ -768,7 +762,7 @@ main(c3_i argc,
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
u3_daemon_commence();
|
u3_king_commence();
|
||||||
|
|
||||||
// uninitialize curl
|
// uninitialize curl
|
||||||
//
|
//
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
/* Stub.
|
/* Stub.
|
||||||
*/
|
*/
|
||||||
# define c3_stub (assert(!"stub"), 0)
|
# define c3_stub c3_assert(!"stub")
|
||||||
|
|
||||||
/* Size in words.
|
/* Size in words.
|
||||||
*/
|
*/
|
||||||
|
@ -248,6 +248,7 @@
|
|||||||
# define c3__cow c3_s3('c','o','w')
|
# define c3__cow c3_s3('c','o','w')
|
||||||
# define c3__cpu c3_s3('c','p','u')
|
# define c3__cpu c3_s3('c','p','u')
|
||||||
# define c3__crad c3_s4('c','r','a','d')
|
# define c3__crad c3_s4('c','r','a','d')
|
||||||
|
# define c3__cram c3_s4('c','r','a','m')
|
||||||
# define c3__crap c3_s4('c','r','a','p')
|
# define c3__crap c3_s4('c','r','a','p')
|
||||||
# define c3__cret c3_s4('c','r','e','t')
|
# define c3__cret c3_s4('c','r','e','t')
|
||||||
# define c3__crib c3_s4('c','r','i','b')
|
# define c3__crib c3_s4('c','r','i','b')
|
||||||
@ -365,6 +366,7 @@
|
|||||||
# define c3__dumb c3_s4('d','u','m','b')
|
# define c3__dumb c3_s4('d','u','m','b')
|
||||||
# define c3__dump c3_s4('d','u','m','p')
|
# define c3__dump c3_s4('d','u','m','p')
|
||||||
# define c3__dust c3_s4('d','u','s','t')
|
# define c3__dust c3_s4('d','u','s','t')
|
||||||
|
# define c3__e c3_s1('e')
|
||||||
# define c3__earl c3_s4('e','a','r','l')
|
# define c3__earl c3_s4('e','a','r','l')
|
||||||
# define c3__east c3_s4('e','a','s','t')
|
# define c3__east c3_s4('e','a','s','t')
|
||||||
# define c3__echo c3_s4('e','c','h','o')
|
# define c3__echo c3_s4('e','c','h','o')
|
||||||
@ -928,6 +930,7 @@
|
|||||||
# define c3__revo c3_s4('r','e','v','o')
|
# define c3__revo c3_s4('r','e','v','o')
|
||||||
# define c3__rin c3_s3('r','i','n')
|
# define c3__rin c3_s3('r','i','n')
|
||||||
# define c3__ring c3_s4('r','i','n','g')
|
# define c3__ring c3_s4('r','i','n','g')
|
||||||
|
# define c3__ripe c3_s4('r','i','p','e')
|
||||||
# define c3__rite c3_s4('r','i','t','e')
|
# define c3__rite c3_s4('r','i','t','e')
|
||||||
# define c3__rock c3_s4('r','o','c','k')
|
# define c3__rock c3_s4('r','o','c','k')
|
||||||
# define c3__roll c3_s4('r','o','l','l')
|
# define c3__roll c3_s4('r','o','l','l')
|
||||||
@ -1062,6 +1065,7 @@
|
|||||||
# define c3__sunt c3_s4('s','u','n','t')
|
# define c3__sunt c3_s4('s','u','n','t')
|
||||||
# define c3__sure c3_s4('s','u','r','e')
|
# define c3__sure c3_s4('s','u','r','e')
|
||||||
# define c3__susp c3_s4('s','u','s','p')
|
# define c3__susp c3_s4('s','u','s','p')
|
||||||
|
# define c3__swap c3_s4('s','w','a','p')
|
||||||
# define c3__sym c3_s3('s','y','m')
|
# define c3__sym c3_s3('s','y','m')
|
||||||
# define c3__sync c3_s4('s','y','n','c')
|
# define c3__sync c3_s4('s','y','n','c')
|
||||||
# define c3__sys c3_s3('s','y','s')
|
# define c3__sys c3_s3('s','y','s')
|
||||||
@ -1206,6 +1210,7 @@
|
|||||||
# define c3__wack c3_s4('w','a','c','k')
|
# define c3__wack c3_s4('w','a','c','k')
|
||||||
# define c3__wail c3_s4('w','a','i','l')
|
# define c3__wail c3_s4('w','a','i','l')
|
||||||
# define c3__wake c3_s4('w','a','k','e')
|
# define c3__wake c3_s4('w','a','k','e')
|
||||||
|
# define c3__walk c3_s4('w','a','l','k')
|
||||||
# define c3__wamp c3_s4('w','a','m','p')
|
# define c3__wamp c3_s4('w','a','m','p')
|
||||||
# define c3__want c3_s4('w','a','n','t')
|
# define c3__want c3_s4('w','a','n','t')
|
||||||
# define c3__warm c3_s4('w','a','r','m')
|
# define c3__warm c3_s4('w','a','r','m')
|
||||||
|
@ -84,6 +84,11 @@
|
|||||||
# define u3nt(a, b, c) u3i_trel(a, b, c)
|
# define u3nt(a, b, c) u3i_trel(a, b, c)
|
||||||
# define u3nq(a, b, c, d) u3i_qual(a, b, c, d)
|
# define u3nq(a, b, c, d) u3i_qual(a, b, c, d)
|
||||||
|
|
||||||
|
|
||||||
|
/* u3nl(), u3_none-terminated varargs list
|
||||||
|
*/
|
||||||
|
# define u3nl u3i_list
|
||||||
|
|
||||||
/* u3du(), u3ud(): noun/cell test.
|
/* u3du(), u3ud(): noun/cell test.
|
||||||
*/
|
*/
|
||||||
# define u3du(som) (u3r_du(som))
|
# define u3du(som) (u3r_du(som))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user